Bug 1274922 part 6 - Rewrite the shell's error reporting to handle exceptions in the embedding. r=jorendorff

This commit is contained in:
Jan de Mooij 2016-05-31 08:07:00 +02:00
parent 847b10eeb0
commit 455fb1c7be
2 changed files with 132 additions and 80 deletions

View File

@ -507,17 +507,14 @@ EnvironmentPreparer::invoke(HandleObject scope, Closure& closure)
MOZ_ASSERT(!JS_IsExceptionPending(cx));
AutoCompartment ac(cx, scope);
AutoReportException are(cx);
if (!closure(cx))
JS_ReportPendingException(cx);
MOZ_ASSERT(!JS_IsExceptionPending(cx));
return;
}
static void
static MOZ_MUST_USE bool
RunFile(JSContext* cx, const char* filename, FILE* file, bool compileOnly)
{
ShellRuntime* sr = GetShellRuntime(cx);
SkipUTF8BOM(file);
// To support the UNIX #! shell hack, gobble the first line if it starts
@ -542,22 +539,23 @@ RunFile(JSContext* cx, const char* filename, FILE* file, bool compileOnly)
.setIsRunOnce(true)
.setNoScriptRval(true);
(void) JS::Compile(cx, options, file, &script);
if (!JS::Compile(cx, options, file, &script))
return false;
MOZ_ASSERT(script);
}
#ifdef DEBUG
if (dumpEntrainedVariables)
AnalyzeEntrainedVariables(cx, script);
#endif
if (script && !compileOnly) {
if (!JS_ExecuteScript(cx, script)) {
if (!sr->quitting && sr->exitCode != EXITCODE_TIMEOUT)
sr->exitCode = EXITCODE_RUNTIME_ERROR;
}
if (!compileOnly) {
if (!JS_ExecuteScript(cx, script))
return false;
int64_t t2 = PRMJ_Now() - t1;
if (printTiming)
printf("runtime = %.3f ms\n", double(t2) / PRMJ_USEC_PER_MSEC);
}
return true;
}
static bool
@ -623,12 +621,10 @@ GetImportMethod(JSContext* cx, HandleObject loader, MutableHandleFunction result
return true;
}
static void
static MOZ_MUST_USE bool
RunModule(JSContext* cx, const char* filename, FILE* file, bool compileOnly)
{
// Exectute a module by calling |Reflect.Loader.import(filename)|.
ShellRuntime* sr = GetShellRuntime(cx);
// Execute a module by calling |Reflect.Loader.import(filename)|.
RootedObject loaderObj(cx);
MOZ_ALWAYS_TRUE(GetLoaderObject(cx, &loaderObj));
@ -641,10 +637,7 @@ RunModule(JSContext* cx, const char* filename, FILE* file, bool compileOnly)
args[1].setUndefined();
RootedValue value(cx);
if (!JS_CallFunction(cx, loaderObj, importFun, args, &value)) {
sr->exitCode = EXITCODE_RUNTIME_ERROR;
return;
}
return JS_CallFunction(cx, loaderObj, importFun, args, &value);
}
#ifdef SPIDERMONKEY_PROMISE
@ -674,8 +667,10 @@ DrainJobQueue(JSContext* cx)
for (size_t i = 0; i < sr->jobQueue.length(); i++) {
job = sr->jobQueue[i];
AutoCompartment ac(cx, job);
if (!JS::Call(cx, UndefinedHandleValue, job, args, &rval))
JS_ReportPendingException(cx);
{
AutoReportException are(cx);
JS::Call(cx, UndefinedHandleValue, job, args, &rval);
}
sr->jobQueue[i].set(nullptr);
}
sr->jobQueue.clear();
@ -771,7 +766,7 @@ EvalAndPrint(JSContext* cx, const char* bytes, size_t length,
return true;
}
static void
static MOZ_MUST_USE bool
ReadEvalPrintLoop(JSContext* cx, FILE* in, bool compileOnly)
{
ShellRuntime* sr = GetShellRuntime(cx);
@ -798,14 +793,14 @@ ReadEvalPrintLoop(JSContext* cx, FILE* in, bool compileOnly)
if (!line) {
if (errno) {
JS_ReportError(cx, strerror(errno));
return;
return false;
}
hitEOF = true;
break;
}
if (!buffer.append(line, strlen(line)) || !buffer.append('\n'))
return;
return false;
lineno++;
if (!ScheduleWatchdog(cx->runtime(), sr->timeoutInterval)) {
@ -817,10 +812,12 @@ ReadEvalPrintLoop(JSContext* cx, FILE* in, bool compileOnly)
if (hitEOF && buffer.empty())
break;
if (!EvalAndPrint(cx, buffer.begin(), buffer.length(), startline, compileOnly)) {
// Catch the error, report it, and keep going.
JS_ReportPendingException(cx);
{
// Report exceptions but keep going.
AutoReportException are(cx);
(void) EvalAndPrint(cx, buffer.begin(), buffer.length(), startline, compileOnly);
}
// If a let or const fail to initialize they will remain in an unusable
// without further intervention. This call cleans up the global scope,
// setting uninitialized lexicals to undefined so that they may still
@ -839,6 +836,8 @@ ReadEvalPrintLoop(JSContext* cx, FILE* in, bool compileOnly)
if (gOutFile->isOpen())
fprintf(gOutFile->fp, "\n");
return true;
}
enum FileKind
@ -847,7 +846,7 @@ enum FileKind
FileModule
};
static void
static MOZ_MUST_USE bool
Process(JSContext* cx, const char* filename, bool forceTTY, FileKind kind = FileScript)
{
FILE* file;
@ -858,22 +857,27 @@ Process(JSContext* cx, const char* filename, bool forceTTY, FileKind kind = File
if (!file) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr,
JSSMSG_CANT_OPEN, filename, strerror(errno));
return;
return false;
}
}
AutoCloseFile autoClose(file);
if (!forceTTY && !isatty(fileno(file))) {
// It's not interactive - just execute it.
if (kind == FileScript)
RunFile(cx, filename, file, compileOnly);
else
RunModule(cx, filename, file, compileOnly);
if (kind == FileScript) {
if (!RunFile(cx, filename, file, compileOnly))
return false;
} else {
if (!RunModule(cx, filename, file, compileOnly))
return false;
}
} else {
// It's an interactive filehandle; drop into read-eval-print loop.
MOZ_ASSERT(kind == FileScript);
ReadEvalPrintLoop(cx, file, compileOnly);
if (!ReadEvalPrintLoop(cx, file, compileOnly))
return false;
}
return true;
}
static bool
@ -1234,7 +1238,6 @@ class AutoNewContext
newcx = NewContext(JS_GetRuntime(cx));
if (!newcx)
return false;
JS::ContextOptionsRef(newcx).setDontReportUncaught(true);
newRequest.emplace(newcx);
newCompartment.emplace(newcx, JS::CurrentGlobalOrNull(cx));
@ -2947,7 +2950,7 @@ WorkerMain(void* arg)
sr->isWorker = true;
JS_SetRuntimePrivate(rt, sr.get());
JS_SetFutexCanWait(rt);
JS_SetErrorReporter(rt, my_ErrorReporter);
JS_SetErrorReporter(rt, WarningReporter);
JS_InitDestroyPrincipalsCallback(rt, ShellPrincipals::destroy);
SetWorkerRuntimeOptions(rt);
@ -2982,6 +2985,7 @@ WorkerMain(void* arg)
options.setFileAndLine("<string>", 1)
.setIsRunOnce(true);
AutoReportException are(cx);
RootedScript script(cx);
if (!JS::Compile(cx, options, input->chars, input->length, &script))
break;
@ -3077,6 +3081,7 @@ EvalInWorker(JSContext* cx, unsigned argc, Value* vp)
return false;
}
args.rval().setUndefined();
return true;
}
@ -5930,13 +5935,55 @@ PrintStackTrace(JSContext* cx, HandleValue exn)
return true;
}
js::shell::AutoReportException::~AutoReportException()
{
if (!JS_IsExceptionPending(cx))
return;
// Get exception object before printing and clearing exception.
RootedValue exn(cx);
(void) JS_GetPendingException(cx, &exn);
JS_ClearPendingException(cx);
ShellRuntime* sr = GetShellRuntime(cx);
js::ErrorReport report(cx);
if (!report.init(cx, exn, js::ErrorReport::WithSideEffects)) {
fprintf(stderr, "out of memory initializing ErrorReport\n");
JS_ClearPendingException(cx);
return;
}
MOZ_ASSERT(!JSREPORT_IS_WARNING(report.report()->flags));
FILE* fp = ErrorFilePointer();
PrintError(cx, fp, report.message(), report.report(), reportWarnings);
{
JS::AutoSaveExceptionState savedExc(cx);
if (!PrintStackTrace(cx, exn))
fputs("(Unable to print stack trace)\n", fp);
savedExc.restore();
}
if (report.report()->errorNumber == JSMSG_OUT_OF_MEMORY)
sr->exitCode = EXITCODE_OUT_OF_MEMORY;
else
sr->exitCode = EXITCODE_RUNTIME_ERROR;
JS_ClearPendingException(cx);
}
void
js::shell::my_ErrorReporter(JSContext* cx, const char* message, JSErrorReport* report)
js::shell::WarningReporter(JSContext* cx, const char* message, JSErrorReport* report)
{
ShellRuntime* sr = GetShellRuntime(cx);
FILE* fp = ErrorFilePointer();
if (report && JSREPORT_IS_WARNING(report->flags) && sr->lastWarningEnabled) {
MOZ_ASSERT(report);
MOZ_ASSERT(JSREPORT_IS_WARNING(report->flags));
if (sr->lastWarningEnabled) {
JS::AutoSaveExceptionState savedExc(cx);
if (!CreateLastWarningObject(cx, report)) {
fputs("Unhandled error happened while creating last warning object.\n", fp);
@ -5945,25 +5992,8 @@ js::shell::my_ErrorReporter(JSContext* cx, const char* message, JSErrorReport* r
savedExc.restore();
}
// Get exception object before printing and clearing exception.
RootedValue exn(cx);
if (JS_IsExceptionPending(cx))
(void) JS_GetPendingException(cx, &exn);
(void) PrintError(cx, fp, message, report, reportWarnings);
if (!exn.isUndefined()) {
JS::AutoSaveExceptionState savedExc(cx);
if (!PrintStackTrace(cx, exn))
fputs("(Unable to print stack trace)\n", fp);
savedExc.restore();
}
if (!JSREPORT_IS_WARNING(report->flags)) {
if (report->errorNumber == JSMSG_OUT_OF_MEMORY)
sr->exitCode = EXITCODE_OUT_OF_MEMORY;
else
sr->exitCode = EXITCODE_RUNTIME_ERROR;
}
// Print the warning.
PrintError(cx, fp, message, report, reportWarnings);
}
static bool
@ -6472,6 +6502,9 @@ NewContext(JSRuntime* rt)
if (!cx)
return nullptr;
JS::ContextOptionsRef(cx).setDontReportUncaught(true);
JS::ContextOptionsRef(cx).setAutoJSAPIOwnsErrorReporting(true);
JSShellContextData* data = NewContextData();
if (!data) {
DestroyContext(cx, false);
@ -6585,6 +6618,8 @@ NewGlobalObject(JSContext* cx, JS::CompartmentOptions& options,
static bool
BindScriptArgs(JSContext* cx, OptionParser* op)
{
AutoReportException are(cx);
MultiStringRange msr = op->getMultiStringArg("scriptArgs");
RootedObject scriptArgs(cx);
scriptArgs = JS_NewArrayObject(cx, 0);
@ -6628,7 +6663,7 @@ OptionFailure(const char* option, const char* str)
return false;
}
static int
static MOZ_MUST_USE bool
ProcessArgs(JSContext* cx, OptionParser* op)
{
ShellRuntime* sr = GetShellRuntime(cx);
@ -6638,7 +6673,7 @@ ProcessArgs(JSContext* cx, OptionParser* op)
/* |scriptArgs| gets bound on the global before any code is run. */
if (!BindScriptArgs(cx, op))
return EXIT_FAILURE;
return false;
MultiStringRange filePaths = op->getMultiStringOption('f');
MultiStringRange codeChunks = op->getMultiStringOption('e');
@ -6649,15 +6684,14 @@ ProcessArgs(JSContext* cx, OptionParser* op)
modulePaths.empty() &&
!op->getStringArg("script"))
{
Process(cx, nullptr, true); /* Interactive. */
return sr->exitCode;
return Process(cx, nullptr, true); /* Interactive. */
}
if (const char* path = op->getStringOption("module-load-path"))
moduleLoadPath = path;
if (!modulePaths.empty() && !InitModuleLoader(cx))
return EXIT_FAILURE;
return false;
while (!filePaths.empty() || !codeChunks.empty() || !modulePaths.empty()) {
size_t fpArgno = filePaths.empty() ? SIZE_MAX : filePaths.argno();
@ -6666,9 +6700,8 @@ ProcessArgs(JSContext* cx, OptionParser* op)
if (fpArgno < ccArgno && fpArgno < mpArgno) {
char* path = filePaths.front();
Process(cx, path, false, FileScript);
if (sr->exitCode)
return sr->exitCode;
if (!Process(cx, path, false, FileScript))
return false;
filePaths.popFront();
} else if (ccArgno < fpArgno && ccArgno < mpArgno) {
const char* code = codeChunks.front();
@ -6676,36 +6709,36 @@ ProcessArgs(JSContext* cx, OptionParser* op)
JS::CompileOptions opts(cx);
opts.setFileAndLine("-e", 1);
if (!JS::Evaluate(cx, opts, code, strlen(code), &rval))
return sr->exitCode ? sr->exitCode : EXITCODE_RUNTIME_ERROR;
return false;
codeChunks.popFront();
if (sr->quitting)
break;
} else {
MOZ_ASSERT(mpArgno < fpArgno && mpArgno < ccArgno);
char* path = modulePaths.front();
Process(cx, path, false, FileModule);
if (sr->exitCode)
return sr->exitCode;
if (!Process(cx, path, false, FileModule))
return false;
modulePaths.popFront();
}
}
if (sr->quitting)
return sr->exitCode ? sr->exitCode : EXIT_SUCCESS;
return false;
/* The |script| argument is processed after all options. */
if (const char* path = op->getStringArg("script")) {
Process(cx, path, false);
if (sr->exitCode)
return sr->exitCode;
if (!Process(cx, path, false))
return false;
}
DrainJobQueue(cx);
if (op->getBoolOption('i'))
Process(cx, nullptr, true);
if (op->getBoolOption('i')) {
if (!Process(cx, nullptr, true))
return false;
}
return sr->exitCode ? sr->exitCode : EXIT_SUCCESS;
return true;
}
static bool
@ -7036,7 +7069,16 @@ Shell(JSContext* cx, OptionParser* op, char** envp)
JSAutoCompartment ac(cx, glob);
int result = ProcessArgs(cx, op);
ShellRuntime* sr = GetShellRuntime(cx);
int result = EXIT_SUCCESS;
{
AutoReportException are(cx);
if (!ProcessArgs(cx, op) && !sr->quitting)
result = EXITCODE_RUNTIME_ERROR;
}
if (sr->exitCode)
result = sr->exitCode;
if (enableDisassemblyDumps)
js::DumpCompartmentPCCounts(cx);
@ -7368,7 +7410,7 @@ main(int argc, char** argv, char** envp)
JS_SetRuntimePrivate(rt, sr.get());
// Waiting is allowed on the shell's main thread, for now.
JS_SetFutexCanWait(rt);
JS_SetErrorReporter(rt, my_ErrorReporter);
JS_SetErrorReporter(rt, WarningReporter);
if (!SetRuntimeOptions(rt, op))
return 1;

View File

@ -24,7 +24,17 @@ const JSErrorFormatString*
my_GetErrorMessage(void* userRef, const unsigned errorNumber);
void
my_ErrorReporter(JSContext* cx, const char* message, JSErrorReport* report);
WarningReporter(JSContext* cx, const char* message, JSErrorReport* report);
class MOZ_STACK_CLASS AutoReportException
{
JSContext* cx;
public:
explicit AutoReportException(JSContext* cx)
: cx(cx)
{}
~AutoReportException();
};
bool
GenerateInterfaceHelp(JSContext* cx, JS::HandleObject obj, const char* name);