зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
77560bd888
Коммит
ac8d39d656
|
@ -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();
|
||||
|
|
Загрузка…
Ссылка в новой задаче