ErrorReport.h (20147B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 /* 7 * Error-reporting APIs. 8 * 9 * Despite the types and structures defined here existing in js/public, 10 * significant parts of their heritage date back to distant SpiderMonkey past, 11 * and they are not all universally well-thought-out as ideal, 12 * intended-to-be-permanent API. We may eventually replace this with something 13 * more consistent with ECMAScript the language and less consistent with 14 * '90s-era JSAPI inventions, but it's doubtful this will happen any time soon. 15 */ 16 17 #ifndef js_ErrorReport_h 18 #define js_ErrorReport_h 19 20 #include "mozilla/Assertions.h" // MOZ_ASSERT 21 #include "mozilla/Maybe.h" // mozilla::Maybe 22 23 #include <cstdarg> 24 #include <iterator> // std::input_iterator_tag, std::iterator 25 #include <stdarg.h> 26 #include <stddef.h> // size_t 27 #include <stdint.h> // int16_t, uint16_t 28 #include <string.h> // strlen 29 30 #include "jstypes.h" // JS_PUBLIC_API 31 32 #include "js/AllocPolicy.h" 33 #include "js/CharacterEncoding.h" // JS::ConstUTF8CharsZ 34 #include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin 35 #include "js/RootingAPI.h" // JS::HandleObject, JS::RootedObject 36 #include "js/UniquePtr.h" // js::UniquePtr 37 #include "js/Value.h" // JS::Value 38 #include "js/Vector.h" // js::Vector 39 40 struct JS_PUBLIC_API JSContext; 41 class JS_PUBLIC_API JSString; 42 43 namespace JS { 44 class ExceptionStack; 45 } 46 namespace js { 47 class SystemAllocPolicy; 48 49 enum ErrorArgumentsType { 50 ArgumentsAreUnicode, 51 ArgumentsAreASCII, 52 ArgumentsAreLatin1, 53 ArgumentsAreUTF8 54 }; 55 } // namespace js 56 57 /** 58 * Possible exception types. These types are part of a JSErrorFormatString 59 * structure. They define which error to throw in case of a runtime error. 60 * 61 * JSEXN_WARN is used for warnings, that are not strictly errors but are handled 62 * using the generalized error reporting mechanism. (One side effect of this 63 * type is to not prepend 'Error:' to warning messages.) This value can go away 64 * if we ever decide to use an entirely separate mechanism for warnings. 65 * 66 * The errors and warnings are arranged in alphabetically within their 67 * respective categories as defined in the comments below. 68 */ 69 enum JSExnType { 70 // Generic Errors 71 JSEXN_ERR, 72 JSEXN_FIRST = JSEXN_ERR, 73 // Internal Errors 74 JSEXN_INTERNALERR, 75 // ECMAScript Errors 76 JSEXN_AGGREGATEERR, 77 JSEXN_EVALERR, 78 JSEXN_RANGEERR, 79 JSEXN_REFERENCEERR, 80 #ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT 81 JSEXN_SUPPRESSEDERR, 82 #endif 83 JSEXN_SYNTAXERR, 84 JSEXN_TYPEERR, 85 JSEXN_URIERR, 86 // Debugger Errors 87 JSEXN_DEBUGGEEWOULDRUN, 88 // WASM Errors 89 JSEXN_WASMCOMPILEERROR, 90 JSEXN_WASMLINKERROR, 91 JSEXN_WASMRUNTIMEERROR, 92 #ifdef ENABLE_WASM_JSPI 93 JSEXN_WASMSUSPENDERROR, 94 #endif 95 JSEXN_ERROR_LIMIT, 96 // Warnings 97 JSEXN_WARN = JSEXN_ERROR_LIMIT, 98 // Error Notes 99 JSEXN_NOTE, 100 JSEXN_LIMIT 101 }; 102 103 struct JSErrorFormatString { 104 /** The error message name in ASCII. */ 105 const char* name; 106 107 /** The error format string in ASCII. */ 108 const char* format; 109 110 /** The number of arguments to expand in the formatted error message. */ 111 uint16_t argCount; 112 113 /** One of the JSExnType constants above. */ 114 int16_t exnType; 115 }; 116 117 using JSErrorCallback = 118 const JSErrorFormatString* (*)(void* userRef, const unsigned errorNumber); 119 120 /** 121 * Base class that implements parts shared by JSErrorReport and 122 * JSErrorNotes::Note. 123 */ 124 class JSErrorBase { 125 private: 126 // The (default) error message. 127 // If ownsMessage_ is true, the it is freed in destructor. 128 JS::ConstUTF8CharsZ message_; 129 130 public: 131 // Source file name, URL, etc., or null. 132 JS::ConstUTF8CharsZ filename; 133 134 // Unique identifier for the script source. 135 unsigned sourceId; 136 137 // Source line number (1-origin). 138 uint32_t lineno; 139 140 // Column number in line in UTF-16 code units. 141 JS::ColumnNumberOneOrigin column; 142 143 // the error number, e.g. see js/public/friend/ErrorNumbers.msg. 144 unsigned errorNumber; 145 146 // Points to JSErrorFormatString::name. 147 // This string must never be freed. 148 const char* errorMessageName; 149 150 private: 151 bool ownsMessage_ : 1; 152 153 public: 154 JSErrorBase() 155 : filename(nullptr), 156 sourceId(0), 157 lineno(0), 158 errorNumber(0), 159 errorMessageName(nullptr), 160 ownsMessage_(false) {} 161 JSErrorBase(JSErrorBase&& other) noexcept 162 : message_(other.message_), 163 filename(other.filename), 164 sourceId(other.sourceId), 165 lineno(other.lineno), 166 column(other.column), 167 errorNumber(other.errorNumber), 168 errorMessageName(other.errorMessageName), 169 ownsMessage_(other.ownsMessage_) { 170 if (ownsMessage_) { 171 other.ownsMessage_ = false; 172 } 173 } 174 175 ~JSErrorBase() { freeMessage(); } 176 177 public: 178 const JS::ConstUTF8CharsZ message() const { return message_; } 179 180 void initOwnedMessage(const char* messageArg) { 181 initBorrowedMessage(messageArg); 182 ownsMessage_ = true; 183 } 184 void initBorrowedMessage(const char* messageArg) { 185 MOZ_ASSERT(!message_); 186 message_ = JS::ConstUTF8CharsZ(messageArg, strlen(messageArg)); 187 } 188 189 JSString* newMessageString(JSContext* cx); 190 191 private: 192 void freeMessage(); 193 }; 194 195 /** 196 * Notes associated with JSErrorReport. 197 */ 198 class JSErrorNotes { 199 public: 200 class Note final : public JSErrorBase {}; 201 202 private: 203 // Stores pointers to each note. 204 js::Vector<js::UniquePtr<Note>, 1, js::SystemAllocPolicy> notes_; 205 206 bool addNoteVA(js::FrontendContext* fc, const char* filename, 207 unsigned sourceId, uint32_t lineno, 208 JS::ColumnNumberOneOrigin column, 209 JSErrorCallback errorCallback, void* userRef, 210 const unsigned errorNumber, 211 js::ErrorArgumentsType argumentsType, va_list ap); 212 213 public: 214 JSErrorNotes(); 215 ~JSErrorNotes(); 216 217 // Add a note to the given position. 218 bool addNoteASCII(JSContext* cx, const char* filename, unsigned sourceId, 219 uint32_t lineno, JS::ColumnNumberOneOrigin column, 220 JSErrorCallback errorCallback, void* userRef, 221 const unsigned errorNumber, ...); 222 bool addNoteASCII(js::FrontendContext* fc, const char* filename, 223 unsigned sourceId, uint32_t lineno, 224 JS::ColumnNumberOneOrigin column, 225 JSErrorCallback errorCallback, void* userRef, 226 const unsigned errorNumber, ...); 227 bool addNoteLatin1(JSContext* cx, const char* filename, unsigned sourceId, 228 uint32_t lineno, JS::ColumnNumberOneOrigin column, 229 JSErrorCallback errorCallback, void* userRef, 230 const unsigned errorNumber, ...); 231 bool addNoteLatin1(js::FrontendContext* fc, const char* filename, 232 unsigned sourceId, uint32_t lineno, 233 JS::ColumnNumberOneOrigin column, 234 JSErrorCallback errorCallback, void* userRef, 235 const unsigned errorNumber, ...); 236 bool addNoteUTF8(JSContext* cx, const char* filename, unsigned sourceId, 237 uint32_t lineno, JS::ColumnNumberOneOrigin column, 238 JSErrorCallback errorCallback, void* userRef, 239 const unsigned errorNumber, ...); 240 bool addNoteUTF8(js::FrontendContext* fc, const char* filename, 241 unsigned sourceId, uint32_t lineno, 242 JS::ColumnNumberOneOrigin column, 243 JSErrorCallback errorCallback, void* userRef, 244 const unsigned errorNumber, ...); 245 246 JS_PUBLIC_API size_t length(); 247 248 // Create a deep copy of notes. 249 js::UniquePtr<JSErrorNotes> copy(JSContext* cx); 250 251 class iterator final { 252 private: 253 js::UniquePtr<Note>* note_; 254 255 public: 256 using iterator_category = std::input_iterator_tag; 257 using value_type = js::UniquePtr<Note>; 258 using difference_type = ptrdiff_t; 259 using pointer = value_type*; 260 using reference = value_type&; 261 262 explicit iterator(js::UniquePtr<Note>* note = nullptr) : note_(note) {} 263 264 bool operator==(iterator other) const { return note_ == other.note_; } 265 bool operator!=(iterator other) const { return !(*this == other); } 266 iterator& operator++() { 267 note_++; 268 return *this; 269 } 270 reference operator*() { return *note_; } 271 }; 272 273 JS_PUBLIC_API iterator begin(); 274 JS_PUBLIC_API iterator end(); 275 }; 276 277 /** 278 * Describes a single error or warning that occurs in the execution of script. 279 */ 280 class JSErrorReport : public JSErrorBase { 281 private: 282 // Offending source line without final '\n'. 283 // If ownsLinebuf_ is true, the buffer is freed in destructor. 284 const char16_t* linebuf_; 285 286 // Number of chars in linebuf_. Does not include trailing '\0'. 287 size_t linebufLength_; 288 289 // The 0-based offset of error token in linebuf_. 290 size_t tokenOffset_; 291 292 public: 293 // Associated notes, or nullptr if there's no note. 294 js::UniquePtr<JSErrorNotes> notes; 295 296 // One of the JSExnType constants. 297 int16_t exnType; 298 299 // See the comment in TransitiveCompileOptions. 300 bool isMuted : 1; 301 302 // This error report is actually a warning. 303 bool isWarning_ : 1; 304 305 private: 306 bool ownsLinebuf_ : 1; 307 308 public: 309 JSErrorReport() 310 : linebuf_(nullptr), 311 linebufLength_(0), 312 tokenOffset_(0), 313 notes(nullptr), 314 exnType(0), 315 isMuted(false), 316 isWarning_(false), 317 ownsLinebuf_(false) {} 318 JSErrorReport(JSErrorReport&& other) noexcept 319 : JSErrorBase(std::move(other)), 320 linebuf_(other.linebuf_), 321 linebufLength_(other.linebufLength_), 322 tokenOffset_(other.tokenOffset_), 323 notes(std::move(other.notes)), 324 exnType(other.exnType), 325 isMuted(other.isMuted), 326 isWarning_(other.isWarning_), 327 ownsLinebuf_(other.ownsLinebuf_) { 328 if (ownsLinebuf_) { 329 other.ownsLinebuf_ = false; 330 } 331 } 332 333 ~JSErrorReport() { freeLinebuf(); } 334 335 public: 336 const char16_t* linebuf() const { return linebuf_; } 337 size_t linebufLength() const { return linebufLength_; } 338 size_t tokenOffset() const { return tokenOffset_; } 339 void initOwnedLinebuf(const char16_t* linebufArg, size_t linebufLengthArg, 340 size_t tokenOffsetArg) { 341 initBorrowedLinebuf(linebufArg, linebufLengthArg, tokenOffsetArg); 342 ownsLinebuf_ = true; 343 } 344 void initBorrowedLinebuf(const char16_t* linebufArg, size_t linebufLengthArg, 345 size_t tokenOffsetArg); 346 347 bool isWarning() const { return isWarning_; } 348 349 private: 350 void freeLinebuf(); 351 }; 352 353 namespace JS { 354 355 struct MOZ_STACK_CLASS JS_PUBLIC_API ErrorReportBuilder { 356 explicit ErrorReportBuilder(JSContext* cx); 357 ~ErrorReportBuilder(); 358 359 enum SniffingBehavior { WithSideEffects, NoSideEffects }; 360 361 /** 362 * Generate a JSErrorReport from the provided thrown value. 363 * 364 * If the value is a (possibly wrapped) Error object, the JSErrorReport will 365 * be exactly initialized from the Error object's information, without 366 * observable side effects. (The Error object's JSErrorReport is reused, if 367 * it has one.) 368 * 369 * Otherwise various attempts are made to derive JSErrorReport information 370 * from |exnStack| and from the current execution state. This process is 371 * *definitely* inconsistent with any standard, and particulars of the 372 * behavior implemented here generally shouldn't be relied upon. 373 * 374 * If the value of |sniffingBehavior| is |WithSideEffects|, some of these 375 * attempts *may* invoke user-configurable behavior when the exception is an 376 * object: converting it to a string, detecting and getting its properties, 377 * accessing its prototype chain, and others are possible. Users *must* 378 * tolerate |ErrorReportBuilder::init| potentially having arbitrary effects. 379 * Any exceptions thrown by these operations will be caught and silently 380 * ignored, and "default" values will be substituted into the JSErrorReport. 381 * 382 * But if the value of |sniffingBehavior| is |NoSideEffects|, these attempts 383 * *will not* invoke any observable side effects. The JSErrorReport will 384 * simply contain fewer, less precise details. 385 * 386 * Unlike some functions involved in error handling, this function adheres 387 * to the usual JSAPI return value error behavior. 388 */ 389 bool init(JSContext* cx, const JS::ExceptionStack& exnStack, 390 SniffingBehavior sniffingBehavior); 391 392 JSErrorReport* report() const { return reportp; } 393 394 const JS::ConstUTF8CharsZ toStringResult() const { return toStringResult_; } 395 396 private: 397 // More or less an equivalent of JS_ReportErrorNumber/js::ReportErrorNumberVA 398 // but fills in an ErrorReport instead of reporting it. Uses varargs to 399 // make it simpler to call js::ExpandErrorArgumentsVA. 400 // 401 // Returns false if we fail to actually populate the ErrorReport 402 // for some reason (probably out of memory). 403 bool populateUncaughtExceptionReportUTF8(JSContext* cx, 404 JS::HandleObject stack, ...); 405 bool populateUncaughtExceptionReportUTF8VA(JSContext* cx, 406 JS::HandleObject stack, 407 va_list ap); 408 409 // Reports exceptions from add-on scopes to telemetry. 410 void ReportAddonExceptionToTelemetry(JSContext* cx); 411 412 // Maybe create an (owned) JSErrorReport when obj is a DOM Exception object. 413 // Return the "toStringResult" str. 414 JSString* maybeCreateReportFromDOMException(JS::HandleObject obj, 415 JSContext* cx); 416 417 // We may have a provided JSErrorReport, so need a way to represent that. 418 JSErrorReport* reportp; 419 420 // Or we may need to synthesize a JSErrorReport one of our own. 421 JSErrorReport ownedReport; 422 423 // Root our exception value to keep a possibly borrowed |reportp| alive. 424 JS::RootedObject exnObject; 425 426 // And for our filename. 427 JS::UniqueChars filename; 428 429 // We may have a result of error.toString(). 430 // FIXME: We should not call error.toString(), since it could have side 431 // effect (see bug 633623). 432 JS::ConstUTF8CharsZ toStringResult_; 433 JS::UniqueChars toStringResultBytesStorage; 434 }; 435 436 // Writes a full report to a file descriptor. Does nothing for JSErrorReports 437 // which are warnings, unless reportWarnings is set. 438 extern JS_PUBLIC_API void PrintError(FILE* file, JSErrorReport* report, 439 bool reportWarnings); 440 441 extern JS_PUBLIC_API void PrintError(FILE* file, 442 const JS::ErrorReportBuilder& builder, 443 bool reportWarnings); 444 445 } // namespace JS 446 447 /* 448 * There are four encoding variants for the error reporting API: 449 * UTF-8 450 * JSAPI's default encoding for error handling. Use this when the encoding 451 * of the error message, format string, and arguments is UTF-8. 452 * ASCII 453 * Equivalent to UTF-8, but also asserts that the error message, format 454 * string, and arguments are all ASCII. Because ASCII is a subset of UTF-8, 455 * any use of this encoding variant *could* be replaced with use of the 456 * UTF-8 variant. This variant exists solely to double-check the 457 * developer's assumption that all these strings truly are ASCII, given that 458 * UTF-8 and ASCII strings regrettably have the same C++ type. 459 * UC = UTF-16 460 * Use this when arguments are UTF-16. The format string must be UTF-8. 461 * Latin1 (planned to be removed) 462 * In this variant, all strings are interpreted byte-for-byte as the 463 * corresponding Unicode codepoint. This encoding may *safely* be used on 464 * any null-terminated string, regardless of its encoding. (You shouldn't 465 * *actually* be uncertain, but in the real world, a string's encoding -- if 466 * promised at all -- may be more...aspirational...than reality.) This 467 * encoding variant will eventually be removed -- work to convert your uses 468 * to UTF-8 as you're able. 469 */ 470 471 namespace JS { 472 const uint16_t MaxNumErrorArguments = 10; 473 }; 474 475 /** 476 * Report an exception represented by the sprintf-like conversion of format 477 * and its arguments. 478 */ 479 extern JS_PUBLIC_API void JS_ReportErrorASCII(JSContext* cx, const char* format, 480 ...) MOZ_FORMAT_PRINTF(2, 3); 481 482 extern JS_PUBLIC_API void JS_ReportErrorLatin1(JSContext* cx, 483 const char* format, ...) 484 MOZ_FORMAT_PRINTF(2, 3); 485 486 extern JS_PUBLIC_API void JS_ReportErrorUTF8(JSContext* cx, const char* format, 487 ...) MOZ_FORMAT_PRINTF(2, 3); 488 489 /* 490 * Use an errorNumber to retrieve the format string, args are char* 491 */ 492 extern JS_PUBLIC_API void JS_ReportErrorNumberASCII( 493 JSContext* cx, JSErrorCallback errorCallback, void* userRef, 494 const unsigned errorNumber, ...); 495 496 extern JS_PUBLIC_API void JS_ReportErrorNumberASCIIVA( 497 JSContext* cx, JSErrorCallback errorCallback, void* userRef, 498 const unsigned errorNumber, va_list ap); 499 500 extern JS_PUBLIC_API void JS_ReportErrorNumberLatin1( 501 JSContext* cx, JSErrorCallback errorCallback, void* userRef, 502 const unsigned errorNumber, ...); 503 504 #ifdef va_start 505 extern JS_PUBLIC_API void JS_ReportErrorNumberLatin1VA( 506 JSContext* cx, JSErrorCallback errorCallback, void* userRef, 507 const unsigned errorNumber, va_list ap); 508 #endif 509 510 extern JS_PUBLIC_API void JS_ReportErrorNumberUTF8( 511 JSContext* cx, JSErrorCallback errorCallback, void* userRef, 512 const unsigned errorNumber, ...); 513 514 #ifdef va_start 515 extern JS_PUBLIC_API void JS_ReportErrorNumberUTF8VA( 516 JSContext* cx, JSErrorCallback errorCallback, void* userRef, 517 const unsigned errorNumber, va_list ap); 518 #endif 519 520 /* 521 * args is null-terminated. That is, a null char* means there are no 522 * more args. The number of args must match the number expected for 523 * errorNumber for the given JSErrorCallback. 524 */ 525 extern JS_PUBLIC_API void JS_ReportErrorNumberUTF8Array( 526 JSContext* cx, JSErrorCallback errorCallback, void* userRef, 527 const unsigned errorNumber, const char** args); 528 529 /* 530 * Use an errorNumber to retrieve the format string, args are char16_t* 531 */ 532 extern JS_PUBLIC_API void JS_ReportErrorNumberUC(JSContext* cx, 533 JSErrorCallback errorCallback, 534 void* userRef, 535 const unsigned errorNumber, 536 ...); 537 538 extern JS_PUBLIC_API void JS_ReportErrorNumberUCArray( 539 JSContext* cx, JSErrorCallback errorCallback, void* userRef, 540 const unsigned errorNumber, const char16_t** args); 541 542 /** 543 * Complain when out of memory. 544 */ 545 extern MOZ_COLD JS_PUBLIC_API void JS_ReportOutOfMemory(JSContext* cx); 546 547 extern JS_PUBLIC_API bool JS_ExpandErrorArgumentsASCII( 548 JSContext* cx, JSErrorCallback errorCallback, const unsigned errorNumber, 549 JSErrorReport* reportp, ...); 550 551 /** 552 * Complain when an allocation size overflows the maximum supported limit. 553 */ 554 extern JS_PUBLIC_API void JS_ReportAllocationOverflow(JSContext* cx); 555 556 namespace JS { 557 558 extern JS_PUBLIC_API bool CreateError( 559 JSContext* cx, JSExnType type, HandleObject stack, HandleString fileName, 560 uint32_t lineNumber, JS::ColumnNumberOneOrigin column, 561 JSErrorReport* report, HandleString message, 562 Handle<mozilla::Maybe<Value>> cause, MutableHandleValue rval); 563 564 /** 565 * An uncatchable exception is used to terminate execution by returning false 566 * or nullptr without reporting a pending exception on the context. These 567 * exceptions are called "uncatchable" because try-catch can't be used to catch 568 * them. 569 * 570 * This is mainly used to terminate JS execution from the interrupt handler. 571 * 572 * If the context has a pending exception, this function will clear it. Also, in 573 * debug builds, it sets a flag on the context to improve exception handling 574 * assertions in the engine. 575 */ 576 extern JS_PUBLIC_API void ReportUncatchableException(JSContext* cx); 577 578 } /* namespace JS */ 579 580 #endif /* js_ErrorReport_h */