From 9a7113f7c8d89d27558942f42603c4edee779d20 Mon Sep 17 00:00:00 2001 From: Gaming4JC Date: Sun, 9 Jun 2019 11:27:16 -0400 Subject: 1283712 - Part 1: Add JSErrorBase, JSErrorNotes, JSErrorNotes::Note, and JSErrorReport.{notes,freeNotes}. --- js/src/jsapi.cpp | 131 +++++++++++++++++++++++++++++++++++++++- js/src/jsapi.h | 174 ++++++++++++++++++++++++++++++++++++++++++----------- js/src/jscntxt.cpp | 41 ++++++++++++- js/src/jscntxt.h | 7 +++ js/src/jsexn.cpp | 114 ++++++++++++++++++++++++++--------- js/src/jsexn.h | 3 + 6 files changed, 401 insertions(+), 69 deletions(-) diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 6d6eacec2..8cb3e5802 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -69,7 +69,6 @@ #include "js/Proxy.h" #include "js/SliceBudget.h" #include "js/StructuredClone.h" -#include "js/UniquePtr.h" #include "js/Utility.h" #include "vm/AsyncFunction.h" #include "vm/DateObject.h" @@ -6300,7 +6299,7 @@ JSErrorReport::freeLinebuf() } JSString* -JSErrorReport::newMessageString(JSContext* cx) +JSErrorBase::newMessageString(JSContext* cx) { if (!message_) return cx->runtime()->emptyString; @@ -6309,7 +6308,7 @@ JSErrorReport::newMessageString(JSContext* cx) } void -JSErrorReport::freeMessage() +JSErrorBase::freeMessage() { if (ownsMessage_) { js_free((void*)message_.get()); @@ -6318,6 +6317,132 @@ JSErrorReport::freeMessage() message_ = JS::ConstUTF8CharsZ(); } +JSErrorNotes::JSErrorNotes() + : notes_() +{} + +JSErrorNotes::~JSErrorNotes() +{ +} + +static UniquePtr +CreateErrorNoteVA(JSContext* cx, + const char* filename, unsigned lineno, unsigned column, + JSErrorCallback errorCallback, void* userRef, + const unsigned errorNumber, + ErrorArgumentsType argumentsType, va_list ap) +{ + auto note = MakeUnique(); + if (!note) + return nullptr; + + note->errorNumber = errorNumber; + note->filename = filename; + note->lineno = lineno; + note->column = column; + + if (!ExpandErrorArgumentsVA(cx, errorCallback, userRef, errorNumber, + nullptr, argumentsType, note.get(), ap)) { + return nullptr; + } + + return note; +} + +bool +JSErrorNotes::addNoteASCII(JSContext* cx, + const char* filename, unsigned lineno, unsigned column, + JSErrorCallback errorCallback, void* userRef, + const unsigned errorNumber, ...) +{ + va_list ap; + va_start(ap, errorNumber); + auto note = CreateErrorNoteVA(cx, filename, lineno, column, errorCallback, userRef, + errorNumber, ArgumentsAreASCII, ap); + va_end(ap); + + if (!note) + return false; + if (!notes_.append(Move(note))) + return false; + return true; +} + +bool +JSErrorNotes::addNoteLatin1(JSContext* cx, + const char* filename, unsigned lineno, unsigned column, + JSErrorCallback errorCallback, void* userRef, + const unsigned errorNumber, ...) +{ + va_list ap; + va_start(ap, errorNumber); + auto note = CreateErrorNoteVA(cx, filename, lineno, column, errorCallback, userRef, + errorNumber, ArgumentsAreLatin1, ap); + va_end(ap); + + if (!note) + return false; + if (!notes_.append(Move(note))) + return false; + return true; +} + +bool +JSErrorNotes::addNoteUTF8(JSContext* cx, + const char* filename, unsigned lineno, unsigned column, + JSErrorCallback errorCallback, void* userRef, + const unsigned errorNumber, ...) +{ + va_list ap; + va_start(ap, errorNumber); + auto note = CreateErrorNoteVA(cx, filename, lineno, column, errorCallback, userRef, + errorNumber, ArgumentsAreUTF8, ap); + va_end(ap); + + if (!note) + return false; + if (!notes_.append(Move(note))) + return false; + return true; +} + +size_t +JSErrorNotes::length() +{ + return notes_.length(); +} + +UniquePtr +JSErrorNotes::copy(JSContext* cx) +{ + auto copiedNotes = MakeUnique(); + if (!copiedNotes) + return nullptr; + + for (auto&& note : *this) { + js::UniquePtr copied(CopyErrorNote(cx, note.get())); + if (!copied) + return nullptr; + + if (!copiedNotes->notes_.append(Move(copied))) + return nullptr; + } + + return copiedNotes; +} + +JSErrorNotes::iterator +JSErrorNotes::begin() +{ + return iterator(notes_.begin()); +} + +JSErrorNotes::iterator +JSErrorNotes::end() +{ + return iterator(notes_.end()); +} + JS_PUBLIC_API(bool) JS_ThrowStopIteration(JSContext* cx) { diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 3f65dad34..99100620a 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -18,6 +18,7 @@ #include "mozilla/RefPtr.h" #include "mozilla/Variant.h" +#include #include #include #include @@ -36,6 +37,7 @@ #include "js/Realm.h" #include "js/RootingAPI.h" #include "js/TracingAPI.h" +#include "js/UniquePtr.h" #include "js/Utility.h" #include "js/Value.h" #include "js/Vector.h" @@ -5362,14 +5364,130 @@ JS_ReportOutOfMemory(JSContext* cx); extern JS_PUBLIC_API(void) JS_ReportAllocationOverflow(JSContext* cx); -class JSErrorReport +/** + * Base class that implements parts shared by JSErrorReport and + * JSErrorNotes::Note. + */ +class JSErrorBase { // The (default) error message. // If ownsMessage_ is true, the it is freed in destructor. JS::ConstUTF8CharsZ message_; + public: + JSErrorBase() + : filename(nullptr), lineno(0), column(0), + errorNumber(0), + ownsMessage_(false) + {} + + ~JSErrorBase() { + freeMessage(); + } + + // Source file name, URL, etc., or null. + const char* filename; + + // Source line number. + unsigned lineno; + + // Zero-based column index in line. + unsigned column; + + // the error number, e.g. see js.msg. + unsigned errorNumber; + + private: + bool ownsMessage_ : 1; + + 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 : public JSErrorBase + {}; + + private: + // Stores pointers to each note. + js::Vector, 1, js::SystemAllocPolicy> notes_; + + public: + JSErrorNotes(); + ~JSErrorNotes(); + + // Add an note to the given position. + bool addNoteASCII(JSContext* cx, + const char* filename, unsigned lineno, unsigned column, + JSErrorCallback errorCallback, void* userRef, + const unsigned errorNumber, ...); + bool addNoteLatin1(JSContext* cx, + const char* filename, unsigned lineno, unsigned column, + JSErrorCallback errorCallback, void* userRef, + const unsigned errorNumber, ...); + bool addNoteUTF8(JSContext* cx, + const char* filename, unsigned lineno, unsigned column, + JSErrorCallback errorCallback, void* userRef, + const unsigned errorNumber, ...); + + size_t length(); + + // Create a deep copy of notes. + js::UniquePtr copy(JSContext* cx); + + class iterator : public std::iterator> + { + js::UniquePtr* note_; + public: + explicit iterator(js::UniquePtr* 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_; + } + }; + iterator begin(); + iterator end(); +}; + +/** + * Describes a single error or warning that occurs in the execution of script. + */ +class JSErrorReport : public JSErrorBase +{ // Offending source line without final '\n'. - // If ownsLinebuf__ is true, the buffer is freed in destructor. + // If ownsLinebuf_ is true, the buffer is freed in destructor. const char16_t* linebuf_; // Number of chars in linebuf_. Does not include trailing '\0'. @@ -5381,28 +5499,29 @@ class JSErrorReport public: JSErrorReport() : linebuf_(nullptr), linebufLength_(0), tokenOffset_(0), - filename(nullptr), lineno(0), column(0), - flags(0), errorNumber(0), - exnType(0), isMuted(false), - ownsLinebuf_(false), ownsMessage_(false) + notes(nullptr), + flags(0), exnType(0), isMuted(false), + ownsLinebuf_(false) {} ~JSErrorReport() { freeLinebuf(); - freeMessage(); } - const char* filename; /* source file name, URL, etc., or null */ - unsigned lineno; /* source line number */ - unsigned column; /* zero-based column index in line */ - unsigned flags; /* error/warning, etc. */ - unsigned errorNumber; /* the error number, e.g. see js.msg */ - int16_t exnType; /* One of the JSExnType constants */ - bool isMuted : 1; /* See the comment in ReadOnlyCompileOptions. */ + // Associated notes, or nullptr if there's no note. + js::UniquePtr notes; + + // error/warning, etc. + unsigned flags; + + // One of the JSExnType constants. + int16_t exnType; + + // See the comment in ReadOnlyCompileOptions. + bool isMuted : 1; private: bool ownsLinebuf_ : 1; - bool ownsMessage_ : 1; public: const char16_t* linebuf() const { @@ -5414,29 +5533,16 @@ class JSErrorReport size_t tokenOffset() const { return tokenOffset_; } - void initOwnedLinebuf(const char16_t* linebufArg, size_t linebufLengthArg, size_t tokenOffsetArg) { + 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); - void freeLinebuf(); - - 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)); - } + void initBorrowedLinebuf(const char16_t* linebufArg, size_t linebufLengthArg, + size_t tokenOffsetArg); - JSString* newMessageString(JSContext* cx); - - void freeMessage(); + private: + void freeLinebuf(); }; /* diff --git a/js/src/jscntxt.cpp b/js/src/jscntxt.cpp index 31d62332d..4e34c0e48 100644 --- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -557,6 +557,18 @@ class MOZ_RAII AutoMessageArgs } }; +static void +SetExnType(JSErrorReport* reportp, int16_t exnType) +{ + reportp->exnType = exnType; +} + +static void +SetExnType(JSErrorNotes::Note* notep, int16_t exnType) +{ + // Do nothing for JSErrorNotes::Note. +} + /* * The arguments from ap need to be packaged up into an array and stored * into the report struct. @@ -568,12 +580,13 @@ class MOZ_RAII AutoMessageArgs * * Returns true if the expansion succeeds (can fail if out of memory). */ +template bool -js::ExpandErrorArgumentsVA(ExclusiveContext* cx, JSErrorCallback callback, +ExpandErrorArgumentsHelper(ExclusiveContext* cx, JSErrorCallback callback, void* userRef, const unsigned errorNumber, const char16_t** messageArgs, ErrorArgumentsType argumentsType, - JSErrorReport* reportp, va_list ap) + T* reportp, va_list ap) { const JSErrorFormatString* efs; @@ -586,7 +599,7 @@ js::ExpandErrorArgumentsVA(ExclusiveContext* cx, JSErrorCallback callback, } if (efs) { - reportp->exnType = efs->exnType; + SetExnType(reportp, efs->exnType); MOZ_ASSERT_IF(argumentsType == ArgumentsAreASCII, JS::StringIsASCII(efs->format)); @@ -669,6 +682,28 @@ js::ExpandErrorArgumentsVA(ExclusiveContext* cx, JSErrorCallback callback, return true; } +bool +js::ExpandErrorArgumentsVA(ExclusiveContext* cx, JSErrorCallback callback, + void* userRef, const unsigned errorNumber, + const char16_t** messageArgs, + ErrorArgumentsType argumentsType, + JSErrorReport* reportp, va_list ap) +{ + return ExpandErrorArgumentsHelper(cx, callback, userRef, errorNumber, + messageArgs, argumentsType, reportp, ap); +} + +bool +js::ExpandErrorArgumentsVA(JSContext* cx, JSErrorCallback callback, + void* userRef, const unsigned errorNumber, + const char16_t** messageArgs, + ErrorArgumentsType argumentsType, + JSErrorNotes::Note* notep, va_list ap) +{ + return ExpandErrorArgumentsHelper(cx, callback, userRef, errorNumber, + messageArgs, argumentsType, notep, ap); +} + bool js::ReportErrorNumberVA(JSContext* cx, unsigned flags, JSErrorCallback callback, void* userRef, const unsigned errorNumber, diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index 0a2841242..c1e676f68 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -621,6 +621,13 @@ ExpandErrorArgumentsVA(ExclusiveContext* cx, JSErrorCallback callback, ErrorArgumentsType argumentsType, JSErrorReport* reportp, va_list ap); +extern bool +ExpandErrorArgumentsVA(JSContext* cx, JSErrorCallback callback, + void* userRef, const unsigned errorNumber, + const char16_t** messageArgs, + ErrorArgumentsType argumentsType, + JSErrorNotes::Note* notep, va_list ap); + /* |callee| requires a usage string provided by JS_DefineFunctionsWithHelp. */ extern void ReportUsageErrorASCII(JSContext* cx, HandleObject callee, const char* msg); diff --git a/js/src/jsexn.cpp b/js/src/jsexn.cpp index 102e6fb34..b07d118b9 100644 --- a/js/src/jsexn.cpp +++ b/js/src/jsexn.cpp @@ -201,28 +201,77 @@ ErrorObject::classes[JSEXN_ERROR_LIMIT] = { IMPLEMENT_ERROR_CLASS(RuntimeError) }; -JSErrorReport* -js::CopyErrorReport(JSContext* cx, JSErrorReport* report) +size_t +ExtraMallocSize(JSErrorReport* report) +{ + if (report->linebuf()) + return (report->linebufLength() + 1) * sizeof(char16_t); + + return 0; +} + +size_t +ExtraMallocSize(JSErrorNotes::Note* note) +{ + return 0; +} + +bool +CopyExtraData(JSContext* cx, uint8_t** cursor, JSErrorReport* copy, JSErrorReport* report) +{ + if (report->linebuf()) { + size_t linebufSize = (report->linebufLength() + 1) * sizeof(char16_t); + const char16_t* linebufCopy = (const char16_t*)(*cursor); + js_memcpy(*cursor, report->linebuf(), linebufSize); + *cursor += linebufSize; + copy->initBorrowedLinebuf(linebufCopy, report->linebufLength(), report->tokenOffset()); + } + + /* Copy non-pointer members. */ + copy->isMuted = report->isMuted; + copy->exnType = report->exnType; + + /* Note that this is before it gets flagged with JSREPORT_EXCEPTION */ + copy->flags = report->flags; + + /* Deep copy notes. */ + if (report->notes) { + auto copiedNotes = report->notes->copy(cx); + if (!copiedNotes) + return false; + copy->notes = Move(copiedNotes); + } else { + copy->notes.reset(nullptr); + } + + return true; +} + +bool +CopyExtraData(JSContext* cx, uint8_t** cursor, JSErrorNotes::Note* copy, JSErrorNotes::Note* report) +{ + return true; +} + +template +static T* +CopyErrorHelper(JSContext* cx, T* report) { /* - * We use a single malloc block to make a deep copy of JSErrorReport with + * We use a single malloc block to make a deep copy of JSErrorReport or + * JSErrorNotes::Note, except JSErrorNotes linked from JSErrorReport with * the following layout: - * JSErrorReport + * JSErrorReport or JSErrorNotes::Note * char array with characters for message_ - * char16_t array with characters for linebuf * char array with characters for filename + * char16_t array with characters for linebuf (only for JSErrorReport) * Such layout together with the properties enforced by the following * asserts does not need any extra alignment padding. */ - JS_STATIC_ASSERT(sizeof(JSErrorReport) % sizeof(const char*) == 0); + JS_STATIC_ASSERT(sizeof(T) % sizeof(const char*) == 0); JS_STATIC_ASSERT(sizeof(const char*) % sizeof(char16_t) == 0); -#define JS_CHARS_SIZE(chars) ((js_strlen(chars) + 1) * sizeof(char16_t)) - size_t filenameSize = report->filename ? strlen(report->filename) + 1 : 0; - size_t linebufSize = 0; - if (report->linebuf()) - linebufSize = (report->linebufLength() + 1) * sizeof(char16_t); size_t messageSize = 0; if (report->message()) messageSize = strlen(report->message().c_str()) + 1; @@ -231,13 +280,13 @@ js::CopyErrorReport(JSContext* cx, JSErrorReport* report) * The mallocSize can not overflow since it represents the sum of the * sizes of already allocated objects. */ - size_t mallocSize = sizeof(JSErrorReport) + messageSize + linebufSize + filenameSize; + size_t mallocSize = sizeof(T) + messageSize + filenameSize + ExtraMallocSize(report); uint8_t* cursor = cx->pod_calloc(mallocSize); if (!cursor) return nullptr; - JSErrorReport* copy = (JSErrorReport*)cursor; - cursor += sizeof(JSErrorReport); + T* copy = new (cursor) T(); + cursor += sizeof(T); if (report->message()) { copy->initBorrowedMessage((const char*)cursor); @@ -245,33 +294,40 @@ js::CopyErrorReport(JSContext* cx, JSErrorReport* report) cursor += messageSize; } - if (report->linebuf()) { - const char16_t* linebufCopy = (const char16_t*)cursor; - js_memcpy(cursor, report->linebuf(), linebufSize); - cursor += linebufSize; - copy->initBorrowedLinebuf(linebufCopy, report->linebufLength(), report->tokenOffset()); - } - if (report->filename) { copy->filename = (const char*)cursor; js_memcpy(cursor, report->filename, filenameSize); + cursor += filenameSize; + } + + if (!CopyExtraData(cx, &cursor, copy, report)) { + /* js_delete calls destructor for T and js_free for pod_calloc. */ + js_delete(copy); + return nullptr; } - MOZ_ASSERT(cursor + filenameSize == (uint8_t*)copy + mallocSize); + + MOZ_ASSERT(cursor == (uint8_t*)copy + mallocSize); /* Copy non-pointer members. */ - copy->isMuted = report->isMuted; copy->lineno = report->lineno; copy->column = report->column; copy->errorNumber = report->errorNumber; - copy->exnType = report->exnType; - /* Note that this is before it gets flagged with JSREPORT_EXCEPTION */ - copy->flags = report->flags; - -#undef JS_CHARS_SIZE return copy; } +JSErrorNotes::Note* +js::CopyErrorNote(JSContext* cx, JSErrorNotes::Note* note) +{ + return CopyErrorHelper(cx, note); +} + +JSErrorReport* +js::CopyErrorReport(JSContext* cx, JSErrorReport* report) +{ + return CopyErrorHelper(cx, report); +} + struct SuppressErrorsGuard { JSContext* cx; @@ -322,7 +378,7 @@ exn_finalize(FreeOp* fop, JSObject* obj) { MOZ_ASSERT(fop->maybeOffMainThread()); if (JSErrorReport* report = obj->as().getErrorReport()) - fop->free_(report); + fop->delete_(report); } JSErrorReport* diff --git a/js/src/jsexn.h b/js/src/jsexn.h index ae6335209..1698bb2e9 100644 --- a/js/src/jsexn.h +++ b/js/src/jsexn.h @@ -18,6 +18,9 @@ namespace js { class ErrorObject; +JSErrorNotes::Note* +CopyErrorNote(JSContext* cx, JSErrorNotes::Note* note); + JSErrorReport* CopyErrorReport(JSContext* cx, JSErrorReport* report); -- cgit v1.2.3