summaryrefslogtreecommitdiffstats
path: root/js/public/ErrorReport.h
diff options
context:
space:
mode:
Diffstat (limited to 'js/public/ErrorReport.h')
-rw-r--r--js/public/ErrorReport.h541
1 files changed, 541 insertions, 0 deletions
diff --git a/js/public/ErrorReport.h b/js/public/ErrorReport.h
new file mode 100644
index 0000000000..cd69092eb2
--- /dev/null
+++ b/js/public/ErrorReport.h
@@ -0,0 +1,541 @@
+/* -*- 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 "mozilla/Maybe.h" // mozilla::Maybe
+
+#include <cstdarg>
+#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/Value.h" // JS::Value
+#include "js/Vector.h" // js::Vector
+
+struct JS_PUBLIC_API JSContext;
+class JS_PUBLIC_API JSString;
+
+namespace JS {
+class ExceptionStack;
+}
+namespace js {
+class SystemAllocPolicy;
+
+enum ErrorArgumentsType {
+ ArgumentsAreUnicode,
+ ArgumentsAreASCII,
+ ArgumentsAreLatin1,
+ ArgumentsAreUTF8
+};
+} // namespace js
+
+/**
+ * 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(JSErrorBase&& other) noexcept
+ : message_(other.message_),
+ filename(other.filename),
+ sourceId(other.sourceId),
+ lineno(other.lineno),
+ column(other.column),
+ errorNumber(other.errorNumber),
+ errorMessageName(other.errorMessageName),
+ ownsMessage_(other.ownsMessage_) {
+ if (ownsMessage_) {
+ other.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_;
+
+ bool addNoteVA(js::FrontendContext* fc, const char* filename,
+ unsigned sourceId, unsigned lineno, unsigned column,
+ JSErrorCallback errorCallback, void* userRef,
+ const unsigned errorNumber,
+ js::ErrorArgumentsType argumentsType, va_list ap);
+
+ 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 addNoteASCII(js::FrontendContext* fc, 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 addNoteLatin1(js::FrontendContext* fc, 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, ...);
+ bool addNoteUTF8(js::FrontendContext* fc, 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(JSErrorReport&& other) noexcept
+ : JSErrorBase(std::move(other)),
+ linebuf_(other.linebuf_),
+ linebufLength_(other.linebufLength_),
+ tokenOffset_(other.tokenOffset_),
+ notes(std::move(other.notes)),
+ exnType(other.exnType),
+ isMuted(other.isMuted),
+ isWarning_(other.isWarning_),
+ ownsLinebuf_(other.ownsLinebuf_) {
+ if (ownsLinebuf_) {
+ other.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, Handle<mozilla::Maybe<Value>> cause,
+ MutableHandleValue rval);
+
+} /* namespace JS */
+
+#endif /* js_ErrorReport_h */