1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "debugger/Script-inl.h"
9 #include "mozilla/Maybe.h" // for Some, Maybe
10 #include "mozilla/Span.h" // for Span
11 #include "mozilla/Vector.h" // for Vector
13 #include <stddef.h> // for ptrdiff_t
14 #include <stdint.h> // for uint32_t, UINT32_MAX, SIZE_MAX, int32_t
16 #include "jsnum.h" // for ToNumber
17 #include "NamespaceImports.h" // for CallArgs, RootedValue
19 #include "builtin/Array.h" // for NewDenseEmptyArray
20 #include "debugger/Debugger.h" // for DebuggerScriptReferent, Debugger
21 #include "debugger/DebugScript.h" // for DebugScript
22 #include "debugger/Source.h" // for DebuggerSource
23 #include "gc/GC.h" // for MemoryUse, MemoryUse::Breakpoint
24 #include "gc/Tracer.h" // for TraceManuallyBarrieredCrossCompartmentEdge
25 #include "gc/Zone.h" // for Zone
26 #include "gc/ZoneAllocator.h" // for AddCellMemory
27 #include "js/CallArgs.h" // for CallArgs, CallArgsFromVp
28 #include "js/ColumnNumber.h" // JS::LimitedColumnNumberOneOrigin, JS::WasmFunctionIndex
29 #include "js/friend/ErrorMessages.h" // for GetErrorMessage, JSMSG_*
30 #include "js/GCVariant.h" // for GCVariant
31 #include "js/HeapAPI.h" // for GCCellPtr
32 #include "js/RootingAPI.h" // for Rooted
33 #include "js/Wrapper.h" // for UncheckedUnwrap
34 #include "vm/ArrayObject.h" // for ArrayObject
35 #include "vm/BytecodeUtil.h" // for GET_JUMP_OFFSET
36 #include "vm/Compartment.h" // for JS::Compartment
37 #include "vm/EnvironmentObject.h" // for EnvironmentCoordinateNameSlow
38 #include "vm/GlobalObject.h" // for GlobalObject
39 #include "vm/JSContext.h" // for JSContext, ReportValueError
40 #include "vm/JSFunction.h" // for JSFunction
41 #include "vm/JSObject.h" // for RequireObject, JSObject
42 #include "vm/JSScript.h" // for BaseScript
43 #include "vm/ObjectOperations.h" // for DefineDataProperty, HasOwnProperty
44 #include "vm/PlainObject.h" // for js::PlainObject
45 #include "vm/Realm.h" // for AutoRealm
46 #include "vm/Runtime.h" // for JSAtomState, JSRuntime
47 #include "vm/StringType.h" // for NameToId, PropertyName, JSAtom
48 #include "wasm/WasmDebug.h" // for ExprLoc, DebugState
49 #include "wasm/WasmInstance.h" // for Instance
50 #include "wasm/WasmJS.h" // for WasmInstanceObject
51 #include "wasm/WasmTypeDecls.h" // for Bytes
53 #include "vm/BytecodeUtil-inl.h" // for BytecodeRangeWithPosition
54 #include "vm/JSAtomUtils-inl.h" // for PrimitiveValueToId
55 #include "vm/JSObject-inl.h" // for NewBuiltinClassInstance, NewObjectWithGivenProto, NewTenuredObjectWithGivenProto
56 #include "vm/JSScript-inl.h" // for JSScript::global
57 #include "vm/ObjectOperations-inl.h" // for GetProperty
58 #include "vm/Realm-inl.h" // for AutoRealm::AutoRealm
65 const JSClassOps
DebuggerScript::classOps_
= {
66 nullptr, // addProperty
67 nullptr, // delProperty
69 nullptr, // newEnumerate
71 nullptr, // mayResolve
75 CallTraceMethod
<DebuggerScript
>, // trace
78 const JSClass
DebuggerScript::class_
= {
79 "Script", JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS
), &classOps_
};
81 void DebuggerScript::trace(JSTracer
* trc
) {
82 // This comes from a private pointer, so no barrier needed.
83 gc::Cell
* cell
= getReferentCell();
85 if (cell
->is
<BaseScript
>()) {
86 BaseScript
* script
= cell
->as
<BaseScript
>();
87 TraceManuallyBarrieredCrossCompartmentEdge(
88 trc
, this, &script
, "Debugger.Script script referent");
89 if (script
!= cell
->as
<BaseScript
>()) {
90 setReservedSlotGCThingAsPrivateUnbarriered(SCRIPT_SLOT
, script
);
93 JSObject
* wasm
= cell
->as
<JSObject
>();
94 TraceManuallyBarrieredCrossCompartmentEdge(
95 trc
, this, &wasm
, "Debugger.Script wasm referent");
96 if (wasm
!= cell
->as
<JSObject
>()) {
97 MOZ_ASSERT(wasm
->is
<WasmInstanceObject
>());
98 setReservedSlotGCThingAsPrivateUnbarriered(SCRIPT_SLOT
, wasm
);
105 NativeObject
* DebuggerScript::initClass(JSContext
* cx
,
106 Handle
<GlobalObject
*> global
,
107 HandleObject debugCtor
) {
108 return InitClass(cx
, debugCtor
, nullptr, nullptr, "Script", construct
, 0,
109 properties_
, methods_
, nullptr, nullptr);
113 DebuggerScript
* DebuggerScript::create(JSContext
* cx
, HandleObject proto
,
114 Handle
<DebuggerScriptReferent
> referent
,
115 Handle
<NativeObject
*> debugger
) {
116 DebuggerScript
* scriptobj
=
117 NewTenuredObjectWithGivenProto
<DebuggerScript
>(cx
, proto
);
122 scriptobj
->setReservedSlot(DebuggerScript::OWNER_SLOT
,
123 ObjectValue(*debugger
));
124 referent
.get().match([&](auto& scriptHandle
) {
125 scriptobj
->setReservedSlotGCThingAsPrivate(SCRIPT_SLOT
, scriptHandle
);
131 static JSScript
* DelazifyScript(JSContext
* cx
, Handle
<BaseScript
*> script
) {
132 if (script
->hasBytecode()) {
133 return script
->asJSScript();
135 MOZ_ASSERT(script
->isFunction());
137 // JSFunction::getOrCreateScript requires an enclosing scope. This requires
138 // the enclosing script to be non-lazy.
139 if (script
->hasEnclosingScript()) {
140 Rooted
<BaseScript
*> enclosingScript(cx
, script
->enclosingScript());
141 if (!DelazifyScript(cx
, enclosingScript
)) {
145 if (!script
->isReadyForDelazification()) {
146 // It didn't work! Delazifying the enclosing script still didn't
147 // delazify this script. This happens when the function
148 // corresponding to this script was removed by constant folding.
149 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
150 JSMSG_DEBUG_OPTIMIZED_OUT_FUN
);
155 MOZ_ASSERT(script
->enclosingScope());
157 RootedFunction
fun(cx
, script
->function());
158 AutoRealm
ar(cx
, fun
);
159 return JSFunction::getOrCreateScript(cx
, fun
);
163 DebuggerScript
* DebuggerScript::check(JSContext
* cx
, HandleValue v
) {
164 JSObject
* thisobj
= RequireObject(cx
, v
);
168 if (!thisobj
->is
<DebuggerScript
>()) {
169 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
170 JSMSG_INCOMPATIBLE_PROTO
, "Debugger.Script",
171 "method", thisobj
->getClass()->name
);
175 return &thisobj
->as
<DebuggerScript
>();
178 struct MOZ_STACK_CLASS
DebuggerScript::CallData
{
180 const CallArgs
& args
;
182 Handle
<DebuggerScript
*> obj
;
183 Rooted
<DebuggerScriptReferent
> referent
;
186 CallData(JSContext
* cx
, const CallArgs
& args
, Handle
<DebuggerScript
*> obj
)
190 referent(cx
, obj
->getReferent()),
193 [[nodiscard
]] bool ensureScriptMaybeLazy() {
194 if (!referent
.is
<BaseScript
*>()) {
195 ReportValueError(cx
, JSMSG_DEBUG_BAD_REFERENT
, JSDVG_SEARCH_STACK
,
196 args
.thisv(), nullptr, "a JS script");
202 [[nodiscard
]] bool ensureScript() {
203 if (!ensureScriptMaybeLazy()) {
206 script
= DelazifyScript(cx
, referent
.as
<BaseScript
*>());
213 bool getIsGeneratorFunction();
214 bool getIsAsyncFunction();
215 bool getIsFunction();
217 bool getDisplayName();
218 bool getParameterNames();
221 bool getStartColumn();
224 bool getSourceStart();
225 bool getSourceLength();
226 bool getMainOffset();
229 bool getChildScripts();
230 bool getPossibleBreakpoints();
231 bool getPossibleBreakpointOffsets();
232 bool getOffsetMetadata();
233 bool getOffsetLocation();
234 bool getEffectfulOffsets();
235 bool getAllOffsets();
236 bool getAllColumnOffsets();
237 bool getLineOffsets();
238 bool setBreakpoint();
239 bool getBreakpoints();
240 bool clearBreakpoint();
241 bool clearAllBreakpoints();
242 bool isInCatchScope();
243 bool getOffsetsCoverage();
245 using Method
= bool (CallData::*)();
247 template <Method MyMethod
>
248 static bool ToNative(JSContext
* cx
, unsigned argc
, Value
* vp
);
251 template <DebuggerScript::CallData::Method MyMethod
>
253 bool DebuggerScript::CallData::ToNative(JSContext
* cx
, unsigned argc
,
255 CallArgs args
= CallArgsFromVp(argc
, vp
);
257 Rooted
<DebuggerScript
*> obj(cx
, DebuggerScript::check(cx
, args
.thisv()));
262 CallData
data(cx
, args
, obj
);
263 return (data
.*MyMethod
)();
266 bool DebuggerScript::CallData::getIsGeneratorFunction() {
267 if (!ensureScriptMaybeLazy()) {
270 args
.rval().setBoolean(obj
->getReferentScript()->isGenerator());
274 bool DebuggerScript::CallData::getIsAsyncFunction() {
275 if (!ensureScriptMaybeLazy()) {
278 args
.rval().setBoolean(obj
->getReferentScript()->isAsync());
282 bool DebuggerScript::CallData::getIsFunction() {
283 if (!ensureScriptMaybeLazy()) {
287 args
.rval().setBoolean(obj
->getReferentScript()->function());
291 bool DebuggerScript::CallData::getIsModule() {
292 if (!ensureScriptMaybeLazy()) {
295 BaseScript
* script
= referent
.as
<BaseScript
*>();
297 args
.rval().setBoolean(script
->isModule());
301 bool DebuggerScript::CallData::getDisplayName() {
302 if (!ensureScriptMaybeLazy()) {
306 JSFunction
* func
= obj
->getReferentScript()->function();
308 args
.rval().setUndefined();
312 JSAtom
* name
= func
->fullDisplayAtom();
314 args
.rval().setUndefined();
318 RootedValue
namev(cx
, StringValue(name
));
319 Debugger
* dbg
= obj
->owner();
320 if (!dbg
->wrapDebuggeeValue(cx
, &namev
)) {
323 args
.rval().set(namev
);
327 bool DebuggerScript::CallData::getParameterNames() {
328 if (!ensureScript()) {
332 RootedFunction
fun(cx
, referent
.as
<BaseScript
*>()->function());
334 args
.rval().setUndefined();
338 ArrayObject
* arr
= GetFunctionParameterNamesArray(cx
, fun
);
343 args
.rval().setObject(*arr
);
347 bool DebuggerScript::CallData::getUrl() {
348 if (!ensureScriptMaybeLazy()) {
352 Rooted
<BaseScript
*> script(cx
, referent
.as
<BaseScript
*>());
354 if (script
->filename()) {
356 if (const char* introducer
= script
->scriptSource()->introducerFilename()) {
358 NewStringCopyUTF8N(cx
, JS::UTF8Chars(introducer
, strlen(introducer
)));
360 const char* filename
= script
->filename();
361 str
= NewStringCopyUTF8N(cx
, JS::UTF8Chars(filename
, strlen(filename
)));
366 args
.rval().setString(str
);
368 args
.rval().setNull();
373 bool DebuggerScript::CallData::getStartLine() {
374 args
.rval().setNumber(
375 referent
.get().match([](BaseScript
*& s
) { return s
->lineno(); },
376 [](WasmInstanceObject
*&) { return (uint32_t)1; }));
380 bool DebuggerScript::CallData::getStartColumn() {
381 JS::LimitedColumnNumberOneOrigin column
= referent
.get().match(
382 [](BaseScript
*& s
) { return s
->column(); },
383 [](WasmInstanceObject
*&) {
384 return JS::LimitedColumnNumberOneOrigin(
385 JS::WasmFunctionIndex::DefaultBinarySourceColumnNumberOneOrigin
);
387 args
.rval().setNumber(column
.oneOriginValue());
391 struct DebuggerScript::GetLineCountMatcher
{
395 explicit GetLineCountMatcher(JSContext
* cx
) : cx_(cx
), totalLines(0.0) {}
396 using ReturnType
= bool;
398 ReturnType
match(Handle
<BaseScript
*> base
) {
399 RootedScript
script(cx_
, DelazifyScript(cx_
, base
));
403 totalLines
= double(GetScriptLineExtent(script
));
406 ReturnType
match(Handle
<WasmInstanceObject
*> instanceObj
) {
407 wasm::Instance
& instance
= instanceObj
->instance();
408 if (instance
.debugEnabled()) {
409 totalLines
= double(instance
.debug().bytecode().length());
417 bool DebuggerScript::CallData::getLineCount() {
418 GetLineCountMatcher
matcher(cx
);
419 if (!referent
.match(matcher
)) {
422 args
.rval().setNumber(matcher
.totalLines
);
426 class DebuggerScript::GetSourceMatcher
{
431 GetSourceMatcher(JSContext
* cx
, Debugger
* dbg
) : cx_(cx
), dbg_(dbg
) {}
433 using ReturnType
= DebuggerSource
*;
435 ReturnType
match(Handle
<BaseScript
*> script
) {
436 Rooted
<ScriptSourceObject
*> source(cx_
, script
->sourceObject());
437 return dbg_
->wrapSource(cx_
, source
);
439 ReturnType
match(Handle
<WasmInstanceObject
*> wasmInstance
) {
440 return dbg_
->wrapWasmSource(cx_
, wasmInstance
);
444 bool DebuggerScript::CallData::getSource() {
445 Debugger
* dbg
= obj
->owner();
447 GetSourceMatcher
matcher(cx
, dbg
);
448 Rooted
<DebuggerSource
*> sourceObject(cx
, referent
.match(matcher
));
453 args
.rval().setObject(*sourceObject
);
457 bool DebuggerScript::CallData::getSourceStart() {
458 if (!ensureScriptMaybeLazy()) {
461 args
.rval().setNumber(uint32_t(obj
->getReferentScript()->sourceStart()));
465 bool DebuggerScript::CallData::getSourceLength() {
466 if (!ensureScriptMaybeLazy()) {
469 args
.rval().setNumber(uint32_t(obj
->getReferentScript()->sourceLength()));
473 bool DebuggerScript::CallData::getMainOffset() {
474 if (!ensureScript()) {
477 args
.rval().setNumber(uint32_t(script
->mainOffset()));
481 bool DebuggerScript::CallData::getGlobal() {
482 if (!ensureScript()) {
485 Debugger
* dbg
= obj
->owner();
487 RootedValue
v(cx
, ObjectValue(script
->global()));
488 if (!dbg
->wrapDebuggeeValue(cx
, &v
)) {
495 bool DebuggerScript::CallData::getFormat() {
496 args
.rval().setString(referent
.get().match(
497 [this](BaseScript
*&) { return cx
->names().js
.get(); },
498 [this](WasmInstanceObject
*&) { return cx
->names().wasm
.get(); }));
502 static bool PushFunctionScript(JSContext
* cx
, Debugger
* dbg
, HandleFunction fun
,
503 HandleObject array
) {
504 // Ignore asm.js natives.
505 if (!IsInterpretedNonSelfHostedFunction(fun
)) {
509 Rooted
<BaseScript
*> script(cx
, fun
->baseScript());
512 // If the function doesn't have script, ignore it.
515 RootedObject
wrapped(cx
, dbg
->wrapScript(cx
, script
));
520 return NewbornArrayPush(cx
, array
, ObjectValue(*wrapped
));
523 static bool PushInnerFunctions(JSContext
* cx
, Debugger
* dbg
, HandleObject array
,
524 mozilla::Span
<const JS::GCCellPtr
> gcThings
) {
525 RootedFunction
fun(cx
);
527 for (JS::GCCellPtr gcThing
: gcThings
) {
528 if (!gcThing
.is
<JSObject
>()) {
532 JSObject
* obj
= &gcThing
.as
<JSObject
>();
533 if (obj
->is
<JSFunction
>()) {
534 fun
= &obj
->as
<JSFunction
>();
536 // Ignore any delazification placeholder functions. These should not be
537 // exposed to debugger in any way.
538 if (fun
->isGhost()) {
542 if (!PushFunctionScript(cx
, dbg
, fun
, array
)) {
551 bool DebuggerScript::CallData::getChildScripts() {
552 if (!ensureScriptMaybeLazy()) {
555 Debugger
* dbg
= obj
->owner();
557 RootedObject
result(cx
, NewDenseEmptyArray(cx
));
562 Rooted
<BaseScript
*> script(cx
, obj
->getReferent().as
<BaseScript
*>());
563 if (!PushInnerFunctions(cx
, dbg
, result
, script
->gcthings())) {
567 args
.rval().setObject(*result
);
571 static bool ScriptOffset(JSContext
* cx
, const Value
& v
, size_t* offsetp
) {
575 bool ok
= v
.isNumber();
580 if (!ok
|| off
!= d
) {
581 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
582 JSMSG_DEBUG_BAD_OFFSET
);
589 static bool EnsureScriptOffsetIsValid(JSContext
* cx
, JSScript
* script
,
591 if (IsValidBytecodeOffset(cx
, script
, offset
)) {
594 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
595 JSMSG_DEBUG_BAD_OFFSET
);
599 static bool IsGeneratorSlotInitialization(JSScript
* script
, size_t offset
,
601 jsbytecode
* pc
= script
->offsetToPC(offset
);
602 if (JSOp(*pc
) != JSOp::SetAliasedVar
) {
606 PropertyName
* name
= EnvironmentCoordinateNameSlow(script
, pc
);
607 return name
== cx
->names().dot_generator_
;
610 static bool EnsureBreakpointIsAllowed(JSContext
* cx
, JSScript
* script
,
612 // Disallow breakpoint for `JSOp::SetAliasedVar` after `JSOp::Generator`.
613 // Those 2 instructions are supposed to be atomic, and nothing should happen
616 // Hitting a breakpoint there breaks the assumption around the existence of
617 // the frame's `GeneratorInfo`.
618 // (see `DebugAPI::slowPathOnNewGenerator` and `DebuggerFrame::create`)
619 if (IsGeneratorSlotInitialization(script
, offset
, cx
)) {
620 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
621 JSMSG_DEBUG_BREAKPOINT_NOT_ALLOWED
);
628 template <bool OnlyOffsets
>
629 class DebuggerScript::GetPossibleBreakpointsMatcher
{
631 MutableHandleObject result_
;
633 Maybe
<size_t> minOffset
;
634 Maybe
<size_t> maxOffset
;
636 Maybe
<uint32_t> minLine
;
637 JS::LimitedColumnNumberOneOrigin minColumn
;
638 Maybe
<uint32_t> maxLine
;
639 JS::LimitedColumnNumberOneOrigin maxColumn
;
641 bool passesQuery(size_t offset
, uint32_t lineno
,
642 JS::LimitedColumnNumberOneOrigin colno
) {
643 // [minOffset, maxOffset) - Inclusive minimum and exclusive maximum.
644 if ((minOffset
&& offset
< *minOffset
) ||
645 (maxOffset
&& offset
>= *maxOffset
)) {
650 if (lineno
< *minLine
|| (lineno
== *minLine
&& colno
< minColumn
)) {
656 if (lineno
> *maxLine
|| (lineno
== *maxLine
&& colno
>= maxColumn
)) {
664 bool maybeAppendEntry(size_t offset
, uint32_t lineno
,
665 JS::LimitedColumnNumberOneOrigin colno
,
667 if (!passesQuery(offset
, lineno
, colno
)) {
672 if (!NewbornArrayPush(cx_
, result_
, NumberValue(offset
))) {
679 Rooted
<PlainObject
*> entry(cx_
, NewPlainObject(cx_
));
684 RootedValue
value(cx_
, NumberValue(offset
));
685 if (!DefineDataProperty(cx_
, entry
, cx_
->names().offset
, value
)) {
689 value
= NumberValue(lineno
);
690 if (!DefineDataProperty(cx_
, entry
, cx_
->names().lineNumber
, value
)) {
694 value
= NumberValue(colno
.oneOriginValue());
695 if (!DefineDataProperty(cx_
, entry
, cx_
->names().columnNumber
, value
)) {
699 value
= BooleanValue(isStepStart
);
700 if (!DefineDataProperty(cx_
, entry
, cx_
->names().isStepStart
, value
)) {
704 if (!NewbornArrayPush(cx_
, result_
, ObjectValue(*entry
))) {
710 template <typename T
>
711 bool parseIntValueImpl(HandleValue value
, T
* result
) {
712 if (!value
.isNumber()) {
716 double doubleOffset
= value
.toNumber();
717 if (doubleOffset
< 0 || (unsigned int)doubleOffset
!= doubleOffset
) {
721 *result
= doubleOffset
;
725 bool parseUint32Value(HandleValue value
, uint32_t* result
) {
726 return parseIntValueImpl(value
, result
);
728 bool parseColumnValue(HandleValue value
,
729 JS::LimitedColumnNumberOneOrigin
* result
) {
731 if (!parseIntValueImpl(value
, &tmp
)) {
737 *result
->addressOfValueForTranscode() = tmp
;
740 bool parseSizeTValue(HandleValue value
, size_t* result
) {
741 return parseIntValueImpl(value
, result
);
744 template <typename T
>
745 bool parseIntValueMaybeImpl(HandleValue value
, Maybe
<T
>* result
) {
747 if (!parseIntValueImpl(value
, &result_
)) {
751 *result
= Some(result_
);
755 bool parseUint32Value(HandleValue value
, Maybe
<uint32_t>* result
) {
756 return parseIntValueMaybeImpl(value
, result
);
758 bool parseSizeTValue(HandleValue value
, Maybe
<size_t>* result
) {
759 return parseIntValueMaybeImpl(value
, result
);
763 explicit GetPossibleBreakpointsMatcher(JSContext
* cx
,
764 MutableHandleObject result
)
765 : cx_(cx
), result_(result
) {}
767 bool parseQuery(HandleObject query
) {
768 RootedValue
lineValue(cx_
);
769 if (!GetProperty(cx_
, query
, query
, cx_
->names().line
, &lineValue
)) {
773 RootedValue
minLineValue(cx_
);
774 if (!GetProperty(cx_
, query
, query
, cx_
->names().minLine
, &minLineValue
)) {
778 RootedValue
minColumnValue(cx_
);
779 if (!GetProperty(cx_
, query
, query
, cx_
->names().minColumn
,
784 RootedValue
minOffsetValue(cx_
);
785 if (!GetProperty(cx_
, query
, query
, cx_
->names().minOffset
,
790 RootedValue
maxLineValue(cx_
);
791 if (!GetProperty(cx_
, query
, query
, cx_
->names().maxLine
, &maxLineValue
)) {
795 RootedValue
maxColumnValue(cx_
);
796 if (!GetProperty(cx_
, query
, query
, cx_
->names().maxColumn
,
801 RootedValue
maxOffsetValue(cx_
);
802 if (!GetProperty(cx_
, query
, query
, cx_
->names().maxOffset
,
807 if (!minOffsetValue
.isUndefined()) {
808 if (!parseSizeTValue(minOffsetValue
, &minOffset
)) {
809 JS_ReportErrorNumberASCII(
810 cx_
, GetErrorMessage
, nullptr, JSMSG_UNEXPECTED_TYPE
,
811 "getPossibleBreakpoints' 'minOffset'", "not an integer");
815 if (!maxOffsetValue
.isUndefined()) {
816 if (!parseSizeTValue(maxOffsetValue
, &maxOffset
)) {
817 JS_ReportErrorNumberASCII(
818 cx_
, GetErrorMessage
, nullptr, JSMSG_UNEXPECTED_TYPE
,
819 "getPossibleBreakpoints' 'maxOffset'", "not an integer");
824 if (!lineValue
.isUndefined()) {
825 if (!minLineValue
.isUndefined() || !maxLineValue
.isUndefined()) {
826 JS_ReportErrorNumberASCII(cx_
, GetErrorMessage
, nullptr,
827 JSMSG_UNEXPECTED_TYPE
,
828 "getPossibleBreakpoints' 'line'",
829 "not allowed alongside 'minLine'/'maxLine'");
834 if (!parseUint32Value(lineValue
, &line
)) {
835 JS_ReportErrorNumberASCII(
836 cx_
, GetErrorMessage
, nullptr, JSMSG_UNEXPECTED_TYPE
,
837 "getPossibleBreakpoints' 'line'", "not an integer");
841 // If no end column is given, we use the default of 0 and wrap to
843 minLine
= Some(line
);
844 maxLine
= Some(line
+ (maxColumnValue
.isUndefined() ? 1 : 0));
847 if (!minLineValue
.isUndefined()) {
848 if (!parseUint32Value(minLineValue
, &minLine
)) {
849 JS_ReportErrorNumberASCII(
850 cx_
, GetErrorMessage
, nullptr, JSMSG_UNEXPECTED_TYPE
,
851 "getPossibleBreakpoints' 'minLine'", "not an integer");
856 if (!minColumnValue
.isUndefined()) {
858 JS_ReportErrorNumberASCII(cx_
, GetErrorMessage
, nullptr,
859 JSMSG_UNEXPECTED_TYPE
,
860 "getPossibleBreakpoints' 'minColumn'",
861 "not allowed without 'line' or 'minLine'");
865 if (!parseColumnValue(minColumnValue
, &minColumn
)) {
866 JS_ReportErrorNumberASCII(
867 cx_
, GetErrorMessage
, nullptr, JSMSG_UNEXPECTED_TYPE
,
868 "getPossibleBreakpoints' 'minColumn'", "not a positive integer");
873 if (!maxLineValue
.isUndefined()) {
874 if (!parseUint32Value(maxLineValue
, &maxLine
)) {
875 JS_ReportErrorNumberASCII(
876 cx_
, GetErrorMessage
, nullptr, JSMSG_UNEXPECTED_TYPE
,
877 "getPossibleBreakpoints' 'maxLine'", "not an integer");
882 if (!maxColumnValue
.isUndefined()) {
884 JS_ReportErrorNumberASCII(cx_
, GetErrorMessage
, nullptr,
885 JSMSG_UNEXPECTED_TYPE
,
886 "getPossibleBreakpoints' 'maxColumn'",
887 "not allowed without 'line' or 'maxLine'");
891 if (!parseColumnValue(maxColumnValue
, &maxColumn
)) {
892 JS_ReportErrorNumberASCII(
893 cx_
, GetErrorMessage
, nullptr, JSMSG_UNEXPECTED_TYPE
,
894 "getPossibleBreakpoints' 'maxColumn'", "not a positive integer");
902 using ReturnType
= bool;
903 ReturnType
match(Handle
<BaseScript
*> base
) {
904 RootedScript
script(cx_
, DelazifyScript(cx_
, base
));
909 // Second pass: build the result array.
910 result_
.set(NewDenseEmptyArray(cx_
));
915 for (BytecodeRangeWithPosition
r(cx_
, script
); !r
.empty(); r
.popFront()) {
916 if (!r
.frontIsBreakablePoint()) {
920 size_t offset
= r
.frontOffset();
921 uint32_t lineno
= r
.frontLineNumber();
922 JS::LimitedColumnNumberOneOrigin colno
= r
.frontColumnNumber();
924 if (!maybeAppendEntry(offset
, lineno
, colno
,
925 r
.frontIsBreakableStepPoint())) {
932 ReturnType
match(Handle
<WasmInstanceObject
*> instanceObj
) {
933 wasm::Instance
& instance
= instanceObj
->instance();
935 Vector
<wasm::ExprLoc
> offsets(cx_
);
936 if (instance
.debugEnabled() &&
937 !instance
.debug().getAllColumnOffsets(&offsets
)) {
941 result_
.set(NewDenseEmptyArray(cx_
));
946 for (uint32_t i
= 0; i
< offsets
.length(); i
++) {
947 uint32_t lineno
= offsets
[i
].lineno
;
948 JS::LimitedColumnNumberOneOrigin
column(offsets
[i
].column
);
949 size_t offset
= offsets
[i
].offset
;
950 if (!maybeAppendEntry(offset
, lineno
, column
, true)) {
958 bool DebuggerScript::CallData::getPossibleBreakpoints() {
959 RootedObject
result(cx
);
960 GetPossibleBreakpointsMatcher
<false> matcher(cx
, &result
);
961 if (args
.length() >= 1 && !args
[0].isUndefined()) {
962 RootedObject
queryObject(cx
, RequireObject(cx
, args
[0]));
963 if (!queryObject
|| !matcher
.parseQuery(queryObject
)) {
967 if (!referent
.match(matcher
)) {
971 args
.rval().setObject(*result
);
975 bool DebuggerScript::CallData::getPossibleBreakpointOffsets() {
976 RootedObject
result(cx
);
977 GetPossibleBreakpointsMatcher
<true> matcher(cx
, &result
);
978 if (args
.length() >= 1 && !args
[0].isUndefined()) {
979 RootedObject
queryObject(cx
, RequireObject(cx
, args
[0]));
980 if (!queryObject
|| !matcher
.parseQuery(queryObject
)) {
984 if (!referent
.match(matcher
)) {
988 args
.rval().setObject(*result
);
992 class DebuggerScript::GetOffsetMetadataMatcher
{
995 MutableHandle
<PlainObject
*> result_
;
998 explicit GetOffsetMetadataMatcher(JSContext
* cx
, size_t offset
,
999 MutableHandle
<PlainObject
*> result
)
1000 : cx_(cx
), offset_(offset
), result_(result
) {}
1001 using ReturnType
= bool;
1002 ReturnType
match(Handle
<BaseScript
*> base
) {
1003 RootedScript
script(cx_
, DelazifyScript(cx_
, base
));
1008 if (!EnsureScriptOffsetIsValid(cx_
, script
, offset_
)) {
1012 result_
.set(NewPlainObject(cx_
));
1017 BytecodeRangeWithPosition
r(cx_
, script
);
1018 while (!r
.empty() && r
.frontOffset() < offset_
) {
1022 RootedValue
value(cx_
, NumberValue(r
.frontLineNumber()));
1023 if (!DefineDataProperty(cx_
, result_
, cx_
->names().lineNumber
, value
)) {
1027 value
= NumberValue(r
.frontColumnNumber().oneOriginValue());
1028 if (!DefineDataProperty(cx_
, result_
, cx_
->names().columnNumber
, value
)) {
1032 value
= BooleanValue(r
.frontIsBreakablePoint());
1033 if (!DefineDataProperty(cx_
, result_
, cx_
->names().isBreakpoint
, value
)) {
1037 value
= BooleanValue(r
.frontIsBreakableStepPoint());
1038 if (!DefineDataProperty(cx_
, result_
, cx_
->names().isStepStart
, value
)) {
1044 ReturnType
match(Handle
<WasmInstanceObject
*> instanceObj
) {
1045 wasm::Instance
& instance
= instanceObj
->instance();
1046 if (!instance
.debugEnabled()) {
1047 JS_ReportErrorNumberASCII(cx_
, GetErrorMessage
, nullptr,
1048 JSMSG_DEBUG_BAD_OFFSET
);
1053 JS::LimitedColumnNumberOneOrigin column
;
1054 if (!instance
.debug().getOffsetLocation(offset_
, &lineno
, &column
)) {
1055 JS_ReportErrorNumberASCII(cx_
, GetErrorMessage
, nullptr,
1056 JSMSG_DEBUG_BAD_OFFSET
);
1060 result_
.set(NewPlainObject(cx_
));
1065 RootedValue
value(cx_
, NumberValue(lineno
));
1066 if (!DefineDataProperty(cx_
, result_
, cx_
->names().lineNumber
, value
)) {
1070 value
= NumberValue(column
.oneOriginValue());
1071 if (!DefineDataProperty(cx_
, result_
, cx_
->names().columnNumber
, value
)) {
1075 value
.setBoolean(true);
1076 if (!DefineDataProperty(cx_
, result_
, cx_
->names().isBreakpoint
, value
)) {
1080 value
.setBoolean(true);
1081 if (!DefineDataProperty(cx_
, result_
, cx_
->names().isStepStart
, value
)) {
1089 bool DebuggerScript::CallData::getOffsetMetadata() {
1090 if (!args
.requireAtLeast(cx
, "Debugger.Script.getOffsetMetadata", 1)) {
1094 if (!ScriptOffset(cx
, args
[0], &offset
)) {
1098 Rooted
<PlainObject
*> result(cx
);
1099 GetOffsetMetadataMatcher
matcher(cx
, offset
, &result
);
1100 if (!referent
.match(matcher
)) {
1104 args
.rval().setObject(*result
);
1111 * FlowGraphSummary::populate(cx, script) computes a summary of script's
1112 * control flow graph used by DebuggerScript_{getAllOffsets,getLineOffsets}.
1114 * An instruction on a given line is an entry point for that line if it can be
1115 * reached from (an instruction on) a different line. We distinguish between the
1118 * The instruction cannot be reached, so the instruction is not an entry
1119 * point for the line it is on.
1121 * The instruction can be reached from a single line. If this line is
1122 * different from the line the instruction is on, the instruction is an
1123 * entry point for that line.
1125 * Similarly, an instruction on a given position (line/column pair) is an
1126 * entry point for that position if it can be reached from (an instruction on) a
1127 * different position. Again, we distinguish between the following cases:
1129 * The instruction cannot be reached, so the instruction is not an entry
1130 * point for the position it is on.
1132 * The instruction can be reached from a single position. If this line is
1133 * different from the position the instruction is on, the instruction is
1134 * an entry point for that position.
1136 class FlowGraphSummary
{
1140 static constexpr uint32_t Line_HasNoEdge
= UINT32_MAX
;
1141 static constexpr uint32_t Column_HasMultipleEdge
= UINT32_MAX
;
1143 // NOTE: column can be Column_HasMultipleEdge.
1144 static Entry
createWithSingleEdgeOrMultipleEdge(uint32_t lineno
,
1146 return Entry(lineno
, column
);
1149 static Entry
createWithMultipleEdgesFromSingleLine(uint32_t lineno
) {
1150 return Entry(lineno
, Column_HasMultipleEdge
);
1153 static Entry
createWithMultipleEdgesFromMultipleLines() {
1154 return Entry(Line_HasNoEdge
, Column_HasMultipleEdge
);
1157 Entry() : lineno_(Line_HasNoEdge
), column_(1) {}
1159 bool hasNoEdges() const {
1160 return lineno_
== Line_HasNoEdge
&& column_
!= Column_HasMultipleEdge
;
1163 bool hasSingleEdge() const {
1164 return lineno_
!= Line_HasNoEdge
&& column_
!= Column_HasMultipleEdge
;
1167 uint32_t lineno() const { return lineno_
; }
1169 // Returns 1-origin column number or the sentinel value
1170 // Column_HasMultipleEdge.
1171 uint32_t columnOrSentinel() const { return column_
; }
1173 JS::LimitedColumnNumberOneOrigin
column() const {
1174 MOZ_ASSERT(column_
!= Column_HasMultipleEdge
);
1175 return JS::LimitedColumnNumberOneOrigin(column_
);
1179 Entry(uint32_t lineno
, uint32_t column
)
1180 : lineno_(lineno
), column_(column
) {}
1182 // Line number (1-origin).
1183 // Line_HasNoEdge for no edge.
1186 // Column number in UTF-16 code units (1-origin).
1187 // Column_HasMultipleEdge for multiple edge.
1191 explicit FlowGraphSummary(JSContext
* cx
) : entries_(cx
) {}
1193 Entry
& operator[](size_t index
) { return entries_
[index
]; }
1195 bool populate(JSContext
* cx
, JSScript
* script
) {
1196 if (!entries_
.growBy(script
->length())) {
1199 unsigned mainOffset
= script
->pcToOffset(script
->main());
1200 entries_
[mainOffset
] = Entry::createWithMultipleEdgesFromMultipleLines();
1202 // The following code uses uint32_t for column numbers.
1203 // The value is either 1-origin column number,
1204 // or Entry::Column_HasMultipleEdge.
1206 uint32_t prevLineno
= script
->lineno();
1207 uint32_t prevColumn
= 1;
1208 JSOp prevOp
= JSOp::Nop
;
1209 for (BytecodeRangeWithPosition
r(cx
, script
); !r
.empty(); r
.popFront()) {
1210 uint32_t lineno
= prevLineno
;
1211 uint32_t column
= prevColumn
;
1212 JSOp op
= r
.frontOpcode();
1214 if (BytecodeFallsThrough(prevOp
)) {
1215 addEdge(prevLineno
, prevColumn
, r
.frontOffset());
1218 // If we visit the branch target before we visit the
1219 // branch op itself, just reuse the previous location.
1220 // This is reasonable for the time being because this
1221 // situation can currently only arise from loop heads,
1222 // where this assumption holds.
1223 if (BytecodeIsJumpTarget(op
) && !entries_
[r
.frontOffset()].hasNoEdges()) {
1224 lineno
= entries_
[r
.frontOffset()].lineno();
1225 column
= entries_
[r
.frontOffset()].columnOrSentinel();
1228 if (r
.frontIsEntryPoint()) {
1229 lineno
= r
.frontLineNumber();
1230 column
= r
.frontColumnNumber().oneOriginValue();
1233 if (IsJumpOpcode(op
)) {
1234 addEdge(lineno
, column
, r
.frontOffset() + GET_JUMP_OFFSET(r
.frontPC()));
1235 } else if (op
== JSOp::TableSwitch
) {
1236 jsbytecode
* const switchPC
= r
.frontPC();
1237 jsbytecode
* pc
= switchPC
;
1238 size_t offset
= r
.frontOffset();
1239 ptrdiff_t step
= JUMP_OFFSET_LEN
;
1240 size_t defaultOffset
= offset
+ GET_JUMP_OFFSET(pc
);
1242 addEdge(lineno
, column
, defaultOffset
);
1244 int32_t low
= GET_JUMP_OFFSET(pc
);
1245 pc
+= JUMP_OFFSET_LEN
;
1246 int ncases
= GET_JUMP_OFFSET(pc
) - low
+ 1;
1247 pc
+= JUMP_OFFSET_LEN
;
1249 for (int i
= 0; i
< ncases
; i
++) {
1250 size_t target
= script
->tableSwitchCaseOffset(switchPC
, i
);
1251 addEdge(lineno
, column
, target
);
1253 } else if (op
== JSOp::Try
) {
1254 // As there is no literal incoming edge into the catch block, we
1255 // make a fake one by copying the JSOp::Try location, as-if this
1256 // was an incoming edge of the catch block. This is needed
1257 // because we only report offsets of entry points which have
1258 // valid incoming edges.
1259 for (const TryNote
& tn
: script
->trynotes()) {
1260 if (tn
.start
== r
.frontOffset() + JSOpLength_Try
) {
1261 uint32_t catchOffset
= tn
.start
+ tn
.length
;
1262 if (tn
.kind() == TryNoteKind::Catch
||
1263 tn
.kind() == TryNoteKind::Finally
) {
1264 addEdge(lineno
, column
, catchOffset
);
1270 prevLineno
= lineno
;
1271 prevColumn
= column
;
1279 // sourceColumn is either 1-origin column number,
1280 // or Entry::Column_HasMultipleEdge.
1281 void addEdge(uint32_t sourceLineno
, uint32_t sourceColumn
,
1282 size_t targetOffset
) {
1283 if (entries_
[targetOffset
].hasNoEdges()) {
1284 entries_
[targetOffset
] =
1285 Entry::createWithSingleEdgeOrMultipleEdge(sourceLineno
, sourceColumn
);
1286 } else if (entries_
[targetOffset
].lineno() != sourceLineno
) {
1287 entries_
[targetOffset
] =
1288 Entry::createWithMultipleEdgesFromMultipleLines();
1289 } else if (entries_
[targetOffset
].columnOrSentinel() != sourceColumn
) {
1290 entries_
[targetOffset
] =
1291 Entry::createWithMultipleEdgesFromSingleLine(sourceLineno
);
1295 Vector
<Entry
> entries_
;
1298 } /* anonymous namespace */
1300 class DebuggerScript::GetOffsetLocationMatcher
{
1303 MutableHandle
<PlainObject
*> result_
;
1306 explicit GetOffsetLocationMatcher(JSContext
* cx
, size_t offset
,
1307 MutableHandle
<PlainObject
*> result
)
1308 : cx_(cx
), offset_(offset
), result_(result
) {}
1309 using ReturnType
= bool;
1310 ReturnType
match(Handle
<BaseScript
*> base
) {
1311 RootedScript
script(cx_
, DelazifyScript(cx_
, base
));
1316 if (!EnsureScriptOffsetIsValid(cx_
, script
, offset_
)) {
1320 FlowGraphSummary
flowData(cx_
);
1321 if (!flowData
.populate(cx_
, script
)) {
1325 result_
.set(NewPlainObject(cx_
));
1330 BytecodeRangeWithPosition
r(cx_
, script
);
1331 while (!r
.empty() && r
.frontOffset() < offset_
) {
1335 size_t offset
= r
.frontOffset();
1336 bool isEntryPoint
= r
.frontIsEntryPoint();
1338 // Line numbers are only correctly defined on entry points. Thus looks
1339 // either for the next valid offset in the flowData, being the last entry
1340 // point flowing into the current offset, or for the next valid entry point.
1341 while (!r
.frontIsEntryPoint() &&
1342 !flowData
[r
.frontOffset()].hasSingleEdge()) {
1344 MOZ_ASSERT(!r
.empty());
1347 // If this is an entry point, take the line number associated with the entry
1348 // point, otherwise settle on the next instruction and take the incoming
1351 JS::LimitedColumnNumberOneOrigin column
;
1352 if (r
.frontIsEntryPoint()) {
1353 lineno
= r
.frontLineNumber();
1354 column
= r
.frontColumnNumber();
1356 MOZ_ASSERT(flowData
[r
.frontOffset()].hasSingleEdge());
1357 lineno
= flowData
[r
.frontOffset()].lineno();
1358 column
= flowData
[r
.frontOffset()].column();
1361 RootedValue
value(cx_
, NumberValue(lineno
));
1362 if (!DefineDataProperty(cx_
, result_
, cx_
->names().lineNumber
, value
)) {
1366 value
= NumberValue(column
.oneOriginValue());
1367 if (!DefineDataProperty(cx_
, result_
, cx_
->names().columnNumber
, value
)) {
1371 // The same entry point test that is used by getAllColumnOffsets.
1372 isEntryPoint
= (isEntryPoint
&& !flowData
[offset
].hasNoEdges() &&
1373 (flowData
[offset
].lineno() != r
.frontLineNumber() ||
1374 flowData
[offset
].columnOrSentinel() !=
1375 r
.frontColumnNumber().oneOriginValue()));
1376 value
.setBoolean(isEntryPoint
);
1377 if (!DefineDataProperty(cx_
, result_
, cx_
->names().isEntryPoint
, value
)) {
1383 ReturnType
match(Handle
<WasmInstanceObject
*> instanceObj
) {
1384 wasm::Instance
& instance
= instanceObj
->instance();
1385 if (!instance
.debugEnabled()) {
1386 JS_ReportErrorNumberASCII(cx_
, GetErrorMessage
, nullptr,
1387 JSMSG_DEBUG_BAD_OFFSET
);
1392 JS::LimitedColumnNumberOneOrigin column
;
1393 if (!instance
.debug().getOffsetLocation(offset_
, &lineno
, &column
)) {
1394 JS_ReportErrorNumberASCII(cx_
, GetErrorMessage
, nullptr,
1395 JSMSG_DEBUG_BAD_OFFSET
);
1399 result_
.set(NewPlainObject(cx_
));
1404 RootedValue
value(cx_
, NumberValue(lineno
));
1405 if (!DefineDataProperty(cx_
, result_
, cx_
->names().lineNumber
, value
)) {
1409 value
= NumberValue(column
.oneOriginValue());
1410 if (!DefineDataProperty(cx_
, result_
, cx_
->names().columnNumber
, value
)) {
1414 value
.setBoolean(true);
1415 if (!DefineDataProperty(cx_
, result_
, cx_
->names().isEntryPoint
, value
)) {
1423 bool DebuggerScript::CallData::getOffsetLocation() {
1424 if (!args
.requireAtLeast(cx
, "Debugger.Script.getOffsetLocation", 1)) {
1428 if (!ScriptOffset(cx
, args
[0], &offset
)) {
1432 Rooted
<PlainObject
*> result(cx
);
1433 GetOffsetLocationMatcher
matcher(cx
, offset
, &result
);
1434 if (!referent
.match(matcher
)) {
1438 args
.rval().setObject(*result
);
1442 // Return whether an opcode is considered effectful: it can have direct side
1443 // effects that can be observed outside of the current frame. Opcodes are not
1444 // effectful if they only modify the current frame's state, modify objects
1445 // created by the current frame, or can potentially call other scripts or
1446 // natives which could have side effects.
1447 static bool BytecodeIsEffectful(JSScript
* script
, size_t offset
) {
1448 jsbytecode
* pc
= script
->offsetToPC(offset
);
1449 JSOp op
= JSOp(*pc
);
1452 case JSOp::StrictSetProp
:
1453 case JSOp::SetPropSuper
:
1454 case JSOp::StrictSetPropSuper
:
1456 case JSOp::StrictSetElem
:
1457 case JSOp::SetElemSuper
:
1458 case JSOp::StrictSetElemSuper
:
1460 case JSOp::StrictSetName
:
1461 case JSOp::SetGName
:
1462 case JSOp::StrictSetGName
:
1464 case JSOp::StrictDelProp
:
1466 case JSOp::StrictDelElem
:
1468 case JSOp::SetAliasedVar
:
1469 case JSOp::InitHomeObject
:
1470 case JSOp::SetIntrinsic
:
1471 case JSOp::InitGLexical
:
1472 case JSOp::GlobalOrEvalDeclInstantiation
:
1473 case JSOp::SetFunName
:
1474 case JSOp::MutateProto
:
1475 case JSOp::DynamicImport
:
1476 case JSOp::InitialYield
:
1479 case JSOp::CanSkipAwait
:
1483 case JSOp::NopDestructuring
:
1484 case JSOp::NopIsAssignOp
:
1485 case JSOp::TryDestructuring
:
1487 case JSOp::JumpTarget
:
1488 case JSOp::Undefined
:
1489 case JSOp::JumpIfTrue
:
1490 case JSOp::JumpIfFalse
:
1495 case JSOp::Coalesce
:
1498 case JSOp::ThrowWithStack
:
1500 case JSOp::TableSwitch
:
1517 case JSOp::ToNumeric
:
1521 case JSOp::ToString
:
1524 case JSOp::StrictEq
:
1525 case JSOp::StrictNe
:
1541 case JSOp::Arguments
:
1544 case JSOp::GetFrameArg
:
1546 case JSOp::GetLocal
:
1547 case JSOp::SetLocal
:
1548 case JSOp::GetActualArg
:
1549 case JSOp::ArgumentsLength
:
1550 case JSOp::ThrowSetConst
:
1551 case JSOp::CheckLexical
:
1552 case JSOp::CheckAliasedLexical
:
1553 case JSOp::InitLexical
:
1554 case JSOp::Uninitialized
:
1558 case JSOp::NewArray
:
1560 case JSOp::NewObject
:
1561 case JSOp::InitElem
:
1562 case JSOp::InitHiddenElem
:
1563 case JSOp::InitLockedElem
:
1564 case JSOp::InitElemInc
:
1565 case JSOp::InitElemArray
:
1566 case JSOp::InitProp
:
1567 case JSOp::InitLockedProp
:
1568 case JSOp::InitHiddenProp
:
1569 case JSOp::InitPropGetter
:
1570 case JSOp::InitHiddenPropGetter
:
1571 case JSOp::InitPropSetter
:
1572 case JSOp::InitHiddenPropSetter
:
1573 case JSOp::InitElemGetter
:
1574 case JSOp::InitHiddenElemGetter
:
1575 case JSOp::InitElemSetter
:
1576 case JSOp::InitHiddenElemSetter
:
1577 case JSOp::SpreadCall
:
1579 case JSOp::CallContent
:
1580 case JSOp::CallIgnoresRv
:
1581 case JSOp::CallIter
:
1582 case JSOp::CallContentIter
:
1584 case JSOp::NewContent
:
1586 case JSOp::StrictEval
:
1589 case JSOp::ResumeKind
:
1590 case JSOp::GetGName
:
1592 case JSOp::GetIntrinsic
:
1593 case JSOp::GetImport
:
1594 case JSOp::BindGName
:
1595 case JSOp::BindName
:
1602 case JSOp::GetAliasedDebugVar
:
1603 case JSOp::GetAliasedVar
:
1606 case JSOp::LoopHead
:
1609 case JSOp::FunctionThis
:
1610 case JSOp::GlobalThis
:
1611 case JSOp::NonSyntacticGlobalThis
:
1613 case JSOp::EnvCallee
:
1614 case JSOp::SuperBase
:
1615 case JSOp::GetPropSuper
:
1616 case JSOp::GetElemSuper
:
1619 case JSOp::CallSiteObj
:
1622 case JSOp::TypeofExpr
:
1623 case JSOp::ToAsyncIter
:
1624 case JSOp::ToPropertyKey
:
1626 case JSOp::PushLexicalEnv
:
1627 case JSOp::PopLexicalEnv
:
1628 case JSOp::FreshenLexicalEnv
:
1629 case JSOp::RecreateLexicalEnv
:
1630 case JSOp::PushClassBodyEnv
:
1632 case JSOp::MoreIter
:
1633 case JSOp::IsNoIter
:
1635 case JSOp::CloseIter
:
1636 case JSOp::OptimizeGetIterator
:
1637 case JSOp::IsNullOrUndefined
:
1640 case JSOp::CheckPrivateField
:
1641 case JSOp::NewPrivateName
:
1643 case JSOp::Instanceof
:
1644 case JSOp::DebugLeaveLexicalEnv
:
1645 case JSOp::Debugger
:
1646 case JSOp::ImplicitThis
:
1647 case JSOp::NewTarget
:
1648 case JSOp::CheckIsObj
:
1649 case JSOp::CheckObjCoercible
:
1650 case JSOp::DebugCheckSelfHosted
:
1651 case JSOp::IsConstructing
:
1652 case JSOp::OptimizeSpreadCall
:
1653 case JSOp::ImportMeta
:
1654 case JSOp::EnterWith
:
1655 case JSOp::LeaveWith
:
1656 case JSOp::SpreadNew
:
1657 case JSOp::SpreadEval
:
1658 case JSOp::StrictSpreadEval
:
1659 case JSOp::CheckClassHeritage
:
1660 case JSOp::FunWithProto
:
1661 case JSOp::ObjWithProto
:
1662 case JSOp::BuiltinObject
:
1663 case JSOp::CheckThis
:
1664 case JSOp::CheckReturn
:
1665 case JSOp::CheckThisReinit
:
1666 case JSOp::SuperFun
:
1667 case JSOp::SpreadSuperCall
:
1668 case JSOp::SuperCall
:
1669 case JSOp::PushVarEnv
:
1670 case JSOp::GetBoundName
:
1671 case JSOp::Exception
:
1672 case JSOp::ExceptionAndStack
:
1673 case JSOp::IsGenClosing
:
1674 case JSOp::FinalYieldRval
:
1676 case JSOp::CheckResumeKind
:
1677 case JSOp::AfterYield
:
1678 case JSOp::MaybeExtractAwaitValue
:
1679 case JSOp::Generator
:
1680 case JSOp::AsyncAwait
:
1681 case JSOp::AsyncResolve
:
1684 case JSOp::ThrowMsg
:
1685 case JSOp::ForceInterpreter
:
1686 #ifdef ENABLE_RECORD_TUPLE
1687 case JSOp::InitRecord
:
1688 case JSOp::AddRecordProperty
:
1689 case JSOp::AddRecordSpread
:
1690 case JSOp::FinishRecord
:
1691 case JSOp::InitTuple
:
1692 case JSOp::AddTupleElement
:
1693 case JSOp::FinishTuple
:
1697 case JSOp::InitAliasedLexical
: {
1698 uint32_t hops
= EnvironmentCoordinate(pc
).hops();
1700 // Initializing aliased lexical in the current scope is almost same
1701 // as JSOp::InitLexical.
1705 // Otherwise this can touch an environment outside of the current scope.
1710 MOZ_ASSERT_UNREACHABLE("Invalid opcode");
1714 bool DebuggerScript::CallData::getEffectfulOffsets() {
1715 if (!ensureScript()) {
1719 RootedObject
result(cx
, NewDenseEmptyArray(cx
));
1723 for (BytecodeRange
r(cx
, script
); !r
.empty(); r
.popFront()) {
1724 size_t offset
= r
.frontOffset();
1725 if (!BytecodeIsEffectful(script
, offset
)) {
1729 if (IsGeneratorSlotInitialization(script
, offset
, cx
)) {
1730 // This is engine-internal operation and not visible outside the
1731 // currently executing frame.
1733 // Also this offset is not allowed for setting breakpoint.
1737 if (!NewbornArrayPush(cx
, result
, NumberValue(offset
))) {
1742 args
.rval().setObject(*result
);
1746 bool DebuggerScript::CallData::getAllOffsets() {
1747 if (!ensureScript()) {
1751 // First pass: determine which offsets in this script are jump targets and
1752 // which line numbers jump to them.
1753 FlowGraphSummary
flowData(cx
);
1754 if (!flowData
.populate(cx
, script
)) {
1758 // Second pass: build the result array.
1759 RootedObject
result(cx
, NewDenseEmptyArray(cx
));
1763 for (BytecodeRangeWithPosition
r(cx
, script
); !r
.empty(); r
.popFront()) {
1764 if (!r
.frontIsEntryPoint()) {
1768 size_t offset
= r
.frontOffset();
1769 uint32_t lineno
= r
.frontLineNumber();
1771 // Make a note, if the current instruction is an entry point for the current
1773 if (!flowData
[offset
].hasNoEdges() && flowData
[offset
].lineno() != lineno
) {
1774 // Get the offsets array for this line.
1775 RootedObject
offsets(cx
);
1776 RootedValue
offsetsv(cx
);
1778 RootedId
id(cx
, PropertyKey::Int(lineno
));
1781 if (!HasOwnProperty(cx
, result
, id
, &found
)) {
1784 if (found
&& !GetProperty(cx
, result
, result
, id
, &offsetsv
)) {
1788 if (offsetsv
.isObject()) {
1789 offsets
= &offsetsv
.toObject();
1791 MOZ_ASSERT(offsetsv
.isUndefined());
1793 // Create an empty offsets array for this line.
1794 // Store it in the result array.
1796 RootedValue
v(cx
, NumberValue(lineno
));
1797 offsets
= NewDenseEmptyArray(cx
);
1798 if (!offsets
|| !PrimitiveValueToId
<CanGC
>(cx
, v
, &id
)) {
1802 RootedValue
value(cx
, ObjectValue(*offsets
));
1803 if (!DefineDataProperty(cx
, result
, id
, value
)) {
1808 // Append the current offset to the offsets array.
1809 if (!NewbornArrayPush(cx
, offsets
, NumberValue(offset
))) {
1815 args
.rval().setObject(*result
);
1819 class DebuggerScript::GetAllColumnOffsetsMatcher
{
1821 MutableHandleObject result_
;
1823 bool appendColumnOffsetEntry(uint32_t lineno
,
1824 JS::LimitedColumnNumberOneOrigin column
,
1826 Rooted
<PlainObject
*> entry(cx_
, NewPlainObject(cx_
));
1831 RootedValue
value(cx_
, NumberValue(lineno
));
1832 if (!DefineDataProperty(cx_
, entry
, cx_
->names().lineNumber
, value
)) {
1836 value
= NumberValue(column
.oneOriginValue());
1837 if (!DefineDataProperty(cx_
, entry
, cx_
->names().columnNumber
, value
)) {
1841 value
= NumberValue(offset
);
1842 if (!DefineDataProperty(cx_
, entry
, cx_
->names().offset
, value
)) {
1846 return NewbornArrayPush(cx_
, result_
, ObjectValue(*entry
));
1850 explicit GetAllColumnOffsetsMatcher(JSContext
* cx
, MutableHandleObject result
)
1851 : cx_(cx
), result_(result
) {}
1852 using ReturnType
= bool;
1853 ReturnType
match(Handle
<BaseScript
*> base
) {
1854 RootedScript
script(cx_
, DelazifyScript(cx_
, base
));
1859 // First pass: determine which offsets in this script are jump targets
1860 // and which positions jump to them.
1861 FlowGraphSummary
flowData(cx_
);
1862 if (!flowData
.populate(cx_
, script
)) {
1866 // Second pass: build the result array.
1867 result_
.set(NewDenseEmptyArray(cx_
));
1872 for (BytecodeRangeWithPosition
r(cx_
, script
); !r
.empty(); r
.popFront()) {
1873 uint32_t lineno
= r
.frontLineNumber();
1874 JS::LimitedColumnNumberOneOrigin column
= r
.frontColumnNumber();
1875 size_t offset
= r
.frontOffset();
1877 // Make a note, if the current instruction is an entry point for
1878 // the current position.
1879 if (r
.frontIsEntryPoint() && !flowData
[offset
].hasNoEdges() &&
1880 (flowData
[offset
].lineno() != lineno
||
1881 flowData
[offset
].columnOrSentinel() != column
.oneOriginValue())) {
1882 if (!appendColumnOffsetEntry(lineno
, column
, offset
)) {
1889 ReturnType
match(Handle
<WasmInstanceObject
*> instanceObj
) {
1890 wasm::Instance
& instance
= instanceObj
->instance();
1892 Vector
<wasm::ExprLoc
> offsets(cx_
);
1893 if (instance
.debugEnabled() &&
1894 !instance
.debug().getAllColumnOffsets(&offsets
)) {
1898 result_
.set(NewDenseEmptyArray(cx_
));
1903 for (uint32_t i
= 0; i
< offsets
.length(); i
++) {
1904 uint32_t lineno
= offsets
[i
].lineno
;
1905 JS::LimitedColumnNumberOneOrigin
column(offsets
[i
].column
);
1906 size_t offset
= offsets
[i
].offset
;
1907 if (!appendColumnOffsetEntry(lineno
, column
, offset
)) {
1915 bool DebuggerScript::CallData::getAllColumnOffsets() {
1916 RootedObject
result(cx
);
1917 GetAllColumnOffsetsMatcher
matcher(cx
, &result
);
1918 if (!referent
.match(matcher
)) {
1922 args
.rval().setObject(*result
);
1926 class DebuggerScript::GetLineOffsetsMatcher
{
1929 MutableHandleObject result_
;
1932 explicit GetLineOffsetsMatcher(JSContext
* cx
, uint32_t lineno
,
1933 MutableHandleObject result
)
1934 : cx_(cx
), lineno_(lineno
), result_(result
) {}
1935 using ReturnType
= bool;
1936 ReturnType
match(Handle
<BaseScript
*> base
) {
1937 RootedScript
script(cx_
, DelazifyScript(cx_
, base
));
1942 // First pass: determine which offsets in this script are jump targets and
1943 // which line numbers jump to them.
1944 FlowGraphSummary
flowData(cx_
);
1945 if (!flowData
.populate(cx_
, script
)) {
1949 result_
.set(NewDenseEmptyArray(cx_
));
1954 // Second pass: build the result array.
1955 for (BytecodeRangeWithPosition
r(cx_
, script
); !r
.empty(); r
.popFront()) {
1956 if (!r
.frontIsEntryPoint()) {
1960 size_t offset
= r
.frontOffset();
1962 // If the op at offset is an entry point, append offset to result.
1963 if (r
.frontLineNumber() == lineno_
&& !flowData
[offset
].hasNoEdges() &&
1964 flowData
[offset
].lineno() != lineno_
) {
1965 if (!NewbornArrayPush(cx_
, result_
, NumberValue(offset
))) {
1973 ReturnType
match(Handle
<WasmInstanceObject
*> instanceObj
) {
1974 wasm::Instance
& instance
= instanceObj
->instance();
1976 Vector
<uint32_t> offsets(cx_
);
1977 if (instance
.debugEnabled() &&
1978 !instance
.debug().getLineOffsets(lineno_
, &offsets
)) {
1982 result_
.set(NewDenseEmptyArray(cx_
));
1987 for (uint32_t i
= 0; i
< offsets
.length(); i
++) {
1988 if (!NewbornArrayPush(cx_
, result_
, NumberValue(offsets
[i
]))) {
1996 bool DebuggerScript::CallData::getLineOffsets() {
1997 if (!args
.requireAtLeast(cx
, "Debugger.Script.getLineOffsets", 1)) {
2001 // Parse lineno argument.
2002 RootedValue
linenoValue(cx
, args
[0]);
2004 if (!ToNumber(cx
, &linenoValue
)) {
2008 double d
= linenoValue
.toNumber();
2009 lineno
= uint32_t(d
);
2011 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2012 JSMSG_DEBUG_BAD_LINE
);
2017 RootedObject
result(cx
);
2018 GetLineOffsetsMatcher
matcher(cx
, lineno
, &result
);
2019 if (!referent
.match(matcher
)) {
2023 args
.rval().setObject(*result
);
2027 struct DebuggerScript::SetBreakpointMatcher
{
2031 RootedObject handler_
;
2032 RootedObject debuggerObject_
;
2034 bool wrapCrossCompartmentEdges() {
2035 if (!cx_
->compartment()->wrap(cx_
, &handler_
) ||
2036 !cx_
->compartment()->wrap(cx_
, &debuggerObject_
)) {
2040 // If the Debugger's compartment has killed incoming wrappers, we may not
2041 // have gotten usable results from the 'wrap' calls. Treat it as a
2043 if (IsDeadProxyObject(handler_
) || IsDeadProxyObject(debuggerObject_
)) {
2044 ReportAccessDenied(cx_
);
2052 explicit SetBreakpointMatcher(JSContext
* cx
, Debugger
* dbg
, size_t offset
,
2053 HandleObject handler
)
2057 handler_(cx
, handler
),
2058 debuggerObject_(cx_
, dbg_
->toJSObject()) {}
2060 using ReturnType
= bool;
2062 ReturnType
match(Handle
<BaseScript
*> base
) {
2063 RootedScript
script(cx_
, DelazifyScript(cx_
, base
));
2068 if (!dbg_
->observesScript(script
)) {
2069 JS_ReportErrorNumberASCII(cx_
, GetErrorMessage
, nullptr,
2070 JSMSG_DEBUG_NOT_DEBUGGING
);
2074 if (!EnsureScriptOffsetIsValid(cx_
, script
, offset_
)) {
2078 if (!EnsureBreakpointIsAllowed(cx_
, script
, offset_
)) {
2082 // Ensure observability *before* setting the breakpoint. If the script is
2083 // not already a debuggee, trying to ensure observability after setting
2084 // the breakpoint (and thus marking the script as a debuggee) will skip
2085 // actually ensuring observability.
2086 if (!dbg_
->ensureExecutionObservabilityOfScript(cx_
, script
)) {
2090 // A Breakpoint belongs logically to its script's compartment, so its
2091 // references to its Debugger and handler must be properly wrapped.
2092 AutoRealm
ar(cx_
, script
);
2093 if (!wrapCrossCompartmentEdges()) {
2097 jsbytecode
* pc
= script
->offsetToPC(offset_
);
2098 JSBreakpointSite
* site
=
2099 DebugScript::getOrCreateBreakpointSite(cx_
, script
, pc
);
2104 if (!cx_
->zone()->new_
<Breakpoint
>(dbg_
, debuggerObject_
, site
, handler_
)) {
2105 site
->destroyIfEmpty(cx_
->runtime()->gcContext());
2108 AddCellMemory(script
, sizeof(Breakpoint
), MemoryUse::Breakpoint
);
2112 ReturnType
match(Handle
<WasmInstanceObject
*> wasmInstance
) {
2113 wasm::Instance
& instance
= wasmInstance
->instance();
2114 if (!instance
.debugEnabled() ||
2115 !instance
.debug().hasBreakpointTrapAtOffset(offset_
)) {
2116 JS_ReportErrorNumberASCII(cx_
, GetErrorMessage
, nullptr,
2117 JSMSG_DEBUG_BAD_OFFSET
);
2121 // A Breakpoint belongs logically to its Instance's compartment, so its
2122 // references to its Debugger and handler must be properly wrapped.
2123 AutoRealm
ar(cx_
, wasmInstance
);
2124 if (!wrapCrossCompartmentEdges()) {
2128 WasmBreakpointSite
* site
= instance
.getOrCreateBreakpointSite(cx_
, offset_
);
2133 if (!cx_
->zone()->new_
<Breakpoint
>(dbg_
, debuggerObject_
, site
, handler_
)) {
2134 site
->destroyIfEmpty(cx_
->runtime()->gcContext());
2137 AddCellMemory(wasmInstance
, sizeof(Breakpoint
), MemoryUse::Breakpoint
);
2143 bool DebuggerScript::CallData::setBreakpoint() {
2144 if (!args
.requireAtLeast(cx
, "Debugger.Script.setBreakpoint", 2)) {
2147 Debugger
* dbg
= obj
->owner();
2150 if (!ScriptOffset(cx
, args
[0], &offset
)) {
2154 RootedObject
handler(cx
, RequireObject(cx
, args
[1]));
2159 SetBreakpointMatcher
matcher(cx
, dbg
, offset
, handler
);
2160 if (!referent
.match(matcher
)) {
2163 args
.rval().setUndefined();
2167 bool DebuggerScript::CallData::getBreakpoints() {
2168 if (!ensureScript()) {
2171 Debugger
* dbg
= obj
->owner();
2174 if (args
.length() > 0) {
2176 if (!ScriptOffset(cx
, args
[0], &offset
) ||
2177 !EnsureScriptOffsetIsValid(cx
, script
, offset
)) {
2180 pc
= script
->offsetToPC(offset
);
2185 RootedObject
arr(cx
, NewDenseEmptyArray(cx
));
2190 for (unsigned i
= 0; i
< script
->length(); i
++) {
2191 JSBreakpointSite
* site
=
2192 DebugScript::getBreakpointSite(script
, script
->offsetToPC(i
));
2196 if (!pc
|| site
->pc
== pc
) {
2197 for (Breakpoint
* bp
= site
->firstBreakpoint(); bp
;
2198 bp
= bp
->nextInSite()) {
2199 if (bp
->debugger
== dbg
) {
2200 RootedObject
handler(cx
, bp
->getHandler());
2201 if (!cx
->compartment()->wrap(cx
, &handler
) ||
2202 !NewbornArrayPush(cx
, arr
, ObjectValue(*handler
))) {
2209 args
.rval().setObject(*arr
);
2213 class DebuggerScript::ClearBreakpointMatcher
{
2216 RootedObject handler_
;
2219 ClearBreakpointMatcher(JSContext
* cx
, Debugger
* dbg
, JSObject
* handler
)
2220 : cx_(cx
), dbg_(dbg
), handler_(cx
, handler
) {}
2221 using ReturnType
= bool;
2223 ReturnType
match(Handle
<BaseScript
*> base
) {
2224 RootedScript
script(cx_
, DelazifyScript(cx_
, base
));
2229 // A Breakpoint belongs logically to its script's compartment, so it holds
2230 // its handler via a cross-compartment wrapper. But the handler passed to
2231 // `clearBreakpoint` is same-compartment with the Debugger. Wrap it here,
2232 // so that `DebugScript::clearBreakpointsIn` gets the right value to
2234 AutoRealm
ar(cx_
, script
);
2235 if (!cx_
->compartment()->wrap(cx_
, &handler_
)) {
2239 DebugScript::clearBreakpointsIn(cx_
->runtime()->gcContext(), script
, dbg_
,
2243 ReturnType
match(Handle
<WasmInstanceObject
*> instanceObj
) {
2244 wasm::Instance
& instance
= instanceObj
->instance();
2245 if (!instance
.debugEnabled()) {
2249 // A Breakpoint belongs logically to its instance's compartment, so it
2250 // holds its handler via a cross-compartment wrapper. But the handler
2251 // passed to `clearBreakpoint` is same-compartment with the Debugger. Wrap
2252 // it here, so that `DebugState::clearBreakpointsIn` gets the right value
2254 AutoRealm
ar(cx_
, instanceObj
);
2255 if (!cx_
->compartment()->wrap(cx_
, &handler_
)) {
2259 instance
.debug().clearBreakpointsIn(cx_
->runtime()->gcContext(),
2260 instanceObj
, dbg_
, handler_
);
2265 bool DebuggerScript::CallData::clearBreakpoint() {
2266 if (!args
.requireAtLeast(cx
, "Debugger.Script.clearBreakpoint", 1)) {
2269 Debugger
* dbg
= obj
->owner();
2271 JSObject
* handler
= RequireObject(cx
, args
[0]);
2276 ClearBreakpointMatcher
matcher(cx
, dbg
, handler
);
2277 if (!referent
.match(matcher
)) {
2281 args
.rval().setUndefined();
2285 bool DebuggerScript::CallData::clearAllBreakpoints() {
2286 Debugger
* dbg
= obj
->owner();
2287 ClearBreakpointMatcher
matcher(cx
, dbg
, nullptr);
2288 if (!referent
.match(matcher
)) {
2291 args
.rval().setUndefined();
2295 class DebuggerScript::IsInCatchScopeMatcher
{
2301 explicit IsInCatchScopeMatcher(JSContext
* cx
, size_t offset
)
2302 : cx_(cx
), offset_(offset
), isInCatch_(false) {}
2303 using ReturnType
= bool;
2305 inline bool isInCatch() const { return isInCatch_
; }
2307 ReturnType
match(Handle
<BaseScript
*> base
) {
2308 RootedScript
script(cx_
, DelazifyScript(cx_
, base
));
2313 if (!EnsureScriptOffsetIsValid(cx_
, script
, offset_
)) {
2317 MOZ_ASSERT(!isInCatch_
);
2318 for (const TryNote
& tn
: script
->trynotes()) {
2319 bool inRange
= tn
.start
<= offset_
&& offset_
< tn
.start
+ tn
.length
;
2320 if (inRange
&& tn
.kind() == TryNoteKind::Catch
) {
2322 } else if (isInCatch_
) {
2323 // For-of loops generate a synthetic catch block to handle
2324 // closing the iterator when throwing an exception. The
2325 // debugger should ignore these synthetic catch blocks, so
2326 // we skip any Catch trynote that is immediately followed
2327 // by a ForOf trynote.
2328 if (inRange
&& tn
.kind() == TryNoteKind::ForOf
) {
2338 ReturnType
match(Handle
<WasmInstanceObject
*> instance
) {
2344 bool DebuggerScript::CallData::isInCatchScope() {
2345 if (!args
.requireAtLeast(cx
, "Debugger.Script.isInCatchScope", 1)) {
2350 if (!ScriptOffset(cx
, args
[0], &offset
)) {
2354 IsInCatchScopeMatcher
matcher(cx
, offset
);
2355 if (!referent
.match(matcher
)) {
2358 args
.rval().setBoolean(matcher
.isInCatch());
2362 bool DebuggerScript::CallData::getOffsetsCoverage() {
2363 if (!ensureScript()) {
2367 Debugger
* dbg
= obj
->owner();
2368 if (dbg
->observesCoverage() != Debugger::Observing
) {
2369 args
.rval().setNull();
2373 // If the script has no coverage information, then skip this and return null
2375 if (!script
->hasScriptCounts()) {
2376 args
.rval().setNull();
2380 ScriptCounts
* sc
= &script
->getScriptCounts();
2382 // If the main ever got visited, then assume that any code before main got
2385 const PCCounts
* counts
=
2386 sc
->maybeGetPCCounts(script
->pcToOffset(script
->main()));
2387 if (counts
->numExec()) {
2391 // Build an array of objects which are composed of 4 properties:
2392 // - offset PC offset of the current opcode.
2393 // - lineNumber Line of the current opcode.
2394 // - columnNumber Column of the current opcode.
2395 // - count Number of times the instruction got executed.
2396 RootedObject
result(cx
, NewDenseEmptyArray(cx
));
2401 RootedId
offsetId(cx
, NameToId(cx
->names().offset
));
2402 RootedId
lineNumberId(cx
, NameToId(cx
->names().lineNumber
));
2403 RootedId
columnNumberId(cx
, NameToId(cx
->names().columnNumber
));
2404 RootedId
countId(cx
, NameToId(cx
->names().count
));
2406 RootedObject
item(cx
);
2407 RootedValue
offsetValue(cx
);
2408 RootedValue
lineNumberValue(cx
);
2409 RootedValue
columnNumberValue(cx
);
2410 RootedValue
countValue(cx
);
2412 // Iterate linearly over the bytecode.
2413 for (BytecodeRangeWithPosition
r(cx
, script
); !r
.empty(); r
.popFront()) {
2414 size_t offset
= r
.frontOffset();
2416 // The beginning of each non-branching sequences of instruction set the
2417 // number of execution of the current instruction and any following
2419 counts
= sc
->maybeGetPCCounts(offset
);
2421 hits
= counts
->numExec();
2424 offsetValue
.setNumber(double(offset
));
2425 lineNumberValue
.setNumber(double(r
.frontLineNumber()));
2426 columnNumberValue
.setNumber(double(r
.frontColumnNumber().oneOriginValue()));
2427 countValue
.setNumber(double(hits
));
2429 // Create a new object with the offset, line number, column number, the
2430 // number of hit counts, and append it to the array.
2431 item
= NewPlainObjectWithProto(cx
, nullptr);
2432 if (!item
|| !DefineDataProperty(cx
, item
, offsetId
, offsetValue
) ||
2433 !DefineDataProperty(cx
, item
, lineNumberId
, lineNumberValue
) ||
2434 !DefineDataProperty(cx
, item
, columnNumberId
, columnNumberValue
) ||
2435 !DefineDataProperty(cx
, item
, countId
, countValue
) ||
2436 !NewbornArrayPush(cx
, result
, ObjectValue(*item
))) {
2440 // If the current instruction has thrown, then decrement the hit counts
2441 // with the number of throws.
2442 counts
= sc
->maybeGetThrowCounts(offset
);
2444 hits
-= counts
->numExec();
2448 args
.rval().setObject(*result
);
2453 bool DebuggerScript::construct(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2454 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr, JSMSG_NO_CONSTRUCTOR
,
2459 const JSPropertySpec
DebuggerScript::properties_
[] = {
2460 JS_DEBUG_PSG("isGeneratorFunction", getIsGeneratorFunction
),
2461 JS_DEBUG_PSG("isAsyncFunction", getIsAsyncFunction
),
2462 JS_DEBUG_PSG("isFunction", getIsFunction
),
2463 JS_DEBUG_PSG("isModule", getIsModule
),
2464 JS_DEBUG_PSG("displayName", getDisplayName
),
2465 JS_DEBUG_PSG("parameterNames", getParameterNames
),
2466 JS_DEBUG_PSG("url", getUrl
),
2467 JS_DEBUG_PSG("startLine", getStartLine
),
2468 JS_DEBUG_PSG("startColumn", getStartColumn
),
2469 JS_DEBUG_PSG("lineCount", getLineCount
),
2470 JS_DEBUG_PSG("source", getSource
),
2471 JS_DEBUG_PSG("sourceStart", getSourceStart
),
2472 JS_DEBUG_PSG("sourceLength", getSourceLength
),
2473 JS_DEBUG_PSG("mainOffset", getMainOffset
),
2474 JS_DEBUG_PSG("global", getGlobal
),
2475 JS_DEBUG_PSG("format", getFormat
),
2478 const JSFunctionSpec
DebuggerScript::methods_
[] = {
2479 JS_DEBUG_FN("getChildScripts", getChildScripts
, 0),
2480 JS_DEBUG_FN("getPossibleBreakpoints", getPossibleBreakpoints
, 0),
2481 JS_DEBUG_FN("getPossibleBreakpointOffsets", getPossibleBreakpointOffsets
,
2483 JS_DEBUG_FN("setBreakpoint", setBreakpoint
, 2),
2484 JS_DEBUG_FN("getBreakpoints", getBreakpoints
, 1),
2485 JS_DEBUG_FN("clearBreakpoint", clearBreakpoint
, 1),
2486 JS_DEBUG_FN("clearAllBreakpoints", clearAllBreakpoints
, 0),
2487 JS_DEBUG_FN("isInCatchScope", isInCatchScope
, 1),
2488 JS_DEBUG_FN("getOffsetMetadata", getOffsetMetadata
, 1),
2489 JS_DEBUG_FN("getOffsetsCoverage", getOffsetsCoverage
, 0),
2490 JS_DEBUG_FN("getEffectfulOffsets", getEffectfulOffsets
, 1),
2492 // The following APIs are deprecated due to their reliance on the
2493 // under-defined 'entrypoint' concept. Make use of getPossibleBreakpoints,
2494 // getPossibleBreakpointOffsets, or getOffsetMetadata instead.
2495 JS_DEBUG_FN("getAllOffsets", getAllOffsets
, 0),
2496 JS_DEBUG_FN("getAllColumnOffsets", getAllColumnOffsets
, 0),
2497 JS_DEBUG_FN("getLineOffsets", getLineOffsets
, 1),
2498 JS_DEBUG_FN("getOffsetLocation", getOffsetLocation
, 0), JS_FS_END
};