js/public/ErrorReport.h
author Antonin LOUBIERE <pyjacpp@laposte.net>
Sun, 26 Sep 2021 09:10:02 +0000
changeset 593160 9237deee110beeaa7605195528a273b2bbc77352
parent 581591 7b65bc3556cd61f9caad78f42268d0047687d9bc
permissions -rw-r--r--
Bug 350079 - Esc in search box now selects all and reverts. r=mak Fix bug, now escape in the search box (if the popup is disimissed) will select all the content and will revert to the last search. Differential Revision: https://phabricator.services.mozilla.com/D120137

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * Error-reporting APIs.
 *
 * Despite the types and structures defined here existing in js/public,
 * significant parts of their heritage date back to distant SpiderMonkey past,
 * and they are not all universally well-thought-out as ideal,
 * intended-to-be-permanent API.  We may eventually replace this with something
 * more consistent with ECMAScript the language and less consistent with
 * '90s-era JSAPI inventions, but it's doubtful this will happen any time soon.
 */

#ifndef js_ErrorReport_h
#define js_ErrorReport_h

#include "mozilla/Assertions.h"  // MOZ_ASSERT

#include <iterator>  // std::input_iterator_tag, std::iterator
#include <stdarg.h>
#include <stddef.h>  // size_t
#include <stdint.h>  // int16_t, uint16_t
#include <string.h>  // strlen

#include "jstypes.h"  // JS_PUBLIC_API

#include "js/AllocPolicy.h"
#include "js/CharacterEncoding.h"  // JS::ConstUTF8CharsZ
#include "js/RootingAPI.h"         // JS::HandleObject, JS::RootedObject
#include "js/UniquePtr.h"          // js::UniquePtr
#include "js/Vector.h"             // js::Vector

struct JS_PUBLIC_API JSContext;
class JS_PUBLIC_API JSString;

namespace JS {
class ExceptionStack;
}
namespace js {
class SystemAllocPolicy;
}

/**
 * Possible exception types. These types are part of a JSErrorFormatString
 * structure. They define which error to throw in case of a runtime error.
 *
 * JSEXN_WARN is used for warnings, that are not strictly errors but are handled
 * using the generalized error reporting mechanism.  (One side effect of this
 * type is to not prepend 'Error:' to warning messages.)  This value can go away
 * if we ever decide to use an entirely separate mechanism for warnings.
 */
enum JSExnType {
  JSEXN_ERR,
  JSEXN_FIRST = JSEXN_ERR,
  JSEXN_INTERNALERR,
  JSEXN_AGGREGATEERR,
  JSEXN_EVALERR,
  JSEXN_RANGEERR,
  JSEXN_REFERENCEERR,
  JSEXN_SYNTAXERR,
  JSEXN_TYPEERR,
  JSEXN_URIERR,
  JSEXN_DEBUGGEEWOULDRUN,
  JSEXN_WASMCOMPILEERROR,
  JSEXN_WASMLINKERROR,
  JSEXN_WASMRUNTIMEERROR,
  JSEXN_ERROR_LIMIT,
  JSEXN_WARN = JSEXN_ERROR_LIMIT,
  JSEXN_NOTE,
  JSEXN_LIMIT
};

struct JSErrorFormatString {
  /** The error message name in ASCII. */
  const char* name;

  /** The error format string in ASCII. */
  const char* format;

  /** The number of arguments to expand in the formatted error message. */
  uint16_t argCount;

  /** One of the JSExnType constants above. */
  int16_t exnType;
};

using JSErrorCallback =
    const JSErrorFormatString* (*)(void* userRef, const unsigned errorNumber);

/**
 * Base class that implements parts shared by JSErrorReport and
 * JSErrorNotes::Note.
 */
class JSErrorBase {
 private:
  // The (default) error message.
  // If ownsMessage_ is true, the it is freed in destructor.
  JS::ConstUTF8CharsZ message_;

 public:
  // Source file name, URL, etc., or null.
  const char* filename;

  // Unique identifier for the script source.
  unsigned sourceId;

  // Source line number.
  unsigned lineno;

  // Zero-based column index in line.
  unsigned column;

  // the error number, e.g. see js/public/friend/ErrorNumbers.msg.
  unsigned errorNumber;

  // Points to JSErrorFormatString::name.
  // This string must never be freed.
  const char* errorMessageName;

 private:
  bool ownsMessage_ : 1;

 public:
  JSErrorBase()
      : filename(nullptr),
        sourceId(0),
        lineno(0),
        column(0),
        errorNumber(0),
        errorMessageName(nullptr),
        ownsMessage_(false) {}

  ~JSErrorBase() { freeMessage(); }

 public:
  const JS::ConstUTF8CharsZ message() const { return message_; }

  void initOwnedMessage(const char* messageArg) {
    initBorrowedMessage(messageArg);
    ownsMessage_ = true;
  }
  void initBorrowedMessage(const char* messageArg) {
    MOZ_ASSERT(!message_);
    message_ = JS::ConstUTF8CharsZ(messageArg, strlen(messageArg));
  }

  JSString* newMessageString(JSContext* cx);

 private:
  void freeMessage();
};

/**
 * Notes associated with JSErrorReport.
 */
class JSErrorNotes {
 public:
  class Note final : public JSErrorBase {};

 private:
  // Stores pointers to each note.
  js::Vector<js::UniquePtr<Note>, 1, js::SystemAllocPolicy> notes_;

 public:
  JSErrorNotes();
  ~JSErrorNotes();

  // Add an note to the given position.
  bool addNoteASCII(JSContext* cx, const char* filename, unsigned sourceId,
                    unsigned lineno, unsigned column,
                    JSErrorCallback errorCallback, void* userRef,
                    const unsigned errorNumber, ...);
  bool addNoteLatin1(JSContext* cx, const char* filename, unsigned sourceId,
                     unsigned lineno, unsigned column,
                     JSErrorCallback errorCallback, void* userRef,
                     const unsigned errorNumber, ...);
  bool addNoteUTF8(JSContext* cx, const char* filename, unsigned sourceId,
                   unsigned lineno, unsigned column,
                   JSErrorCallback errorCallback, void* userRef,
                   const unsigned errorNumber, ...);

  JS_PUBLIC_API size_t length();

  // Create a deep copy of notes.
  js::UniquePtr<JSErrorNotes> copy(JSContext* cx);

  class iterator final {
   private:
    js::UniquePtr<Note>* note_;

   public:
    using iterator_category = std::input_iterator_tag;
    using value_type = js::UniquePtr<Note>;
    using difference_type = ptrdiff_t;
    using pointer = value_type*;
    using reference = value_type&;

    explicit iterator(js::UniquePtr<Note>* note = nullptr) : note_(note) {}

    bool operator==(iterator other) const { return note_ == other.note_; }
    bool operator!=(iterator other) const { return !(*this == other); }
    iterator& operator++() {
      note_++;
      return *this;
    }
    reference operator*() { return *note_; }
  };

  JS_PUBLIC_API iterator begin();
  JS_PUBLIC_API iterator end();
};

/**
 * Describes a single error or warning that occurs in the execution of script.
 */
class JSErrorReport : public JSErrorBase {
 private:
  // Offending source line without final '\n'.
  // If ownsLinebuf_ is true, the buffer is freed in destructor.
  const char16_t* linebuf_;

  // Number of chars in linebuf_. Does not include trailing '\0'.
  size_t linebufLength_;

  // The 0-based offset of error token in linebuf_.
  size_t tokenOffset_;

 public:
  // Associated notes, or nullptr if there's no note.
  js::UniquePtr<JSErrorNotes> notes;

  // One of the JSExnType constants.
  int16_t exnType;

  // See the comment in TransitiveCompileOptions.
  bool isMuted : 1;

  // This error report is actually a warning.
  bool isWarning_ : 1;

 private:
  bool ownsLinebuf_ : 1;

 public:
  JSErrorReport()
      : linebuf_(nullptr),
        linebufLength_(0),
        tokenOffset_(0),
        notes(nullptr),
        exnType(0),
        isMuted(false),
        isWarning_(false),
        ownsLinebuf_(false) {}

  ~JSErrorReport() { freeLinebuf(); }

 public:
  const char16_t* linebuf() const { return linebuf_; }
  size_t linebufLength() const { return linebufLength_; }
  size_t tokenOffset() const { return tokenOffset_; }
  void initOwnedLinebuf(const char16_t* linebufArg, size_t linebufLengthArg,
                        size_t tokenOffsetArg) {
    initBorrowedLinebuf(linebufArg, linebufLengthArg, tokenOffsetArg);
    ownsLinebuf_ = true;
  }
  void initBorrowedLinebuf(const char16_t* linebufArg, size_t linebufLengthArg,
                           size_t tokenOffsetArg);

  bool isWarning() const { return isWarning_; }

 private:
  void freeLinebuf();
};

namespace JS {

struct MOZ_STACK_CLASS JS_PUBLIC_API ErrorReportBuilder {
  explicit ErrorReportBuilder(JSContext* cx);
  ~ErrorReportBuilder();

  enum SniffingBehavior { WithSideEffects, NoSideEffects };

  /**
   * Generate a JSErrorReport from the provided thrown value.
   *
   * If the value is a (possibly wrapped) Error object, the JSErrorReport will
   * be exactly initialized from the Error object's information, without
   * observable side effects. (The Error object's JSErrorReport is reused, if
   * it has one.)
   *
   * Otherwise various attempts are made to derive JSErrorReport information
   * from |exnStack| and from the current execution state.  This process is
   * *definitely* inconsistent with any standard, and particulars of the
   * behavior implemented here generally shouldn't be relied upon.
   *
   * If the value of |sniffingBehavior| is |WithSideEffects|, some of these
   * attempts *may* invoke user-configurable behavior when the exception is an
   * object: converting it to a string, detecting and getting its properties,
   * accessing its prototype chain, and others are possible.  Users *must*
   * tolerate |ErrorReportBuilder::init| potentially having arbitrary effects.
   * Any exceptions thrown by these operations will be caught and silently
   * ignored, and "default" values will be substituted into the JSErrorReport.
   *
   * But if the value of |sniffingBehavior| is |NoSideEffects|, these attempts
   * *will not* invoke any observable side effects.  The JSErrorReport will
   * simply contain fewer, less precise details.
   *
   * Unlike some functions involved in error handling, this function adheres
   * to the usual JSAPI return value error behavior.
   */
  bool init(JSContext* cx, const JS::ExceptionStack& exnStack,
            SniffingBehavior sniffingBehavior);

  JSErrorReport* report() const { return reportp; }

  const JS::ConstUTF8CharsZ toStringResult() const { return toStringResult_; }

 private:
  // More or less an equivalent of JS_ReportErrorNumber/js::ReportErrorNumberVA
  // but fills in an ErrorReport instead of reporting it.  Uses varargs to
  // make it simpler to call js::ExpandErrorArgumentsVA.
  //
  // Returns false if we fail to actually populate the ErrorReport
  // for some reason (probably out of memory).
  bool populateUncaughtExceptionReportUTF8(JSContext* cx,
                                           JS::HandleObject stack, ...);
  bool populateUncaughtExceptionReportUTF8VA(JSContext* cx,
                                             JS::HandleObject stack,
                                             va_list ap);

  // Reports exceptions from add-on scopes to telemetry.
  void ReportAddonExceptionToTelemetry(JSContext* cx);

  // We may have a provided JSErrorReport, so need a way to represent that.
  JSErrorReport* reportp;

  // Or we may need to synthesize a JSErrorReport one of our own.
  JSErrorReport ownedReport;

  // Root our exception value to keep a possibly borrowed |reportp| alive.
  JS::RootedObject exnObject;

  // And for our filename.
  JS::UniqueChars filename;

  // We may have a result of error.toString().
  // FIXME: We should not call error.toString(), since it could have side
  //        effect (see bug 633623).
  JS::ConstUTF8CharsZ toStringResult_;
  JS::UniqueChars toStringResultBytesStorage;
};

// Writes a full report to a file descriptor. Does nothing for JSErrorReports
// which are warnings, unless reportWarnings is set.
extern JS_PUBLIC_API void PrintError(FILE* file, JSErrorReport* report,
                                     bool reportWarnings);

extern JS_PUBLIC_API void PrintError(FILE* file,
                                     const JS::ErrorReportBuilder& builder,
                                     bool reportWarnings);

}  // namespace JS

/*
 * There are four encoding variants for the error reporting API:
 *   UTF-8
 *     JSAPI's default encoding for error handling.  Use this when the encoding
 *     of the error message, format string, and arguments is UTF-8.
 *   ASCII
 *     Equivalent to UTF-8, but also asserts that the error message, format
 *     string, and arguments are all ASCII.  Because ASCII is a subset of UTF-8,
 *     any use of this encoding variant *could* be replaced with use of the
 *     UTF-8 variant.  This variant exists solely to double-check the
 *     developer's assumption that all these strings truly are ASCII, given that
 *     UTF-8 and ASCII strings regrettably have the same C++ type.
 *   UC = UTF-16
 *     Use this when arguments are UTF-16.  The format string must be UTF-8.
 *   Latin1 (planned to be removed)
 *     In this variant, all strings are interpreted byte-for-byte as the
 *     corresponding Unicode codepoint.  This encoding may *safely* be used on
 *     any null-terminated string, regardless of its encoding.  (You shouldn't
 *     *actually* be uncertain, but in the real world, a string's encoding -- if
 *     promised at all -- may be more...aspirational...than reality.)  This
 *     encoding variant will eventually be removed -- work to convert your uses
 *     to UTF-8 as you're able.
 */

namespace JS {
const uint16_t MaxNumErrorArguments = 10;
};

/**
 * Report an exception represented by the sprintf-like conversion of format
 * and its arguments.
 */
extern JS_PUBLIC_API void JS_ReportErrorASCII(JSContext* cx, const char* format,
                                              ...) MOZ_FORMAT_PRINTF(2, 3);

extern JS_PUBLIC_API void JS_ReportErrorLatin1(JSContext* cx,
                                               const char* format, ...)
    MOZ_FORMAT_PRINTF(2, 3);

extern JS_PUBLIC_API void JS_ReportErrorUTF8(JSContext* cx, const char* format,
                                             ...) MOZ_FORMAT_PRINTF(2, 3);

/*
 * Use an errorNumber to retrieve the format string, args are char*
 */
extern JS_PUBLIC_API void JS_ReportErrorNumberASCII(
    JSContext* cx, JSErrorCallback errorCallback, void* userRef,
    const unsigned errorNumber, ...);

extern JS_PUBLIC_API void JS_ReportErrorNumberASCIIVA(
    JSContext* cx, JSErrorCallback errorCallback, void* userRef,
    const unsigned errorNumber, va_list ap);

extern JS_PUBLIC_API void JS_ReportErrorNumberLatin1(
    JSContext* cx, JSErrorCallback errorCallback, void* userRef,
    const unsigned errorNumber, ...);

#ifdef va_start
extern JS_PUBLIC_API void JS_ReportErrorNumberLatin1VA(
    JSContext* cx, JSErrorCallback errorCallback, void* userRef,
    const unsigned errorNumber, va_list ap);
#endif

extern JS_PUBLIC_API void JS_ReportErrorNumberUTF8(
    JSContext* cx, JSErrorCallback errorCallback, void* userRef,
    const unsigned errorNumber, ...);

#ifdef va_start
extern JS_PUBLIC_API void JS_ReportErrorNumberUTF8VA(
    JSContext* cx, JSErrorCallback errorCallback, void* userRef,
    const unsigned errorNumber, va_list ap);
#endif

/*
 * args is null-terminated.  That is, a null char* means there are no
 * more args.  The number of args must match the number expected for
 * errorNumber for the given JSErrorCallback.
 */
extern JS_PUBLIC_API void JS_ReportErrorNumberUTF8Array(
    JSContext* cx, JSErrorCallback errorCallback, void* userRef,
    const unsigned errorNumber, const char** args);

/*
 * Use an errorNumber to retrieve the format string, args are char16_t*
 */
extern JS_PUBLIC_API void JS_ReportErrorNumberUC(JSContext* cx,
                                                 JSErrorCallback errorCallback,
                                                 void* userRef,
                                                 const unsigned errorNumber,
                                                 ...);

extern JS_PUBLIC_API void JS_ReportErrorNumberUCArray(
    JSContext* cx, JSErrorCallback errorCallback, void* userRef,
    const unsigned errorNumber, const char16_t** args);

/**
 * Complain when out of memory.
 */
extern MOZ_COLD JS_PUBLIC_API void JS_ReportOutOfMemory(JSContext* cx);

extern JS_PUBLIC_API bool JS_ExpandErrorArgumentsASCII(
    JSContext* cx, JSErrorCallback errorCallback, const unsigned errorNumber,
    JSErrorReport* reportp, ...);

/**
 * Complain when an allocation size overflows the maximum supported limit.
 */
extern JS_PUBLIC_API void JS_ReportAllocationOverflow(JSContext* cx);

namespace JS {

extern JS_PUBLIC_API bool CreateError(
    JSContext* cx, JSExnType type, HandleObject stack, HandleString fileName,
    uint32_t lineNumber, uint32_t columnNumber, JSErrorReport* report,
    HandleString message, MutableHandleValue rval);

} /* namespace JS */

#endif /* js_ErrorReport_h */