Merge inbound to mozilla-central. a=merge

This commit is contained in:
Mihai Alexandru Michis 2019-07-26 00:51:09 +03:00
commit 124c0de476
38 changed files with 1904 additions and 337 deletions

View File

@ -839,7 +839,7 @@ static const uint32_t JSCLASS_FOREGROUND_FINALIZE =
// application. // application.
static const uint32_t JSCLASS_GLOBAL_APPLICATION_SLOTS = 5; static const uint32_t JSCLASS_GLOBAL_APPLICATION_SLOTS = 5;
static const uint32_t JSCLASS_GLOBAL_SLOT_COUNT = static const uint32_t JSCLASS_GLOBAL_SLOT_COUNT =
JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 37; JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 38;
#define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n) \ #define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n) \
(JSCLASS_IS_GLOBAL | \ (JSCLASS_IS_GLOBAL | \

View File

@ -134,6 +134,9 @@ class JS_PUBLIC_API TransitiveCompileOptions {
uint32_t introductionOffset = 0; uint32_t introductionOffset = 0;
bool hasIntroductionInfo = false; bool hasIntroductionInfo = false;
// Mask of operation kinds which should be instrumented.
uint32_t instrumentationKinds = 0;
protected: protected:
TransitiveCompileOptions() = default; TransitiveCompileOptions() = default;

View File

@ -284,6 +284,15 @@ class DebugAPI {
// Whether any debugger is observing debugger statements in a realm. // Whether any debugger is observing debugger statements in a realm.
static bool hasDebuggerStatementHook(GlobalObject* global); static bool hasDebuggerStatementHook(GlobalObject* global);
/*
* Get any instrumentation ID which has been associated with a script using
* the specified debugger object.
*/
static bool getScriptInstrumentationId(JSContext* cx,
HandleObject dbgObject,
HandleScript script,
MutableHandleValue rval);
private: private:
static bool stepModeEnabledSlow(JSScript* script); static bool stepModeEnabledSlow(JSScript* script);
static bool hasBreakpointsAtSlow(JSScript* script, jsbytecode* pc); static bool hasBreakpointsAtSlow(JSScript* script, jsbytecode* pc);

View File

@ -48,6 +48,7 @@
#include "vm/AsyncFunction.h" #include "vm/AsyncFunction.h"
#include "vm/AsyncIteration.h" #include "vm/AsyncIteration.h"
#include "vm/GeckoProfiler.h" #include "vm/GeckoProfiler.h"
#include "vm/Instrumentation.h"
#include "vm/JSContext.h" #include "vm/JSContext.h"
#include "vm/JSObject.h" #include "vm/JSObject.h"
#include "vm/Realm.h" #include "vm/Realm.h"
@ -6408,6 +6409,19 @@ JSObject* Debugger::wrapWasmSource(JSContext* cx,
return wrapVariantReferent(cx, referent); return wrapVariantReferent(cx, referent);
} }
bool DebugAPI::getScriptInstrumentationId(JSContext* cx,
HandleObject dbgObject,
HandleScript script,
MutableHandleValue rval) {
Debugger* dbg = Debugger::fromJSObject(dbgObject);
DebuggerScript* dbgScript = dbg->wrapScript(cx, script);
if (!dbgScript) {
return false;
}
rval.set(dbgScript->getInstrumentationId());
return true;
}
static bool DebuggerSource_construct(JSContext* cx, unsigned argc, Value* vp) { static bool DebuggerSource_construct(JSContext* cx, unsigned argc, Value* vp) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR, JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR,
"Debugger.Source"); "Debugger.Source");

View File

@ -10,6 +10,7 @@
#include "debugger/Script.h" #include "debugger/Script.h"
#include "proxy/ScriptedProxyHandler.h" #include "proxy/ScriptedProxyHandler.h"
#include "vm/EnvironmentObject.h" #include "vm/EnvironmentObject.h"
#include "vm/Instrumentation.h"
#include "vm/WrapperObject.h" #include "vm/WrapperObject.h"
#include "debugger/Debugger-inl.h" #include "debugger/Debugger-inl.h"
@ -1239,6 +1240,101 @@ bool DebuggerObject::unwrapMethod(JSContext* cx, unsigned argc, Value* vp) {
return true; return true;
} }
/* static */
bool DebuggerObject::setInstrumentationMethod(JSContext* cx, unsigned argc,
Value* vp) {
THIS_DEBUGOBJECT(cx, argc, vp, "setInstrumentation", args, object);
if (!args.requireAtLeast(
cx, "Debugger.Object.prototype.setInstrumentation", 2)) {
return false;
}
if (!DebuggerObject::requireGlobal(cx, object)) {
return false;
}
RootedGlobalObject global(cx, &object->referent()->as<GlobalObject>());
RootedValue v(cx, args[0]);
if (!object->owner()->unwrapDebuggeeValue(cx, &v)) {
return false;
}
if (!v.isObject()) {
JS_ReportErrorASCII(cx, "Instrumentation callback must be an object");
return false;
}
RootedObject callback(cx, &v.toObject());
if (!args[1].isObject()) {
JS_ReportErrorASCII(cx, "Instrumentation kinds must be an object");
return false;
}
RootedObject kindsObj(cx, &args[1].toObject());
unsigned length = 0;
if (!GetLengthProperty(cx, kindsObj, &length)) {
return false;
}
Rooted<ValueVector> values(cx, ValueVector(cx));
if (!values.growBy(length) ||
!GetElements(cx, kindsObj, length, values.begin())) {
return false;
}
Rooted<StringVector> kinds(cx, StringVector(cx));
for (size_t i = 0; i < values.length(); i++) {
if (!values[i].isString()) {
JS_ReportErrorASCII(cx, "Instrumentation kind must be a string");
return false;
}
if (!kinds.append(values[i].toString())) {
return false;
}
}
{
AutoRealm ar(cx, global);
RootedObject dbgObject(cx, object->owner()->toJSObject());
if (!RealmInstrumentation::install(cx, global, callback, dbgObject,
kinds)) {
return false;
}
}
args.rval().setUndefined();
return true;
}
/* static */
bool DebuggerObject::setInstrumentationActiveMethod(JSContext* cx,
unsigned argc,
Value* vp) {
THIS_DEBUGOBJECT(cx, argc, vp, "setInstrumentationActive", args, object);
if (!DebuggerObject::requireGlobal(cx, object)) {
return false;
}
if (!args.requireAtLeast(
cx, "Debugger.Object.prototype.setInstrumentationActive", 1)) {
return false;
}
RootedGlobalObject global(cx, &object->referent()->as<GlobalObject>());
bool active = ToBoolean(args[0]);
{
AutoRealm ar(cx, global);
if (!RealmInstrumentation::setActive(cx, global, object->owner(), active)) {
return false;
}
}
args.rval().setUndefined();
return true;
}
const JSPropertySpec DebuggerObject::properties_[] = { const JSPropertySpec DebuggerObject::properties_[] = {
JS_PSG("callable", DebuggerObject::callableGetter, 0), JS_PSG("callable", DebuggerObject::callableGetter, 0),
JS_PSG("isBoundFunction", DebuggerObject::isBoundFunctionGetter, 0), JS_PSG("isBoundFunction", DebuggerObject::isBoundFunctionGetter, 0),
@ -1311,6 +1407,9 @@ const JSFunctionSpec DebuggerObject::methods_[] = {
JS_FN("makeDebuggeeValue", DebuggerObject::makeDebuggeeValueMethod, 1, 0), JS_FN("makeDebuggeeValue", DebuggerObject::makeDebuggeeValueMethod, 1, 0),
JS_FN("unsafeDereference", DebuggerObject::unsafeDereferenceMethod, 0, 0), JS_FN("unsafeDereference", DebuggerObject::unsafeDereferenceMethod, 0, 0),
JS_FN("unwrap", DebuggerObject::unwrapMethod, 0, 0), JS_FN("unwrap", DebuggerObject::unwrapMethod, 0, 0),
JS_FN("setInstrumentation", DebuggerObject::setInstrumentationMethod, 2, 0),
JS_FN("setInstrumentationActive",
DebuggerObject::setInstrumentationActiveMethod, 1, 0),
JS_FS_END}; JS_FS_END};
/* static */ /* static */

View File

@ -306,6 +306,11 @@ class DebuggerObject : public NativeObject {
Value* vp); Value* vp);
static MOZ_MUST_USE bool unwrapMethod(JSContext* cx, unsigned argc, static MOZ_MUST_USE bool unwrapMethod(JSContext* cx, unsigned argc,
Value* vp); Value* vp);
static MOZ_MUST_USE bool setInstrumentationMethod(JSContext* cx,
unsigned argc, Value* vp);
static MOZ_MUST_USE bool setInstrumentationActiveMethod(JSContext* cx,
unsigned argc,
Value* vp);
static MOZ_MUST_USE bool getErrorReport(JSContext* cx, static MOZ_MUST_USE bool getErrorReport(JSContext* cx,
HandleObject maybeError, HandleObject maybeError,
JSErrorReport*& report); JSErrorReport*& report);

View File

@ -2083,6 +2083,28 @@ bool DebuggerScript::getOffsetsCoverage(JSContext* cx, unsigned argc,
return true; return true;
} }
/* static */
bool DebuggerScript::setInstrumentationId(JSContext* cx, unsigned argc,
Value* vp) {
THIS_DEBUGSCRIPT_SCRIPT_MAYBE_LAZY(cx, argc, vp, "setInstrumentationId", args,
obj);
if (!obj->getInstrumentationId().isUndefined()) {
JS_ReportErrorASCII(cx, "Script instrumentation ID is already set");
return false;
}
if (!args.get(0).isNumber()) {
JS_ReportErrorASCII(cx, "Script instrumentation ID must be a number");
return false;
}
obj->setReservedSlot(INSTRUMENTATION_ID_SLOT, args.get(0));
args.rval().setUndefined();
return true;
}
/* static */ /* static */
bool DebuggerScript::construct(JSContext* cx, unsigned argc, Value* vp) { bool DebuggerScript::construct(JSContext* cx, unsigned argc, Value* vp) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR, JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR,
@ -2119,6 +2141,7 @@ const JSFunctionSpec DebuggerScript::methods_[] = {
JS_FN("getOffsetsCoverage", getOffsetsCoverage, 0, 0), JS_FN("getOffsetsCoverage", getOffsetsCoverage, 0, 0),
JS_FN("getSuccessorOffsets", getSuccessorOffsets, 1, 0), JS_FN("getSuccessorOffsets", getSuccessorOffsets, 1, 0),
JS_FN("getPredecessorOffsets", getPredecessorOffsets, 1, 0), JS_FN("getPredecessorOffsets", getPredecessorOffsets, 1, 0),
JS_FN("setInstrumentationId", setInstrumentationId, 1, 0),
// The following APIs are deprecated due to their reliance on the // The following APIs are deprecated due to their reliance on the
// under-defined 'entrypoint' concept. Make use of getPossibleBreakpoints, // under-defined 'entrypoint' concept. Make use of getPossibleBreakpoints,

View File

@ -33,6 +33,10 @@ class DebuggerScript : public NativeObject {
enum { enum {
OWNER_SLOT, OWNER_SLOT,
// Holds any instrumentation ID that has been assigned to the script.
INSTRUMENTATION_ID_SLOT,
RESERVED_SLOTS, RESERVED_SLOTS,
}; };
@ -84,6 +88,7 @@ class DebuggerScript : public NativeObject {
static bool clearAllBreakpoints(JSContext* cx, unsigned argc, Value* vp); static bool clearAllBreakpoints(JSContext* cx, unsigned argc, Value* vp);
static bool isInCatchScope(JSContext* cx, unsigned argc, Value* vp); static bool isInCatchScope(JSContext* cx, unsigned argc, Value* vp);
static bool getOffsetsCoverage(JSContext* cx, unsigned argc, Value* vp); static bool getOffsetsCoverage(JSContext* cx, unsigned argc, Value* vp);
static bool setInstrumentationId(JSContext* cx, unsigned argc, Value* vp);
static bool construct(JSContext* cx, unsigned argc, Value* vp); static bool construct(JSContext* cx, unsigned argc, Value* vp);
template <typename T> template <typename T>
@ -93,6 +98,10 @@ class DebuggerScript : public NativeObject {
Value* vp, const char* name, Value* vp, const char* name,
bool successor); bool successor);
Value getInstrumentationId() const {
return getSlot(INSTRUMENTATION_ID_SLOT);
}
private: private:
static const ClassOps classOps_; static const ClassOps classOps_;

View File

@ -581,3 +581,44 @@ of exotic object like an opaque wrapper.
<code>forceLexicalInitializationByName(<i>binding</i>)</code> <code>forceLexicalInitializationByName(<i>binding</i>)</code>
: If <i>binding</i> is in an uninitialized state initialize it to undefined : If <i>binding</i> is in an uninitialized state initialize it to undefined
and return true, otherwise do nothing and return false. and return true, otherwise do nothing and return false.
`setInstrumentation(callback, kinds)`
: If the referent is a global object, this specifies how instrumentation
should be installed on scripts created in the future in this global's realm.
If the referent is not a global object, throw a `TypeError`. If the global
already has instrumentation specified, throw an `Error`. `callback` is a
`Debugger.Object` wrapping the callback to invoke, and `kinds` is an array
of strings specifying the kinds of operations at which the callback should
be invoked. Instrumentation is initially inactive, and can be activated via
`DebuggerObject.setInstrumentationActive`. When instrumentation is active
and an operation described by one of the instrumentation kinds executes,
the callback is invoked with at least three arguments: the string kind of
operation executing, the ID for the script specified by
`Debugger.Script.setInstrumentationId`, and the bytecode offset
of the script location following the instrumentation. More call arguments
are possible for some instrumentation kinds. If an instrumented script is
invoked without having `setInstrumentationId` called on it, it will throw an
`Error`. The possible instrumentation kinds and any extra arguments are as
follows:
* `main`: The main entry point of a script.
* `entry`: Points other than the main entry point where a frame for the
script might begin executing when it was not previously on the stack.
Only applies to generator/async scripts.
* `exit`: Points at which a script's frame will be popped or suspended.
* `breakpoint`: Breakpoint sites in a script.
* `getProperty`: Property read operations, including `x.f` and
destructuring operations. The callback will be additionally invoked with
the object and property name.
* `setProperty`: Property write operations. The callback will be
additionally invoked with the object, property name, and rhs.
* `getElement`: Element read operations. The callback will be additionally
invoked with the object and element value.
* `setElement`: Element write operations. The callback will be
additionally invoked with the object, element value, and rhs.
`setInstrumentationActive(active)`
: If the referent is a global object, set whether instrumentation is active
in the global's realm. The instrumentation callback is only invoked when
instrumentation is active, and code will run faster when instrumentation is
inactive. If the referent is not a global object, throw a `TypeError`.
If the referent has not had instrumentation installed, throw an `Error`.

View File

@ -345,6 +345,12 @@ methods of other kinds of objects.
**If the instance refers to WebAssembly code**, throw a `TypeError`. **If the instance refers to WebAssembly code**, throw a `TypeError`.
`setInstrumentationId(id)`:
: **If the instance refers to a `JSScript`**, set the value which will be
supplied as the script's ID to instrumentation callbacks in the script's
realm. See `Debugger.Object.setInstrumentation()`.
**If the instance refers to WebAssembly code**, throw a `TypeError`.
### Deprecated Debugger.Script Prototype Functions ### Deprecated Debugger.Script Prototype Functions

View File

@ -123,6 +123,7 @@ BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent,
: BytecodeEmitter(parent, sc, script, lazyScript, lineNum, emitterMode, : BytecodeEmitter(parent, sc, script, lazyScript, lineNum, emitterMode,
fieldInitializers) { fieldInitializers) {
parser = handle; parser = handle;
instrumentationKinds = parser->options().instrumentationKinds;
} }
BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent,
@ -135,6 +136,7 @@ BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent,
fieldInitializers) { fieldInitializers) {
ep_.emplace(parser); ep_.emplace(parser);
this->parser = ep_.ptr(); this->parser = ep_.ptr();
instrumentationKinds = this->parser->options().instrumentationKinds;
} }
void BytecodeEmitter::initFromBodyPosition(TokenPos bodyPosition) { void BytecodeEmitter::initFromBodyPosition(TokenPos bodyPosition) {
@ -177,6 +179,10 @@ bool BytecodeEmitter::markStepBreakpoint() {
return true; return true;
} }
if (!emitInstrumentation(InstrumentationKind::Breakpoint)) {
return false;
}
if (!newSrcNote(SRC_STEP_SEP)) { if (!newSrcNote(SRC_STEP_SEP)) {
return false; return false;
} }
@ -203,6 +209,10 @@ bool BytecodeEmitter::markSimpleBreakpoint() {
// having two breakpoints with the same line/column position. // having two breakpoints with the same line/column position.
// Note: This assumes that the position for the call has already been set. // Note: This assumes that the position for the call has already been set.
if (!bytecodeSection().isDuplicateLocation()) { if (!bytecodeSection().isDuplicateLocation()) {
if (!emitInstrumentation(InstrumentationKind::Breakpoint)) {
return false;
}
if (!newSrcNote(SRC_BREAKPOINT)) { if (!newSrcNote(SRC_BREAKPOINT)) {
return false; return false;
} }
@ -445,25 +455,33 @@ bool BytecodeEmitter::emitCall(JSOp op, uint16_t argc, ParseNode* pn) {
return emitCall(op, argc, pn ? Some(pn->pn_pos.begin) : Nothing()); return emitCall(op, argc, pn ? Some(pn->pn_pos.begin) : Nothing());
} }
bool BytecodeEmitter::emitDupAt(unsigned slotFromTop) { bool BytecodeEmitter::emitDupAt(unsigned slotFromTop, unsigned count) {
MOZ_ASSERT(slotFromTop < unsigned(bytecodeSection().stackDepth())); MOZ_ASSERT(slotFromTop < unsigned(bytecodeSection().stackDepth()));
MOZ_ASSERT(slotFromTop + 1 >= count);
if (slotFromTop == 0) { if (slotFromTop == 0 && count == 1) {
return emit1(JSOP_DUP); return emit1(JSOP_DUP);
} }
if (slotFromTop == 1 && count == 2) {
return emit1(JSOP_DUP2);
}
if (slotFromTop >= JS_BIT(24)) { if (slotFromTop >= JS_BIT(24)) {
reportError(nullptr, JSMSG_TOO_MANY_LOCALS); reportError(nullptr, JSMSG_TOO_MANY_LOCALS);
return false; return false;
} }
BytecodeOffset off; for (unsigned i = 0; i < count; i++) {
if (!emitN(JSOP_DUPAT, 3, &off)) { BytecodeOffset off;
return false; if (!emitN(JSOP_DUPAT, 3, &off)) {
return false;
}
jsbytecode* pc = bytecodeSection().code(off);
SET_UINT24(pc, slotFromTop);
} }
jsbytecode* pc = bytecodeSection().code(off);
SET_UINT24(pc, slotFromTop);
return true; return true;
} }
@ -891,7 +909,7 @@ bool BytecodeEmitter::emitIndexOp(JSOp op, uint32_t index) {
return true; return true;
} }
bool BytecodeEmitter::emitAtomOp(JSAtom* atom, JSOp op) { bool BytecodeEmitter::emitAtomOp(JSAtom* atom, JSOp op, ShouldInstrument shouldInstrument) {
MOZ_ASSERT(atom); MOZ_ASSERT(atom);
// .generator lookups should be emitted as JSOP_GETALIASEDVAR instead of // .generator lookups should be emitted as JSOP_GETALIASEDVAR instead of
@ -911,12 +929,18 @@ bool BytecodeEmitter::emitAtomOp(JSAtom* atom, JSOp op) {
return false; return false;
} }
return emitAtomOp(index, op); return emitAtomOp(index, op, shouldInstrument);
} }
bool BytecodeEmitter::emitAtomOp(uint32_t atomIndex, JSOp op) { bool BytecodeEmitter::emitAtomOp(uint32_t atomIndex, JSOp op,
ShouldInstrument shouldInstrument) {
MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM); MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM);
if (shouldInstrument != ShouldInstrument::No &&
!emitInstrumentationForOpcode(op, atomIndex)) {
return false;
}
return emitIndexOp(op, atomIndex); return emitIndexOp(op, atomIndex);
} }
@ -1822,7 +1846,7 @@ bool BytecodeEmitter::emitPropLHS(PropertyAccess* prop) {
while (true) { while (true) {
// Walk back up the list, emitting annotated name ops. // Walk back up the list, emitting annotated name ops.
if (!emitAtomOp(pndot->key().atom(), JSOP_GETPROP)) { if (!emitAtomOp(pndot->key().atom(), JSOP_GETPROP, ShouldInstrument::Yes)) {
return false; return false;
} }
@ -1895,7 +1919,13 @@ bool BytecodeEmitter::emitNameIncDec(UnaryNode* incDec) {
return true; return true;
} }
bool BytecodeEmitter::emitElemOpBase(JSOp op) { bool BytecodeEmitter::emitElemOpBase(JSOp op,
ShouldInstrument shouldInstrument) {
if (shouldInstrument != ShouldInstrument::No &&
!emitInstrumentationForOpcode(op, 0)) {
return false;
}
if (!emit1(op)) { if (!emit1(op)) {
return false; return false;
} }
@ -2266,6 +2296,11 @@ bool BytecodeEmitter::allocateResumeIndexRange(
} }
bool BytecodeEmitter::emitYieldOp(JSOp op) { bool BytecodeEmitter::emitYieldOp(JSOp op) {
// All yield operations pop or suspend the current frame.
if (!emitInstrumentation(InstrumentationKind::Exit)) {
return false;
}
if (op == JSOP_FINALYIELDRVAL) { if (op == JSOP_FINALYIELDRVAL) {
return emit1(JSOP_FINALYIELDRVAL); return emit1(JSOP_FINALYIELDRVAL);
} }
@ -2288,6 +2323,10 @@ bool BytecodeEmitter::emitYieldOp(JSOp op) {
SET_RESUMEINDEX(bytecodeSection().code(off), resumeIndex); SET_RESUMEINDEX(bytecodeSection().code(off), resumeIndex);
if (!emitInstrumentation(InstrumentationKind::Entry)) {
return false;
}
BytecodeOffset unusedOffset; BytecodeOffset unusedOffset;
return emitJumpTargetOp(JSOP_AFTERYIELD, &unusedOffset); return emitJumpTargetOp(JSOP_AFTERYIELD, &unusedOffset);
} }
@ -2419,7 +2458,9 @@ bool BytecodeEmitter::emitScript(ParseNode* body) {
return false; return false;
} }
switchToMain(); if (!switchToMain()) {
return false;
}
ParseNode* scopeBody = scope->scopeBody(); ParseNode* scopeBody = scope->scopeBody();
if (!emitLexicalScopeBody(scopeBody, EMIT_LINENOTE)) { if (!emitLexicalScopeBody(scopeBody, EMIT_LINENOTE)) {
@ -2440,7 +2481,9 @@ bool BytecodeEmitter::emitScript(ParseNode* body) {
} }
} }
switchToMain(); if (!switchToMain()) {
return false;
}
if (!emitTree(body)) { if (!emitTree(body)) {
return false; return false;
@ -2454,7 +2497,7 @@ bool BytecodeEmitter::emitScript(ParseNode* body) {
return false; return false;
} }
if (!emit1(JSOP_RETRVAL)) { if (!emitReturnRval()) {
return false; return false;
} }
@ -2954,14 +2997,10 @@ bool BytecodeEmitter::emitIteratorCloseInScope(
// [stack] ... RET ITER UNDEF // [stack] ... RET ITER UNDEF
return false; return false;
} }
if (!emitDupAt(2)) { if (!emitDupAt(2, 2)) {
// [stack] ... RET ITER UNDEF RET // [stack] ... RET ITER UNDEF RET
return false; return false;
} }
if (!emitDupAt(2)) {
// [stack] ... RET ITER UNDEF RET ITER
return false;
}
} }
if (!emitCall(JSOP_CALL, 0)) { if (!emitCall(JSOP_CALL, 0)) {
@ -2998,15 +3037,11 @@ bool BytecodeEmitter::emitIteratorCloseInScope(
} }
if (!tryCatch->emitCatch()) { if (!tryCatch->emitCatch()) {
// [stack] ... RET ITER RESULT // [stack] ... RET ITER RESULT EXC
return false; return false;
} }
// Just ignore the exception thrown by call and await. // Just ignore the exception thrown by call and await.
if (!emit1(JSOP_EXCEPTION)) {
// [stack] ... RET ITER RESULT EXC
return false;
}
if (!emit1(JSOP_POP)) { if (!emit1(JSOP_POP)) {
// [stack] ... RET ITER RESULT // [stack] ... RET ITER RESULT
return false; return false;
@ -3378,14 +3413,10 @@ bool BytecodeEmitter::emitDestructuringOpsArray(ListNode* pattern,
// If iterator is not completed, create a new array with the rest // If iterator is not completed, create a new array with the rest
// of the iterator. // of the iterator.
if (!emitDupAt(emitted + 1)) { if (!emitDupAt(emitted + 1, 2)) {
// [stack] ... OBJ NEXT ITER LREF* NEXT // [stack] ... OBJ NEXT ITER LREF* NEXT
return false; return false;
} }
if (!emitDupAt(emitted + 1)) {
// [stack] ... OBJ NEXT ITER LREF* NEXT ITER
return false;
}
if (!emitUint32Operand(JSOP_NEWARRAY, 0)) { if (!emitUint32Operand(JSOP_NEWARRAY, 0)) {
// [stack] ... OBJ NEXT ITER LREF* NEXT ITER ARRAY // [stack] ... OBJ NEXT ITER LREF* NEXT ITER ARRAY
return false; return false;
@ -3474,14 +3505,10 @@ bool BytecodeEmitter::emitDestructuringOpsArray(ListNode* pattern,
} }
} }
if (!emitDupAt(emitted + 1)) { if (!emitDupAt(emitted + 1, 2)) {
// [stack] ... OBJ NEXT ITER LREF* NEXT // [stack] ... OBJ NEXT ITER LREF* NEXT
return false; return false;
} }
if (!emitDupAt(emitted + 1)) {
// [stack] ... OBJ NEXT ITER LREF* NEXT ITER
return false;
}
if (!emitIteratorNext(Some(pattern->pn_pos.begin))) { if (!emitIteratorNext(Some(pattern->pn_pos.begin))) {
// [stack] ... OBJ NEXT ITER LREF* RESULT // [stack] ... OBJ NEXT ITER LREF* RESULT
return false; return false;
@ -3735,7 +3762,8 @@ bool BytecodeEmitter::emitDestructuringOpsObject(ListNode* pattern,
} }
} else if (key->isKind(ParseNodeKind::ObjectPropertyName) || } else if (key->isKind(ParseNodeKind::ObjectPropertyName) ||
key->isKind(ParseNodeKind::StringExpr)) { key->isKind(ParseNodeKind::StringExpr)) {
if (!emitAtomOp(key->as<NameNode>().atom(), JSOP_GETPROP)) { if (!emitAtomOp(key->as<NameNode>().atom(), JSOP_GETPROP,
ShouldInstrument::Yes)) {
// [stack] ... SET? RHS LREF* PROP // [stack] ... SET? RHS LREF* PROP
return false; return false;
} }
@ -4593,11 +4621,6 @@ bool BytecodeEmitter::emitCatch(BinaryNode* catchClause) {
// We must be nested under a try-finally statement. // We must be nested under a try-finally statement.
MOZ_ASSERT(innermostNestableControl->is<TryFinallyControl>()); MOZ_ASSERT(innermostNestableControl->is<TryFinallyControl>());
/* Pick up the pending exception and bind it to the catch variable. */
if (!emit1(JSOP_EXCEPTION)) {
return false;
}
ParseNode* param = catchClause->left(); ParseNode* param = catchClause->left();
if (!param) { if (!param) {
// Catch parameter was omitted; just discard the exception. // Catch parameter was omitted; just discard the exception.
@ -5215,14 +5238,10 @@ bool BytecodeEmitter::emitSpread(bool allowSelfHosted) {
return false; return false;
} }
if (!emitDupAt(3)) { if (!emitDupAt(3, 2)) {
// [stack] NEXT ITER ARR I NEXT // [stack] NEXT ITER ARR I NEXT
return false; return false;
} }
if (!emitDupAt(3)) {
// [stack] NEXT ITER ARR I NEXT ITER
return false;
}
if (!emitIteratorNext(Nothing(), IteratorKind::Sync, allowSelfHosted)) { if (!emitIteratorNext(Nothing(), IteratorKind::Sync, allowSelfHosted)) {
// [stack] ITER ARR I RESULT // [stack] ITER ARR I RESULT
return false; return false;
@ -6073,13 +6092,16 @@ bool BytecodeEmitter::emitReturn(UnaryNode* returnNode) {
} }
} else if (isDerivedClassConstructor) { } else if (isDerivedClassConstructor) {
MOZ_ASSERT(bytecodeSection().code()[top.value()] == JSOP_SETRVAL); MOZ_ASSERT(bytecodeSection().code()[top.value()] == JSOP_SETRVAL);
if (!emit1(JSOP_RETRVAL)) { if (!emitReturnRval()) {
return false; return false;
} }
} else if (top + BytecodeOffsetDiff(JSOP_RETURN_LENGTH) != } else if (top + BytecodeOffsetDiff(JSOP_RETURN_LENGTH) !=
bytecodeSection().offset()) { bytecodeSection().offset() ||
// If we are instrumenting, make sure we use RETRVAL and add any
// instrumentation for the frame exit.
instrumentationKinds) {
bytecodeSection().code()[top.value()] = JSOP_SETRVAL; bytecodeSection().code()[top.value()] = JSOP_SETRVAL;
if (!emit1(JSOP_RETRVAL)) { if (!emitReturnRval()) {
return false; return false;
} }
} }
@ -6319,16 +6341,13 @@ bool BytecodeEmitter::emitYieldStar(ParseNode* iter) {
} }
if (!tryCatch.emitCatch()) { if (!tryCatch.emitCatch()) {
// [stack] NEXT ITER RESULT
return false;
}
MOZ_ASSERT(bytecodeSection().stackDepth() == startDepth);
if (!emit1(JSOP_EXCEPTION)) {
// [stack] NEXT ITER RESULT EXCEPTION // [stack] NEXT ITER RESULT EXCEPTION
return false; return false;
} }
// The exception was already pushed by emitCatch().
MOZ_ASSERT(bytecodeSection().stackDepth() == startDepth + 1);
if (!emitDupAt(2)) { if (!emitDupAt(2)) {
// [stack] NEXT ITER RESULT EXCEPTION ITER // [stack] NEXT ITER RESULT EXCEPTION ITER
return false; return false;
@ -8864,6 +8883,140 @@ bool BytecodeEmitter::emitExportDefault(BinaryNode* exportNode) {
return true; return true;
} }
MOZ_NEVER_INLINE bool BytecodeEmitter::emitInstrumentationSlow(
InstrumentationKind kind,
const std::function<bool(uint32_t)>& pushOperandsCallback) {
MOZ_ASSERT(instrumentationKinds);
if (!(instrumentationKinds & (uint32_t) kind)) {
return true;
}
// Instrumentation is emitted in the form of a call to the realm's
// instrumentation callback, guarded by a test of whether instrumentation is
// currently active in the realm. The callback is invoked with the kind of
// operation which is executing, the current script's instrumentation ID, and
// the offset of the bytecode location after the instrumentation. Some
// operation kinds have more arguments, which will be pushed by
// pushOperandsCallback.
unsigned initialDepth = bytecodeSection().stackDepth();
InternalIfEmitter ifEmitter(this);
if (!emit1(JSOP_INSTRUMENTATION_ACTIVE)) {
return false;
}
// [stack] ACTIVE
if (!ifEmitter.emitThen()) {
return false;
}
// [stack]
// Push the instrumentation callback for the current realm as the callee.
if (!emit1(JSOP_INSTRUMENTATION_CALLBACK)) {
return false;
}
// [stack] CALLBACK
// Push undefined for the call's |this| value.
if (!emit1(JSOP_UNDEFINED)) {
return false;
}
// [stack] CALLBACK UNDEFINED
JSAtom* atom = RealmInstrumentation::getInstrumentationKindName(cx, kind);
if (!atom) {
return false;
}
uint32_t index;
if (!makeAtomIndex(atom, &index)) {
return false;
}
if (!emitAtomOp(index, JSOP_STRING)) {
return false;
}
// [stack] CALLBACK UNDEFINED KIND
if (!emit1(JSOP_INSTRUMENTATION_SCRIPT_ID)) {
return false;
}
// [stack] CALLBACK UNDEFINED KIND SCRIPT
// Push the offset of the bytecode location following the instrumentation.
BytecodeOffset updateOffset;
if (!emitN(JSOP_INT32, 4, &updateOffset)) {
return false;
}
// [stack] CALLBACK UNDEFINED KIND SCRIPT OFFSET
unsigned numPushed = bytecodeSection().stackDepth() - initialDepth;
if (pushOperandsCallback && !pushOperandsCallback(numPushed)) {
return false;
}
// [stack] CALLBACK UNDEFINED KIND SCRIPT OFFSET ...EXTRA_ARGS
unsigned argc = bytecodeSection().stackDepth() - initialDepth - 2;
if (!emitCall(JSOP_CALL_IGNORES_RV, argc)) {
return false;
}
// [stack] RV
if (!emit1(JSOP_POP)) {
return false;
}
// [stack]
if (!ifEmitter.emitEnd()) {
return false;
}
SET_INT32(bytecodeSection().code(updateOffset),
bytecodeSection().code().length());
return true;
}
MOZ_NEVER_INLINE bool BytecodeEmitter::emitInstrumentationForOpcodeSlow(
JSOp op, uint32_t atomIndex) {
MOZ_ASSERT(instrumentationKinds);
switch (op) {
case JSOP_GETPROP:
case JSOP_CALLPROP:
case JSOP_LENGTH:
return emitInstrumentationSlow(InstrumentationKind::GetProperty,
[=](uint32_t pushed) {
return emitDupAt(pushed) && emitAtomOp(atomIndex, JSOP_STRING);
});
case JSOP_SETPROP:
case JSOP_STRICTSETPROP:
return emitInstrumentationSlow(InstrumentationKind::SetProperty,
[=](uint32_t pushed) {
return emitDupAt(pushed + 1) &&
emitAtomOp(atomIndex, JSOP_STRING) &&
emitDupAt(pushed + 2);
});
case JSOP_GETELEM:
case JSOP_CALLELEM:
return emitInstrumentationSlow(InstrumentationKind::GetElement,
[=](uint32_t pushed) {
return emitDupAt(pushed + 1, 2);
});
case JSOP_SETELEM:
case JSOP_STRICTSETELEM:
return emitInstrumentationSlow(InstrumentationKind::SetElement,
[=](uint32_t pushed) {
return emitDupAt(pushed + 2, 3);
});
default:
return true;
}
}
bool BytecodeEmitter::emitTree( bool BytecodeEmitter::emitTree(
ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::WantValue */, ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::WantValue */,
EmitLineNumberNote emitLineNote /* = EMIT_LINENOTE */) { EmitLineNumberNote emitLineNote /* = EMIT_LINENOTE */) {

View File

@ -40,6 +40,7 @@
#include "js/TypeDecls.h" // jsbytecode #include "js/TypeDecls.h" // jsbytecode
#include "vm/BigIntType.h" // BigInt #include "vm/BigIntType.h" // BigInt
#include "vm/BytecodeUtil.h" // JSOp #include "vm/BytecodeUtil.h" // JSOp
#include "vm/Instrumentation.h" // InstrumentationKind
#include "vm/Interpreter.h" // CheckIsObjectKind, CheckIsCallableKind #include "vm/Interpreter.h" // CheckIsObjectKind, CheckIsCallableKind
#include "vm/Iteration.h" // IteratorKind #include "vm/Iteration.h" // IteratorKind
#include "vm/JSFunction.h" // JSFunction, FunctionPrefixKind #include "vm/JSFunction.h" // JSFunction, FunctionPrefixKind
@ -160,6 +161,10 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
// The end location of a function body that is being emitted. // The end location of a function body that is being emitted.
mozilla::Maybe<uint32_t> functionBodyEndPos = {}; mozilla::Maybe<uint32_t> functionBodyEndPos = {};
// Mask of operation kinds which need instrumentation. This is obtained from
// the compile options and copied here for efficiency.
uint32_t instrumentationKinds = 0;
/* /*
* Note that BytecodeEmitters are magic: they own the arena "top-of-stack" * Note that BytecodeEmitters are magic: they own the arena "top-of-stack"
* space above their tempMark points. This means that you cannot alloc from * space above their tempMark points. This means that you cannot alloc from
@ -320,9 +325,11 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
bool inPrologue() const { return mainOffset_.isNothing(); } bool inPrologue() const { return mainOffset_.isNothing(); }
void switchToMain() { MOZ_MUST_USE bool switchToMain() {
MOZ_ASSERT(inPrologue()); MOZ_ASSERT(inPrologue());
mainOffset_.emplace(bytecodeSection().code().length()); mainOffset_.emplace(bytecodeSection().code().length());
return emitInstrumentation(InstrumentationKind::Main);
} }
void setFunctionBodyEndPos(uint32_t pos) { void setFunctionBodyEndPos(uint32_t pos) {
@ -411,9 +418,10 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
// Emit three bytecodes, an opcode with two bytes of immediate operands. // Emit three bytecodes, an opcode with two bytes of immediate operands.
MOZ_MUST_USE bool emit3(JSOp op, jsbytecode op1, jsbytecode op2); MOZ_MUST_USE bool emit3(JSOp op, jsbytecode op1, jsbytecode op2);
// Helper to emit JSOP_DUPAT. The argument is the value's depth on the // Helper to duplicate one or more stack values. |slotFromTop| is the value's
// JS stack, as measured from the top. // depth on the JS stack, as measured from the top. |count| is the number of
MOZ_MUST_USE bool emitDupAt(unsigned slotFromTop); // values to duplicate, in theiro original order.
MOZ_MUST_USE bool emitDupAt(unsigned slotFromTop, unsigned count = 1);
// Helper to emit JSOP_POP or JSOP_POPN. // Helper to emit JSOP_POP or JSOP_POPN.
MOZ_MUST_USE bool emitPopN(unsigned n); MOZ_MUST_USE bool emitPopN(unsigned n);
@ -473,8 +481,12 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
MOZ_MUST_USE bool emitIndex32(JSOp op, uint32_t index); MOZ_MUST_USE bool emitIndex32(JSOp op, uint32_t index);
MOZ_MUST_USE bool emitIndexOp(JSOp op, uint32_t index); MOZ_MUST_USE bool emitIndexOp(JSOp op, uint32_t index);
MOZ_MUST_USE bool emitAtomOp(JSAtom* atom, JSOp op); MOZ_MUST_USE bool emitAtomOp(
MOZ_MUST_USE bool emitAtomOp(uint32_t atomIndex, JSOp op); JSAtom* atom, JSOp op,
ShouldInstrument shouldInstrument = ShouldInstrument::No);
MOZ_MUST_USE bool emitAtomOp(
uint32_t atomIndex, JSOp op,
ShouldInstrument shouldInstrument = ShouldInstrument::No);
MOZ_MUST_USE bool emitArrayLiteral(ListNode* array); MOZ_MUST_USE bool emitArrayLiteral(ListNode* array);
MOZ_MUST_USE bool emitArray(ParseNode* arrayHead, uint32_t count); MOZ_MUST_USE bool emitArray(ParseNode* arrayHead, uint32_t count);
@ -579,7 +591,8 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
MOZ_MUST_USE bool emitElemObjAndKey(PropertyByValue* elem, bool isSuper, MOZ_MUST_USE bool emitElemObjAndKey(PropertyByValue* elem, bool isSuper,
ElemOpEmitter& eoe); ElemOpEmitter& eoe);
MOZ_MUST_USE bool emitElemOpBase(JSOp op); MOZ_MUST_USE bool emitElemOpBase(
JSOp op, ShouldInstrument shouldInstrument = ShouldInstrument::No);
MOZ_MUST_USE bool emitElemOp(PropertyByValue* elem, JSOp op); MOZ_MUST_USE bool emitElemOp(PropertyByValue* elem, JSOp op);
MOZ_MUST_USE bool emitElemIncDec(UnaryNode* incDec); MOZ_MUST_USE bool emitElemIncDec(UnaryNode* incDec);
@ -779,6 +792,29 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
MOZ_MUST_USE bool emitPipeline(ListNode* node); MOZ_MUST_USE bool emitPipeline(ListNode* node);
MOZ_MUST_USE bool emitExportDefault(BinaryNode* exportNode); MOZ_MUST_USE bool emitExportDefault(BinaryNode* exportNode);
MOZ_MUST_USE bool emitReturnRval() {
return emitInstrumentation(InstrumentationKind::Exit) &&
emit1(JSOP_RETRVAL);
}
MOZ_MUST_USE bool emitInstrumentation(InstrumentationKind kind,
uint32_t npopped = 0) {
return MOZ_LIKELY(!instrumentationKinds) ||
emitInstrumentationSlow(kind, std::function<bool(uint32_t)>());
}
MOZ_MUST_USE bool emitInstrumentationForOpcode(JSOp op, uint32_t atomIndex) {
return MOZ_LIKELY(!instrumentationKinds) ||
emitInstrumentationForOpcodeSlow(op, atomIndex);
}
private:
MOZ_MUST_USE bool emitInstrumentationSlow(
InstrumentationKind kind,
const std::function<bool(uint32_t)>& pushOperandsCallback);
MOZ_MUST_USE bool emitInstrumentationForOpcodeSlow(JSOp op,
uint32_t atomIndex);
}; };
class MOZ_RAII AutoCheckUnstableEmitterScope { class MOZ_RAII AutoCheckUnstableEmitterScope {

View File

@ -70,17 +70,7 @@ bool ElemOpEmitter::emitGet() {
} }
if (isIncDec() || isCompoundAssignment()) { if (isIncDec() || isCompoundAssignment()) {
if (isSuper()) { if (isSuper()) {
// There's no such thing as JSOP_DUP3, so we have to be creative. if (!bce_->emitDupAt(2, 3)) {
// Note that pushing things again is no fewer JSOps.
if (!bce_->emitDupAt(2)) {
// [stack] THIS KEY SUPERBASE THIS
return false;
}
if (!bce_->emitDupAt(2)) {
// [stack] THIS KEY SUPERBASE THIS KEY
return false;
}
if (!bce_->emitDupAt(2)) {
// [stack] THIS KEY SUPERBASE THIS KEY SUPERBASE // [stack] THIS KEY SUPERBASE THIS KEY SUPERBASE
return false; return false;
} }
@ -100,7 +90,7 @@ bool ElemOpEmitter::emitGet() {
} else { } else {
op = JSOP_GETELEM; op = JSOP_GETELEM;
} }
if (!bce_->emitElemOpBase(op)) { if (!bce_->emitElemOpBase(op, ShouldInstrument::Yes)) {
// [stack] # if Get // [stack] # if Get
// [stack] ELEM // [stack] ELEM
// [stack] # if Call // [stack] # if Call
@ -221,7 +211,7 @@ bool ElemOpEmitter::emitAssignment(EmitSetFunctionName emitSetFunName) {
: isSuper() ? bce_->sc->strict() ? JSOP_STRICTSETELEM_SUPER : isSuper() ? bce_->sc->strict() ? JSOP_STRICTSETELEM_SUPER
: JSOP_SETELEM_SUPER : JSOP_SETELEM_SUPER
: bce_->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM; : bce_->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM;
if (!bce_->emitElemOpBase(setOp)) { if (!bce_->emitElemOpBase(setOp, ShouldInstrument::Yes)) {
// [stack] ELEM // [stack] ELEM
return false; return false;
} }
@ -300,7 +290,7 @@ bool ElemOpEmitter::emitIncDec() {
isSuper() isSuper()
? (bce_->sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER) ? (bce_->sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER)
: (bce_->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM); : (bce_->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM);
if (!bce_->emitElemOpBase(setOp)) { if (!bce_->emitElemOpBase(setOp, ShouldInstrument::Yes)) {
// [stack] N? N+1 // [stack] N? N+1
return false; return false;
} }

View File

@ -41,14 +41,10 @@ bool ForOfLoopControl::emitBeginCodeNeedingIteratorClose(BytecodeEmitter* bce) {
bool ForOfLoopControl::emitEndCodeNeedingIteratorClose(BytecodeEmitter* bce) { bool ForOfLoopControl::emitEndCodeNeedingIteratorClose(BytecodeEmitter* bce) {
if (!tryCatch_->emitCatch()) { if (!tryCatch_->emitCatch()) {
// [stack] ITER ...
return false;
}
if (!bce->emit1(JSOP_EXCEPTION)) {
// [stack] ITER ... EXCEPTION // [stack] ITER ... EXCEPTION
return false; return false;
} }
unsigned slotFromTop = bce->bytecodeSection().stackDepth() - iterDepth_; unsigned slotFromTop = bce->bytecodeSection().stackDepth() - iterDepth_;
if (!bce->emitDupAt(slotFromTop)) { if (!bce->emitDupAt(slotFromTop)) {
// [stack] ITER ... EXCEPTION ITER // [stack] ITER ... EXCEPTION ITER

View File

@ -425,7 +425,9 @@ bool FunctionScriptEmitter::prepareForParameters() {
// parameter exprs, any unobservable environment ops (like pushing the // parameter exprs, any unobservable environment ops (like pushing the
// call object, setting '.this', etc) need to go in the prologue, else it // call object, setting '.this', etc) need to go in the prologue, else it
// messes up breakpoint tests. // messes up breakpoint tests.
bce_->switchToMain(); if (!bce_->switchToMain()) {
return false;
}
} }
if (!functionEmitterScope_->enterFunction(bce_, funbox_)) { if (!functionEmitterScope_->enterFunction(bce_, funbox_)) {
@ -438,7 +440,9 @@ bool FunctionScriptEmitter::prepareForParameters() {
} }
if (!funbox_->hasParameterExprs) { if (!funbox_->hasParameterExprs) {
bce_->switchToMain(); if (!bce_->switchToMain()) {
return false;
}
} }
// Parameters can't reuse the reject try-catch block from the function body, // Parameters can't reuse the reject try-catch block from the function body,
@ -503,13 +507,10 @@ bool FunctionScriptEmitter::emitAsyncFunctionRejectPrologue() {
bool FunctionScriptEmitter::emitAsyncFunctionRejectEpilogue() { bool FunctionScriptEmitter::emitAsyncFunctionRejectEpilogue() {
if (!rejectTryCatch_->emitCatch()) { if (!rejectTryCatch_->emitCatch()) {
return false;
}
if (!bce_->emit1(JSOP_EXCEPTION)) {
// [stack] EXC // [stack] EXC
return false; return false;
} }
if (!bce_->emitGetDotGeneratorInInnermostScope()) { if (!bce_->emitGetDotGeneratorInInnermostScope()) {
// [stack] EXC GEN // [stack] EXC GEN
return false; return false;
@ -527,7 +528,7 @@ bool FunctionScriptEmitter::emitAsyncFunctionRejectEpilogue() {
// [stack] GEN // [stack] GEN
return false; return false;
} }
if (!bce_->emit1(JSOP_FINALYIELDRVAL)) { if (!bce_->emitYieldOp(JSOP_FINALYIELDRVAL)) {
// [stack] // [stack]
return false; return false;
} }
@ -723,7 +724,7 @@ bool FunctionScriptEmitter::emitEndBody() {
// Always end the script with a JSOP_RETRVAL. Some other parts of the // Always end the script with a JSOP_RETRVAL. Some other parts of the
// codebase depend on this opcode, // codebase depend on this opcode,
// e.g. InterpreterRegs::setToEndOfScript. // e.g. InterpreterRegs::setToEndOfScript.
if (!bce_->emit1(JSOP_RETRVAL)) { if (!bce_->emitReturnRval()) {
// [stack] // [stack]
return false; return false;
} }

View File

@ -78,7 +78,7 @@ bool PropOpEmitter::emitGet(JSAtom* prop) {
} else { } else {
op = isLength_ ? JSOP_LENGTH : JSOP_GETPROP; op = isLength_ ? JSOP_LENGTH : JSOP_GETPROP;
} }
if (!bce_->emitAtomOp(propAtomIndex_, op)) { if (!bce_->emitAtomOp(propAtomIndex_, op, ShouldInstrument::Yes)) {
// [stack] # if Get // [stack] # if Get
// [stack] PROP // [stack] PROP
// [stack] # if Call // [stack] # if Call
@ -190,7 +190,7 @@ bool PropOpEmitter::emitAssignment(JSAtom* prop) {
: isSuper() ? bce_->sc->strict() ? JSOP_STRICTSETPROP_SUPER : isSuper() ? bce_->sc->strict() ? JSOP_STRICTSETPROP_SUPER
: JSOP_SETPROP_SUPER : JSOP_SETPROP_SUPER
: bce_->sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP; : bce_->sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP;
if (!bce_->emitAtomOp(propAtomIndex_, setOp)) { if (!bce_->emitAtomOp(propAtomIndex_, setOp, ShouldInstrument::Yes)) {
// [stack] VAL // [stack] VAL
return false; return false;
} }
@ -263,7 +263,7 @@ bool PropOpEmitter::emitIncDec(JSAtom* prop) {
isSuper() isSuper()
? bce_->sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER ? bce_->sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER
: bce_->sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP; : bce_->sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP;
if (!bce_->emitAtomOp(propAtomIndex_, setOp)) { if (!bce_->emitAtomOp(propAtomIndex_, setOp, ShouldInstrument::Yes)) {
// [stack] N? N+1 // [stack] N? N+1
return false; return false;
} }

View File

@ -124,6 +124,14 @@ bool TryEmitter::emitCatch() {
} }
} }
if (!bce_->emit1(JSOP_EXCEPTION)) {
return false;
}
if (!instrumentEntryPoint()) {
return false;
}
#ifdef DEBUG #ifdef DEBUG
state_ = State::Catch; state_ = State::Catch;
#endif #endif
@ -221,6 +229,10 @@ bool TryEmitter::emitFinally(
} }
} }
if (!instrumentEntryPoint()) {
return false;
}
#ifdef DEBUG #ifdef DEBUG
state_ = State::Finally; state_ = State::Finally;
#endif #endif
@ -293,3 +305,15 @@ bool TryEmitter::emitEnd() {
#endif #endif
return true; return true;
} }
bool TryEmitter::instrumentEntryPoint() {
// Frames for async functions can resume execution at catch or finally blocks
// if an await operation threw an exception. While the frame might already be
// on the stack, the Entry instrumentation kind only indicates that a new
// frame *might* have been pushed.
if (bce_->sc->isFunctionBox() &&
bce_->sc->asFunctionBox()->isAsync()) {
return bce_->emitInstrumentation(InstrumentationKind::Entry);
}
return true;
}

View File

@ -31,7 +31,7 @@ struct BytecodeEmitter;
// tryCatch.emitTry(); // tryCatch.emitTry();
// emit(try_block); // emit(try_block);
// tryCatch.emitCatch(); // tryCatch.emitCatch();
// emit(ex and catch_block); // use JSOP_EXCEPTION to get exception // emit(catch_block); // Pending exception is on stack
// tryCatch.emitEnd(); // tryCatch.emitEnd();
// //
// `try { try_block } finally { finally_block }` // `try { try_block } finally { finally_block }`
@ -50,7 +50,7 @@ struct BytecodeEmitter;
// tryCatch.emitTry(); // tryCatch.emitTry();
// emit(try_block); // emit(try_block);
// tryCatch.emitCatch(); // tryCatch.emitCatch();
// emit(ex and catch_block); // emit(catch_block);
// tryCatch.emitFinally(Some(finally_pos)); // tryCatch.emitFinally(Some(finally_pos));
// emit(finally_block); // emit(finally_block);
// tryCatch.emitEnd(); // tryCatch.emitEnd();
@ -211,6 +211,7 @@ class MOZ_STACK_CLASS TryEmitter {
MOZ_MUST_USE bool emitTryEnd(); MOZ_MUST_USE bool emitTryEnd();
MOZ_MUST_USE bool emitCatchEnd(); MOZ_MUST_USE bool emitCatchEnd();
MOZ_MUST_USE bool emitFinallyEnd(); MOZ_MUST_USE bool emitFinallyEnd();
MOZ_MUST_USE bool instrumentEntryPoint();
}; };
} /* namespace frontend */ } /* namespace frontend */

View File

@ -136,7 +136,8 @@ enum class ZealMode {
_(DebuggerFrameIterData) \ _(DebuggerFrameIterData) \
_(DebuggerOnStepHandler) \ _(DebuggerOnStepHandler) \
_(DebuggerOnPopHandler) \ _(DebuggerOnPopHandler) \
_(GlobalDebuggerVector) _(GlobalDebuggerVector) \
_(RealmInstrumentation)
#define JS_FOR_EACH_MEMORY_USE(_) \ #define JS_FOR_EACH_MEMORY_USE(_) \
JS_FOR_EACH_PUBLIC_MEMORY_USE(_) \ JS_FOR_EACH_PUBLIC_MEMORY_USE(_) \

View File

@ -0,0 +1,196 @@
// Test breakpoint site instrumentation functionality with a variety of control
// flow constructs.
load(libdir + 'eqArrayHelper.js');
var g = newGlobal({ newCompartment: true });
var dbg = Debugger(g);
var gdbg = dbg.addDebuggee(g);
var allScripts = [];
function setScriptId(script) {
script.setInstrumentationId(allScripts.length);
allScripts.push(script);
script.getChildScripts().forEach(setScriptId);
}
dbg.onNewScript = setScriptId;
function getOffsetLine(scriptId, offset) {
const script = allScripts[scriptId];
return script.getOffsetMetadata(offset).lineNumber;
}
const executedLines = [];
gdbg.setInstrumentation(
gdbg.makeDebuggeeValue((kind, script, offset) => {
executedLines.push(getOffsetLine(script, offset));
}),
["breakpoint"]
);
function testFunction(fn, expected) {
for (var i = 0; i < 3; i++) {
fn();
}
assertEq(executedLines.length, 0);
gdbg.setInstrumentationActive(true);
for (var i = 0; i < 5; i++) {
executedLines.length = 0;
fn();
assertEqArray(executedLines, expected);
}
executedLines.length = 0;
gdbg.setInstrumentationActive(false);
}
g.eval(`
function basic() {
var a = 0;
a++;
}
`);
testFunction(() => g.basic(),
[3, 4, 5]);
g.eval(`
function cforloop() {
for (var i = 0;
i < 5;
i++) {
}
}
`);
testFunction(() => g.cforloop(),
[3, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 7]);
g.eval(`
function ifstatement(n) {
if (n) {
return 1;
}
return 0;
}
`);
testFunction(() => g.ifstatement(true), [3, 4]);
testFunction(() => g.ifstatement(false), [3, 6]);
g.eval(`
function whileloop(n) {
while (n < 3) {
n++;
}
}
`);
testFunction(() => g.whileloop(1),
[3, 4, 3, 4, 3, 6]);
g.eval(`
function dowhileloop(n) {
do {
if (++n == 3) {
break;
}
} while (true);
}
`);
testFunction(() => g.dowhileloop(1),
[4, 7, 4, 5, 8]);
g.eval(`
function forinloop(v) {
for (let i in v) {
var a = i;
}
}
`);
testFunction(() => g.forinloop([1,2,3]),
[3, 4, 4, 4, 6]);
g.eval(`
function forofloop(v) {
for (const i of v) {
var a = i;
}
}
`);
testFunction(() => g.forofloop([1,2,3]),
[3, 4, 4, 4, 6]);
g.eval(`
function normalswitch(v) {
switch (v) {
case 3:
return 0;
case "three":
return 1;
default:
return 2;
}
}
`);
testFunction(() => g.normalswitch(3), [3, 5]);
testFunction(() => g.normalswitch("three"), [3, 7]);
testFunction(() => g.normalswitch(2), [3, 9]);
g.eval(`
function tableswitch(v) {
switch (v) {
case 0:
case 1:
case 2:
break;
case 3:
return 1;
default:
return 2;
}
}
`);
testFunction(() => g.tableswitch(0), [3, 7, 13]);
testFunction(() => g.tableswitch(3), [3, 9]);
testFunction(() => g.tableswitch(5), [3, 11]);
g.eval(`
function trycatch(f) {
try {
f();
} catch (e) {
return e;
}
}
`);
testFunction(() => g.trycatch(() => { throw 3 }), [4, 6]);
testFunction(() => g.trycatch(() => {}), [4, 8]);
g.eval(`
function tryfinally(f) {
var a;
try {
f();
} finally {
a = 0;
}
a = 1;
}
`);
testFunction(() => {
try { g.tryfinally(() => { throw 3 }); } catch (e) {}
}, [5, 7]);
testFunction(() => g.tryfinally(() => {}), [5, 7, 9, 10]);

View File

@ -0,0 +1,127 @@
// Test main/entry/exit point instrumentation.
load(libdir + 'eqArrayHelper.js');
ignoreUnhandledRejections();
var g = newGlobal({ newCompartment: true });
var dbg = Debugger(g);
var gdbg = dbg.addDebuggee(g);
var allScripts = [];
function setScriptId(script) {
script.setInstrumentationId(allScripts.length);
allScripts.push(script);
script.getChildScripts().forEach(setScriptId);
}
dbg.onNewScript = setScriptId;
const executedPoints = [];
function hitPoint(kind, scriptId) {
const name = allScripts[scriptId].displayName;
executedPoints.push(`${name}:${kind}`);
}
gdbg.setInstrumentation(
gdbg.makeDebuggeeValue(hitPoint),
["main", "entry", "exit"]
);
function testFunction(fn, expected) {
for (var i = 0; i < 3; i++) {
try { fn(); } catch (e) {}
drainJobQueue();
}
assertEq(executedPoints.length, 0);
gdbg.setInstrumentationActive(true);
for (var i = 0; i < 5; i++) {
executedPoints.length = 0;
try { fn(); } catch (e) {}
drainJobQueue();
assertEqArray(executedPoints, expected);
}
executedPoints.length = 0;
gdbg.setInstrumentationActive(false);
}
g.eval(`
function basic() {
var a = 0;
a++;
}
`);
testFunction(() => g.basic(), ["basic:main", "basic:exit"]);
g.eval(`
function thrower(v) {
if (v) {
throw new Error();
}
}
`);
testFunction(() => g.thrower(0), ["thrower:main", "thrower:exit"]);
testFunction(() => g.thrower(1), ["thrower:main"]);
g.eval(`
function* yielder() { yield 1; }
function iterator() {
for (var n of yielder()) {}
}
`);
testFunction(() => g.iterator(), [
"iterator:main",
"yielder:main", "yielder:exit",
"yielder:entry", "yielder:exit",
"yielder:entry", "yielder:exit",
"iterator:exit"
]);
g.eval(`
function promiser(n) {
return new Promise(function promiseInternal(resolve, reject) {
if (n) {
reject(new Error());
} else {
resolve();
}
});
}
function f1() {}
async function asyncer(n) { await promiser(n) }
async function asyncerCatch(n) { try { await promiser(n) } catch (e) { f1() } }
async function asyncerFinally(n) { try { await promiser(n) } finally { f1() } }
`);
testFunction(() => g.asyncer(0), [
"asyncer:main",
"promiser:main", "promiseInternal:main", "promiseInternal:exit", "promiser:exit",
"asyncer:exit",
"asyncer:entry", "asyncer:exit"
]);
testFunction(() => g.asyncer(1), [
"asyncer:main",
"promiser:main", "promiseInternal:main", "promiseInternal:exit", "promiser:exit",
"asyncer:exit",
"asyncer:entry", "asyncer:exit"
]);
testFunction(() => g.asyncerCatch(1), [
"asyncerCatch:main",
"promiser:main", "promiseInternal:main", "promiseInternal:exit", "promiser:exit",
"asyncerCatch:exit",
"asyncerCatch:entry", "f1:main", "f1:exit", "asyncerCatch:exit"
]);
testFunction(() => g.asyncerFinally(1), [
"asyncerFinally:main",
"promiser:main", "promiseInternal:main", "promiseInternal:exit", "promiser:exit",
"asyncerFinally:exit",
"asyncerFinally:entry", "f1:main", "f1:exit", "asyncerFinally:entry", "asyncerFinally:exit"
]);

View File

@ -0,0 +1,72 @@
// Test instrumentation functionality when using generators and async functions.
load(libdir + 'eqArrayHelper.js');
var g = newGlobal({ newCompartment: true });
var dbg = Debugger(g);
var gdbg = dbg.addDebuggee(g);
var allScripts = [];
function setScriptId(script) {
script.setInstrumentationId(allScripts.length);
allScripts.push(script);
script.getChildScripts().forEach(setScriptId);
}
dbg.onNewScript = setScriptId;
function getOffsetLine(scriptId, offset) {
const script = allScripts[scriptId];
return script.getOffsetMetadata(offset).lineNumber;
}
const executedLines = [];
gdbg.setInstrumentation(
gdbg.makeDebuggeeValue((kind, script, offset) => {
executedLines.push(getOffsetLine(script, offset));
}),
["breakpoint"]
);
function testFunction(fn, expected) {
gdbg.setInstrumentationActive(true);
for (var i = 0; i < 5; i++) {
executedLines.length = 0;
fn();
assertEqArray(executedLines, expected);
}
gdbg.setInstrumentationActive(false);
}
g.eval(`
async function asyncfun() {
await Promise.resolve(0);
await Promise.resolve(1);
var a = 0;
await Promise.resolve(2);
a++;
}
`);
testFunction(() => {
async function f() { await g.asyncfun(); }
f();
drainJobQueue();
}, [3, 3, 4, 4, 5, 6, 6, 7]);
g.eval(`
function *generator() {
yield 1;
var a = 0;
yield 2;
a++;
}
`);
testFunction(() => {
for (const i of g.generator()) {}
}, [3, 4, 5, 6]);

View File

@ -0,0 +1,34 @@
// Test that instrumenting a function does not interfere with script
// disassembly when reporting errors.
var g = newGlobal({ newCompartment: true });
var dbg = Debugger(g);
var gdbg = dbg.addDebuggee(g);
function setScriptId(script) {
script.setInstrumentationId(0);
script.getChildScripts().forEach(setScriptId);
}
dbg.onNewScript = setScriptId;
const executedLines = [];
gdbg.setInstrumentation(
gdbg.makeDebuggeeValue(() => {}),
["breakpoint"]
);
g.eval(`
function foo(v) {
v.f().g();
}
`);
var gotException = false;
try {
g.foo({ f: () => { return { g: 0 } } });
} catch (e) {
gotException = true;
assertEq(e.toString().includes("v.f(...).g is not a function"), true);
}
assertEq(gotException, true);

View File

@ -0,0 +1,43 @@
// Test some inputs that should fail instrumentation validation.
var g = newGlobal({ newCompartment: true });
var dbg = Debugger(g);
var gdbg = dbg.addDebuggee(g);
function assertThrows(fn, text) {
try {
fn();
assertEq(true, false);
} catch (e) {
if (!e.toString().includes(text)) {
print(`Expected ${text}, got ${e}`);
assertEq(true, false);
}
}
}
assertThrows(() => gdbg.setInstrumentation(undefined, []),
"Instrumentation callback must be an object");
assertThrows(() => gdbg.setInstrumentation(gdbg.makeDebuggeeValue({}), ["foo"]),
"Unknown instrumentation kind");
let lastScript = null;
dbg.onNewScript = script => lastScript = script;
assertThrows(() => {
g.eval(`x = 3`);
lastScript.setInstrumentationId("twelve");
}, "Script instrumentation ID must be a number");
assertThrows(() => {
g.eval(`x = 3`);
lastScript.setInstrumentationId(10);
lastScript.setInstrumentationId(11);
}, "Script instrumentation ID is already set");
dbg.onNewScript = undefined;
gdbg.setInstrumentation(gdbg.makeDebuggeeValue({}), ["main"]);
gdbg.setInstrumentationActive(true);
assertThrows(() => { g.eval(`x = 3`) },
"Instrumentation ID not set for script");

View File

@ -0,0 +1,105 @@
// Test instrumentation on property and element accesses.
load(libdir + 'eqArrayHelper.js');
var g = newGlobal({ newCompartment: true });
var dbg = Debugger(g);
var gdbg = dbg.addDebuggee(g);
function setScriptId(script) {
script.setInstrumentationId(0);
script.getChildScripts().forEach(setScriptId);
}
dbg.onNewScript = setScriptId;
function getOffsetLine(scriptId, offset) {
const script = allScripts[scriptId];
return script.getOffsetMetadata(offset).lineNumber;
}
const executedOperations = [];
function propertyRead(script, offset, obj, prop) {
executedOperations.push(`read:${obj.label}:${prop}`);
}
function propertyWrite(script, offset, obj, prop, rhs) {
executedOperations.push(`write:${obj.label}:${prop}:${typeof rhs}`);
}
function callback(kind, ...args) {
switch (kind) {
case "getProperty":
case "getElement":
propertyRead(...args);
break;
case "setProperty":
case "setElement":
propertyWrite(...args);
}
}
gdbg.setInstrumentation(
gdbg.makeDebuggeeValue(callback),
["getProperty", "getElement", "setProperty", "setElement"]
);
function testFunction(fn, expected) {
gdbg.setInstrumentationActive(true);
for (var i = 0; i < 5; i++) {
executedOperations.length = 0;
fn();
assertEqArray(executedOperations, expected);
}
gdbg.setInstrumentationActive(false);
}
g.eval(`
function basic(o, i) {
o.x = o.x + 1;
o[i] = o[i] + 1;
}
`);
testFunction(() => g.basic({ label: 0}, 3), [
"read:0:x",
"write:0:x:number",
"read:0:3",
"write:0:3:number",
]);
g.eval(`
function incdec(o, i) {
o.x++;
o[i]++;
}
`);
testFunction(() => g.incdec({ label: 0}, 3), [
"read:0:x",
"write:0:x:number",
"read:0:3",
"write:0:3:number",
]);
g.eval(`
function destructure({ f, g }) {
const { h, i } = f;
}
`);
testFunction(() => g.destructure({ label: 0, f: { label: 1 }}, 3), [
"read:0:f",
"read:0:g",
"read:1:h",
"read:1:i",
]);
g.eval(`
function destructureArray([a, b]) {}
`);
// Array destructuring uses iterators instead of getElement accesses,
// and no property accesses will be captured by instrumentation.
testFunction(() => g.destructureArray([1, 2]), []);

View File

@ -6724,6 +6724,83 @@ bool BaselineCodeGen<Handler>::emit_JSOP_DYNAMIC_IMPORT() {
return true; return true;
} }
template <>
bool BaselineCompilerCodeGen::emit_JSOP_INSTRUMENTATION_ACTIVE() {
frame.syncStack(0);
// RealmInstrumentation cannot be removed from a global without destroying the
// entire realm, so its active address can be embedded into jitcode.
const int32_t* address = RealmInstrumentation::addressOfActive(cx->global());
Register scratch = R0.scratchReg();
masm.load32(AbsoluteAddress(address), scratch);
masm.tagValue(JSVAL_TYPE_BOOLEAN, scratch, R0);
frame.push(R0, JSVAL_TYPE_BOOLEAN);
return true;
}
template <>
bool BaselineInterpreterCodeGen::emit_JSOP_INSTRUMENTATION_ACTIVE() {
prepareVMCall();
using Fn = bool (*)(JSContext*, MutableHandleValue);
if (!callVM<Fn, InstrumentationActiveOperation>()) {
return false;
}
frame.push(R0);
return true;
}
template <>
bool BaselineCompilerCodeGen::emit_JSOP_INSTRUMENTATION_CALLBACK() {
JSObject* obj = RealmInstrumentation::getCallback(cx->global());
MOZ_ASSERT(obj);
frame.push(ObjectValue(*obj));
return true;
}
template <>
bool BaselineInterpreterCodeGen::emit_JSOP_INSTRUMENTATION_CALLBACK() {
prepareVMCall();
using Fn = JSObject* (*)(JSContext*);
if (!callVM<Fn, InstrumentationCallbackOperation>()) {
return false;
}
masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0);
frame.push(R0);
return true;
}
template <>
bool BaselineCompilerCodeGen::emit_JSOP_INSTRUMENTATION_SCRIPT_ID() {
int32_t scriptId;
RootedScript script(cx, handler.script());
if (!RealmInstrumentation::getScriptId(cx, cx->global(), script,
&scriptId)) {
return false;
}
frame.push(Int32Value(scriptId));
return true;
}
template <>
bool BaselineInterpreterCodeGen::emit_JSOP_INSTRUMENTATION_SCRIPT_ID() {
prepareVMCall();
pushScriptArg();
using Fn = bool (*)(JSContext*, HandleScript, MutableHandleValue);
if (!callVM<Fn, InstrumentationScriptIdOperation>()) {
return false;
}
frame.push(R0);
return true;
}
template <typename Handler> template <typename Handler>
bool BaselineCodeGen<Handler>::emitPrologue() { bool BaselineCodeGen<Handler>::emitPrologue() {
#ifdef JS_USE_LINK_REGISTER #ifdef JS_USE_LINK_REGISTER

View File

@ -19,242 +19,245 @@ enum class GeneratorResumeKind;
namespace jit { namespace jit {
#define OPCODE_LIST(_) \ #define OPCODE_LIST(_) \
_(JSOP_NOP) \ _(JSOP_NOP) \
_(JSOP_NOP_DESTRUCTURING) \ _(JSOP_NOP_DESTRUCTURING) \
_(JSOP_LABEL) \ _(JSOP_LABEL) \
_(JSOP_ITERNEXT) \ _(JSOP_ITERNEXT) \
_(JSOP_POP) \ _(JSOP_POP) \
_(JSOP_POPN) \ _(JSOP_POPN) \
_(JSOP_DUPAT) \ _(JSOP_DUPAT) \
_(JSOP_ENTERWITH) \ _(JSOP_ENTERWITH) \
_(JSOP_LEAVEWITH) \ _(JSOP_LEAVEWITH) \
_(JSOP_DUP) \ _(JSOP_DUP) \
_(JSOP_DUP2) \ _(JSOP_DUP2) \
_(JSOP_SWAP) \ _(JSOP_SWAP) \
_(JSOP_PICK) \ _(JSOP_PICK) \
_(JSOP_UNPICK) \ _(JSOP_UNPICK) \
_(JSOP_GOTO) \ _(JSOP_GOTO) \
_(JSOP_IFEQ) \ _(JSOP_IFEQ) \
_(JSOP_IFNE) \ _(JSOP_IFNE) \
_(JSOP_AND) \ _(JSOP_AND) \
_(JSOP_OR) \ _(JSOP_OR) \
_(JSOP_NOT) \ _(JSOP_NOT) \
_(JSOP_POS) \ _(JSOP_POS) \
_(JSOP_TONUMERIC) \ _(JSOP_TONUMERIC) \
_(JSOP_LOOPHEAD) \ _(JSOP_LOOPHEAD) \
_(JSOP_LOOPENTRY) \ _(JSOP_LOOPENTRY) \
_(JSOP_VOID) \ _(JSOP_VOID) \
_(JSOP_UNDEFINED) \ _(JSOP_UNDEFINED) \
_(JSOP_HOLE) \ _(JSOP_HOLE) \
_(JSOP_NULL) \ _(JSOP_NULL) \
_(JSOP_TRUE) \ _(JSOP_TRUE) \
_(JSOP_FALSE) \ _(JSOP_FALSE) \
_(JSOP_ZERO) \ _(JSOP_ZERO) \
_(JSOP_ONE) \ _(JSOP_ONE) \
_(JSOP_INT8) \ _(JSOP_INT8) \
_(JSOP_INT32) \ _(JSOP_INT32) \
_(JSOP_UINT16) \ _(JSOP_UINT16) \
_(JSOP_UINT24) \ _(JSOP_UINT24) \
_(JSOP_RESUMEINDEX) \ _(JSOP_RESUMEINDEX) \
_(JSOP_DOUBLE) \ _(JSOP_DOUBLE) \
_(JSOP_BIGINT) \ _(JSOP_BIGINT) \
_(JSOP_STRING) \ _(JSOP_STRING) \
_(JSOP_SYMBOL) \ _(JSOP_SYMBOL) \
_(JSOP_OBJECT) \ _(JSOP_OBJECT) \
_(JSOP_CALLSITEOBJ) \ _(JSOP_CALLSITEOBJ) \
_(JSOP_REGEXP) \ _(JSOP_REGEXP) \
_(JSOP_LAMBDA) \ _(JSOP_LAMBDA) \
_(JSOP_LAMBDA_ARROW) \ _(JSOP_LAMBDA_ARROW) \
_(JSOP_SETFUNNAME) \ _(JSOP_SETFUNNAME) \
_(JSOP_BITOR) \ _(JSOP_BITOR) \
_(JSOP_BITXOR) \ _(JSOP_BITXOR) \
_(JSOP_BITAND) \ _(JSOP_BITAND) \
_(JSOP_LSH) \ _(JSOP_LSH) \
_(JSOP_RSH) \ _(JSOP_RSH) \
_(JSOP_URSH) \ _(JSOP_URSH) \
_(JSOP_ADD) \ _(JSOP_ADD) \
_(JSOP_SUB) \ _(JSOP_SUB) \
_(JSOP_MUL) \ _(JSOP_MUL) \
_(JSOP_DIV) \ _(JSOP_DIV) \
_(JSOP_MOD) \ _(JSOP_MOD) \
_(JSOP_POW) \ _(JSOP_POW) \
_(JSOP_LT) \ _(JSOP_LT) \
_(JSOP_LE) \ _(JSOP_LE) \
_(JSOP_GT) \ _(JSOP_GT) \
_(JSOP_GE) \ _(JSOP_GE) \
_(JSOP_EQ) \ _(JSOP_EQ) \
_(JSOP_NE) \ _(JSOP_NE) \
_(JSOP_STRICTEQ) \ _(JSOP_STRICTEQ) \
_(JSOP_STRICTNE) \ _(JSOP_STRICTNE) \
_(JSOP_CONDSWITCH) \ _(JSOP_CONDSWITCH) \
_(JSOP_CASE) \ _(JSOP_CASE) \
_(JSOP_DEFAULT) \ _(JSOP_DEFAULT) \
_(JSOP_LINENO) \ _(JSOP_LINENO) \
_(JSOP_BITNOT) \ _(JSOP_BITNOT) \
_(JSOP_NEG) \ _(JSOP_NEG) \
_(JSOP_NEWARRAY) \ _(JSOP_NEWARRAY) \
_(JSOP_NEWARRAY_COPYONWRITE) \ _(JSOP_NEWARRAY_COPYONWRITE) \
_(JSOP_INITELEM_ARRAY) \ _(JSOP_INITELEM_ARRAY) \
_(JSOP_NEWOBJECT) \ _(JSOP_NEWOBJECT) \
_(JSOP_NEWINIT) \ _(JSOP_NEWINIT) \
_(JSOP_INITELEM) \ _(JSOP_INITELEM) \
_(JSOP_INITELEM_GETTER) \ _(JSOP_INITELEM_GETTER) \
_(JSOP_INITELEM_SETTER) \ _(JSOP_INITELEM_SETTER) \
_(JSOP_INITELEM_INC) \ _(JSOP_INITELEM_INC) \
_(JSOP_MUTATEPROTO) \ _(JSOP_MUTATEPROTO) \
_(JSOP_INITPROP) \ _(JSOP_INITPROP) \
_(JSOP_INITLOCKEDPROP) \ _(JSOP_INITLOCKEDPROP) \
_(JSOP_INITHIDDENPROP) \ _(JSOP_INITHIDDENPROP) \
_(JSOP_INITPROP_GETTER) \ _(JSOP_INITPROP_GETTER) \
_(JSOP_INITPROP_SETTER) \ _(JSOP_INITPROP_SETTER) \
_(JSOP_GETELEM) \ _(JSOP_GETELEM) \
_(JSOP_SETELEM) \ _(JSOP_SETELEM) \
_(JSOP_STRICTSETELEM) \ _(JSOP_STRICTSETELEM) \
_(JSOP_CALLELEM) \ _(JSOP_CALLELEM) \
_(JSOP_DELELEM) \ _(JSOP_DELELEM) \
_(JSOP_STRICTDELELEM) \ _(JSOP_STRICTDELELEM) \
_(JSOP_GETELEM_SUPER) \ _(JSOP_GETELEM_SUPER) \
_(JSOP_SETELEM_SUPER) \ _(JSOP_SETELEM_SUPER) \
_(JSOP_STRICTSETELEM_SUPER) \ _(JSOP_STRICTSETELEM_SUPER) \
_(JSOP_IN) \ _(JSOP_IN) \
_(JSOP_HASOWN) \ _(JSOP_HASOWN) \
_(JSOP_GETGNAME) \ _(JSOP_GETGNAME) \
_(JSOP_BINDGNAME) \ _(JSOP_BINDGNAME) \
_(JSOP_SETGNAME) \ _(JSOP_SETGNAME) \
_(JSOP_STRICTSETGNAME) \ _(JSOP_STRICTSETGNAME) \
_(JSOP_SETNAME) \ _(JSOP_SETNAME) \
_(JSOP_STRICTSETNAME) \ _(JSOP_STRICTSETNAME) \
_(JSOP_GETPROP) \ _(JSOP_GETPROP) \
_(JSOP_SETPROP) \ _(JSOP_SETPROP) \
_(JSOP_STRICTSETPROP) \ _(JSOP_STRICTSETPROP) \
_(JSOP_CALLPROP) \ _(JSOP_CALLPROP) \
_(JSOP_DELPROP) \ _(JSOP_DELPROP) \
_(JSOP_STRICTDELPROP) \ _(JSOP_STRICTDELPROP) \
_(JSOP_GETPROP_SUPER) \ _(JSOP_GETPROP_SUPER) \
_(JSOP_SETPROP_SUPER) \ _(JSOP_SETPROP_SUPER) \
_(JSOP_STRICTSETPROP_SUPER) \ _(JSOP_STRICTSETPROP_SUPER) \
_(JSOP_LENGTH) \ _(JSOP_LENGTH) \
_(JSOP_GETBOUNDNAME) \ _(JSOP_GETBOUNDNAME) \
_(JSOP_GETALIASEDVAR) \ _(JSOP_GETALIASEDVAR) \
_(JSOP_SETALIASEDVAR) \ _(JSOP_SETALIASEDVAR) \
_(JSOP_GETNAME) \ _(JSOP_GETNAME) \
_(JSOP_BINDNAME) \ _(JSOP_BINDNAME) \
_(JSOP_DELNAME) \ _(JSOP_DELNAME) \
_(JSOP_GETIMPORT) \ _(JSOP_GETIMPORT) \
_(JSOP_GETINTRINSIC) \ _(JSOP_GETINTRINSIC) \
_(JSOP_SETINTRINSIC) \ _(JSOP_SETINTRINSIC) \
_(JSOP_BINDVAR) \ _(JSOP_BINDVAR) \
_(JSOP_DEFVAR) \ _(JSOP_DEFVAR) \
_(JSOP_DEFCONST) \ _(JSOP_DEFCONST) \
_(JSOP_DEFLET) \ _(JSOP_DEFLET) \
_(JSOP_DEFFUN) \ _(JSOP_DEFFUN) \
_(JSOP_GETLOCAL) \ _(JSOP_GETLOCAL) \
_(JSOP_SETLOCAL) \ _(JSOP_SETLOCAL) \
_(JSOP_GETARG) \ _(JSOP_GETARG) \
_(JSOP_SETARG) \ _(JSOP_SETARG) \
_(JSOP_CHECKLEXICAL) \ _(JSOP_CHECKLEXICAL) \
_(JSOP_INITLEXICAL) \ _(JSOP_INITLEXICAL) \
_(JSOP_INITGLEXICAL) \ _(JSOP_INITGLEXICAL) \
_(JSOP_CHECKALIASEDLEXICAL) \ _(JSOP_CHECKALIASEDLEXICAL) \
_(JSOP_INITALIASEDLEXICAL) \ _(JSOP_INITALIASEDLEXICAL) \
_(JSOP_UNINITIALIZED) \ _(JSOP_UNINITIALIZED) \
_(JSOP_CALL) \ _(JSOP_CALL) \
_(JSOP_CALL_IGNORES_RV) \ _(JSOP_CALL_IGNORES_RV) \
_(JSOP_CALLITER) \ _(JSOP_CALLITER) \
_(JSOP_FUNCALL) \ _(JSOP_FUNCALL) \
_(JSOP_FUNAPPLY) \ _(JSOP_FUNAPPLY) \
_(JSOP_NEW) \ _(JSOP_NEW) \
_(JSOP_EVAL) \ _(JSOP_EVAL) \
_(JSOP_STRICTEVAL) \ _(JSOP_STRICTEVAL) \
_(JSOP_SPREADCALL) \ _(JSOP_SPREADCALL) \
_(JSOP_SPREADNEW) \ _(JSOP_SPREADNEW) \
_(JSOP_SPREADEVAL) \ _(JSOP_SPREADEVAL) \
_(JSOP_STRICTSPREADEVAL) \ _(JSOP_STRICTSPREADEVAL) \
_(JSOP_OPTIMIZE_SPREADCALL) \ _(JSOP_OPTIMIZE_SPREADCALL) \
_(JSOP_IMPLICITTHIS) \ _(JSOP_IMPLICITTHIS) \
_(JSOP_GIMPLICITTHIS) \ _(JSOP_GIMPLICITTHIS) \
_(JSOP_INSTANCEOF) \ _(JSOP_INSTANCEOF) \
_(JSOP_TYPEOF) \ _(JSOP_TYPEOF) \
_(JSOP_TYPEOFEXPR) \ _(JSOP_TYPEOFEXPR) \
_(JSOP_THROWMSG) \ _(JSOP_THROWMSG) \
_(JSOP_THROW) \ _(JSOP_THROW) \
_(JSOP_TRY) \ _(JSOP_TRY) \
_(JSOP_FINALLY) \ _(JSOP_FINALLY) \
_(JSOP_GOSUB) \ _(JSOP_GOSUB) \
_(JSOP_RETSUB) \ _(JSOP_RETSUB) \
_(JSOP_PUSHLEXICALENV) \ _(JSOP_PUSHLEXICALENV) \
_(JSOP_POPLEXICALENV) \ _(JSOP_POPLEXICALENV) \
_(JSOP_FRESHENLEXICALENV) \ _(JSOP_FRESHENLEXICALENV) \
_(JSOP_RECREATELEXICALENV) \ _(JSOP_RECREATELEXICALENV) \
_(JSOP_DEBUGLEAVELEXICALENV) \ _(JSOP_DEBUGLEAVELEXICALENV) \
_(JSOP_PUSHVARENV) \ _(JSOP_PUSHVARENV) \
_(JSOP_POPVARENV) \ _(JSOP_POPVARENV) \
_(JSOP_EXCEPTION) \ _(JSOP_EXCEPTION) \
_(JSOP_DEBUGGER) \ _(JSOP_DEBUGGER) \
_(JSOP_ARGUMENTS) \ _(JSOP_ARGUMENTS) \
_(JSOP_REST) \ _(JSOP_REST) \
_(JSOP_TOASYNCITER) \ _(JSOP_TOASYNCITER) \
_(JSOP_TOID) \ _(JSOP_TOID) \
_(JSOP_TOSTRING) \ _(JSOP_TOSTRING) \
_(JSOP_TABLESWITCH) \ _(JSOP_TABLESWITCH) \
_(JSOP_ITER) \ _(JSOP_ITER) \
_(JSOP_MOREITER) \ _(JSOP_MOREITER) \
_(JSOP_ISNOITER) \ _(JSOP_ISNOITER) \
_(JSOP_ENDITER) \ _(JSOP_ENDITER) \
_(JSOP_ISGENCLOSING) \ _(JSOP_ISGENCLOSING) \
_(JSOP_GENERATOR) \ _(JSOP_GENERATOR) \
_(JSOP_INITIALYIELD) \ _(JSOP_INITIALYIELD) \
_(JSOP_YIELD) \ _(JSOP_YIELD) \
_(JSOP_AWAIT) \ _(JSOP_AWAIT) \
_(JSOP_TRYSKIPAWAIT) \ _(JSOP_TRYSKIPAWAIT) \
_(JSOP_AFTERYIELD) \ _(JSOP_AFTERYIELD) \
_(JSOP_FINALYIELDRVAL) \ _(JSOP_FINALYIELDRVAL) \
_(JSOP_RESUME) \ _(JSOP_RESUME) \
_(JSOP_ASYNCAWAIT) \ _(JSOP_ASYNCAWAIT) \
_(JSOP_ASYNCRESOLVE) \ _(JSOP_ASYNCRESOLVE) \
_(JSOP_CALLEE) \ _(JSOP_CALLEE) \
_(JSOP_ENVCALLEE) \ _(JSOP_ENVCALLEE) \
_(JSOP_SUPERBASE) \ _(JSOP_SUPERBASE) \
_(JSOP_SUPERFUN) \ _(JSOP_SUPERFUN) \
_(JSOP_GETRVAL) \ _(JSOP_GETRVAL) \
_(JSOP_SETRVAL) \ _(JSOP_SETRVAL) \
_(JSOP_RETRVAL) \ _(JSOP_RETRVAL) \
_(JSOP_RETURN) \ _(JSOP_RETURN) \
_(JSOP_FUNCTIONTHIS) \ _(JSOP_FUNCTIONTHIS) \
_(JSOP_GLOBALTHIS) \ _(JSOP_GLOBALTHIS) \
_(JSOP_CHECKISOBJ) \ _(JSOP_CHECKISOBJ) \
_(JSOP_CHECKISCALLABLE) \ _(JSOP_CHECKISCALLABLE) \
_(JSOP_CHECKTHIS) \ _(JSOP_CHECKTHIS) \
_(JSOP_CHECKTHISREINIT) \ _(JSOP_CHECKTHISREINIT) \
_(JSOP_CHECKRETURN) \ _(JSOP_CHECKRETURN) \
_(JSOP_NEWTARGET) \ _(JSOP_NEWTARGET) \
_(JSOP_SUPERCALL) \ _(JSOP_SUPERCALL) \
_(JSOP_SPREADSUPERCALL) \ _(JSOP_SPREADSUPERCALL) \
_(JSOP_THROWSETCONST) \ _(JSOP_THROWSETCONST) \
_(JSOP_THROWSETALIASEDCONST) \ _(JSOP_THROWSETALIASEDCONST) \
_(JSOP_THROWSETCALLEE) \ _(JSOP_THROWSETCALLEE) \
_(JSOP_INITHIDDENPROP_GETTER) \ _(JSOP_INITHIDDENPROP_GETTER) \
_(JSOP_INITHIDDENPROP_SETTER) \ _(JSOP_INITHIDDENPROP_SETTER) \
_(JSOP_INITHIDDENELEM) \ _(JSOP_INITHIDDENELEM) \
_(JSOP_INITHIDDENELEM_GETTER) \ _(JSOP_INITHIDDENELEM_GETTER) \
_(JSOP_INITHIDDENELEM_SETTER) \ _(JSOP_INITHIDDENELEM_SETTER) \
_(JSOP_CHECKOBJCOERCIBLE) \ _(JSOP_CHECKOBJCOERCIBLE) \
_(JSOP_DEBUGCHECKSELFHOSTED) \ _(JSOP_DEBUGCHECKSELFHOSTED) \
_(JSOP_JUMPTARGET) \ _(JSOP_JUMPTARGET) \
_(JSOP_IS_CONSTRUCTING) \ _(JSOP_IS_CONSTRUCTING) \
_(JSOP_TRY_DESTRUCTURING) \ _(JSOP_TRY_DESTRUCTURING) \
_(JSOP_CHECKCLASSHERITAGE) \ _(JSOP_CHECKCLASSHERITAGE) \
_(JSOP_INITHOMEOBJECT) \ _(JSOP_INITHOMEOBJECT) \
_(JSOP_BUILTINPROTO) \ _(JSOP_BUILTINPROTO) \
_(JSOP_OBJWITHPROTO) \ _(JSOP_OBJWITHPROTO) \
_(JSOP_FUNWITHPROTO) \ _(JSOP_FUNWITHPROTO) \
_(JSOP_CLASSCONSTRUCTOR) \ _(JSOP_CLASSCONSTRUCTOR) \
_(JSOP_DERIVEDCONSTRUCTOR) \ _(JSOP_DERIVEDCONSTRUCTOR) \
_(JSOP_IMPORTMETA) \ _(JSOP_IMPORTMETA) \
_(JSOP_DYNAMIC_IMPORT) \ _(JSOP_DYNAMIC_IMPORT) \
_(JSOP_INC) \ _(JSOP_INC) \
_(JSOP_DEC) _(JSOP_DEC) \
_(JSOP_INSTRUMENTATION_ACTIVE) \
_(JSOP_INSTRUMENTATION_CALLBACK) \
_(JSOP_INSTRUMENTATION_SCRIPT_ID)
enum class ScriptGCThingType { RegExp, Function, Scope, BigInt }; enum class ScriptGCThingType { RegExp, Function, Scope, BigInt };

View File

@ -23,6 +23,7 @@
#include "jit/MIRGraph.h" #include "jit/MIRGraph.h"
#include "vm/ArgumentsObject.h" #include "vm/ArgumentsObject.h"
#include "vm/EnvironmentObject.h" #include "vm/EnvironmentObject.h"
#include "vm/Instrumentation.h"
#include "vm/Opcodes.h" #include "vm/Opcodes.h"
#include "vm/RegExpStatics.h" #include "vm/RegExpStatics.h"
#include "vm/SelfHosting.h" #include "vm/SelfHosting.h"
@ -2485,6 +2486,15 @@ AbortReasonOr<Ok> IonBuilder::inspectOpcode(JSOp op) {
case JSOP_LOOPENTRY: case JSOP_LOOPENTRY:
return jsop_loopentry(); return jsop_loopentry();
case JSOP_INSTRUMENTATION_ACTIVE:
return jsop_instrumentation_active();
case JSOP_INSTRUMENTATION_CALLBACK:
return jsop_instrumentation_callback();
case JSOP_INSTRUMENTATION_SCRIPT_ID:
return jsop_instrumentation_scriptid();
// ===== NOT Yet Implemented ===== // ===== NOT Yet Implemented =====
// Read below! // Read below!
@ -13479,6 +13489,35 @@ AbortReasonOr<Ok> IonBuilder::jsop_dynamic_import() {
return resumeAfter(ins); return resumeAfter(ins);
} }
AbortReasonOr<Ok> IonBuilder::jsop_instrumentation_active() {
// All IonScripts in the realm are discarded when instrumentation activity
// changes, so we can treat the value we get as a constant.
bool active = RealmInstrumentation::isActive(&script()->global());
pushConstant(BooleanValue(active));
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_instrumentation_callback() {
JSObject* obj = RealmInstrumentation::getCallback(&script()->global());
MOZ_ASSERT(obj);
pushConstant(ObjectValue(*obj));
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_instrumentation_scriptid() {
// Getting the script ID requires interacting with the Debugger used for
// instrumentation, but cannot run script.
JSContext* cx = TlsContext.get();
int32_t scriptId;
RootedScript script(cx, this->script());
if (!RealmInstrumentation::getScriptId(cx, cx->global(), script, &scriptId)) {
return abort(AbortReason::Error);
}
pushConstant(Int32Value(scriptId));
return Ok();
}
MInstruction* IonBuilder::addConvertElementsToDoubles(MDefinition* elements) { MInstruction* IonBuilder::addConvertElementsToDoubles(MDefinition* elements) {
MInstruction* convert = MConvertElementsToDoubles::New(alloc(), elements); MInstruction* convert = MConvertElementsToDoubles::New(alloc(), elements);
current->add(convert); current->add(convert);

View File

@ -671,6 +671,9 @@ class IonBuilder : public MIRGenerator,
AbortReasonOr<Ok> jsop_implicitthis(PropertyName* name); AbortReasonOr<Ok> jsop_implicitthis(PropertyName* name);
AbortReasonOr<Ok> jsop_importmeta(); AbortReasonOr<Ok> jsop_importmeta();
AbortReasonOr<Ok> jsop_dynamic_import(); AbortReasonOr<Ok> jsop_dynamic_import();
AbortReasonOr<Ok> jsop_instrumentation_active();
AbortReasonOr<Ok> jsop_instrumentation_callback();
AbortReasonOr<Ok> jsop_instrumentation_scriptid();
/* Inlining. */ /* Inlining. */

View File

@ -16,6 +16,7 @@
#include "vm/AsyncFunction.h" #include "vm/AsyncFunction.h"
#include "vm/AsyncIteration.h" #include "vm/AsyncIteration.h"
#include "vm/EqualityOperations.h" #include "vm/EqualityOperations.h"
#include "vm/Instrumentation.h"
#include "vm/Interpreter.h" #include "vm/Interpreter.h"
#include "vm/TypedArrayObject.h" #include "vm/TypedArrayObject.h"
@ -131,6 +132,9 @@ namespace jit {
_(InitPropGetterSetterOperation, js::InitPropGetterSetterOperation) \ _(InitPropGetterSetterOperation, js::InitPropGetterSetterOperation) \
_(InitRestParameter, js::jit::InitRestParameter) \ _(InitRestParameter, js::jit::InitRestParameter) \
_(InlineTypedObjectCreateCopy, js::InlineTypedObject::createCopy) \ _(InlineTypedObjectCreateCopy, js::InlineTypedObject::createCopy) \
_(InstrumentationActiveOperation, js::InstrumentationActiveOperation) \
_(InstrumentationCallbackOperation, js::InstrumentationCallbackOperation) \
_(InstrumentationScriptIdOperation, js::InstrumentationScriptIdOperation) \
_(Int32ToString, js::Int32ToString<CanGC>) \ _(Int32ToString, js::Int32ToString<CanGC>) \
_(InterpretResume, js::jit::InterpretResume) \ _(InterpretResume, js::jit::InterpretResume) \
_(InterruptCheck, js::jit::InterruptCheck) \ _(InterruptCheck, js::jit::InterruptCheck) \

View File

@ -78,6 +78,7 @@
#include "vm/EnvironmentObject.h" #include "vm/EnvironmentObject.h"
#include "vm/ErrorObject.h" #include "vm/ErrorObject.h"
#include "vm/HelperThreads.h" #include "vm/HelperThreads.h"
#include "vm/Instrumentation.h"
#include "vm/Interpreter.h" #include "vm/Interpreter.h"
#include "vm/Iteration.h" #include "vm/Iteration.h"
#include "vm/JSAtom.h" #include "vm/JSAtom.h"
@ -3585,6 +3586,11 @@ JS::CompileOptions::CompileOptions(JSContext* cx)
forceFullParse_ = cx->realm()->behaviors().disableLazyParsing() || forceFullParse_ = cx->realm()->behaviors().disableLazyParsing() ||
coverage::IsLCovEnabled() || coverage::IsLCovEnabled() ||
mozilla::recordreplay::IsRecordingOrReplaying(); mozilla::recordreplay::IsRecordingOrReplaying();
// If instrumentation is enabled in the realm, the compiler should insert the
// requested kinds of instrumentation into all scripts.
instrumentationKinds =
RealmInstrumentation::getInstrumentationKinds(cx->global());
} }
CompileOptions& CompileOptions::setIntroductionInfoToCaller( CompileOptions& CompileOptions::setIntroductionInfoToCaller(

View File

@ -292,6 +292,7 @@ UNIFIED_SOURCES += [
'vm/HelperThreads.cpp', 'vm/HelperThreads.cpp',
'vm/Id.cpp', 'vm/Id.cpp',
'vm/Initialization.cpp', 'vm/Initialization.cpp',
'vm/Instrumentation.cpp',
'vm/Iteration.cpp', 'vm/Iteration.cpp',
'vm/JSAtom.cpp', 'vm/JSAtom.cpp',
'vm/JSContext.cpp', 'vm/JSContext.cpp',

View File

@ -106,6 +106,7 @@ class GlobalObject : public NativeObject {
FOR_OF_PIC_CHAIN, FOR_OF_PIC_CHAIN,
WINDOW_PROXY, WINDOW_PROXY,
GLOBAL_THIS_RESOLVED, GLOBAL_THIS_RESOLVED,
INSTRUMENTATION,
/* Total reserved-slot count for global objects. */ /* Total reserved-slot count for global objects. */
RESERVED_SLOTS RESERVED_SLOTS
@ -892,6 +893,15 @@ class GlobalObject : public NativeObject {
setReservedSlot(WINDOW_PROXY, ObjectValue(*windowProxy)); setReservedSlot(WINDOW_PROXY, ObjectValue(*windowProxy));
} }
JSObject* getInstrumentationHolder() const {
Value v = getReservedSlot(INSTRUMENTATION);
MOZ_ASSERT(v.isObject() || v.isUndefined());
return v.isObject() ? &v.toObject() : nullptr;
}
void setInstrumentationHolder(JSObject* instrumentation) {
setReservedSlot(INSTRUMENTATION, ObjectValue(*instrumentation));
}
// A class used in place of a prototype during off-thread parsing. // A class used in place of a prototype during off-thread parsing.
struct OffThreadPlaceholderObject : public NativeObject { struct OffThreadPlaceholderObject : public NativeObject {
static const int32_t SlotIndexSlot = 0; static const int32_t SlotIndexSlot = 0;

View File

@ -0,0 +1,269 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "vm/Instrumentation.h"
#include "jsapi.h"
#include "debugger/DebugAPI.h"
#include "vm/JSObject-inl.h"
namespace js {
RealmInstrumentation::RealmInstrumentation(Zone* zone, JSObject* callback,
JSObject* dbgObject, uint32_t kinds)
: callback(callback), dbgObject(dbgObject), kinds(kinds) {}
void RealmInstrumentation::trace(JSTracer* trc) {
TraceEdge(trc, &callback, "RealmInstrumentation::callback");
TraceEdge(trc, &dbgObject, "RealmInstrumentation::dbgObject");
}
enum InstrumentationHolderSlots {
RealmInstrumentationSlot,
ReservedSlotCount,
};
static RealmInstrumentation* GetInstrumentation(JSObject* obj) {
Value v = JS_GetReservedSlot(obj, RealmInstrumentationSlot);
return static_cast<RealmInstrumentation*>(v.toPrivate());
}
/* static */
void RealmInstrumentation::holderFinalize(FreeOp* fop, JSObject* obj) {
RealmInstrumentation* instrumentation = GetInstrumentation(obj);
fop->delete_(obj, instrumentation, MemoryUse::RealmInstrumentation);
}
/* static */
void RealmInstrumentation::holderTrace(JSTracer* trc, JSObject* obj) {
RealmInstrumentation* instrumentation = GetInstrumentation(obj);
instrumentation->trace(trc);
}
static const ClassOps InstrumentationHolderClassOps = {
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* enumerate */
nullptr, /* newEnumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
RealmInstrumentation::holderFinalize,
nullptr, /* call */
nullptr, /* hasInstance */
nullptr, /* construct */
RealmInstrumentation::holderTrace,
};
static const Class InstrumentationHolderClass = {
"Instrumentation Holder",
JSCLASS_HAS_RESERVED_SLOTS(ReservedSlotCount) | JSCLASS_FOREGROUND_FINALIZE,
&InstrumentationHolderClassOps, JS_NULL_CLASS_SPEC, JS_NULL_CLASS_EXT
};
static const char* instrumentationNames[] = {
#define DEFINE_INSTRUMENTATION_STRING(_1, String, _2) String,
FOR_EACH_INSTRUMENTATION_KIND(DEFINE_INSTRUMENTATION_STRING)
#undef DEFINE_INSTRUMENTATION_STRING
};
static bool StringToInstrumentationKind(JSContext* cx,
HandleString str,
InstrumentationKind* result) {
for (size_t i = 0; i < mozilla::ArrayLength(instrumentationNames); i++) {
bool match;
if (!JS_StringEqualsAscii(cx, str, instrumentationNames[i], &match)) {
return false;
}
if (match) {
*result = (InstrumentationKind) (1 << i);
return true;
}
}
JS_ReportErrorASCII(cx, "Unknown instrumentation kind");
return false;
}
/* static */
JSAtom* RealmInstrumentation::getInstrumentationKindName(
JSContext* cx, InstrumentationKind kind) {
for (size_t i = 0; i < mozilla::ArrayLength(instrumentationNames); i++) {
if (kind == (InstrumentationKind) (1 << i)) {
JSString* str = JS_AtomizeString(cx, instrumentationNames[i]);
if (!str) {
return nullptr;
}
return &str->asAtom();
}
}
MOZ_CRASH("Unexpected instrumentation kind");
}
/* static */
bool RealmInstrumentation::install(JSContext* cx, Handle<GlobalObject*> global,
HandleObject callbackArg,
HandleObject dbgObjectArg,
Handle<StringVector> kindStrings) {
MOZ_ASSERT(global == cx->global());
if (global->getInstrumentationHolder()) {
JS_ReportErrorASCII(cx, "Global already has instrumentation specified");
return false;
}
RootedObject callback(cx, callbackArg);
if (!cx->compartment()->wrap(cx, &callback)) {
return false;
}
RootedObject dbgObject(cx, dbgObjectArg);
if (!cx->compartment()->wrap(cx, &dbgObject)) {
return false;
}
uint32_t kinds = 0;
for (size_t i = 0; i < kindStrings.length(); i++) {
HandleString str = kindStrings[i];
InstrumentationKind kind;
if (!StringToInstrumentationKind(cx, str, &kind)) {
return false;
}
kinds |= (uint32_t) kind;
}
UniquePtr<RealmInstrumentation> instrumentation =
MakeUnique<RealmInstrumentation>(cx->zone(), callback, dbgObject, kinds);
if (!instrumentation) {
ReportOutOfMemory(cx);
return false;
}
JSObject* holder = NewBuiltinClassInstance(cx, &InstrumentationHolderClass);
if (!holder) {
return false;
}
InitReservedSlot(&holder->as<NativeObject>(), RealmInstrumentationSlot,
instrumentation.release(), MemoryUse::RealmInstrumentation);
global->setInstrumentationHolder(holder);
return true;
}
/* static */
bool RealmInstrumentation::setActive(JSContext* cx,
Handle<GlobalObject*> global,
Debugger* dbg,
bool active) {
MOZ_ASSERT(global == cx->global());
RootedObject holder(cx, global->getInstrumentationHolder());
if (!holder) {
JS_ReportErrorASCII(cx, "Global does not have instrumentation specified");
return false;
}
RealmInstrumentation* instrumentation = GetInstrumentation(holder);
if (active != instrumentation->active) {
instrumentation->active = active;
// For simplicity, discard all Ion code in the entire zone when
// instrumentation activity changes.
js::CancelOffThreadIonCompile(cx->runtime());
cx->zone()->setPreservingCode(false);
cx->zone()->discardJitCode(cx->runtime()->defaultFreeOp(),
Zone::KeepBaselineCode);
}
return true;
}
/* static */
bool RealmInstrumentation::isActive(GlobalObject* global) {
JSObject* holder = global->getInstrumentationHolder();
MOZ_ASSERT(holder);
RealmInstrumentation* instrumentation = GetInstrumentation(holder);
return instrumentation->active;
}
/* static */
const int32_t* RealmInstrumentation::addressOfActive(GlobalObject* global) {
JSObject* holder = global->getInstrumentationHolder();
MOZ_ASSERT(holder);
RealmInstrumentation* instrumentation = GetInstrumentation(holder);
return &instrumentation->active;
}
/* static */
JSObject* RealmInstrumentation::getCallback(GlobalObject* global) {
JSObject* holder = global->getInstrumentationHolder();
MOZ_ASSERT(holder);
RealmInstrumentation* instrumentation = GetInstrumentation(holder);
return instrumentation->callback;
}
/* static */
uint32_t RealmInstrumentation::getInstrumentationKinds(GlobalObject* global) {
JSObject* holder = global->getInstrumentationHolder();
if (!holder) {
return 0;
}
RealmInstrumentation* instrumentation = GetInstrumentation(holder);
return instrumentation->kinds;
}
/* static */
bool RealmInstrumentation::getScriptId(JSContext* cx,
Handle<GlobalObject*> global,
HandleScript script,
int32_t* id) {
MOZ_ASSERT(global == cx->global());
RootedObject holder(cx, global->getInstrumentationHolder());
RealmInstrumentation* instrumentation = GetInstrumentation(holder);
RootedObject dbgObject(cx, UncheckedUnwrap(instrumentation->dbgObject));
AutoRealm ar(cx, dbgObject);
RootedValue idValue(cx);
if (!DebugAPI::getScriptInstrumentationId(cx, dbgObject, script, &idValue)) {
return false;
}
if (!idValue.isNumber()) {
JS_ReportErrorASCII(cx, "Instrumentation ID not set for script");
return false;
}
*id = idValue.toNumber();
return true;
}
bool InstrumentationActiveOperation(JSContext* cx, MutableHandleValue rv) {
rv.setBoolean(RealmInstrumentation::isActive(cx->global()));
return true;
}
JSObject* InstrumentationCallbackOperation(JSContext* cx) {
return RealmInstrumentation::getCallback(cx->global());
}
bool InstrumentationScriptIdOperation(JSContext* cx, HandleScript script,
MutableHandleValue rv) {
int32_t id;
if (!RealmInstrumentation::getScriptId(cx, cx->global(), script, &id)) {
return false;
}
rv.setInt32(id);
return true;
}
} // namespace js

113
js/src/vm/Instrumentation.h Normal file
View File

@ -0,0 +1,113 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef vm_Instrumentation_h
#define vm_Instrumentation_h
#include "js/UniquePtr.h"
#include "vm/GlobalObject.h"
namespace js {
// Logic related to instrumentation which can be performed in a realm.
#define FOR_EACH_INSTRUMENTATION_KIND(MACRO) \
/* The main entry point of a script. */ \
MACRO(Main, "main", 1 << 0) \
/* Points other than the main entry point where a frame for the script */ \
/* might start executing. */ \
MACRO(Entry, "entry", 1 << 1) \
/* Points at which a script's frame will be popped or suspended. */ \
MACRO(Exit, "exit", 1 << 2) \
/* Breakpoint sites. */ \
MACRO(Breakpoint, "breakpoint", 1 << 3) \
/* Property access operations. */ \
MACRO(GetProperty, "getProperty", 1 << 4) \
MACRO(SetProperty, "setProperty", 1 << 5) \
MACRO(GetElement, "getElement", 1 << 6) \
MACRO(SetElement, "setElement", 1 << 7)
// Points at which instrumentation can be added on the scripts in a realm.
enum class InstrumentationKind {
#define DEFINE_INSTRUMENTATION_ENUM(Name, _1, Value) Name = Value,
FOR_EACH_INSTRUMENTATION_KIND(DEFINE_INSTRUMENTATION_ENUM)
#undef DEFINE_INSTRUMENTATION_ENUM
};
class RealmInstrumentation {
// Callback invoked on instrumentation operations.
GCPtrObject callback;
// Debugger with which the instrumentation is associated. This debugger's
// Debugger.Script instances store instrumentation IDs for scripts in the
// realm.
GCPtrObject dbgObject;
// Mask of the InstrumentationKind operations which should be instrumented.
uint32_t kinds = 0;
// Whether instrumentation is currently active in the realm. This is an
// int32_t so it can be directly accessed from JIT code.
int32_t active = 0;
void trace(JSTracer* trc);
friend struct GCManagedDeletePolicy<RealmInstrumentation>;
public:
static bool install(JSContext* cx, Handle<GlobalObject*> global,
HandleObject callback,
HandleObject dbgObject,
Handle<StringVector> kinds);
static JSObject* getCallback(GlobalObject* global);
// Get the mask of operation kinds which should be instrumented.
static uint32_t getInstrumentationKinds(GlobalObject* global);
// Get the string name of an instrumentation kind.
static JSAtom* getInstrumentationKindName(JSContext* cx,
InstrumentationKind kind);
static bool getScriptId(JSContext* cx, Handle<GlobalObject*> global,
HandleScript script, int32_t* id);
static bool setActive(JSContext* cx, Handle<GlobalObject*> global,
Debugger* dbg, bool active);
static bool isActive(GlobalObject* global);
static const int32_t* addressOfActive(GlobalObject* global);
// This is public for js_new.
RealmInstrumentation(Zone* zone, JSObject* callback, JSObject* dbgObject,
uint32_t kinds);
static void holderFinalize(FreeOp* fop, JSObject* obj);
static void holderTrace(JSTracer* trc, JSObject* obj);
};
// For use in the frontend when an opcode may or may not need instrumentation.
enum class ShouldInstrument {
No,
Yes,
};
bool InstrumentationActiveOperation(JSContext* cx, MutableHandleValue rv);
JSObject* InstrumentationCallbackOperation(JSContext* cx);
bool InstrumentationScriptIdOperation(JSContext* cx, HandleScript script,
MutableHandleValue rv);
} // namespace js
namespace JS {
template <>
struct DeletePolicy<js::RealmInstrumentation>
: public js::GCManagedDeletePolicy<js::RealmInstrumentation> {};
} /* namespace JS */
#endif /* vm_Instrumentation_h */

View File

@ -39,6 +39,7 @@
#include "vm/BytecodeUtil.h" #include "vm/BytecodeUtil.h"
#include "vm/EqualityOperations.h" // js::StrictlyEqual #include "vm/EqualityOperations.h" // js::StrictlyEqual
#include "vm/GeneratorObject.h" #include "vm/GeneratorObject.h"
#include "vm/Instrumentation.h"
#include "vm/Iteration.h" #include "vm/Iteration.h"
#include "vm/JSAtom.h" #include "vm/JSAtom.h"
#include "vm/JSContext.h" #include "vm/JSContext.h"
@ -4350,6 +4351,31 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_CALLER bool Interpret(JSContext* cx,
CASE(JSOP_BIGINT) { PUSH_BIGINT(script->getBigInt(REGS.pc)); } CASE(JSOP_BIGINT) { PUSH_BIGINT(script->getBigInt(REGS.pc)); }
END_CASE(JSOP_BIGINT) END_CASE(JSOP_BIGINT)
CASE(JSOP_INSTRUMENTATION_ACTIVE) {
ReservedRooted<Value> rval(&rootValue0);
if (!InstrumentationActiveOperation(cx, &rval)) {
goto error;
}
PUSH_COPY(rval);
}
END_CASE(JSOP_INSTRUMENTATION_ACTIVE)
CASE(JSOP_INSTRUMENTATION_CALLBACK) {
JSObject* obj = InstrumentationCallbackOperation(cx);
MOZ_ASSERT(obj);
PUSH_OBJECT(*obj);
}
END_CASE(JSOP_INSTRUMENTATION_CALLBACK)
CASE(JSOP_INSTRUMENTATION_SCRIPT_ID) {
ReservedRooted<Value> rval(&rootValue0);
if (!InstrumentationScriptIdOperation(cx, script, &rval)) {
goto error;
}
PUSH_COPY(rval);
}
END_CASE(JSOP_INSTRUMENTATION_SCRIPT_ID)
DEFAULT() { DEFAULT() {
char numBuf[12]; char numBuf[12];
SprintfLiteral(numBuf, "%d", *REGS.pc); SprintfLiteral(numBuf, "%d", *REGS.pc);

View File

@ -1014,6 +1014,16 @@ XDRResult js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope,
JSContext* cx = xdr->cx(); JSContext* cx = xdr->cx();
RootedScript script(cx); RootedScript script(cx);
// Instrumented scripts cannot be encoded, as they have extra instructions
// which are not normally present. Globals with instrumentation enabled must
// compile scripts via the bytecode emitter, which will insert these
// instructions.
if (xdr->hasOptions()
? !!xdr->options().instrumentationKinds
: !!cx->global()->getInstrumentationHolder()) {
return xdr->fail(JS::TranscodeResult_Failure);
}
if (mode == XDR_ENCODE) { if (mode == XDR_ENCODE) {
script = scriptp.get(); script = scriptp.get();
MOZ_ASSERT(script->functionNonDelazifying() == fun); MOZ_ASSERT(script->functionNonDelazifying() == fun);

View File

@ -2547,7 +2547,28 @@
* Operands: uint32_t constIndex * Operands: uint32_t constIndex
* Stack: => val * Stack: => val
*/ \ */ \
MACRO(JSOP_BIGINT, 237, "bigint", NULL, 5, 0, 1, JOF_BIGINT) MACRO(JSOP_BIGINT, 237, "bigint", NULL, 5, 0, 1, JOF_BIGINT) \
/*
* Pushes a boolean indicating if instrumentation is active.
* Category: Other
* Operands:
* Stack: => val
*/ \
MACRO(JSOP_INSTRUMENTATION_ACTIVE, 238, "instrumentationActive", NULL, 1, 0, 1, JOF_BYTE) \
/*
* Pushes the instrumentation callback for the current realm.
* Category: Other
* Operands:
* Stack: => val
*/ \
MACRO(JSOP_INSTRUMENTATION_CALLBACK, 239, "instrumentationCallback", NULL, 1, 0, 1, JOF_BYTE) \
/*
* Pushes the current script's instrumentation ID.
* Category: Other
* Operands:
* Stack: => val
*/ \
MACRO(JSOP_INSTRUMENTATION_SCRIPT_ID, 240, "instrumentationScriptId", NULL, 1, 0, 1, JOF_BYTE)
// clang-format on // clang-format on
/* /*
@ -2555,9 +2576,6 @@
* a power of two. Use this macro to do so. * a power of two. Use this macro to do so.
*/ */
#define FOR_EACH_TRAILING_UNUSED_OPCODE(MACRO) \ #define FOR_EACH_TRAILING_UNUSED_OPCODE(MACRO) \
MACRO(238) \
MACRO(239) \
MACRO(240) \
MACRO(241) \ MACRO(241) \
MACRO(242) \ MACRO(242) \
MACRO(243) \ MACRO(243) \