Bug 1517868 - Report unhandled rejections in JS shell. r=jorendorff

Differential Revision: https://phabricator.services.mozilla.com/D15792

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Tooru Fujisawa 2019-01-08 02:34:57 +00:00
Родитель 77560bd888
Коммит ac8d39d656
26 изменённых файлов: 254 добавлений и 4 удалений

Просмотреть файл

@ -4,6 +4,8 @@
// 11.4.3.4 AsyncGeneratorReject, step 7.
// 11.4.3.5 AsyncGeneratorResumeNext, step 10.b.ii.2.
ignoreUnhandledRejections();
var asyncIter = async function*(){ yield; }();
asyncIter.next();

Просмотреть файл

@ -1,5 +1,7 @@
load(libdir + "asserts.js");
ignoreUnhandledRejections();
let g = newGlobal();
let dbg = new Debugger();
let gw = dbg.addDebuggee(g);

Просмотреть файл

@ -1,6 +1,8 @@
// A Debugger can {return:} from the first onEnterFrame for an async function.
// (The exact behavior is undocumented; we're testing that it doesn't crash.)
ignoreUnhandledRejections();
let g = newGlobal();
g.hit2 = false;
g.eval(`async function f(x) { await x; return "ponies"; }`);

Просмотреть файл

@ -2,6 +2,8 @@
// Tests that wasm module can accept URL and sourceMapURL from response
// when instantiateStreaming is used.
ignoreUnhandledRejections();
try {
WebAssembly.compileStreaming();
} catch (err) {

Просмотреть файл

@ -1,6 +1,8 @@
if (helperThreadCount() == 0)
quit();
ignoreUnhandledRejections();
gczeal(9);
function rejectionTracker(state) {}
setPromiseRejectionTrackerCallback(rejectionTracker)

Просмотреть файл

@ -1,3 +1,5 @@
ignoreUnhandledRejections();
function checkGetOffsetsCoverage() {
var g = newGlobal();
var dbg = Debugger(g);

Просмотреть файл

@ -1,5 +1,7 @@
// |jit-test| skip-if: !('oomTest' in this)
ignoreUnhandledRejections();
(function () {
g = newGlobal();
g.parent = this;

Просмотреть файл

@ -3,5 +3,7 @@
// Note: without --ion-offthread-compile=off this test takes a long time and
// may timeout on some platforms. See bug 1507721.
ignoreUnhandledRejections();
oomTest(() => import("module1.js"));
oomTest(() => import("cyclicImport1.js"));

Просмотреть файл

@ -1,2 +1,4 @@
ignoreUnhandledRejections();
if (typeof oomTest === 'function')
oomTest(Function(`new Promise(res=>res)`));

Просмотреть файл

@ -1,3 +1,5 @@
// This just shouldn't crash.
ignoreUnhandledRejections();
Promise.resolve = () => 42;
Promise.all([1]);

Просмотреть файл

@ -1,3 +1,5 @@
ignoreUnhandledRejections();
const global = newGlobal();
const OtherPromise = global.Promise;
class SubPromise extends OtherPromise {}

Просмотреть файл

@ -0,0 +1,3 @@
// |jit-test| error:Error while printing unhandled rejection
Promise.reject({ toSource() { throw "another error"; } });

Просмотреть файл

@ -0,0 +1,3 @@
// |jit-test| error:Unhandled rejection: "some reason"
Promise.reject("some reason");

Просмотреть файл

@ -1,5 +1,7 @@
load(libdir + "asserts.js");
ignoreUnhandledRejections();
const g = newGlobal({sameCompartmentAs: this});
let resolve, reject;

Просмотреть файл

@ -2,6 +2,8 @@
load(libdir + "asserts.js");
ignoreUnhandledRejections();
let g = newGlobal();
let stream = new ReadableStream({
start(controller) {

Просмотреть файл

@ -1,3 +1,5 @@
ignoreUnhandledRejections();
Object.defineProperty(Promise, Symbol.species, {
value: function(g) {
g(function() {}, function() {})

Просмотреть файл

@ -1,5 +1,7 @@
// |jit-test| --no-ion; --no-baseline; skip-if: !('oomTest' in this)
ignoreUnhandledRejections();
function test() {
let controller;
let stream = new ReadableStream({

Просмотреть файл

@ -1,4 +1,7 @@
// |jit-test| skip-if: !('oomAfterAllocations' in this)
ignoreUnhandledRejections();
try {
WebAssembly.compileStreaming();
} catch (err) {

Просмотреть файл

@ -67,7 +67,9 @@
#include "shellmoduleloader.out.h"
#include "builtin/Array.h"
#include "builtin/MapObject.h"
#include "builtin/ModuleObject.h"
#include "builtin/Promise.h"
#include "builtin/RegExp.h"
#include "builtin/TestingFunctions.h"
#if defined(JS_BUILD_BINAST)
@ -601,14 +603,16 @@ extern MOZ_EXPORT void add_history(char* line);
ShellContext::ShellContext(JSContext* cx)
: isWorker(false),
lastWarningEnabled(false),
trackUnhandledRejections(true),
timeoutInterval(-1.0),
startTime(PRMJ_Now()),
serviceInterrupt(false),
haveInterruptFunc(false),
interruptFunc(cx, NullValue()),
lastWarningEnabled(false),
lastWarning(cx, NullValue()),
promiseRejectionTrackerCallback(cx, NullValue()),
unhandledRejectedPromises(cx),
watchdogLock(mutexid::ShellContextWatchdog),
exitCode(0),
quitting(false),
@ -1054,9 +1058,56 @@ static bool GlobalOfFirstJobInQueue(JSContext* cx, unsigned argc, Value* vp) {
return true;
}
static bool TrackUnhandledRejections(JSContext* cx, JS::HandleObject promise,
JS::PromiseRejectionHandlingState state) {
ShellContext* sc = GetShellContext(cx);
if (!sc->trackUnhandledRejections) {
return true;
}
if (!sc->unhandledRejectedPromises) {
sc->unhandledRejectedPromises = SetObject::create(cx);
if (!sc->unhandledRejectedPromises) {
return false;
}
}
RootedValue promiseVal(cx, ObjectValue(*promise));
Maybe<AutoRealm> ar;
if (cx->realm() != sc->unhandledRejectedPromises->realm()) {
ar.emplace(cx, sc->unhandledRejectedPromises);
if (!cx->compartment()->wrap(cx, &promiseVal)) {
return false;
}
}
switch (state) {
case JS::PromiseRejectionHandlingState::Unhandled:
if (!SetObject::add(cx, sc->unhandledRejectedPromises, promiseVal)) {
return false;
}
break;
case JS::PromiseRejectionHandlingState::Handled:
bool deleted = false;
if (!SetObject::delete_(cx, sc->unhandledRejectedPromises, promiseVal,
&deleted)) {
return false;
}
MOZ_ASSERT(deleted);
break;
}
return true;
}
static void ForwardingPromiseRejectionTrackerCallback(
JSContext* cx, JS::HandleObject promise,
JS::PromiseRejectionHandlingState state, void* data) {
if (!TrackUnhandledRejections(cx, promise, state)) {
return;
}
RootedValue callback(cx,
GetShellContext(cx)->promiseRejectionTrackerCallback);
if (callback.isNull()) {
@ -1092,8 +1143,6 @@ static bool SetPromiseRejectionTrackerCallback(JSContext* cx, unsigned argc,
}
GetShellContext(cx)->promiseRejectionTrackerCallback = args[0];
JS::SetPromiseRejectionTrackerCallback(
cx, ForwardingPromiseRejectionTrackerCallback);
args.rval().setUndefined();
return true;
@ -1548,6 +1597,16 @@ static bool AddPromiseReactions(JSContext* cx, unsigned argc, Value* vp) {
return JS::AddPromiseReactions(cx, promise, onResolve, onReject);
}
static bool IgnoreUnhandledRejections(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
ShellContext* sc = GetShellContext(cx);
sc->trackUnhandledRejections = false;
args.rval().setUndefined();
return true;
}
static bool Options(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
@ -8524,6 +8583,12 @@ JS_FN_HELP("parseBin", BinParse, 1, 0,
"addPromiseReactions(promise, onResolve, onReject)",
" Calls the JS::AddPromiseReactions JSAPI function with the given arguments."),
JS_FN_HELP("ignoreUnhandledRejections", IgnoreUnhandledRejections, 0, 0,
"ignoreUnhandledRejections()",
" By default, js shell tracks unhandled promise rejections and reports\n"
" them at the end of the exectuion. If a testcase isn't interested\n"
" in those rejections, call this to stop tracking and reporting."),
JS_FN_HELP("getMaxArgs", GetMaxArgs, 0, 0,
"getMaxArgs()",
" Return the maximum number of supported args for a call."),
@ -10485,6 +10550,104 @@ static void SetWorkerContextOptions(JSContext* cx) {
JS_SetNativeStackQuota(cx, gMaxStackSize);
}
static MOZ_MUST_USE bool PrintUnhandledRejection(
JSContext* cx, Handle<PromiseObject*> promise) {
RootedValue reason(cx, promise->reason());
RootedObject site(cx, promise->resolutionSite());
RootedString str(cx, JS_ValueToSource(cx, reason));
if (!str) {
return false;
}
UniqueChars utf8chars = JS_EncodeStringToUTF8(cx, str);
if (!utf8chars) {
return false;
}
FILE* fp = ErrorFilePointer();
fprintf(fp, "Unhandled rejection: %s\n", utf8chars.get());
if (!site) {
fputs("(no stack trace available)\n", stderr);
return true;
}
JSPrincipals* principals = cx->realm()->principals();
RootedString stackStr(cx);
if (!BuildStackString(cx, principals, site, &stackStr, 2)) {
return false;
}
UniqueChars stack = JS_EncodeStringToUTF8(cx, stackStr);
if (!stack) {
return false;
}
fputs("Stack:\n", fp);
fputs(stack.get(), fp);
return true;
}
static MOZ_MUST_USE bool ReportUnhandledRejections(JSContext* cx) {
ShellContext* sc = GetShellContext(cx);
if (!sc->trackUnhandledRejections) {
return true;
}
if (!sc->unhandledRejectedPromises) {
return true;
}
AutoRealm ar(cx, sc->unhandledRejectedPromises);
if (!SetObject::size(cx, sc->unhandledRejectedPromises)) {
return true;
}
sc->exitCode = EXITCODE_RUNTIME_ERROR;
RootedValue iter(cx);
if (!SetObject::iterator(cx, SetObject::IteratorKind::Values,
sc->unhandledRejectedPromises, &iter)) {
return false;
}
Rooted<SetIteratorObject*> iterObj(cx,
&iter.toObject().as<SetIteratorObject>());
RootedArrayObject resultObj(
cx, &SetIteratorObject::createResult(cx)->as<ArrayObject>());
if (!resultObj) {
return false;
}
while (true) {
bool done = SetIteratorObject::next(iterObj, resultObj, cx);
if (done) {
break;
}
RootedObject obj(cx, &resultObj->getDenseElement(0).toObject());
obj = CheckedUnwrap(obj);
if (!obj) {
return false;
}
Handle<PromiseObject*> promise = obj.as<PromiseObject>();
AutoRealm ar2(cx, promise);
if (!PrintUnhandledRejection(cx, promise)) {
return false;
}
}
sc->unhandledRejectedPromises = nullptr;
return true;
}
static int Shell(JSContext* cx, OptionParser* op, char** envp) {
if (op->getBoolOption("wasm-compile-and-serialize")) {
if (!WasmCompileAndSerialize(cx)) {
@ -10547,6 +10710,14 @@ static int Shell(JSContext* cx, OptionParser* op, char** envp) {
js::RunJobs(cx);
}
// Only if there's no other error, report unhandled rejections.
if (!result && !sc->exitCode) {
if (!ReportUnhandledRejections(cx)) {
FILE* fp = ErrorFilePointer();
fputs("Error while printing unhandled rejection\n", fp);
}
}
if (sc->exitCode) {
result = sc->exitCode;
}
@ -11082,6 +11253,9 @@ int main(int argc, char** argv, char** envp) {
});
JS::InitConsumeStreamCallback(cx, ConsumeBufferSource, ReportStreamError);
JS::SetPromiseRejectionTrackerCallback(
cx, ForwardingPromiseRejectionTrackerCallback);
JS_SetNativeStackQuota(cx, gMaxStackSize);
JS::dbg::SetDebuggerMallocSizeOf(cx, moz_malloc_size_of);

Просмотреть файл

@ -14,6 +14,7 @@
#include "jsapi.h"
#include "builtin/MapObject.h"
#include "js/GCVector.h"
#include "threading/ConditionVariable.h"
#include "threading/LockGuard.h"
@ -131,14 +132,24 @@ struct ShellContext {
~ShellContext();
bool isWorker;
bool lastWarningEnabled;
// Track promise rejections and report unhandled rejections.
bool trackUnhandledRejections;
double timeoutInterval;
double startTime;
mozilla::Atomic<bool> serviceInterrupt;
mozilla::Atomic<bool> haveInterruptFunc;
JS::PersistentRootedValue interruptFunc;
bool lastWarningEnabled;
JS::PersistentRootedValue lastWarning;
JS::PersistentRootedValue promiseRejectionTrackerCallback;
// Rejected promises that are not yet handled. Added when rejection
// happens, and removed when rejection is handled. This uses SetObject to
// report unhandled rejections in the rejected order.
JS::PersistentRooted<SetObject*> unhandledRejectedPromises;
#ifdef SINGLESTEP_PROFILING
Vector<StackChars, 0, SystemAllocPolicy> stacks;
#endif

Просмотреть файл

@ -1,5 +1,9 @@
// |reftest| skip-if(!this.ReadableStream||!this.drainJobQueue)
if ("ignoreUnhandledRejections" in this) {
ignoreUnhandledRejections();
}
// Example of a stream that produces data on demand, the "pull" model.
let fibStream = new ReadableStream({
start(controller) {

Просмотреть файл

@ -1,5 +1,9 @@
// |reftest| skip-if(!this.ReadableStream||!this.drainJobQueue)
if ("ignoreUnhandledRejections" in this) {
ignoreUnhandledRejections();
}
// Example of a stream that enqueues data asynchronously, whether the reader
// wants it or not, the "push" model.
let fbStream = new ReadableStream({

Просмотреть файл

@ -1,5 +1,9 @@
// |reftest| skip-if(!xulRuntime.shell) -- needs drainJobQueue
if ("ignoreUnhandledRejections" in this) {
ignoreUnhandledRejections();
}
// Spot-check subclassing of stream constructors.
// ReadableStream can be subclassed.

Просмотреть файл

@ -590,3 +590,8 @@ function $DONE(failure) {
else
reportCompare(0, 0);
}
// Some tests in test262 leave promise rejections unhandled.
if ("ignoreUnhandledRejections" in this) {
ignoreUnhandledRejections();
}

Просмотреть файл

@ -86,3 +86,8 @@ if (!("setTimeout" in this)) {
this.clearInterval = this.clearTimeout;
}
// Some tests in web platform tests leave promise rejections unhandled.
if ("ignoreUnhandledRejections" in this) {
ignoreUnhandledRejections();
}

Просмотреть файл

@ -1,5 +1,9 @@
// |reftest| skip-if(!this.hasOwnProperty("ReadableStream"))
if ("ignoreUnhandledRejections" in this) {
ignoreUnhandledRejections();
}
async function test() {
if (typeof newGlobal !== 'undefined') {
otherGlobal = newGlobal();