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.h361
1 files changed, 361 insertions, 0 deletions
diff --git a/js/public/ErrorReport.h b/js/public/ErrorReport.h
new file mode 100644
index 0000000000..a53e348c04
--- /dev/null
+++ b/js/public/ErrorReport.h
@@ -0,0 +1,361 @@
+/* -*- 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 types and structures.
+ *
+ * Despite these types and structures 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 <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" // js::SystemAllocPolicy
+#include "js/CharacterEncoding.h" // JS::ConstUTF8CharsZ
+#include "js/Exception.h" // JS::ExceptionStack
+#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;
+
+/**
+ * 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(JSContext* cx, FILE* file,
+ JSErrorReport* report,
+ bool reportWarnings);
+
+extern JS_PUBLIC_API void PrintError(JSContext* cx, FILE* file,
+ const JS::ErrorReportBuilder& builder,
+ bool reportWarnings);
+
+} // namespace JS
+
+#endif /* js_ErrorReport_h */