tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 */