Bug 1283712 - Part 1: Add JSErrorBase, JSErrorNotes, JSErrorNotes::Note, and JSErrorReport.{notes,freeNotes}. r=jwalden

This commit is contained in:
Tooru Fujisawa 2017-02-15 23:53:05 +09:00
parent 6e3978d74d
commit 5b355e2da8
6 changed files with 410 additions and 78 deletions

View File

@ -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"
@ -6262,7 +6261,7 @@ JSErrorReport::freeLinebuf()
}
JSString*
JSErrorReport::newMessageString(JSContext* cx)
JSErrorBase::newMessageString(JSContext* cx)
{
if (!message_)
return cx->runtime()->emptyString;
@ -6271,7 +6270,7 @@ JSErrorReport::newMessageString(JSContext* cx)
}
void
JSErrorReport::freeMessage()
JSErrorBase::freeMessage()
{
if (ownsMessage_) {
js_free((void*)message_.get());
@ -6280,6 +6279,132 @@ JSErrorReport::freeMessage()
message_ = JS::ConstUTF8CharsZ();
}
JSErrorNotes::JSErrorNotes()
: notes_()
{}
JSErrorNotes::~JSErrorNotes()
{
}
static UniquePtr<JSErrorNotes::Note>
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<JSErrorNotes::Note>();
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>
JSErrorNotes::copy(JSContext* cx)
{
auto copiedNotes = MakeUnique<JSErrorNotes>();
if (!copiedNotes)
return nullptr;
for (auto&& note : *this) {
js::UniquePtr<JSErrorNotes::Note> 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)
{

View File

@ -17,6 +17,7 @@
#include "mozilla/RefPtr.h"
#include "mozilla/Variant.h"
#include <iterator>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
@ -36,6 +37,7 @@
#include "js/RefCounted.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"
@ -5389,65 +5391,43 @@ 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_;
// 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:
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)
JSErrorBase()
: filename(nullptr), lineno(0), column(0),
errorNumber(0),
ownsMessage_(false)
{}
~JSErrorReport() {
freeLinebuf();
~JSErrorBase() {
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. */
// 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 ownsLinebuf_ : 1;
bool ownsMessage_ : 1;
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);
void freeLinebuf();
const JS::ConstUTF8CharsZ message() const {
return message_;
}
@ -5463,9 +5443,135 @@ class JSErrorReport
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<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 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<JSErrorNotes> copy(JSContext* cx);
class iterator : public std::iterator<std::input_iterator_tag, js::UniquePtr<Note>>
{
js::UniquePtr<Note>* note_;
public:
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_;
}
};
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.
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:
JSErrorReport()
: linebuf_(nullptr), linebufLength_(0), tokenOffset_(0),
notes(nullptr),
flags(0), exnType(0), isMuted(false),
ownsLinebuf_(false)
{}
~JSErrorReport() {
freeLinebuf();
}
// Associated notes, or nullptr if there's no note.
js::UniquePtr<JSErrorNotes> 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;
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);
private:
void freeLinebuf();
};
/*
* JSErrorReport flag values. These may be freely composed.
*/

View File

@ -622,6 +622,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.
@ -633,12 +645,13 @@ class MOZ_RAII AutoMessageArgs
*
* Returns true if the expansion succeeds (can fail if out of memory).
*/
template <typename T>
bool
js::ExpandErrorArgumentsVA(JSContext* cx, JSErrorCallback callback,
ExpandErrorArgumentsHelper(JSContext* 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;
@ -651,7 +664,7 @@ js::ExpandErrorArgumentsVA(JSContext* cx, JSErrorCallback callback,
}
if (efs) {
reportp->exnType = efs->exnType;
SetExnType(reportp, efs->exnType);
MOZ_ASSERT_IF(argumentsType == ArgumentsAreASCII, JS::StringIsASCII(efs->format));
@ -734,6 +747,28 @@ js::ExpandErrorArgumentsVA(JSContext* cx, JSErrorCallback callback,
return true;
}
bool
js::ExpandErrorArgumentsVA(JSContext* 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,

View File

@ -993,6 +993,13 @@ ExpandErrorArgumentsVA(JSContext* 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);

View File

@ -205,28 +205,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 <typename T>
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;
@ -235,13 +284,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<uint8_t>(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);
@ -249,33 +298,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;
}
MOZ_ASSERT(cursor + filenameSize == (uint8_t*)copy + mallocSize);
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 == (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;
@ -326,7 +382,7 @@ exn_finalize(FreeOp* fop, JSObject* obj)
{
MOZ_ASSERT(fop->maybeOnHelperThread());
if (JSErrorReport* report = obj->as<ErrorObject>().getErrorReport())
fop->free_(report);
fop->delete_(report);
}
JSErrorReport*

View File

@ -18,6 +18,9 @@
namespace js {
class ErrorObject;
JSErrorNotes::Note*
CopyErrorNote(JSContext* cx, JSErrorNotes::Note* note);
JSErrorReport*
CopyErrorReport(JSContext* cx, JSErrorReport* report);