mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-19 16:25:38 +00:00
Merge inbound to mozilla-central. a=merge
This commit is contained in:
commit
124c0de476
@ -839,7 +839,7 @@ static const uint32_t JSCLASS_FOREGROUND_FINALIZE =
|
||||
// application.
|
||||
static const uint32_t JSCLASS_GLOBAL_APPLICATION_SLOTS = 5;
|
||||
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) \
|
||||
(JSCLASS_IS_GLOBAL | \
|
||||
|
@ -134,6 +134,9 @@ class JS_PUBLIC_API TransitiveCompileOptions {
|
||||
uint32_t introductionOffset = 0;
|
||||
bool hasIntroductionInfo = false;
|
||||
|
||||
// Mask of operation kinds which should be instrumented.
|
||||
uint32_t instrumentationKinds = 0;
|
||||
|
||||
protected:
|
||||
TransitiveCompileOptions() = default;
|
||||
|
||||
|
@ -284,6 +284,15 @@ class DebugAPI {
|
||||
// Whether any debugger is observing debugger statements in a realm.
|
||||
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:
|
||||
static bool stepModeEnabledSlow(JSScript* script);
|
||||
static bool hasBreakpointsAtSlow(JSScript* script, jsbytecode* pc);
|
||||
|
@ -48,6 +48,7 @@
|
||||
#include "vm/AsyncFunction.h"
|
||||
#include "vm/AsyncIteration.h"
|
||||
#include "vm/GeckoProfiler.h"
|
||||
#include "vm/Instrumentation.h"
|
||||
#include "vm/JSContext.h"
|
||||
#include "vm/JSObject.h"
|
||||
#include "vm/Realm.h"
|
||||
@ -6408,6 +6409,19 @@ JSObject* Debugger::wrapWasmSource(JSContext* cx,
|
||||
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) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR,
|
||||
"Debugger.Source");
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "debugger/Script.h"
|
||||
#include "proxy/ScriptedProxyHandler.h"
|
||||
#include "vm/EnvironmentObject.h"
|
||||
#include "vm/Instrumentation.h"
|
||||
#include "vm/WrapperObject.h"
|
||||
|
||||
#include "debugger/Debugger-inl.h"
|
||||
@ -1239,6 +1240,101 @@ bool DebuggerObject::unwrapMethod(JSContext* cx, unsigned argc, Value* vp) {
|
||||
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_[] = {
|
||||
JS_PSG("callable", DebuggerObject::callableGetter, 0),
|
||||
JS_PSG("isBoundFunction", DebuggerObject::isBoundFunctionGetter, 0),
|
||||
@ -1311,6 +1407,9 @@ const JSFunctionSpec DebuggerObject::methods_[] = {
|
||||
JS_FN("makeDebuggeeValue", DebuggerObject::makeDebuggeeValueMethod, 1, 0),
|
||||
JS_FN("unsafeDereference", DebuggerObject::unsafeDereferenceMethod, 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};
|
||||
|
||||
/* static */
|
||||
|
@ -306,6 +306,11 @@ class DebuggerObject : public NativeObject {
|
||||
Value* vp);
|
||||
static MOZ_MUST_USE bool unwrapMethod(JSContext* cx, unsigned argc,
|
||||
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,
|
||||
HandleObject maybeError,
|
||||
JSErrorReport*& report);
|
||||
|
@ -2083,6 +2083,28 @@ bool DebuggerScript::getOffsetsCoverage(JSContext* cx, unsigned argc,
|
||||
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 */
|
||||
bool DebuggerScript::construct(JSContext* cx, unsigned argc, Value* vp) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR,
|
||||
@ -2119,6 +2141,7 @@ const JSFunctionSpec DebuggerScript::methods_[] = {
|
||||
JS_FN("getOffsetsCoverage", getOffsetsCoverage, 0, 0),
|
||||
JS_FN("getSuccessorOffsets", getSuccessorOffsets, 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
|
||||
// under-defined 'entrypoint' concept. Make use of getPossibleBreakpoints,
|
||||
|
@ -33,6 +33,10 @@ class DebuggerScript : public NativeObject {
|
||||
|
||||
enum {
|
||||
OWNER_SLOT,
|
||||
|
||||
// Holds any instrumentation ID that has been assigned to the script.
|
||||
INSTRUMENTATION_ID_SLOT,
|
||||
|
||||
RESERVED_SLOTS,
|
||||
};
|
||||
|
||||
@ -84,6 +88,7 @@ class DebuggerScript : public NativeObject {
|
||||
static bool clearAllBreakpoints(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 setInstrumentationId(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool construct(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
template <typename T>
|
||||
@ -93,6 +98,10 @@ class DebuggerScript : public NativeObject {
|
||||
Value* vp, const char* name,
|
||||
bool successor);
|
||||
|
||||
Value getInstrumentationId() const {
|
||||
return getSlot(INSTRUMENTATION_ID_SLOT);
|
||||
}
|
||||
|
||||
private:
|
||||
static const ClassOps classOps_;
|
||||
|
||||
|
@ -581,3 +581,44 @@ of exotic object like an opaque wrapper.
|
||||
<code>forceLexicalInitializationByName(<i>binding</i>)</code>
|
||||
: If <i>binding</i> is in an uninitialized state initialize it to undefined
|
||||
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`.
|
||||
|
@ -345,6 +345,12 @@ methods of other kinds of objects.
|
||||
|
||||
**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
|
||||
|
||||
|
@ -123,6 +123,7 @@ BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent,
|
||||
: BytecodeEmitter(parent, sc, script, lazyScript, lineNum, emitterMode,
|
||||
fieldInitializers) {
|
||||
parser = handle;
|
||||
instrumentationKinds = parser->options().instrumentationKinds;
|
||||
}
|
||||
|
||||
BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent,
|
||||
@ -135,6 +136,7 @@ BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent,
|
||||
fieldInitializers) {
|
||||
ep_.emplace(parser);
|
||||
this->parser = ep_.ptr();
|
||||
instrumentationKinds = this->parser->options().instrumentationKinds;
|
||||
}
|
||||
|
||||
void BytecodeEmitter::initFromBodyPosition(TokenPos bodyPosition) {
|
||||
@ -177,6 +179,10 @@ bool BytecodeEmitter::markStepBreakpoint() {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!emitInstrumentation(InstrumentationKind::Breakpoint)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!newSrcNote(SRC_STEP_SEP)) {
|
||||
return false;
|
||||
}
|
||||
@ -203,6 +209,10 @@ bool BytecodeEmitter::markSimpleBreakpoint() {
|
||||
// having two breakpoints with the same line/column position.
|
||||
// Note: This assumes that the position for the call has already been set.
|
||||
if (!bytecodeSection().isDuplicateLocation()) {
|
||||
if (!emitInstrumentation(InstrumentationKind::Breakpoint)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!newSrcNote(SRC_BREAKPOINT)) {
|
||||
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());
|
||||
}
|
||||
|
||||
bool BytecodeEmitter::emitDupAt(unsigned slotFromTop) {
|
||||
bool BytecodeEmitter::emitDupAt(unsigned slotFromTop, unsigned count) {
|
||||
MOZ_ASSERT(slotFromTop < unsigned(bytecodeSection().stackDepth()));
|
||||
MOZ_ASSERT(slotFromTop + 1 >= count);
|
||||
|
||||
if (slotFromTop == 0) {
|
||||
if (slotFromTop == 0 && count == 1) {
|
||||
return emit1(JSOP_DUP);
|
||||
}
|
||||
|
||||
if (slotFromTop == 1 && count == 2) {
|
||||
return emit1(JSOP_DUP2);
|
||||
}
|
||||
|
||||
if (slotFromTop >= JS_BIT(24)) {
|
||||
reportError(nullptr, JSMSG_TOO_MANY_LOCALS);
|
||||
return false;
|
||||
}
|
||||
|
||||
BytecodeOffset off;
|
||||
if (!emitN(JSOP_DUPAT, 3, &off)) {
|
||||
return false;
|
||||
for (unsigned i = 0; i < count; i++) {
|
||||
BytecodeOffset off;
|
||||
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;
|
||||
}
|
||||
|
||||
@ -891,7 +909,7 @@ bool BytecodeEmitter::emitIndexOp(JSOp op, uint32_t index) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BytecodeEmitter::emitAtomOp(JSAtom* atom, JSOp op) {
|
||||
bool BytecodeEmitter::emitAtomOp(JSAtom* atom, JSOp op, ShouldInstrument shouldInstrument) {
|
||||
MOZ_ASSERT(atom);
|
||||
|
||||
// .generator lookups should be emitted as JSOP_GETALIASEDVAR instead of
|
||||
@ -911,12 +929,18 @@ bool BytecodeEmitter::emitAtomOp(JSAtom* atom, JSOp op) {
|
||||
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);
|
||||
|
||||
if (shouldInstrument != ShouldInstrument::No &&
|
||||
!emitInstrumentationForOpcode(op, atomIndex)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return emitIndexOp(op, atomIndex);
|
||||
}
|
||||
|
||||
@ -1822,7 +1846,7 @@ bool BytecodeEmitter::emitPropLHS(PropertyAccess* prop) {
|
||||
|
||||
while (true) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
@ -1895,7 +1919,13 @@ bool BytecodeEmitter::emitNameIncDec(UnaryNode* incDec) {
|
||||
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)) {
|
||||
return false;
|
||||
}
|
||||
@ -2266,6 +2296,11 @@ bool BytecodeEmitter::allocateResumeIndexRange(
|
||||
}
|
||||
|
||||
bool BytecodeEmitter::emitYieldOp(JSOp op) {
|
||||
// All yield operations pop or suspend the current frame.
|
||||
if (!emitInstrumentation(InstrumentationKind::Exit)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (op == JSOP_FINALYIELDRVAL) {
|
||||
return emit1(JSOP_FINALYIELDRVAL);
|
||||
}
|
||||
@ -2288,6 +2323,10 @@ bool BytecodeEmitter::emitYieldOp(JSOp op) {
|
||||
|
||||
SET_RESUMEINDEX(bytecodeSection().code(off), resumeIndex);
|
||||
|
||||
if (!emitInstrumentation(InstrumentationKind::Entry)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BytecodeOffset unusedOffset;
|
||||
return emitJumpTargetOp(JSOP_AFTERYIELD, &unusedOffset);
|
||||
}
|
||||
@ -2419,7 +2458,9 @@ bool BytecodeEmitter::emitScript(ParseNode* body) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switchToMain();
|
||||
if (!switchToMain()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ParseNode* scopeBody = scope->scopeBody();
|
||||
if (!emitLexicalScopeBody(scopeBody, EMIT_LINENOTE)) {
|
||||
@ -2440,7 +2481,9 @@ bool BytecodeEmitter::emitScript(ParseNode* body) {
|
||||
}
|
||||
}
|
||||
|
||||
switchToMain();
|
||||
if (!switchToMain()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!emitTree(body)) {
|
||||
return false;
|
||||
@ -2454,7 +2497,7 @@ bool BytecodeEmitter::emitScript(ParseNode* body) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!emit1(JSOP_RETRVAL)) {
|
||||
if (!emitReturnRval()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -2954,14 +2997,10 @@ bool BytecodeEmitter::emitIteratorCloseInScope(
|
||||
// [stack] ... RET ITER UNDEF
|
||||
return false;
|
||||
}
|
||||
if (!emitDupAt(2)) {
|
||||
if (!emitDupAt(2, 2)) {
|
||||
// [stack] ... RET ITER UNDEF RET
|
||||
return false;
|
||||
}
|
||||
if (!emitDupAt(2)) {
|
||||
// [stack] ... RET ITER UNDEF RET ITER
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!emitCall(JSOP_CALL, 0)) {
|
||||
@ -2998,15 +3037,11 @@ bool BytecodeEmitter::emitIteratorCloseInScope(
|
||||
}
|
||||
|
||||
if (!tryCatch->emitCatch()) {
|
||||
// [stack] ... RET ITER RESULT
|
||||
// [stack] ... RET ITER RESULT EXC
|
||||
return false;
|
||||
}
|
||||
|
||||
// Just ignore the exception thrown by call and await.
|
||||
if (!emit1(JSOP_EXCEPTION)) {
|
||||
// [stack] ... RET ITER RESULT EXC
|
||||
return false;
|
||||
}
|
||||
if (!emit1(JSOP_POP)) {
|
||||
// [stack] ... RET ITER RESULT
|
||||
return false;
|
||||
@ -3378,14 +3413,10 @@ bool BytecodeEmitter::emitDestructuringOpsArray(ListNode* pattern,
|
||||
|
||||
// If iterator is not completed, create a new array with the rest
|
||||
// of the iterator.
|
||||
if (!emitDupAt(emitted + 1)) {
|
||||
if (!emitDupAt(emitted + 1, 2)) {
|
||||
// [stack] ... OBJ NEXT ITER LREF* NEXT
|
||||
return false;
|
||||
}
|
||||
if (!emitDupAt(emitted + 1)) {
|
||||
// [stack] ... OBJ NEXT ITER LREF* NEXT ITER
|
||||
return false;
|
||||
}
|
||||
if (!emitUint32Operand(JSOP_NEWARRAY, 0)) {
|
||||
// [stack] ... OBJ NEXT ITER LREF* NEXT ITER ARRAY
|
||||
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
|
||||
return false;
|
||||
}
|
||||
if (!emitDupAt(emitted + 1)) {
|
||||
// [stack] ... OBJ NEXT ITER LREF* NEXT ITER
|
||||
return false;
|
||||
}
|
||||
if (!emitIteratorNext(Some(pattern->pn_pos.begin))) {
|
||||
// [stack] ... OBJ NEXT ITER LREF* RESULT
|
||||
return false;
|
||||
@ -3735,7 +3762,8 @@ bool BytecodeEmitter::emitDestructuringOpsObject(ListNode* pattern,
|
||||
}
|
||||
} else if (key->isKind(ParseNodeKind::ObjectPropertyName) ||
|
||||
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
|
||||
return false;
|
||||
}
|
||||
@ -4593,11 +4621,6 @@ bool BytecodeEmitter::emitCatch(BinaryNode* catchClause) {
|
||||
// We must be nested under a try-finally statement.
|
||||
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();
|
||||
if (!param) {
|
||||
// Catch parameter was omitted; just discard the exception.
|
||||
@ -5215,14 +5238,10 @@ bool BytecodeEmitter::emitSpread(bool allowSelfHosted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!emitDupAt(3)) {
|
||||
if (!emitDupAt(3, 2)) {
|
||||
// [stack] NEXT ITER ARR I NEXT
|
||||
return false;
|
||||
}
|
||||
if (!emitDupAt(3)) {
|
||||
// [stack] NEXT ITER ARR I NEXT ITER
|
||||
return false;
|
||||
}
|
||||
if (!emitIteratorNext(Nothing(), IteratorKind::Sync, allowSelfHosted)) {
|
||||
// [stack] ITER ARR I RESULT
|
||||
return false;
|
||||
@ -6073,13 +6092,16 @@ bool BytecodeEmitter::emitReturn(UnaryNode* returnNode) {
|
||||
}
|
||||
} else if (isDerivedClassConstructor) {
|
||||
MOZ_ASSERT(bytecodeSection().code()[top.value()] == JSOP_SETRVAL);
|
||||
if (!emit1(JSOP_RETRVAL)) {
|
||||
if (!emitReturnRval()) {
|
||||
return false;
|
||||
}
|
||||
} 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;
|
||||
if (!emit1(JSOP_RETRVAL)) {
|
||||
if (!emitReturnRval()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -6319,16 +6341,13 @@ bool BytecodeEmitter::emitYieldStar(ParseNode* iter) {
|
||||
}
|
||||
|
||||
if (!tryCatch.emitCatch()) {
|
||||
// [stack] NEXT ITER RESULT
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(bytecodeSection().stackDepth() == startDepth);
|
||||
|
||||
if (!emit1(JSOP_EXCEPTION)) {
|
||||
// [stack] NEXT ITER RESULT EXCEPTION
|
||||
return false;
|
||||
}
|
||||
|
||||
// The exception was already pushed by emitCatch().
|
||||
MOZ_ASSERT(bytecodeSection().stackDepth() == startDepth + 1);
|
||||
|
||||
if (!emitDupAt(2)) {
|
||||
// [stack] NEXT ITER RESULT EXCEPTION ITER
|
||||
return false;
|
||||
@ -8864,6 +8883,140 @@ bool BytecodeEmitter::emitExportDefault(BinaryNode* exportNode) {
|
||||
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(
|
||||
ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::WantValue */,
|
||||
EmitLineNumberNote emitLineNote /* = EMIT_LINENOTE */) {
|
||||
|
@ -40,6 +40,7 @@
|
||||
#include "js/TypeDecls.h" // jsbytecode
|
||||
#include "vm/BigIntType.h" // BigInt
|
||||
#include "vm/BytecodeUtil.h" // JSOp
|
||||
#include "vm/Instrumentation.h" // InstrumentationKind
|
||||
#include "vm/Interpreter.h" // CheckIsObjectKind, CheckIsCallableKind
|
||||
#include "vm/Iteration.h" // IteratorKind
|
||||
#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.
|
||||
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"
|
||||
* 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(); }
|
||||
|
||||
void switchToMain() {
|
||||
MOZ_MUST_USE bool switchToMain() {
|
||||
MOZ_ASSERT(inPrologue());
|
||||
mainOffset_.emplace(bytecodeSection().code().length());
|
||||
|
||||
return emitInstrumentation(InstrumentationKind::Main);
|
||||
}
|
||||
|
||||
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.
|
||||
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
|
||||
// JS stack, as measured from the top.
|
||||
MOZ_MUST_USE bool emitDupAt(unsigned slotFromTop);
|
||||
// Helper to duplicate one or more stack values. |slotFromTop| is the value's
|
||||
// depth on the JS stack, as measured from the top. |count| is the number of
|
||||
// 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.
|
||||
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 emitIndexOp(JSOp op, uint32_t index);
|
||||
|
||||
MOZ_MUST_USE bool emitAtomOp(JSAtom* atom, JSOp op);
|
||||
MOZ_MUST_USE bool emitAtomOp(uint32_t atomIndex, JSOp op);
|
||||
MOZ_MUST_USE bool emitAtomOp(
|
||||
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 emitArray(ParseNode* arrayHead, uint32_t count);
|
||||
@ -579,7 +591,8 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
|
||||
|
||||
MOZ_MUST_USE bool emitElemObjAndKey(PropertyByValue* elem, bool isSuper,
|
||||
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 emitElemIncDec(UnaryNode* incDec);
|
||||
|
||||
@ -779,6 +792,29 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
|
||||
MOZ_MUST_USE bool emitPipeline(ListNode* node);
|
||||
|
||||
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 {
|
||||
|
@ -70,17 +70,7 @@ bool ElemOpEmitter::emitGet() {
|
||||
}
|
||||
if (isIncDec() || isCompoundAssignment()) {
|
||||
if (isSuper()) {
|
||||
// There's no such thing as JSOP_DUP3, so we have to be creative.
|
||||
// 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)) {
|
||||
if (!bce_->emitDupAt(2, 3)) {
|
||||
// [stack] THIS KEY SUPERBASE THIS KEY SUPERBASE
|
||||
return false;
|
||||
}
|
||||
@ -100,7 +90,7 @@ bool ElemOpEmitter::emitGet() {
|
||||
} else {
|
||||
op = JSOP_GETELEM;
|
||||
}
|
||||
if (!bce_->emitElemOpBase(op)) {
|
||||
if (!bce_->emitElemOpBase(op, ShouldInstrument::Yes)) {
|
||||
// [stack] # if Get
|
||||
// [stack] ELEM
|
||||
// [stack] # if Call
|
||||
@ -221,7 +211,7 @@ bool ElemOpEmitter::emitAssignment(EmitSetFunctionName emitSetFunName) {
|
||||
: isSuper() ? bce_->sc->strict() ? JSOP_STRICTSETELEM_SUPER
|
||||
: JSOP_SETELEM_SUPER
|
||||
: bce_->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM;
|
||||
if (!bce_->emitElemOpBase(setOp)) {
|
||||
if (!bce_->emitElemOpBase(setOp, ShouldInstrument::Yes)) {
|
||||
// [stack] ELEM
|
||||
return false;
|
||||
}
|
||||
@ -300,7 +290,7 @@ bool ElemOpEmitter::emitIncDec() {
|
||||
isSuper()
|
||||
? (bce_->sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER)
|
||||
: (bce_->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM);
|
||||
if (!bce_->emitElemOpBase(setOp)) {
|
||||
if (!bce_->emitElemOpBase(setOp, ShouldInstrument::Yes)) {
|
||||
// [stack] N? N+1
|
||||
return false;
|
||||
}
|
||||
|
@ -41,14 +41,10 @@ bool ForOfLoopControl::emitBeginCodeNeedingIteratorClose(BytecodeEmitter* bce) {
|
||||
|
||||
bool ForOfLoopControl::emitEndCodeNeedingIteratorClose(BytecodeEmitter* bce) {
|
||||
if (!tryCatch_->emitCatch()) {
|
||||
// [stack] ITER ...
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bce->emit1(JSOP_EXCEPTION)) {
|
||||
// [stack] ITER ... EXCEPTION
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned slotFromTop = bce->bytecodeSection().stackDepth() - iterDepth_;
|
||||
if (!bce->emitDupAt(slotFromTop)) {
|
||||
// [stack] ITER ... EXCEPTION ITER
|
||||
|
@ -425,7 +425,9 @@ bool FunctionScriptEmitter::prepareForParameters() {
|
||||
// parameter exprs, any unobservable environment ops (like pushing the
|
||||
// call object, setting '.this', etc) need to go in the prologue, else it
|
||||
// messes up breakpoint tests.
|
||||
bce_->switchToMain();
|
||||
if (!bce_->switchToMain()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!functionEmitterScope_->enterFunction(bce_, funbox_)) {
|
||||
@ -438,7 +440,9 @@ bool FunctionScriptEmitter::prepareForParameters() {
|
||||
}
|
||||
|
||||
if (!funbox_->hasParameterExprs) {
|
||||
bce_->switchToMain();
|
||||
if (!bce_->switchToMain()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Parameters can't reuse the reject try-catch block from the function body,
|
||||
@ -503,13 +507,10 @@ bool FunctionScriptEmitter::emitAsyncFunctionRejectPrologue() {
|
||||
|
||||
bool FunctionScriptEmitter::emitAsyncFunctionRejectEpilogue() {
|
||||
if (!rejectTryCatch_->emitCatch()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bce_->emit1(JSOP_EXCEPTION)) {
|
||||
// [stack] EXC
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bce_->emitGetDotGeneratorInInnermostScope()) {
|
||||
// [stack] EXC GEN
|
||||
return false;
|
||||
@ -527,7 +528,7 @@ bool FunctionScriptEmitter::emitAsyncFunctionRejectEpilogue() {
|
||||
// [stack] GEN
|
||||
return false;
|
||||
}
|
||||
if (!bce_->emit1(JSOP_FINALYIELDRVAL)) {
|
||||
if (!bce_->emitYieldOp(JSOP_FINALYIELDRVAL)) {
|
||||
// [stack]
|
||||
return false;
|
||||
}
|
||||
@ -723,7 +724,7 @@ bool FunctionScriptEmitter::emitEndBody() {
|
||||
// Always end the script with a JSOP_RETRVAL. Some other parts of the
|
||||
// codebase depend on this opcode,
|
||||
// e.g. InterpreterRegs::setToEndOfScript.
|
||||
if (!bce_->emit1(JSOP_RETRVAL)) {
|
||||
if (!bce_->emitReturnRval()) {
|
||||
// [stack]
|
||||
return false;
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ bool PropOpEmitter::emitGet(JSAtom* prop) {
|
||||
} else {
|
||||
op = isLength_ ? JSOP_LENGTH : JSOP_GETPROP;
|
||||
}
|
||||
if (!bce_->emitAtomOp(propAtomIndex_, op)) {
|
||||
if (!bce_->emitAtomOp(propAtomIndex_, op, ShouldInstrument::Yes)) {
|
||||
// [stack] # if Get
|
||||
// [stack] PROP
|
||||
// [stack] # if Call
|
||||
@ -190,7 +190,7 @@ bool PropOpEmitter::emitAssignment(JSAtom* prop) {
|
||||
: isSuper() ? bce_->sc->strict() ? JSOP_STRICTSETPROP_SUPER
|
||||
: JSOP_SETPROP_SUPER
|
||||
: bce_->sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP;
|
||||
if (!bce_->emitAtomOp(propAtomIndex_, setOp)) {
|
||||
if (!bce_->emitAtomOp(propAtomIndex_, setOp, ShouldInstrument::Yes)) {
|
||||
// [stack] VAL
|
||||
return false;
|
||||
}
|
||||
@ -263,7 +263,7 @@ bool PropOpEmitter::emitIncDec(JSAtom* prop) {
|
||||
isSuper()
|
||||
? bce_->sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER
|
||||
: bce_->sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP;
|
||||
if (!bce_->emitAtomOp(propAtomIndex_, setOp)) {
|
||||
if (!bce_->emitAtomOp(propAtomIndex_, setOp, ShouldInstrument::Yes)) {
|
||||
// [stack] N? N+1
|
||||
return false;
|
||||
}
|
||||
|
@ -124,6 +124,14 @@ bool TryEmitter::emitCatch() {
|
||||
}
|
||||
}
|
||||
|
||||
if (!bce_->emit1(JSOP_EXCEPTION)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!instrumentEntryPoint()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
state_ = State::Catch;
|
||||
#endif
|
||||
@ -221,6 +229,10 @@ bool TryEmitter::emitFinally(
|
||||
}
|
||||
}
|
||||
|
||||
if (!instrumentEntryPoint()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
state_ = State::Finally;
|
||||
#endif
|
||||
@ -293,3 +305,15 @@ bool TryEmitter::emitEnd() {
|
||||
#endif
|
||||
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;
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ struct BytecodeEmitter;
|
||||
// tryCatch.emitTry();
|
||||
// emit(try_block);
|
||||
// tryCatch.emitCatch();
|
||||
// emit(ex and catch_block); // use JSOP_EXCEPTION to get exception
|
||||
// emit(catch_block); // Pending exception is on stack
|
||||
// tryCatch.emitEnd();
|
||||
//
|
||||
// `try { try_block } finally { finally_block }`
|
||||
@ -50,7 +50,7 @@ struct BytecodeEmitter;
|
||||
// tryCatch.emitTry();
|
||||
// emit(try_block);
|
||||
// tryCatch.emitCatch();
|
||||
// emit(ex and catch_block);
|
||||
// emit(catch_block);
|
||||
// tryCatch.emitFinally(Some(finally_pos));
|
||||
// emit(finally_block);
|
||||
// tryCatch.emitEnd();
|
||||
@ -211,6 +211,7 @@ class MOZ_STACK_CLASS TryEmitter {
|
||||
MOZ_MUST_USE bool emitTryEnd();
|
||||
MOZ_MUST_USE bool emitCatchEnd();
|
||||
MOZ_MUST_USE bool emitFinallyEnd();
|
||||
MOZ_MUST_USE bool instrumentEntryPoint();
|
||||
};
|
||||
|
||||
} /* namespace frontend */
|
||||
|
@ -136,7 +136,8 @@ enum class ZealMode {
|
||||
_(DebuggerFrameIterData) \
|
||||
_(DebuggerOnStepHandler) \
|
||||
_(DebuggerOnPopHandler) \
|
||||
_(GlobalDebuggerVector)
|
||||
_(GlobalDebuggerVector) \
|
||||
_(RealmInstrumentation)
|
||||
|
||||
#define JS_FOR_EACH_MEMORY_USE(_) \
|
||||
JS_FOR_EACH_PUBLIC_MEMORY_USE(_) \
|
||||
|
196
js/src/jit-test/tests/debug/Debugger-setInstrumentation-01.js
Normal file
196
js/src/jit-test/tests/debug/Debugger-setInstrumentation-01.js
Normal 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]);
|
127
js/src/jit-test/tests/debug/Debugger-setInstrumentation-02.js
Normal file
127
js/src/jit-test/tests/debug/Debugger-setInstrumentation-02.js
Normal 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"
|
||||
]);
|
@ -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]);
|
@ -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);
|
@ -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");
|
105
js/src/jit-test/tests/debug/Debugger-setInstrumentation-06.js
Normal file
105
js/src/jit-test/tests/debug/Debugger-setInstrumentation-06.js
Normal 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]), []);
|
@ -6724,6 +6724,83 @@ bool BaselineCodeGen<Handler>::emit_JSOP_DYNAMIC_IMPORT() {
|
||||
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>
|
||||
bool BaselineCodeGen<Handler>::emitPrologue() {
|
||||
#ifdef JS_USE_LINK_REGISTER
|
||||
|
@ -19,242 +19,245 @@ enum class GeneratorResumeKind;
|
||||
|
||||
namespace jit {
|
||||
|
||||
#define OPCODE_LIST(_) \
|
||||
_(JSOP_NOP) \
|
||||
_(JSOP_NOP_DESTRUCTURING) \
|
||||
_(JSOP_LABEL) \
|
||||
_(JSOP_ITERNEXT) \
|
||||
_(JSOP_POP) \
|
||||
_(JSOP_POPN) \
|
||||
_(JSOP_DUPAT) \
|
||||
_(JSOP_ENTERWITH) \
|
||||
_(JSOP_LEAVEWITH) \
|
||||
_(JSOP_DUP) \
|
||||
_(JSOP_DUP2) \
|
||||
_(JSOP_SWAP) \
|
||||
_(JSOP_PICK) \
|
||||
_(JSOP_UNPICK) \
|
||||
_(JSOP_GOTO) \
|
||||
_(JSOP_IFEQ) \
|
||||
_(JSOP_IFNE) \
|
||||
_(JSOP_AND) \
|
||||
_(JSOP_OR) \
|
||||
_(JSOP_NOT) \
|
||||
_(JSOP_POS) \
|
||||
_(JSOP_TONUMERIC) \
|
||||
_(JSOP_LOOPHEAD) \
|
||||
_(JSOP_LOOPENTRY) \
|
||||
_(JSOP_VOID) \
|
||||
_(JSOP_UNDEFINED) \
|
||||
_(JSOP_HOLE) \
|
||||
_(JSOP_NULL) \
|
||||
_(JSOP_TRUE) \
|
||||
_(JSOP_FALSE) \
|
||||
_(JSOP_ZERO) \
|
||||
_(JSOP_ONE) \
|
||||
_(JSOP_INT8) \
|
||||
_(JSOP_INT32) \
|
||||
_(JSOP_UINT16) \
|
||||
_(JSOP_UINT24) \
|
||||
_(JSOP_RESUMEINDEX) \
|
||||
_(JSOP_DOUBLE) \
|
||||
_(JSOP_BIGINT) \
|
||||
_(JSOP_STRING) \
|
||||
_(JSOP_SYMBOL) \
|
||||
_(JSOP_OBJECT) \
|
||||
_(JSOP_CALLSITEOBJ) \
|
||||
_(JSOP_REGEXP) \
|
||||
_(JSOP_LAMBDA) \
|
||||
_(JSOP_LAMBDA_ARROW) \
|
||||
_(JSOP_SETFUNNAME) \
|
||||
_(JSOP_BITOR) \
|
||||
_(JSOP_BITXOR) \
|
||||
_(JSOP_BITAND) \
|
||||
_(JSOP_LSH) \
|
||||
_(JSOP_RSH) \
|
||||
_(JSOP_URSH) \
|
||||
_(JSOP_ADD) \
|
||||
_(JSOP_SUB) \
|
||||
_(JSOP_MUL) \
|
||||
_(JSOP_DIV) \
|
||||
_(JSOP_MOD) \
|
||||
_(JSOP_POW) \
|
||||
_(JSOP_LT) \
|
||||
_(JSOP_LE) \
|
||||
_(JSOP_GT) \
|
||||
_(JSOP_GE) \
|
||||
_(JSOP_EQ) \
|
||||
_(JSOP_NE) \
|
||||
_(JSOP_STRICTEQ) \
|
||||
_(JSOP_STRICTNE) \
|
||||
_(JSOP_CONDSWITCH) \
|
||||
_(JSOP_CASE) \
|
||||
_(JSOP_DEFAULT) \
|
||||
_(JSOP_LINENO) \
|
||||
_(JSOP_BITNOT) \
|
||||
_(JSOP_NEG) \
|
||||
_(JSOP_NEWARRAY) \
|
||||
_(JSOP_NEWARRAY_COPYONWRITE) \
|
||||
_(JSOP_INITELEM_ARRAY) \
|
||||
_(JSOP_NEWOBJECT) \
|
||||
_(JSOP_NEWINIT) \
|
||||
_(JSOP_INITELEM) \
|
||||
_(JSOP_INITELEM_GETTER) \
|
||||
_(JSOP_INITELEM_SETTER) \
|
||||
_(JSOP_INITELEM_INC) \
|
||||
_(JSOP_MUTATEPROTO) \
|
||||
_(JSOP_INITPROP) \
|
||||
_(JSOP_INITLOCKEDPROP) \
|
||||
_(JSOP_INITHIDDENPROP) \
|
||||
_(JSOP_INITPROP_GETTER) \
|
||||
_(JSOP_INITPROP_SETTER) \
|
||||
_(JSOP_GETELEM) \
|
||||
_(JSOP_SETELEM) \
|
||||
_(JSOP_STRICTSETELEM) \
|
||||
_(JSOP_CALLELEM) \
|
||||
_(JSOP_DELELEM) \
|
||||
_(JSOP_STRICTDELELEM) \
|
||||
_(JSOP_GETELEM_SUPER) \
|
||||
_(JSOP_SETELEM_SUPER) \
|
||||
_(JSOP_STRICTSETELEM_SUPER) \
|
||||
_(JSOP_IN) \
|
||||
_(JSOP_HASOWN) \
|
||||
_(JSOP_GETGNAME) \
|
||||
_(JSOP_BINDGNAME) \
|
||||
_(JSOP_SETGNAME) \
|
||||
_(JSOP_STRICTSETGNAME) \
|
||||
_(JSOP_SETNAME) \
|
||||
_(JSOP_STRICTSETNAME) \
|
||||
_(JSOP_GETPROP) \
|
||||
_(JSOP_SETPROP) \
|
||||
_(JSOP_STRICTSETPROP) \
|
||||
_(JSOP_CALLPROP) \
|
||||
_(JSOP_DELPROP) \
|
||||
_(JSOP_STRICTDELPROP) \
|
||||
_(JSOP_GETPROP_SUPER) \
|
||||
_(JSOP_SETPROP_SUPER) \
|
||||
_(JSOP_STRICTSETPROP_SUPER) \
|
||||
_(JSOP_LENGTH) \
|
||||
_(JSOP_GETBOUNDNAME) \
|
||||
_(JSOP_GETALIASEDVAR) \
|
||||
_(JSOP_SETALIASEDVAR) \
|
||||
_(JSOP_GETNAME) \
|
||||
_(JSOP_BINDNAME) \
|
||||
_(JSOP_DELNAME) \
|
||||
_(JSOP_GETIMPORT) \
|
||||
_(JSOP_GETINTRINSIC) \
|
||||
_(JSOP_SETINTRINSIC) \
|
||||
_(JSOP_BINDVAR) \
|
||||
_(JSOP_DEFVAR) \
|
||||
_(JSOP_DEFCONST) \
|
||||
_(JSOP_DEFLET) \
|
||||
_(JSOP_DEFFUN) \
|
||||
_(JSOP_GETLOCAL) \
|
||||
_(JSOP_SETLOCAL) \
|
||||
_(JSOP_GETARG) \
|
||||
_(JSOP_SETARG) \
|
||||
_(JSOP_CHECKLEXICAL) \
|
||||
_(JSOP_INITLEXICAL) \
|
||||
_(JSOP_INITGLEXICAL) \
|
||||
_(JSOP_CHECKALIASEDLEXICAL) \
|
||||
_(JSOP_INITALIASEDLEXICAL) \
|
||||
_(JSOP_UNINITIALIZED) \
|
||||
_(JSOP_CALL) \
|
||||
_(JSOP_CALL_IGNORES_RV) \
|
||||
_(JSOP_CALLITER) \
|
||||
_(JSOP_FUNCALL) \
|
||||
_(JSOP_FUNAPPLY) \
|
||||
_(JSOP_NEW) \
|
||||
_(JSOP_EVAL) \
|
||||
_(JSOP_STRICTEVAL) \
|
||||
_(JSOP_SPREADCALL) \
|
||||
_(JSOP_SPREADNEW) \
|
||||
_(JSOP_SPREADEVAL) \
|
||||
_(JSOP_STRICTSPREADEVAL) \
|
||||
_(JSOP_OPTIMIZE_SPREADCALL) \
|
||||
_(JSOP_IMPLICITTHIS) \
|
||||
_(JSOP_GIMPLICITTHIS) \
|
||||
_(JSOP_INSTANCEOF) \
|
||||
_(JSOP_TYPEOF) \
|
||||
_(JSOP_TYPEOFEXPR) \
|
||||
_(JSOP_THROWMSG) \
|
||||
_(JSOP_THROW) \
|
||||
_(JSOP_TRY) \
|
||||
_(JSOP_FINALLY) \
|
||||
_(JSOP_GOSUB) \
|
||||
_(JSOP_RETSUB) \
|
||||
_(JSOP_PUSHLEXICALENV) \
|
||||
_(JSOP_POPLEXICALENV) \
|
||||
_(JSOP_FRESHENLEXICALENV) \
|
||||
_(JSOP_RECREATELEXICALENV) \
|
||||
_(JSOP_DEBUGLEAVELEXICALENV) \
|
||||
_(JSOP_PUSHVARENV) \
|
||||
_(JSOP_POPVARENV) \
|
||||
_(JSOP_EXCEPTION) \
|
||||
_(JSOP_DEBUGGER) \
|
||||
_(JSOP_ARGUMENTS) \
|
||||
_(JSOP_REST) \
|
||||
_(JSOP_TOASYNCITER) \
|
||||
_(JSOP_TOID) \
|
||||
_(JSOP_TOSTRING) \
|
||||
_(JSOP_TABLESWITCH) \
|
||||
_(JSOP_ITER) \
|
||||
_(JSOP_MOREITER) \
|
||||
_(JSOP_ISNOITER) \
|
||||
_(JSOP_ENDITER) \
|
||||
_(JSOP_ISGENCLOSING) \
|
||||
_(JSOP_GENERATOR) \
|
||||
_(JSOP_INITIALYIELD) \
|
||||
_(JSOP_YIELD) \
|
||||
_(JSOP_AWAIT) \
|
||||
_(JSOP_TRYSKIPAWAIT) \
|
||||
_(JSOP_AFTERYIELD) \
|
||||
_(JSOP_FINALYIELDRVAL) \
|
||||
_(JSOP_RESUME) \
|
||||
_(JSOP_ASYNCAWAIT) \
|
||||
_(JSOP_ASYNCRESOLVE) \
|
||||
_(JSOP_CALLEE) \
|
||||
_(JSOP_ENVCALLEE) \
|
||||
_(JSOP_SUPERBASE) \
|
||||
_(JSOP_SUPERFUN) \
|
||||
_(JSOP_GETRVAL) \
|
||||
_(JSOP_SETRVAL) \
|
||||
_(JSOP_RETRVAL) \
|
||||
_(JSOP_RETURN) \
|
||||
_(JSOP_FUNCTIONTHIS) \
|
||||
_(JSOP_GLOBALTHIS) \
|
||||
_(JSOP_CHECKISOBJ) \
|
||||
_(JSOP_CHECKISCALLABLE) \
|
||||
_(JSOP_CHECKTHIS) \
|
||||
_(JSOP_CHECKTHISREINIT) \
|
||||
_(JSOP_CHECKRETURN) \
|
||||
_(JSOP_NEWTARGET) \
|
||||
_(JSOP_SUPERCALL) \
|
||||
_(JSOP_SPREADSUPERCALL) \
|
||||
_(JSOP_THROWSETCONST) \
|
||||
_(JSOP_THROWSETALIASEDCONST) \
|
||||
_(JSOP_THROWSETCALLEE) \
|
||||
_(JSOP_INITHIDDENPROP_GETTER) \
|
||||
_(JSOP_INITHIDDENPROP_SETTER) \
|
||||
_(JSOP_INITHIDDENELEM) \
|
||||
_(JSOP_INITHIDDENELEM_GETTER) \
|
||||
_(JSOP_INITHIDDENELEM_SETTER) \
|
||||
_(JSOP_CHECKOBJCOERCIBLE) \
|
||||
_(JSOP_DEBUGCHECKSELFHOSTED) \
|
||||
_(JSOP_JUMPTARGET) \
|
||||
_(JSOP_IS_CONSTRUCTING) \
|
||||
_(JSOP_TRY_DESTRUCTURING) \
|
||||
_(JSOP_CHECKCLASSHERITAGE) \
|
||||
_(JSOP_INITHOMEOBJECT) \
|
||||
_(JSOP_BUILTINPROTO) \
|
||||
_(JSOP_OBJWITHPROTO) \
|
||||
_(JSOP_FUNWITHPROTO) \
|
||||
_(JSOP_CLASSCONSTRUCTOR) \
|
||||
_(JSOP_DERIVEDCONSTRUCTOR) \
|
||||
_(JSOP_IMPORTMETA) \
|
||||
_(JSOP_DYNAMIC_IMPORT) \
|
||||
_(JSOP_INC) \
|
||||
_(JSOP_DEC)
|
||||
#define OPCODE_LIST(_) \
|
||||
_(JSOP_NOP) \
|
||||
_(JSOP_NOP_DESTRUCTURING) \
|
||||
_(JSOP_LABEL) \
|
||||
_(JSOP_ITERNEXT) \
|
||||
_(JSOP_POP) \
|
||||
_(JSOP_POPN) \
|
||||
_(JSOP_DUPAT) \
|
||||
_(JSOP_ENTERWITH) \
|
||||
_(JSOP_LEAVEWITH) \
|
||||
_(JSOP_DUP) \
|
||||
_(JSOP_DUP2) \
|
||||
_(JSOP_SWAP) \
|
||||
_(JSOP_PICK) \
|
||||
_(JSOP_UNPICK) \
|
||||
_(JSOP_GOTO) \
|
||||
_(JSOP_IFEQ) \
|
||||
_(JSOP_IFNE) \
|
||||
_(JSOP_AND) \
|
||||
_(JSOP_OR) \
|
||||
_(JSOP_NOT) \
|
||||
_(JSOP_POS) \
|
||||
_(JSOP_TONUMERIC) \
|
||||
_(JSOP_LOOPHEAD) \
|
||||
_(JSOP_LOOPENTRY) \
|
||||
_(JSOP_VOID) \
|
||||
_(JSOP_UNDEFINED) \
|
||||
_(JSOP_HOLE) \
|
||||
_(JSOP_NULL) \
|
||||
_(JSOP_TRUE) \
|
||||
_(JSOP_FALSE) \
|
||||
_(JSOP_ZERO) \
|
||||
_(JSOP_ONE) \
|
||||
_(JSOP_INT8) \
|
||||
_(JSOP_INT32) \
|
||||
_(JSOP_UINT16) \
|
||||
_(JSOP_UINT24) \
|
||||
_(JSOP_RESUMEINDEX) \
|
||||
_(JSOP_DOUBLE) \
|
||||
_(JSOP_BIGINT) \
|
||||
_(JSOP_STRING) \
|
||||
_(JSOP_SYMBOL) \
|
||||
_(JSOP_OBJECT) \
|
||||
_(JSOP_CALLSITEOBJ) \
|
||||
_(JSOP_REGEXP) \
|
||||
_(JSOP_LAMBDA) \
|
||||
_(JSOP_LAMBDA_ARROW) \
|
||||
_(JSOP_SETFUNNAME) \
|
||||
_(JSOP_BITOR) \
|
||||
_(JSOP_BITXOR) \
|
||||
_(JSOP_BITAND) \
|
||||
_(JSOP_LSH) \
|
||||
_(JSOP_RSH) \
|
||||
_(JSOP_URSH) \
|
||||
_(JSOP_ADD) \
|
||||
_(JSOP_SUB) \
|
||||
_(JSOP_MUL) \
|
||||
_(JSOP_DIV) \
|
||||
_(JSOP_MOD) \
|
||||
_(JSOP_POW) \
|
||||
_(JSOP_LT) \
|
||||
_(JSOP_LE) \
|
||||
_(JSOP_GT) \
|
||||
_(JSOP_GE) \
|
||||
_(JSOP_EQ) \
|
||||
_(JSOP_NE) \
|
||||
_(JSOP_STRICTEQ) \
|
||||
_(JSOP_STRICTNE) \
|
||||
_(JSOP_CONDSWITCH) \
|
||||
_(JSOP_CASE) \
|
||||
_(JSOP_DEFAULT) \
|
||||
_(JSOP_LINENO) \
|
||||
_(JSOP_BITNOT) \
|
||||
_(JSOP_NEG) \
|
||||
_(JSOP_NEWARRAY) \
|
||||
_(JSOP_NEWARRAY_COPYONWRITE) \
|
||||
_(JSOP_INITELEM_ARRAY) \
|
||||
_(JSOP_NEWOBJECT) \
|
||||
_(JSOP_NEWINIT) \
|
||||
_(JSOP_INITELEM) \
|
||||
_(JSOP_INITELEM_GETTER) \
|
||||
_(JSOP_INITELEM_SETTER) \
|
||||
_(JSOP_INITELEM_INC) \
|
||||
_(JSOP_MUTATEPROTO) \
|
||||
_(JSOP_INITPROP) \
|
||||
_(JSOP_INITLOCKEDPROP) \
|
||||
_(JSOP_INITHIDDENPROP) \
|
||||
_(JSOP_INITPROP_GETTER) \
|
||||
_(JSOP_INITPROP_SETTER) \
|
||||
_(JSOP_GETELEM) \
|
||||
_(JSOP_SETELEM) \
|
||||
_(JSOP_STRICTSETELEM) \
|
||||
_(JSOP_CALLELEM) \
|
||||
_(JSOP_DELELEM) \
|
||||
_(JSOP_STRICTDELELEM) \
|
||||
_(JSOP_GETELEM_SUPER) \
|
||||
_(JSOP_SETELEM_SUPER) \
|
||||
_(JSOP_STRICTSETELEM_SUPER) \
|
||||
_(JSOP_IN) \
|
||||
_(JSOP_HASOWN) \
|
||||
_(JSOP_GETGNAME) \
|
||||
_(JSOP_BINDGNAME) \
|
||||
_(JSOP_SETGNAME) \
|
||||
_(JSOP_STRICTSETGNAME) \
|
||||
_(JSOP_SETNAME) \
|
||||
_(JSOP_STRICTSETNAME) \
|
||||
_(JSOP_GETPROP) \
|
||||
_(JSOP_SETPROP) \
|
||||
_(JSOP_STRICTSETPROP) \
|
||||
_(JSOP_CALLPROP) \
|
||||
_(JSOP_DELPROP) \
|
||||
_(JSOP_STRICTDELPROP) \
|
||||
_(JSOP_GETPROP_SUPER) \
|
||||
_(JSOP_SETPROP_SUPER) \
|
||||
_(JSOP_STRICTSETPROP_SUPER) \
|
||||
_(JSOP_LENGTH) \
|
||||
_(JSOP_GETBOUNDNAME) \
|
||||
_(JSOP_GETALIASEDVAR) \
|
||||
_(JSOP_SETALIASEDVAR) \
|
||||
_(JSOP_GETNAME) \
|
||||
_(JSOP_BINDNAME) \
|
||||
_(JSOP_DELNAME) \
|
||||
_(JSOP_GETIMPORT) \
|
||||
_(JSOP_GETINTRINSIC) \
|
||||
_(JSOP_SETINTRINSIC) \
|
||||
_(JSOP_BINDVAR) \
|
||||
_(JSOP_DEFVAR) \
|
||||
_(JSOP_DEFCONST) \
|
||||
_(JSOP_DEFLET) \
|
||||
_(JSOP_DEFFUN) \
|
||||
_(JSOP_GETLOCAL) \
|
||||
_(JSOP_SETLOCAL) \
|
||||
_(JSOP_GETARG) \
|
||||
_(JSOP_SETARG) \
|
||||
_(JSOP_CHECKLEXICAL) \
|
||||
_(JSOP_INITLEXICAL) \
|
||||
_(JSOP_INITGLEXICAL) \
|
||||
_(JSOP_CHECKALIASEDLEXICAL) \
|
||||
_(JSOP_INITALIASEDLEXICAL) \
|
||||
_(JSOP_UNINITIALIZED) \
|
||||
_(JSOP_CALL) \
|
||||
_(JSOP_CALL_IGNORES_RV) \
|
||||
_(JSOP_CALLITER) \
|
||||
_(JSOP_FUNCALL) \
|
||||
_(JSOP_FUNAPPLY) \
|
||||
_(JSOP_NEW) \
|
||||
_(JSOP_EVAL) \
|
||||
_(JSOP_STRICTEVAL) \
|
||||
_(JSOP_SPREADCALL) \
|
||||
_(JSOP_SPREADNEW) \
|
||||
_(JSOP_SPREADEVAL) \
|
||||
_(JSOP_STRICTSPREADEVAL) \
|
||||
_(JSOP_OPTIMIZE_SPREADCALL) \
|
||||
_(JSOP_IMPLICITTHIS) \
|
||||
_(JSOP_GIMPLICITTHIS) \
|
||||
_(JSOP_INSTANCEOF) \
|
||||
_(JSOP_TYPEOF) \
|
||||
_(JSOP_TYPEOFEXPR) \
|
||||
_(JSOP_THROWMSG) \
|
||||
_(JSOP_THROW) \
|
||||
_(JSOP_TRY) \
|
||||
_(JSOP_FINALLY) \
|
||||
_(JSOP_GOSUB) \
|
||||
_(JSOP_RETSUB) \
|
||||
_(JSOP_PUSHLEXICALENV) \
|
||||
_(JSOP_POPLEXICALENV) \
|
||||
_(JSOP_FRESHENLEXICALENV) \
|
||||
_(JSOP_RECREATELEXICALENV) \
|
||||
_(JSOP_DEBUGLEAVELEXICALENV) \
|
||||
_(JSOP_PUSHVARENV) \
|
||||
_(JSOP_POPVARENV) \
|
||||
_(JSOP_EXCEPTION) \
|
||||
_(JSOP_DEBUGGER) \
|
||||
_(JSOP_ARGUMENTS) \
|
||||
_(JSOP_REST) \
|
||||
_(JSOP_TOASYNCITER) \
|
||||
_(JSOP_TOID) \
|
||||
_(JSOP_TOSTRING) \
|
||||
_(JSOP_TABLESWITCH) \
|
||||
_(JSOP_ITER) \
|
||||
_(JSOP_MOREITER) \
|
||||
_(JSOP_ISNOITER) \
|
||||
_(JSOP_ENDITER) \
|
||||
_(JSOP_ISGENCLOSING) \
|
||||
_(JSOP_GENERATOR) \
|
||||
_(JSOP_INITIALYIELD) \
|
||||
_(JSOP_YIELD) \
|
||||
_(JSOP_AWAIT) \
|
||||
_(JSOP_TRYSKIPAWAIT) \
|
||||
_(JSOP_AFTERYIELD) \
|
||||
_(JSOP_FINALYIELDRVAL) \
|
||||
_(JSOP_RESUME) \
|
||||
_(JSOP_ASYNCAWAIT) \
|
||||
_(JSOP_ASYNCRESOLVE) \
|
||||
_(JSOP_CALLEE) \
|
||||
_(JSOP_ENVCALLEE) \
|
||||
_(JSOP_SUPERBASE) \
|
||||
_(JSOP_SUPERFUN) \
|
||||
_(JSOP_GETRVAL) \
|
||||
_(JSOP_SETRVAL) \
|
||||
_(JSOP_RETRVAL) \
|
||||
_(JSOP_RETURN) \
|
||||
_(JSOP_FUNCTIONTHIS) \
|
||||
_(JSOP_GLOBALTHIS) \
|
||||
_(JSOP_CHECKISOBJ) \
|
||||
_(JSOP_CHECKISCALLABLE) \
|
||||
_(JSOP_CHECKTHIS) \
|
||||
_(JSOP_CHECKTHISREINIT) \
|
||||
_(JSOP_CHECKRETURN) \
|
||||
_(JSOP_NEWTARGET) \
|
||||
_(JSOP_SUPERCALL) \
|
||||
_(JSOP_SPREADSUPERCALL) \
|
||||
_(JSOP_THROWSETCONST) \
|
||||
_(JSOP_THROWSETALIASEDCONST) \
|
||||
_(JSOP_THROWSETCALLEE) \
|
||||
_(JSOP_INITHIDDENPROP_GETTER) \
|
||||
_(JSOP_INITHIDDENPROP_SETTER) \
|
||||
_(JSOP_INITHIDDENELEM) \
|
||||
_(JSOP_INITHIDDENELEM_GETTER) \
|
||||
_(JSOP_INITHIDDENELEM_SETTER) \
|
||||
_(JSOP_CHECKOBJCOERCIBLE) \
|
||||
_(JSOP_DEBUGCHECKSELFHOSTED) \
|
||||
_(JSOP_JUMPTARGET) \
|
||||
_(JSOP_IS_CONSTRUCTING) \
|
||||
_(JSOP_TRY_DESTRUCTURING) \
|
||||
_(JSOP_CHECKCLASSHERITAGE) \
|
||||
_(JSOP_INITHOMEOBJECT) \
|
||||
_(JSOP_BUILTINPROTO) \
|
||||
_(JSOP_OBJWITHPROTO) \
|
||||
_(JSOP_FUNWITHPROTO) \
|
||||
_(JSOP_CLASSCONSTRUCTOR) \
|
||||
_(JSOP_DERIVEDCONSTRUCTOR) \
|
||||
_(JSOP_IMPORTMETA) \
|
||||
_(JSOP_DYNAMIC_IMPORT) \
|
||||
_(JSOP_INC) \
|
||||
_(JSOP_DEC) \
|
||||
_(JSOP_INSTRUMENTATION_ACTIVE) \
|
||||
_(JSOP_INSTRUMENTATION_CALLBACK) \
|
||||
_(JSOP_INSTRUMENTATION_SCRIPT_ID)
|
||||
|
||||
enum class ScriptGCThingType { RegExp, Function, Scope, BigInt };
|
||||
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "jit/MIRGraph.h"
|
||||
#include "vm/ArgumentsObject.h"
|
||||
#include "vm/EnvironmentObject.h"
|
||||
#include "vm/Instrumentation.h"
|
||||
#include "vm/Opcodes.h"
|
||||
#include "vm/RegExpStatics.h"
|
||||
#include "vm/SelfHosting.h"
|
||||
@ -2485,6 +2486,15 @@ AbortReasonOr<Ok> IonBuilder::inspectOpcode(JSOp op) {
|
||||
case 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 =====
|
||||
// Read below!
|
||||
|
||||
@ -13479,6 +13489,35 @@ AbortReasonOr<Ok> IonBuilder::jsop_dynamic_import() {
|
||||
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* convert = MConvertElementsToDoubles::New(alloc(), elements);
|
||||
current->add(convert);
|
||||
|
@ -671,6 +671,9 @@ class IonBuilder : public MIRGenerator,
|
||||
AbortReasonOr<Ok> jsop_implicitthis(PropertyName* name);
|
||||
AbortReasonOr<Ok> jsop_importmeta();
|
||||
AbortReasonOr<Ok> jsop_dynamic_import();
|
||||
AbortReasonOr<Ok> jsop_instrumentation_active();
|
||||
AbortReasonOr<Ok> jsop_instrumentation_callback();
|
||||
AbortReasonOr<Ok> jsop_instrumentation_scriptid();
|
||||
|
||||
/* Inlining. */
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "vm/AsyncFunction.h"
|
||||
#include "vm/AsyncIteration.h"
|
||||
#include "vm/EqualityOperations.h"
|
||||
#include "vm/Instrumentation.h"
|
||||
#include "vm/Interpreter.h"
|
||||
#include "vm/TypedArrayObject.h"
|
||||
|
||||
@ -131,6 +132,9 @@ namespace jit {
|
||||
_(InitPropGetterSetterOperation, js::InitPropGetterSetterOperation) \
|
||||
_(InitRestParameter, js::jit::InitRestParameter) \
|
||||
_(InlineTypedObjectCreateCopy, js::InlineTypedObject::createCopy) \
|
||||
_(InstrumentationActiveOperation, js::InstrumentationActiveOperation) \
|
||||
_(InstrumentationCallbackOperation, js::InstrumentationCallbackOperation) \
|
||||
_(InstrumentationScriptIdOperation, js::InstrumentationScriptIdOperation) \
|
||||
_(Int32ToString, js::Int32ToString<CanGC>) \
|
||||
_(InterpretResume, js::jit::InterpretResume) \
|
||||
_(InterruptCheck, js::jit::InterruptCheck) \
|
||||
|
@ -78,6 +78,7 @@
|
||||
#include "vm/EnvironmentObject.h"
|
||||
#include "vm/ErrorObject.h"
|
||||
#include "vm/HelperThreads.h"
|
||||
#include "vm/Instrumentation.h"
|
||||
#include "vm/Interpreter.h"
|
||||
#include "vm/Iteration.h"
|
||||
#include "vm/JSAtom.h"
|
||||
@ -3585,6 +3586,11 @@ JS::CompileOptions::CompileOptions(JSContext* cx)
|
||||
forceFullParse_ = cx->realm()->behaviors().disableLazyParsing() ||
|
||||
coverage::IsLCovEnabled() ||
|
||||
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(
|
||||
|
@ -292,6 +292,7 @@ UNIFIED_SOURCES += [
|
||||
'vm/HelperThreads.cpp',
|
||||
'vm/Id.cpp',
|
||||
'vm/Initialization.cpp',
|
||||
'vm/Instrumentation.cpp',
|
||||
'vm/Iteration.cpp',
|
||||
'vm/JSAtom.cpp',
|
||||
'vm/JSContext.cpp',
|
||||
|
@ -106,6 +106,7 @@ class GlobalObject : public NativeObject {
|
||||
FOR_OF_PIC_CHAIN,
|
||||
WINDOW_PROXY,
|
||||
GLOBAL_THIS_RESOLVED,
|
||||
INSTRUMENTATION,
|
||||
|
||||
/* Total reserved-slot count for global objects. */
|
||||
RESERVED_SLOTS
|
||||
@ -892,6 +893,15 @@ class GlobalObject : public NativeObject {
|
||||
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.
|
||||
struct OffThreadPlaceholderObject : public NativeObject {
|
||||
static const int32_t SlotIndexSlot = 0;
|
||||
|
269
js/src/vm/Instrumentation.cpp
Normal file
269
js/src/vm/Instrumentation.cpp
Normal 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
113
js/src/vm/Instrumentation.h
Normal 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 */
|
@ -39,6 +39,7 @@
|
||||
#include "vm/BytecodeUtil.h"
|
||||
#include "vm/EqualityOperations.h" // js::StrictlyEqual
|
||||
#include "vm/GeneratorObject.h"
|
||||
#include "vm/Instrumentation.h"
|
||||
#include "vm/Iteration.h"
|
||||
#include "vm/JSAtom.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)); }
|
||||
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() {
|
||||
char numBuf[12];
|
||||
SprintfLiteral(numBuf, "%d", *REGS.pc);
|
||||
|
@ -1014,6 +1014,16 @@ XDRResult js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope,
|
||||
JSContext* cx = xdr->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) {
|
||||
script = scriptp.get();
|
||||
MOZ_ASSERT(script->functionNonDelazifying() == fun);
|
||||
|
@ -2547,7 +2547,28 @@
|
||||
* Operands: uint32_t constIndex
|
||||
* 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
|
||||
|
||||
/*
|
||||
@ -2555,9 +2576,6 @@
|
||||
* a power of two. Use this macro to do so.
|
||||
*/
|
||||
#define FOR_EACH_TRAILING_UNUSED_OPCODE(MACRO) \
|
||||
MACRO(238) \
|
||||
MACRO(239) \
|
||||
MACRO(240) \
|
||||
MACRO(241) \
|
||||
MACRO(242) \
|
||||
MACRO(243) \
|
||||
|
Loading…
Reference in New Issue
Block a user