ErrorReporting.cpp (21318B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "vm/ErrorReporting.h" 8 9 #include "mozilla/PodOperations.h" 10 11 #include <stdarg.h> 12 #include <utility> 13 14 #include "jsexn.h" 15 #include "jsfriendapi.h" 16 17 #include "frontend/FrontendContext.h" // AutoReportFrontendContext 18 #include "js/CharacterEncoding.h" // JS::ConstUTF8CharsZ 19 #include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin, JS::TaggedColumnNumberOneOrigin 20 #include "js/ErrorReport.h" // JSErrorBase 21 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* 22 #include "js/Printf.h" // JS_vsmprintf 23 #include "js/Warnings.h" // JS::WarningReporter 24 #include "vm/FrameIter.h" 25 #include "vm/GlobalObject.h" 26 #include "vm/JSContext.h" 27 28 using namespace js; 29 30 using JS::HandleObject; 31 using JS::HandleValue; 32 using JS::UniqueTwoByteChars; 33 34 void js::CallWarningReporter(JSContext* cx, JSErrorReport* reportp) { 35 MOZ_ASSERT(reportp->isWarning()); 36 37 if (JS::WarningReporter warningReporter = cx->runtime()->warningReporter) { 38 warningReporter(cx, reportp); 39 } 40 } 41 42 bool js::CompileError::throwError(JSContext* cx) { 43 if (isWarning()) { 44 CallWarningReporter(cx, this); 45 return true; 46 } 47 48 // If there's a runtime exception type associated with this error 49 // number, set that as the pending exception. For errors occurring at 50 // compile time, this is very likely to be a JSEXN_SYNTAXERR. 51 return ErrorToException(cx, this, nullptr, nullptr); 52 } 53 54 bool js::ReportExceptionClosure::operator()(JSContext* cx) { 55 cx->setPendingException(exn_, ShouldCaptureStack::Always); 56 return false; 57 } 58 59 bool js::ReportCompileWarning(FrontendContext* fc, ErrorMetadata&& metadata, 60 UniquePtr<JSErrorNotes> notes, 61 unsigned errorNumber, va_list* args) { 62 // On the main thread, report the error immediately. When compiling off 63 // thread, save the error so that the thread finishing the parse can report 64 // it later. 65 CompileError err; 66 67 err.notes = std::move(notes); 68 err.isWarning_ = true; 69 err.errorNumber = errorNumber; 70 71 err.filename = JS::ConstUTF8CharsZ(metadata.filename); 72 err.lineno = metadata.lineNumber; 73 err.column = metadata.columnNumber; 74 err.isMuted = metadata.isMuted; 75 76 if (UniqueTwoByteChars lineOfContext = std::move(metadata.lineOfContext)) { 77 err.initOwnedLinebuf(lineOfContext.release(), metadata.lineLength, 78 metadata.tokenOffset); 79 } 80 81 if (!ExpandErrorArgumentsVA(fc, GetErrorMessage, nullptr, errorNumber, 82 ArgumentsAreLatin1, &err, *args)) { 83 return false; 84 } 85 86 return fc->reportWarning(std::move(err)); 87 } 88 89 static void ReportCompileErrorImpl(FrontendContext* fc, 90 js::ErrorMetadata&& metadata, 91 js::UniquePtr<JSErrorNotes> notes, 92 unsigned errorNumber, va_list* args, 93 ErrorArgumentsType argumentsType) { 94 js::CompileError err; 95 96 err.notes = std::move(notes); 97 err.isWarning_ = false; 98 err.errorNumber = errorNumber; 99 100 err.filename = JS::ConstUTF8CharsZ(metadata.filename); 101 err.lineno = metadata.lineNumber; 102 err.column = metadata.columnNumber; 103 err.isMuted = metadata.isMuted; 104 105 if (UniqueTwoByteChars lineOfContext = std::move(metadata.lineOfContext)) { 106 err.initOwnedLinebuf(lineOfContext.release(), metadata.lineLength, 107 metadata.tokenOffset); 108 } 109 110 if (!js::ExpandErrorArgumentsVA(fc, js::GetErrorMessage, nullptr, errorNumber, 111 argumentsType, &err, *args)) { 112 return; 113 } 114 115 fc->reportError(std::move(err)); 116 } 117 118 void js::ReportCompileErrorLatin1(FrontendContext* fc, ErrorMetadata&& metadata, 119 UniquePtr<JSErrorNotes> notes, 120 unsigned errorNumber, ...) { 121 va_list args; 122 va_start(args, errorNumber); 123 ReportCompileErrorLatin1VA(fc, std::move(metadata), std::move(notes), 124 errorNumber, &args); 125 va_end(args); 126 } 127 128 void js::ReportCompileErrorUTF8(FrontendContext* fc, ErrorMetadata&& metadata, 129 UniquePtr<JSErrorNotes> notes, 130 unsigned errorNumber, ...) { 131 va_list args; 132 va_start(args, errorNumber); 133 ReportCompileErrorUTF8VA(fc, std::move(metadata), std::move(notes), 134 errorNumber, &args); 135 va_end(args); 136 } 137 138 void js::ReportCompileErrorLatin1VA(FrontendContext* fc, 139 ErrorMetadata&& metadata, 140 UniquePtr<JSErrorNotes> notes, 141 unsigned errorNumber, va_list* args) { 142 ReportCompileErrorImpl(fc, std::move(metadata), std::move(notes), errorNumber, 143 args, ArgumentsAreLatin1); 144 } 145 146 void js::ReportCompileErrorUTF8VA(FrontendContext* fc, ErrorMetadata&& metadata, 147 UniquePtr<JSErrorNotes> notes, 148 unsigned errorNumber, va_list* args) { 149 ReportCompileErrorImpl(fc, std::move(metadata), std::move(notes), errorNumber, 150 args, ArgumentsAreUTF8); 151 } 152 153 void js::ReportErrorToGlobal(JSContext* cx, Handle<GlobalObject*> global, 154 HandleValue error) { 155 MOZ_ASSERT(!cx->isExceptionPending()); 156 #ifdef DEBUG 157 // No assertSameCompartment version that doesn't take JSContext... 158 if (error.isObject()) { 159 AssertSameCompartment(global, &error.toObject()); 160 } 161 #endif // DEBUG 162 js::ReportExceptionClosure report(error); 163 PrepareScriptEnvironmentAndInvoke(cx, global, report); 164 } 165 166 static bool ReportError(JSContext* cx, JSErrorReport* reportp, 167 JSErrorCallback callback, void* userRef) { 168 if (reportp->isWarning()) { 169 CallWarningReporter(cx, reportp); 170 return true; 171 } 172 173 // Check the error report, and set a JavaScript-catchable exception 174 // if the error is defined to have an associated exception. 175 return ErrorToException(cx, reportp, callback, userRef); 176 } 177 178 /* 179 * The given JSErrorReport object have been zeroed and must not outlive 180 * cx->fp() (otherwise owned fields may become invalid). 181 */ 182 static void PopulateReportBlame(JSContext* cx, JSErrorReport* report) { 183 JS::Realm* realm = cx->realm(); 184 if (!realm) { 185 return; 186 } 187 188 /* 189 * Walk stack until we find a frame that is associated with a non-builtin 190 * rather than a builtin frame and which we're allowed to know about. 191 */ 192 NonBuiltinFrameIter iter(cx, realm->principals()); 193 if (iter.done()) { 194 return; 195 } 196 197 report->filename = JS::ConstUTF8CharsZ(iter.filename()); 198 if (iter.hasScript()) { 199 report->sourceId = iter.script()->scriptSource()->id(); 200 } 201 JS::TaggedColumnNumberOneOrigin column; 202 report->lineno = iter.computeLine(&column); 203 report->column = JS::ColumnNumberOneOrigin(column.oneOriginValue()); 204 report->isMuted = iter.mutedErrors(); 205 } 206 207 class MOZ_RAII AutoMessageArgs { 208 size_t totalLength_; 209 /* only {0} thru {9} supported */ 210 mozilla::Array<const char*, JS::MaxNumErrorArguments> args_; 211 mozilla::Array<size_t, JS::MaxNumErrorArguments> lengths_; 212 uint16_t count_; 213 bool allocatedElements_ : 1; 214 215 public: 216 AutoMessageArgs() : totalLength_(0), count_(0), allocatedElements_(false) { 217 PodArrayZero(args_); 218 } 219 220 ~AutoMessageArgs() { 221 /* free the arguments only if we allocated them */ 222 if (allocatedElements_) { 223 uint16_t i = 0; 224 while (i < count_) { 225 if (args_[i]) { 226 js_free((void*)args_[i]); 227 } 228 i++; 229 } 230 } 231 } 232 233 const char* args(size_t i) const { 234 MOZ_ASSERT(i < count_); 235 return args_[i]; 236 } 237 238 size_t totalLength() const { return totalLength_; } 239 240 size_t lengths(size_t i) const { 241 MOZ_ASSERT(i < count_); 242 return lengths_[i]; 243 } 244 245 uint16_t count() const { return count_; } 246 247 /* Gather the arguments into an array, and accumulate their sizes. 248 * 249 * We could template on the type of argsArg, but we're already trusting people 250 * to do the right thing with varargs, so might as well trust them on this 251 * part too. Upstream consumers do assert that it's the right thing. Also, 252 * if argsArg were strongly typed we'd still need casting below for this to 253 * compile, because typeArg is not known at compile-time here. 254 */ 255 template <typename Allocator> 256 bool init(Allocator* alloc, void* argsArg, uint16_t countArg, 257 ErrorArgumentsType typeArg, va_list ap) { 258 MOZ_ASSERT(countArg > 0); 259 260 count_ = countArg; 261 262 for (uint16_t i = 0; i < count_; i++) { 263 switch (typeArg) { 264 case ArgumentsAreASCII: 265 case ArgumentsAreUTF8: { 266 const char* c = argsArg ? static_cast<const char**>(argsArg)[i] 267 : va_arg(ap, const char*); 268 args_[i] = c; 269 MOZ_ASSERT_IF(typeArg == ArgumentsAreASCII, 270 JS::StringIsASCII(args_[i])); 271 lengths_[i] = strlen(args_[i]); 272 break; 273 } 274 case ArgumentsAreLatin1: { 275 MOZ_ASSERT(!argsArg); 276 const Latin1Char* latin1 = va_arg(ap, Latin1Char*); 277 size_t len = strlen(reinterpret_cast<const char*>(latin1)); 278 mozilla::Range<const Latin1Char> range(latin1, len); 279 char* utf8 = JS::CharsToNewUTF8CharsZ(alloc, range).c_str(); 280 if (!utf8) { 281 return false; 282 } 283 284 args_[i] = utf8; 285 lengths_[i] = strlen(utf8); 286 allocatedElements_ = true; 287 break; 288 } 289 case ArgumentsAreUnicode: { 290 const char16_t* uc = argsArg 291 ? static_cast<const char16_t**>(argsArg)[i] 292 : va_arg(ap, const char16_t*); 293 size_t len = js_strlen(uc); 294 mozilla::Range<const char16_t> range(uc, len); 295 char* utf8 = JS::CharsToNewUTF8CharsZ(alloc, range).c_str(); 296 if (!utf8) { 297 return false; 298 } 299 300 args_[i] = utf8; 301 lengths_[i] = strlen(utf8); 302 allocatedElements_ = true; 303 break; 304 } 305 } 306 totalLength_ += lengths_[i]; 307 } 308 return true; 309 } 310 }; 311 312 /* 313 * The arguments from ap need to be packaged up into an array and stored 314 * into the report struct. 315 * 316 * The format string addressed by the error number may contain operands 317 * identified by the format {N}, where N is a decimal digit. Each of these 318 * is to be replaced by the Nth argument from the va_list. The complete 319 * message is placed into reportp->message_. 320 * 321 * Returns true if the expansion succeeds (can fail if out of memory). 322 * 323 * messageArgs is a `const char**` or a `const char16_t**` but templating on 324 * that is not worth it here because AutoMessageArgs takes a void* anyway, and 325 * using void* here simplifies our callers a bit. 326 */ 327 template <typename T> 328 static bool ExpandErrorArgumentsHelper(FrontendContext* fc, 329 JSErrorCallback callback, void* userRef, 330 const unsigned errorNumber, 331 void* messageArgs, 332 ErrorArgumentsType argumentsType, 333 T* reportp, va_list ap) { 334 const JSErrorFormatString* efs; 335 336 if (!callback) { 337 callback = GetErrorMessage; 338 } 339 340 efs = fc->gcSafeCallback(callback, userRef, errorNumber); 341 342 if (efs) { 343 if constexpr (std::is_same_v<T, JSErrorReport>) { 344 reportp->exnType = efs->exnType; 345 } 346 347 MOZ_ASSERT(reportp->errorNumber == errorNumber); 348 reportp->errorMessageName = efs->name; 349 350 MOZ_ASSERT_IF(argumentsType == ArgumentsAreASCII, 351 JS::StringIsASCII(efs->format)); 352 353 uint16_t argCount = efs->argCount; 354 MOZ_RELEASE_ASSERT(argCount <= JS::MaxNumErrorArguments); 355 if (argCount > 0) { 356 /* 357 * Parse the error format, substituting the argument X 358 * for {X} in the format. 359 */ 360 if (efs->format) { 361 const char* fmt; 362 char* out; 363 #ifdef DEBUG 364 int expandedArgs = 0; 365 #endif 366 size_t expandedLength; 367 size_t len = strlen(efs->format); 368 369 AutoMessageArgs args; 370 if (!args.init(fc->getAllocator(), messageArgs, argCount, argumentsType, 371 ap)) { 372 return false; 373 } 374 375 expandedLength = len - (3 * args.count()) /* exclude the {n} */ 376 + args.totalLength(); 377 378 /* 379 * Note - the above calculation assumes that each argument 380 * is used once and only once in the expansion !!! 381 */ 382 char* utf8 = out = 383 fc->getAllocator()->pod_malloc<char>(expandedLength + 1); 384 if (!out) { 385 return false; 386 } 387 388 fmt = efs->format; 389 while (*fmt) { 390 if (*fmt == '{') { 391 if (mozilla::IsAsciiDigit(fmt[1])) { 392 int d = AsciiDigitToNumber(fmt[1]); 393 MOZ_RELEASE_ASSERT(d < args.count()); 394 strncpy(out, args.args(d), args.lengths(d)); 395 out += args.lengths(d); 396 fmt += 3; 397 #ifdef DEBUG 398 expandedArgs++; 399 #endif 400 continue; 401 } 402 } 403 *out++ = *fmt++; 404 } 405 MOZ_ASSERT(expandedArgs == args.count()); 406 *out = 0; 407 408 reportp->initOwnedMessage(utf8); 409 } 410 } else { 411 /* Non-null messageArgs should have at least one non-null arg. */ 412 MOZ_ASSERT(!messageArgs); 413 /* 414 * Zero arguments: the format string (if it exists) is the 415 * entire message. 416 */ 417 if (efs->format) { 418 reportp->initBorrowedMessage(efs->format); 419 } 420 } 421 } 422 if (!reportp->message()) { 423 /* where's the right place for this ??? */ 424 const char* defaultErrorMessage = 425 "No error message available for error number %d"; 426 size_t nbytes = strlen(defaultErrorMessage) + 16; 427 char* message = fc->getAllocator()->pod_malloc<char>(nbytes); 428 if (!message) { 429 return false; 430 } 431 snprintf(message, nbytes, defaultErrorMessage, errorNumber); 432 reportp->initOwnedMessage(message); 433 } 434 return true; 435 } 436 437 bool js::ExpandErrorArgumentsVA(FrontendContext* fc, JSErrorCallback callback, 438 void* userRef, const unsigned errorNumber, 439 const char16_t** messageArgs, 440 ErrorArgumentsType argumentsType, 441 JSErrorReport* reportp, va_list ap) { 442 MOZ_ASSERT(argumentsType == ArgumentsAreUnicode); 443 return ExpandErrorArgumentsHelper(fc, callback, userRef, errorNumber, 444 messageArgs, argumentsType, reportp, ap); 445 } 446 447 bool js::ExpandErrorArgumentsVA(FrontendContext* fc, JSErrorCallback callback, 448 void* userRef, const unsigned errorNumber, 449 const char** messageArgs, 450 ErrorArgumentsType argumentsType, 451 JSErrorReport* reportp, va_list ap) { 452 MOZ_ASSERT(argumentsType != ArgumentsAreUnicode); 453 return ExpandErrorArgumentsHelper(fc, callback, userRef, errorNumber, 454 messageArgs, argumentsType, reportp, ap); 455 } 456 457 bool js::ExpandErrorArgumentsVA(FrontendContext* fc, JSErrorCallback callback, 458 void* userRef, const unsigned errorNumber, 459 ErrorArgumentsType argumentsType, 460 JSErrorReport* reportp, va_list ap) { 461 return ExpandErrorArgumentsHelper(fc, callback, userRef, errorNumber, nullptr, 462 argumentsType, reportp, ap); 463 } 464 465 bool js::ExpandErrorArgumentsVA(FrontendContext* fc, JSErrorCallback callback, 466 void* userRef, const unsigned errorNumber, 467 const char16_t** messageArgs, 468 ErrorArgumentsType argumentsType, 469 JSErrorNotes::Note* notep, va_list ap) { 470 return ExpandErrorArgumentsHelper(fc, callback, userRef, errorNumber, 471 messageArgs, argumentsType, notep, ap); 472 } 473 474 bool js::ReportErrorNumberVA(JSContext* cx, IsWarning isWarning, 475 JSErrorCallback callback, void* userRef, 476 const unsigned errorNumber, 477 ErrorArgumentsType argumentsType, va_list ap) { 478 JSErrorReport report; 479 report.isWarning_ = isWarning == IsWarning::Yes; 480 report.errorNumber = errorNumber; 481 PopulateReportBlame(cx, &report); 482 483 AutoReportFrontendContext fc(cx); 484 if (!ExpandErrorArgumentsVA(&fc, callback, userRef, errorNumber, 485 argumentsType, &report, ap)) { 486 return false; 487 } 488 489 if (!ReportError(cx, &report, callback, userRef)) { 490 return false; 491 } 492 493 return report.isWarning(); 494 } 495 496 template <typename CharT> 497 static bool ExpandErrorArguments(FrontendContext* fc, JSErrorCallback callback, 498 void* userRef, const unsigned errorNumber, 499 const CharT** messageArgs, 500 js::ErrorArgumentsType argumentsType, 501 JSErrorReport* reportp, ...) { 502 va_list ap; 503 va_start(ap, reportp); 504 bool expanded = 505 js::ExpandErrorArgumentsVA(fc, callback, userRef, errorNumber, 506 messageArgs, argumentsType, reportp, ap); 507 va_end(ap); 508 return expanded; 509 } 510 511 template <js::ErrorArgumentsType argType, typename CharT> 512 static bool ReportErrorNumberArray(JSContext* cx, IsWarning isWarning, 513 JSErrorCallback callback, void* userRef, 514 const unsigned errorNumber, 515 const CharT** args) { 516 static_assert( 517 (argType == ArgumentsAreUnicode && std::is_same_v<CharT, char16_t>) || 518 (argType != ArgumentsAreUnicode && std::is_same_v<CharT, char>), 519 "Mismatch between character type and argument type"); 520 521 JSErrorReport report; 522 report.isWarning_ = isWarning == IsWarning::Yes; 523 report.errorNumber = errorNumber; 524 PopulateReportBlame(cx, &report); 525 526 AutoReportFrontendContext fc(cx); 527 if (!ExpandErrorArguments(&fc, callback, userRef, errorNumber, args, argType, 528 &report)) { 529 return false; 530 } 531 532 if (!ReportError(cx, &report, callback, userRef)) { 533 return false; 534 } 535 536 return report.isWarning(); 537 } 538 539 bool js::ReportErrorNumberUCArray(JSContext* cx, IsWarning isWarning, 540 JSErrorCallback callback, void* userRef, 541 const unsigned errorNumber, 542 const char16_t** args) { 543 return ReportErrorNumberArray<ArgumentsAreUnicode>( 544 cx, isWarning, callback, userRef, errorNumber, args); 545 } 546 547 bool js::ReportErrorNumberUTF8Array(JSContext* cx, IsWarning isWarning, 548 JSErrorCallback callback, void* userRef, 549 const unsigned errorNumber, 550 const char** args) { 551 return ReportErrorNumberArray<ArgumentsAreUTF8>(cx, isWarning, callback, 552 userRef, errorNumber, args); 553 } 554 555 bool js::ReportErrorVA(JSContext* cx, IsWarning isWarning, const char* format, 556 js::ErrorArgumentsType argumentsType, va_list ap) { 557 JSErrorReport report; 558 559 UniqueChars message(JS_vsmprintf(format, ap)); 560 if (!message) { 561 ReportOutOfMemory(cx); 562 return false; 563 } 564 565 MOZ_ASSERT_IF(argumentsType == ArgumentsAreASCII, 566 JS::StringIsASCII(message.get())); 567 568 report.isWarning_ = isWarning == IsWarning::Yes; 569 report.errorNumber = JSMSG_USER_DEFINED_ERROR; 570 if (argumentsType == ArgumentsAreASCII || argumentsType == ArgumentsAreUTF8) { 571 report.initOwnedMessage(message.release()); 572 } else { 573 MOZ_ASSERT(argumentsType == ArgumentsAreLatin1); 574 JS::Latin1Chars latin1(message.get(), strlen(message.get())); 575 JS::UTF8CharsZ utf8(JS::CharsToNewUTF8CharsZ(cx, latin1)); 576 if (!utf8) { 577 return false; 578 } 579 report.initOwnedMessage(reinterpret_cast<const char*>(utf8.get())); 580 } 581 PopulateReportBlame(cx, &report); 582 583 if (!ReportError(cx, &report, nullptr, nullptr)) { 584 return false; 585 } 586 587 return report.isWarning(); 588 } 589 590 void js::MaybePrintAndClearPendingException(JSContext* cx) { 591 if (!cx->isExceptionPending()) { 592 return; 593 } 594 595 AutoClearPendingException acpe(cx); 596 597 JS::ExceptionStack exnStack(cx); 598 if (!JS::StealPendingExceptionStack(cx, &exnStack)) { 599 fprintf(stderr, "error getting pending exception\n"); 600 return; 601 } 602 603 JS::ErrorReportBuilder report(cx); 604 if (!report.init(cx, exnStack, JS::ErrorReportBuilder::WithSideEffects)) { 605 fprintf(stderr, "out of memory initializing JS::ErrorReportBuilder\n"); 606 return; 607 } 608 609 MOZ_ASSERT(!report.report()->isWarning()); 610 JS::PrintError(stderr, report, true); 611 }