summaryrefslogtreecommitdiffstats
path: root/js/src/vm/ErrorReporting.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/vm/ErrorReporting.cpp')
-rw-r--r--js/src/vm/ErrorReporting.cpp585
1 files changed, 585 insertions, 0 deletions
diff --git a/js/src/vm/ErrorReporting.cpp b/js/src/vm/ErrorReporting.cpp
new file mode 100644
index 0000000000..cac51e7e0a
--- /dev/null
+++ b/js/src/vm/ErrorReporting.cpp
@@ -0,0 +1,585 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+#include "vm/ErrorReporting.h"
+
+#include <stdarg.h>
+#include <utility>
+
+#include "jsexn.h"
+#include "jsfriendapi.h"
+
+#include "frontend/FrontendContext.h" // AutoReportFrontendContext
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "js/Printf.h" // JS_vsmprintf
+#include "js/Warnings.h" // JS::WarningReporter
+#include "vm/FrameIter.h"
+#include "vm/GlobalObject.h"
+#include "vm/JSContext.h"
+
+using namespace js;
+
+using JS::HandleObject;
+using JS::HandleValue;
+using JS::UniqueTwoByteChars;
+
+void js::CallWarningReporter(JSContext* cx, JSErrorReport* reportp) {
+ MOZ_ASSERT(reportp->isWarning());
+
+ if (JS::WarningReporter warningReporter = cx->runtime()->warningReporter) {
+ warningReporter(cx, reportp);
+ }
+}
+
+bool js::CompileError::throwError(JSContext* cx) {
+ if (isWarning()) {
+ CallWarningReporter(cx, this);
+ return true;
+ }
+
+ // If there's a runtime exception type associated with this error
+ // number, set that as the pending exception. For errors occurring at
+ // compile time, this is very likely to be a JSEXN_SYNTAXERR.
+ return ErrorToException(cx, this, nullptr, nullptr);
+}
+
+bool js::ReportExceptionClosure::operator()(JSContext* cx) {
+ cx->setPendingException(exn_, ShouldCaptureStack::Always);
+ return false;
+}
+
+bool js::ReportCompileWarning(FrontendContext* fc, ErrorMetadata&& metadata,
+ UniquePtr<JSErrorNotes> notes,
+ unsigned errorNumber, va_list* args) {
+ // On the main thread, report the error immediately. When compiling off
+ // thread, save the error so that the thread finishing the parse can report
+ // it later.
+ CompileError err;
+
+ err.notes = std::move(notes);
+ err.isWarning_ = true;
+ err.errorNumber = errorNumber;
+
+ err.filename = metadata.filename;
+ err.lineno = metadata.lineNumber;
+ err.column = metadata.columnNumber;
+ err.isMuted = metadata.isMuted;
+
+ if (UniqueTwoByteChars lineOfContext = std::move(metadata.lineOfContext)) {
+ err.initOwnedLinebuf(lineOfContext.release(), metadata.lineLength,
+ metadata.tokenOffset);
+ }
+
+ if (!ExpandErrorArgumentsVA(fc, GetErrorMessage, nullptr, errorNumber,
+ ArgumentsAreLatin1, &err, *args)) {
+ return false;
+ }
+
+ return fc->reportWarning(std::move(err));
+}
+
+static void ReportCompileErrorImpl(FrontendContext* fc,
+ js::ErrorMetadata&& metadata,
+ js::UniquePtr<JSErrorNotes> notes,
+ unsigned errorNumber, va_list* args,
+ ErrorArgumentsType argumentsType) {
+ js::CompileError err;
+
+ err.notes = std::move(notes);
+ err.isWarning_ = false;
+ err.errorNumber = errorNumber;
+
+ err.filename = metadata.filename;
+ err.lineno = metadata.lineNumber;
+ err.column = metadata.columnNumber;
+ err.isMuted = metadata.isMuted;
+
+ if (UniqueTwoByteChars lineOfContext = std::move(metadata.lineOfContext)) {
+ err.initOwnedLinebuf(lineOfContext.release(), metadata.lineLength,
+ metadata.tokenOffset);
+ }
+
+ if (!js::ExpandErrorArgumentsVA(fc, js::GetErrorMessage, nullptr, errorNumber,
+ argumentsType, &err, *args)) {
+ return;
+ }
+
+ fc->reportError(std::move(err));
+}
+
+void js::ReportCompileErrorLatin1(FrontendContext* fc, ErrorMetadata&& metadata,
+ UniquePtr<JSErrorNotes> notes,
+ unsigned errorNumber, va_list* args) {
+ ReportCompileErrorImpl(fc, std::move(metadata), std::move(notes), errorNumber,
+ args, ArgumentsAreLatin1);
+}
+
+void js::ReportCompileErrorUTF8(FrontendContext* fc, ErrorMetadata&& metadata,
+ UniquePtr<JSErrorNotes> notes,
+ unsigned errorNumber, va_list* args) {
+ ReportCompileErrorImpl(fc, std::move(metadata), std::move(notes), errorNumber,
+ args, ArgumentsAreUTF8);
+}
+
+void js::ReportErrorToGlobal(JSContext* cx, Handle<GlobalObject*> global,
+ HandleValue error) {
+ MOZ_ASSERT(!cx->isExceptionPending());
+#ifdef DEBUG
+ // No assertSameCompartment version that doesn't take JSContext...
+ if (error.isObject()) {
+ AssertSameCompartment(global, &error.toObject());
+ }
+#endif // DEBUG
+ js::ReportExceptionClosure report(error);
+ PrepareScriptEnvironmentAndInvoke(cx, global, report);
+}
+
+static bool ReportError(JSContext* cx, JSErrorReport* reportp,
+ JSErrorCallback callback, void* userRef) {
+ if (reportp->isWarning()) {
+ CallWarningReporter(cx, reportp);
+ return true;
+ }
+
+ // Check the error report, and set a JavaScript-catchable exception
+ // if the error is defined to have an associated exception.
+ return ErrorToException(cx, reportp, callback, userRef);
+}
+
+/*
+ * The given JSErrorReport object have been zeroed and must not outlive
+ * cx->fp() (otherwise owned fields may become invalid).
+ */
+static void PopulateReportBlame(JSContext* cx, JSErrorReport* report) {
+ JS::Realm* realm = cx->realm();
+ if (!realm) {
+ return;
+ }
+
+ /*
+ * Walk stack until we find a frame that is associated with a non-builtin
+ * rather than a builtin frame and which we're allowed to know about.
+ */
+ NonBuiltinFrameIter iter(cx, realm->principals());
+ if (iter.done()) {
+ return;
+ }
+
+ report->filename = iter.filename();
+ if (iter.hasScript()) {
+ report->sourceId = iter.script()->scriptSource()->id();
+ }
+ uint32_t column;
+ report->lineno = iter.computeLine(&column);
+ report->column = FixupColumnForDisplay(column);
+ report->isMuted = iter.mutedErrors();
+}
+
+class MOZ_RAII AutoMessageArgs {
+ size_t totalLength_;
+ /* only {0} thru {9} supported */
+ mozilla::Array<const char*, JS::MaxNumErrorArguments> args_;
+ mozilla::Array<size_t, JS::MaxNumErrorArguments> lengths_;
+ uint16_t count_;
+ bool allocatedElements_ : 1;
+
+ public:
+ AutoMessageArgs() : totalLength_(0), count_(0), allocatedElements_(false) {
+ PodArrayZero(args_);
+ }
+
+ ~AutoMessageArgs() {
+ /* free the arguments only if we allocated them */
+ if (allocatedElements_) {
+ uint16_t i = 0;
+ while (i < count_) {
+ if (args_[i]) {
+ js_free((void*)args_[i]);
+ }
+ i++;
+ }
+ }
+ }
+
+ const char* args(size_t i) const {
+ MOZ_ASSERT(i < count_);
+ return args_[i];
+ }
+
+ size_t totalLength() const { return totalLength_; }
+
+ size_t lengths(size_t i) const {
+ MOZ_ASSERT(i < count_);
+ return lengths_[i];
+ }
+
+ uint16_t count() const { return count_; }
+
+ /* Gather the arguments into an array, and accumulate their sizes.
+ *
+ * We could template on the type of argsArg, but we're already trusting people
+ * to do the right thing with varargs, so might as well trust them on this
+ * part too. Upstream consumers do assert that it's the right thing. Also,
+ * if argsArg were strongly typed we'd still need casting below for this to
+ * compile, because typeArg is not known at compile-time here.
+ */
+ template <typename Allocator>
+ bool init(Allocator* alloc, void* argsArg, uint16_t countArg,
+ ErrorArgumentsType typeArg, va_list ap) {
+ MOZ_ASSERT(countArg > 0);
+
+ count_ = countArg;
+
+ for (uint16_t i = 0; i < count_; i++) {
+ switch (typeArg) {
+ case ArgumentsAreASCII:
+ case ArgumentsAreUTF8: {
+ const char* c = argsArg ? static_cast<const char**>(argsArg)[i]
+ : va_arg(ap, const char*);
+ args_[i] = c;
+ MOZ_ASSERT_IF(typeArg == ArgumentsAreASCII,
+ JS::StringIsASCII(args_[i]));
+ lengths_[i] = strlen(args_[i]);
+ break;
+ }
+ case ArgumentsAreLatin1: {
+ MOZ_ASSERT(!argsArg);
+ const Latin1Char* latin1 = va_arg(ap, Latin1Char*);
+ size_t len = strlen(reinterpret_cast<const char*>(latin1));
+ mozilla::Range<const Latin1Char> range(latin1, len);
+ char* utf8 = JS::CharsToNewUTF8CharsZ(alloc, range).c_str();
+ if (!utf8) {
+ return false;
+ }
+
+ args_[i] = utf8;
+ lengths_[i] = strlen(utf8);
+ allocatedElements_ = true;
+ break;
+ }
+ case ArgumentsAreUnicode: {
+ const char16_t* uc = argsArg
+ ? static_cast<const char16_t**>(argsArg)[i]
+ : va_arg(ap, const char16_t*);
+ size_t len = js_strlen(uc);
+ mozilla::Range<const char16_t> range(uc, len);
+ char* utf8 = JS::CharsToNewUTF8CharsZ(alloc, range).c_str();
+ if (!utf8) {
+ return false;
+ }
+
+ args_[i] = utf8;
+ lengths_[i] = strlen(utf8);
+ allocatedElements_ = true;
+ break;
+ }
+ }
+ totalLength_ += lengths_[i];
+ }
+ return true;
+ }
+};
+
+/*
+ * The arguments from ap need to be packaged up into an array and stored
+ * into the report struct.
+ *
+ * The format string addressed by the error number may contain operands
+ * identified by the format {N}, where N is a decimal digit. Each of these
+ * is to be replaced by the Nth argument from the va_list. The complete
+ * message is placed into reportp->message_.
+ *
+ * Returns true if the expansion succeeds (can fail if out of memory).
+ *
+ * messageArgs is a `const char**` or a `const char16_t**` but templating on
+ * that is not worth it here because AutoMessageArgs takes a void* anyway, and
+ * using void* here simplifies our callers a bit.
+ */
+template <typename T>
+static bool ExpandErrorArgumentsHelper(FrontendContext* fc,
+ JSErrorCallback callback, void* userRef,
+ const unsigned errorNumber,
+ void* messageArgs,
+ ErrorArgumentsType argumentsType,
+ T* reportp, va_list ap) {
+ const JSErrorFormatString* efs;
+
+ if (!callback) {
+ callback = GetErrorMessage;
+ }
+
+ efs = fc->gcSafeCallback(callback, userRef, errorNumber);
+
+ if (efs) {
+ if constexpr (std::is_same_v<T, JSErrorReport>) {
+ reportp->exnType = efs->exnType;
+ }
+
+ MOZ_ASSERT(reportp->errorNumber == errorNumber);
+ reportp->errorMessageName = efs->name;
+
+ MOZ_ASSERT_IF(argumentsType == ArgumentsAreASCII,
+ JS::StringIsASCII(efs->format));
+
+ uint16_t argCount = efs->argCount;
+ MOZ_RELEASE_ASSERT(argCount <= JS::MaxNumErrorArguments);
+ if (argCount > 0) {
+ /*
+ * Parse the error format, substituting the argument X
+ * for {X} in the format.
+ */
+ if (efs->format) {
+ const char* fmt;
+ char* out;
+#ifdef DEBUG
+ int expandedArgs = 0;
+#endif
+ size_t expandedLength;
+ size_t len = strlen(efs->format);
+
+ AutoMessageArgs args;
+ if (!args.init(fc->getAllocator(), messageArgs, argCount, argumentsType,
+ ap)) {
+ return false;
+ }
+
+ expandedLength = len - (3 * args.count()) /* exclude the {n} */
+ + args.totalLength();
+
+ /*
+ * Note - the above calculation assumes that each argument
+ * is used once and only once in the expansion !!!
+ */
+ char* utf8 = out =
+ fc->getAllocator()->pod_malloc<char>(expandedLength + 1);
+ if (!out) {
+ return false;
+ }
+
+ fmt = efs->format;
+ while (*fmt) {
+ if (*fmt == '{') {
+ if (mozilla::IsAsciiDigit(fmt[1])) {
+ int d = AsciiDigitToNumber(fmt[1]);
+ MOZ_RELEASE_ASSERT(d < args.count());
+ strncpy(out, args.args(d), args.lengths(d));
+ out += args.lengths(d);
+ fmt += 3;
+#ifdef DEBUG
+ expandedArgs++;
+#endif
+ continue;
+ }
+ }
+ *out++ = *fmt++;
+ }
+ MOZ_ASSERT(expandedArgs == args.count());
+ *out = 0;
+
+ reportp->initOwnedMessage(utf8);
+ }
+ } else {
+ /* Non-null messageArgs should have at least one non-null arg. */
+ MOZ_ASSERT(!messageArgs);
+ /*
+ * Zero arguments: the format string (if it exists) is the
+ * entire message.
+ */
+ if (efs->format) {
+ reportp->initBorrowedMessage(efs->format);
+ }
+ }
+ }
+ if (!reportp->message()) {
+ /* where's the right place for this ??? */
+ const char* defaultErrorMessage =
+ "No error message available for error number %d";
+ size_t nbytes = strlen(defaultErrorMessage) + 16;
+ char* message = fc->getAllocator()->pod_malloc<char>(nbytes);
+ if (!message) {
+ return false;
+ }
+ snprintf(message, nbytes, defaultErrorMessage, errorNumber);
+ reportp->initOwnedMessage(message);
+ }
+ return true;
+}
+
+bool js::ExpandErrorArgumentsVA(FrontendContext* fc, JSErrorCallback callback,
+ void* userRef, const unsigned errorNumber,
+ const char16_t** messageArgs,
+ ErrorArgumentsType argumentsType,
+ JSErrorReport* reportp, va_list ap) {
+ MOZ_ASSERT(argumentsType == ArgumentsAreUnicode);
+ return ExpandErrorArgumentsHelper(fc, callback, userRef, errorNumber,
+ messageArgs, argumentsType, reportp, ap);
+}
+
+bool js::ExpandErrorArgumentsVA(FrontendContext* fc, JSErrorCallback callback,
+ void* userRef, const unsigned errorNumber,
+ const char** messageArgs,
+ ErrorArgumentsType argumentsType,
+ JSErrorReport* reportp, va_list ap) {
+ MOZ_ASSERT(argumentsType != ArgumentsAreUnicode);
+ return ExpandErrorArgumentsHelper(fc, callback, userRef, errorNumber,
+ messageArgs, argumentsType, reportp, ap);
+}
+
+bool js::ExpandErrorArgumentsVA(FrontendContext* fc, JSErrorCallback callback,
+ void* userRef, const unsigned errorNumber,
+ ErrorArgumentsType argumentsType,
+ JSErrorReport* reportp, va_list ap) {
+ return ExpandErrorArgumentsHelper(fc, callback, userRef, errorNumber, nullptr,
+ argumentsType, reportp, ap);
+}
+
+bool js::ExpandErrorArgumentsVA(FrontendContext* fc, JSErrorCallback callback,
+ void* userRef, const unsigned errorNumber,
+ const char16_t** messageArgs,
+ ErrorArgumentsType argumentsType,
+ JSErrorNotes::Note* notep, va_list ap) {
+ return ExpandErrorArgumentsHelper(fc, callback, userRef, errorNumber,
+ messageArgs, argumentsType, notep, ap);
+}
+
+bool js::ReportErrorNumberVA(JSContext* cx, IsWarning isWarning,
+ JSErrorCallback callback, void* userRef,
+ const unsigned errorNumber,
+ ErrorArgumentsType argumentsType, va_list ap) {
+ JSErrorReport report;
+ report.isWarning_ = isWarning == IsWarning::Yes;
+ report.errorNumber = errorNumber;
+ PopulateReportBlame(cx, &report);
+
+ AutoReportFrontendContext fc(cx);
+ if (!ExpandErrorArgumentsVA(&fc, callback, userRef, errorNumber,
+ argumentsType, &report, ap)) {
+ return false;
+ }
+
+ if (!ReportError(cx, &report, callback, userRef)) {
+ return false;
+ }
+
+ return report.isWarning();
+}
+
+template <typename CharT>
+static bool ExpandErrorArguments(FrontendContext* fc, JSErrorCallback callback,
+ void* userRef, const unsigned errorNumber,
+ const CharT** messageArgs,
+ js::ErrorArgumentsType argumentsType,
+ JSErrorReport* reportp, ...) {
+ va_list ap;
+ va_start(ap, reportp);
+ bool expanded =
+ js::ExpandErrorArgumentsVA(fc, callback, userRef, errorNumber,
+ messageArgs, argumentsType, reportp, ap);
+ va_end(ap);
+ return expanded;
+}
+
+template <js::ErrorArgumentsType argType, typename CharT>
+static bool ReportErrorNumberArray(JSContext* cx, IsWarning isWarning,
+ JSErrorCallback callback, void* userRef,
+ const unsigned errorNumber,
+ const CharT** args) {
+ static_assert(
+ (argType == ArgumentsAreUnicode && std::is_same_v<CharT, char16_t>) ||
+ (argType != ArgumentsAreUnicode && std::is_same_v<CharT, char>),
+ "Mismatch between character type and argument type");
+
+ JSErrorReport report;
+ report.isWarning_ = isWarning == IsWarning::Yes;
+ report.errorNumber = errorNumber;
+ PopulateReportBlame(cx, &report);
+
+ AutoReportFrontendContext fc(cx);
+ if (!ExpandErrorArguments(&fc, callback, userRef, errorNumber, args, argType,
+ &report)) {
+ return false;
+ }
+
+ if (!ReportError(cx, &report, callback, userRef)) {
+ return false;
+ }
+
+ return report.isWarning();
+}
+
+bool js::ReportErrorNumberUCArray(JSContext* cx, IsWarning isWarning,
+ JSErrorCallback callback, void* userRef,
+ const unsigned errorNumber,
+ const char16_t** args) {
+ return ReportErrorNumberArray<ArgumentsAreUnicode>(
+ cx, isWarning, callback, userRef, errorNumber, args);
+}
+
+bool js::ReportErrorNumberUTF8Array(JSContext* cx, IsWarning isWarning,
+ JSErrorCallback callback, void* userRef,
+ const unsigned errorNumber,
+ const char** args) {
+ return ReportErrorNumberArray<ArgumentsAreUTF8>(cx, isWarning, callback,
+ userRef, errorNumber, args);
+}
+
+bool js::ReportErrorVA(JSContext* cx, IsWarning isWarning, const char* format,
+ js::ErrorArgumentsType argumentsType, va_list ap) {
+ JSErrorReport report;
+
+ UniqueChars message(JS_vsmprintf(format, ap));
+ if (!message) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ MOZ_ASSERT_IF(argumentsType == ArgumentsAreASCII,
+ JS::StringIsASCII(message.get()));
+
+ report.isWarning_ = isWarning == IsWarning::Yes;
+ report.errorNumber = JSMSG_USER_DEFINED_ERROR;
+ if (argumentsType == ArgumentsAreASCII || argumentsType == ArgumentsAreUTF8) {
+ report.initOwnedMessage(message.release());
+ } else {
+ MOZ_ASSERT(argumentsType == ArgumentsAreLatin1);
+ JS::Latin1Chars latin1(message.get(), strlen(message.get()));
+ JS::UTF8CharsZ utf8(JS::CharsToNewUTF8CharsZ(cx, latin1));
+ if (!utf8) {
+ return false;
+ }
+ report.initOwnedMessage(reinterpret_cast<const char*>(utf8.get()));
+ }
+ PopulateReportBlame(cx, &report);
+
+ if (!ReportError(cx, &report, nullptr, nullptr)) {
+ return false;
+ }
+
+ return report.isWarning();
+}
+
+void js::MaybePrintAndClearPendingException(JSContext* cx) {
+ if (!cx->isExceptionPending()) {
+ return;
+ }
+
+ AutoClearPendingException acpe(cx);
+
+ JS::ExceptionStack exnStack(cx);
+ if (!JS::StealPendingExceptionStack(cx, &exnStack)) {
+ fprintf(stderr, "error getting pending exception\n");
+ return;
+ }
+
+ JS::ErrorReportBuilder report(cx);
+ if (!report.init(cx, exnStack, JS::ErrorReportBuilder::WithSideEffects)) {
+ fprintf(stderr, "out of memory initializing JS::ErrorReportBuilder\n");
+ return;
+ }
+
+ MOZ_ASSERT(!report.report()->isWarning());
+ JS::PrintError(stderr, report, true);
+}