Bug 1843499 - Part 1: Add ThrowWithStack and ExceptionAndStack opcodes. r=iain
[gecko.git] / js / src / debugger / Script.cpp
blob9762da93260200848f4d741418d0b0a76d1e2c43
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
60 using namespace js;
62 using mozilla::Maybe;
63 using mozilla::Some;
65 const JSClassOps DebuggerScript::classOps_ = {
66 nullptr, // addProperty
67 nullptr, // delProperty
68 nullptr, // enumerate
69 nullptr, // newEnumerate
70 nullptr, // resolve
71 nullptr, // mayResolve
72 nullptr, // finalize
73 nullptr, // call
74 nullptr, // construct
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();
84 if (cell) {
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);
92 } else {
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);
104 /* static */
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);
112 /* static */
113 DebuggerScript* DebuggerScript::create(JSContext* cx, HandleObject proto,
114 Handle<DebuggerScriptReferent> referent,
115 Handle<NativeObject*> debugger) {
116 DebuggerScript* scriptobj =
117 NewTenuredObjectWithGivenProto<DebuggerScript>(cx, proto);
118 if (!scriptobj) {
119 return nullptr;
122 scriptobj->setReservedSlot(DebuggerScript::OWNER_SLOT,
123 ObjectValue(*debugger));
124 referent.get().match([&](auto& scriptHandle) {
125 scriptobj->setReservedSlotGCThingAsPrivate(SCRIPT_SLOT, scriptHandle);
128 return scriptobj;
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)) {
142 return nullptr;
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);
151 return nullptr;
155 MOZ_ASSERT(script->enclosingScope());
157 RootedFunction fun(cx, script->function());
158 AutoRealm ar(cx, fun);
159 return JSFunction::getOrCreateScript(cx, fun);
162 /* static */
163 DebuggerScript* DebuggerScript::check(JSContext* cx, HandleValue v) {
164 JSObject* thisobj = RequireObject(cx, v);
165 if (!thisobj) {
166 return nullptr;
168 if (!thisobj->is<DebuggerScript>()) {
169 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
170 JSMSG_INCOMPATIBLE_PROTO, "Debugger.Script",
171 "method", thisobj->getClass()->name);
172 return nullptr;
175 return &thisobj->as<DebuggerScript>();
178 struct MOZ_STACK_CLASS DebuggerScript::CallData {
179 JSContext* cx;
180 const CallArgs& args;
182 Handle<DebuggerScript*> obj;
183 Rooted<DebuggerScriptReferent> referent;
184 RootedScript script;
186 CallData(JSContext* cx, const CallArgs& args, Handle<DebuggerScript*> obj)
187 : cx(cx),
188 args(args),
189 obj(obj),
190 referent(cx, obj->getReferent()),
191 script(cx) {}
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");
197 return false;
199 return true;
202 [[nodiscard]] bool ensureScript() {
203 if (!ensureScriptMaybeLazy()) {
204 return false;
206 script = DelazifyScript(cx, referent.as<BaseScript*>());
207 if (!script) {
208 return false;
210 return true;
213 bool getIsGeneratorFunction();
214 bool getIsAsyncFunction();
215 bool getIsFunction();
216 bool getIsModule();
217 bool getDisplayName();
218 bool getParameterNames();
219 bool getUrl();
220 bool getStartLine();
221 bool getStartColumn();
222 bool getLineCount();
223 bool getSource();
224 bool getSourceStart();
225 bool getSourceLength();
226 bool getMainOffset();
227 bool getGlobal();
228 bool getFormat();
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>
252 /* static */
253 bool DebuggerScript::CallData::ToNative(JSContext* cx, unsigned argc,
254 Value* vp) {
255 CallArgs args = CallArgsFromVp(argc, vp);
257 Rooted<DebuggerScript*> obj(cx, DebuggerScript::check(cx, args.thisv()));
258 if (!obj) {
259 return false;
262 CallData data(cx, args, obj);
263 return (data.*MyMethod)();
266 bool DebuggerScript::CallData::getIsGeneratorFunction() {
267 if (!ensureScriptMaybeLazy()) {
268 return false;
270 args.rval().setBoolean(obj->getReferentScript()->isGenerator());
271 return true;
274 bool DebuggerScript::CallData::getIsAsyncFunction() {
275 if (!ensureScriptMaybeLazy()) {
276 return false;
278 args.rval().setBoolean(obj->getReferentScript()->isAsync());
279 return true;
282 bool DebuggerScript::CallData::getIsFunction() {
283 if (!ensureScriptMaybeLazy()) {
284 return false;
287 args.rval().setBoolean(obj->getReferentScript()->function());
288 return true;
291 bool DebuggerScript::CallData::getIsModule() {
292 if (!ensureScriptMaybeLazy()) {
293 return false;
295 BaseScript* script = referent.as<BaseScript*>();
297 args.rval().setBoolean(script->isModule());
298 return true;
301 bool DebuggerScript::CallData::getDisplayName() {
302 if (!ensureScriptMaybeLazy()) {
303 return false;
306 JSFunction* func = obj->getReferentScript()->function();
307 if (!func) {
308 args.rval().setUndefined();
309 return true;
312 JSAtom* name = func->fullDisplayAtom();
313 if (!name) {
314 args.rval().setUndefined();
315 return true;
318 RootedValue namev(cx, StringValue(name));
319 Debugger* dbg = obj->owner();
320 if (!dbg->wrapDebuggeeValue(cx, &namev)) {
321 return false;
323 args.rval().set(namev);
324 return true;
327 bool DebuggerScript::CallData::getParameterNames() {
328 if (!ensureScript()) {
329 return false;
332 RootedFunction fun(cx, referent.as<BaseScript*>()->function());
333 if (!fun) {
334 args.rval().setUndefined();
335 return true;
338 ArrayObject* arr = GetFunctionParameterNamesArray(cx, fun);
339 if (!arr) {
340 return false;
343 args.rval().setObject(*arr);
344 return true;
347 bool DebuggerScript::CallData::getUrl() {
348 if (!ensureScriptMaybeLazy()) {
349 return false;
352 Rooted<BaseScript*> script(cx, referent.as<BaseScript*>());
354 if (script->filename()) {
355 JSString* str;
356 if (const char* introducer = script->scriptSource()->introducerFilename()) {
357 str =
358 NewStringCopyUTF8N(cx, JS::UTF8Chars(introducer, strlen(introducer)));
359 } else {
360 const char* filename = script->filename();
361 str = NewStringCopyUTF8N(cx, JS::UTF8Chars(filename, strlen(filename)));
363 if (!str) {
364 return false;
366 args.rval().setString(str);
367 } else {
368 args.rval().setNull();
370 return true;
373 bool DebuggerScript::CallData::getStartLine() {
374 args.rval().setNumber(
375 referent.get().match([](BaseScript*& s) { return s->lineno(); },
376 [](WasmInstanceObject*&) { return (uint32_t)1; }));
377 return true;
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());
388 return true;
391 struct DebuggerScript::GetLineCountMatcher {
392 JSContext* cx_;
393 double totalLines;
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));
400 if (!script) {
401 return false;
403 totalLines = double(GetScriptLineExtent(script));
404 return true;
406 ReturnType match(Handle<WasmInstanceObject*> instanceObj) {
407 wasm::Instance& instance = instanceObj->instance();
408 if (instance.debugEnabled()) {
409 totalLines = double(instance.debug().bytecode().length());
410 } else {
411 totalLines = 0;
413 return true;
417 bool DebuggerScript::CallData::getLineCount() {
418 GetLineCountMatcher matcher(cx);
419 if (!referent.match(matcher)) {
420 return false;
422 args.rval().setNumber(matcher.totalLines);
423 return true;
426 class DebuggerScript::GetSourceMatcher {
427 JSContext* cx_;
428 Debugger* dbg_;
430 public:
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));
449 if (!sourceObject) {
450 return false;
453 args.rval().setObject(*sourceObject);
454 return true;
457 bool DebuggerScript::CallData::getSourceStart() {
458 if (!ensureScriptMaybeLazy()) {
459 return false;
461 args.rval().setNumber(uint32_t(obj->getReferentScript()->sourceStart()));
462 return true;
465 bool DebuggerScript::CallData::getSourceLength() {
466 if (!ensureScriptMaybeLazy()) {
467 return false;
469 args.rval().setNumber(uint32_t(obj->getReferentScript()->sourceLength()));
470 return true;
473 bool DebuggerScript::CallData::getMainOffset() {
474 if (!ensureScript()) {
475 return false;
477 args.rval().setNumber(uint32_t(script->mainOffset()));
478 return true;
481 bool DebuggerScript::CallData::getGlobal() {
482 if (!ensureScript()) {
483 return false;
485 Debugger* dbg = obj->owner();
487 RootedValue v(cx, ObjectValue(script->global()));
488 if (!dbg->wrapDebuggeeValue(cx, &v)) {
489 return false;
491 args.rval().set(v);
492 return true;
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(); }));
499 return true;
502 static bool PushFunctionScript(JSContext* cx, Debugger* dbg, HandleFunction fun,
503 HandleObject array) {
504 // Ignore asm.js natives.
505 if (!IsInterpretedNonSelfHostedFunction(fun)) {
506 return true;
509 Rooted<BaseScript*> script(cx, fun->baseScript());
510 MOZ_ASSERT(script);
511 if (!script) {
512 // If the function doesn't have script, ignore it.
513 return true;
515 RootedObject wrapped(cx, dbg->wrapScript(cx, script));
516 if (!wrapped) {
517 return false;
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>()) {
529 continue;
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()) {
539 continue;
542 if (!PushFunctionScript(cx, dbg, fun, array)) {
543 return false;
548 return true;
551 bool DebuggerScript::CallData::getChildScripts() {
552 if (!ensureScriptMaybeLazy()) {
553 return false;
555 Debugger* dbg = obj->owner();
557 RootedObject result(cx, NewDenseEmptyArray(cx));
558 if (!result) {
559 return false;
562 Rooted<BaseScript*> script(cx, obj->getReferent().as<BaseScript*>());
563 if (!PushInnerFunctions(cx, dbg, result, script->gcthings())) {
564 return false;
567 args.rval().setObject(*result);
568 return true;
571 static bool ScriptOffset(JSContext* cx, const Value& v, size_t* offsetp) {
572 double d;
573 size_t off;
575 bool ok = v.isNumber();
576 if (ok) {
577 d = v.toNumber();
578 off = size_t(d);
580 if (!ok || off != d) {
581 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
582 JSMSG_DEBUG_BAD_OFFSET);
583 return false;
585 *offsetp = off;
586 return true;
589 static bool EnsureScriptOffsetIsValid(JSContext* cx, JSScript* script,
590 size_t offset) {
591 if (IsValidBytecodeOffset(cx, script, offset)) {
592 return true;
594 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
595 JSMSG_DEBUG_BAD_OFFSET);
596 return false;
599 static bool IsGeneratorSlotInitialization(JSScript* script, size_t offset,
600 JSContext* cx) {
601 jsbytecode* pc = script->offsetToPC(offset);
602 if (JSOp(*pc) != JSOp::SetAliasedVar) {
603 return false;
606 PropertyName* name = EnvironmentCoordinateNameSlow(script, pc);
607 return name == cx->names().dot_generator_;
610 static bool EnsureBreakpointIsAllowed(JSContext* cx, JSScript* script,
611 size_t offset) {
612 // Disallow breakpoint for `JSOp::SetAliasedVar` after `JSOp::Generator`.
613 // Those 2 instructions are supposed to be atomic, and nothing should happen
614 // in between them.
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);
622 return false;
625 return true;
628 template <bool OnlyOffsets>
629 class DebuggerScript::GetPossibleBreakpointsMatcher {
630 JSContext* cx_;
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)) {
646 return false;
649 if (minLine) {
650 if (lineno < *minLine || (lineno == *minLine && colno < minColumn)) {
651 return false;
655 if (maxLine) {
656 if (lineno > *maxLine || (lineno == *maxLine && colno >= maxColumn)) {
657 return false;
661 return true;
664 bool maybeAppendEntry(size_t offset, uint32_t lineno,
665 JS::LimitedColumnNumberOneOrigin colno,
666 bool isStepStart) {
667 if (!passesQuery(offset, lineno, colno)) {
668 return true;
671 if (OnlyOffsets) {
672 if (!NewbornArrayPush(cx_, result_, NumberValue(offset))) {
673 return false;
676 return true;
679 Rooted<PlainObject*> entry(cx_, NewPlainObject(cx_));
680 if (!entry) {
681 return false;
684 RootedValue value(cx_, NumberValue(offset));
685 if (!DefineDataProperty(cx_, entry, cx_->names().offset, value)) {
686 return false;
689 value = NumberValue(lineno);
690 if (!DefineDataProperty(cx_, entry, cx_->names().lineNumber, value)) {
691 return false;
694 value = NumberValue(colno.oneOriginValue());
695 if (!DefineDataProperty(cx_, entry, cx_->names().columnNumber, value)) {
696 return false;
699 value = BooleanValue(isStepStart);
700 if (!DefineDataProperty(cx_, entry, cx_->names().isStepStart, value)) {
701 return false;
704 if (!NewbornArrayPush(cx_, result_, ObjectValue(*entry))) {
705 return false;
707 return true;
710 template <typename T>
711 bool parseIntValueImpl(HandleValue value, T* result) {
712 if (!value.isNumber()) {
713 return false;
716 double doubleOffset = value.toNumber();
717 if (doubleOffset < 0 || (unsigned int)doubleOffset != doubleOffset) {
718 return false;
721 *result = doubleOffset;
722 return true;
725 bool parseUint32Value(HandleValue value, uint32_t* result) {
726 return parseIntValueImpl(value, result);
728 bool parseColumnValue(HandleValue value,
729 JS::LimitedColumnNumberOneOrigin* result) {
730 uint32_t tmp;
731 if (!parseIntValueImpl(value, &tmp)) {
732 return false;
734 if (tmp == 0) {
735 return false;
737 *result->addressOfValueForTranscode() = tmp;
738 return true;
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) {
746 T result_;
747 if (!parseIntValueImpl(value, &result_)) {
748 return false;
751 *result = Some(result_);
752 return true;
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);
762 public:
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)) {
770 return false;
773 RootedValue minLineValue(cx_);
774 if (!GetProperty(cx_, query, query, cx_->names().minLine, &minLineValue)) {
775 return false;
778 RootedValue minColumnValue(cx_);
779 if (!GetProperty(cx_, query, query, cx_->names().minColumn,
780 &minColumnValue)) {
781 return false;
784 RootedValue minOffsetValue(cx_);
785 if (!GetProperty(cx_, query, query, cx_->names().minOffset,
786 &minOffsetValue)) {
787 return false;
790 RootedValue maxLineValue(cx_);
791 if (!GetProperty(cx_, query, query, cx_->names().maxLine, &maxLineValue)) {
792 return false;
795 RootedValue maxColumnValue(cx_);
796 if (!GetProperty(cx_, query, query, cx_->names().maxColumn,
797 &maxColumnValue)) {
798 return false;
801 RootedValue maxOffsetValue(cx_);
802 if (!GetProperty(cx_, query, query, cx_->names().maxOffset,
803 &maxOffsetValue)) {
804 return false;
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");
812 return false;
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");
820 return false;
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'");
830 return false;
833 uint32_t line;
834 if (!parseUint32Value(lineValue, &line)) {
835 JS_ReportErrorNumberASCII(
836 cx_, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
837 "getPossibleBreakpoints' 'line'", "not an integer");
838 return false;
841 // If no end column is given, we use the default of 0 and wrap to
842 // the next line.
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");
852 return false;
856 if (!minColumnValue.isUndefined()) {
857 if (!minLine) {
858 JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr,
859 JSMSG_UNEXPECTED_TYPE,
860 "getPossibleBreakpoints' 'minColumn'",
861 "not allowed without 'line' or 'minLine'");
862 return false;
865 if (!parseColumnValue(minColumnValue, &minColumn)) {
866 JS_ReportErrorNumberASCII(
867 cx_, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
868 "getPossibleBreakpoints' 'minColumn'", "not a positive integer");
869 return false;
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");
878 return false;
882 if (!maxColumnValue.isUndefined()) {
883 if (!maxLine) {
884 JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr,
885 JSMSG_UNEXPECTED_TYPE,
886 "getPossibleBreakpoints' 'maxColumn'",
887 "not allowed without 'line' or 'maxLine'");
888 return false;
891 if (!parseColumnValue(maxColumnValue, &maxColumn)) {
892 JS_ReportErrorNumberASCII(
893 cx_, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
894 "getPossibleBreakpoints' 'maxColumn'", "not a positive integer");
895 return false;
899 return true;
902 using ReturnType = bool;
903 ReturnType match(Handle<BaseScript*> base) {
904 RootedScript script(cx_, DelazifyScript(cx_, base));
905 if (!script) {
906 return false;
909 // Second pass: build the result array.
910 result_.set(NewDenseEmptyArray(cx_));
911 if (!result_) {
912 return false;
915 for (BytecodeRangeWithPosition r(cx_, script); !r.empty(); r.popFront()) {
916 if (!r.frontIsBreakablePoint()) {
917 continue;
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())) {
926 return false;
930 return true;
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)) {
938 return false;
941 result_.set(NewDenseEmptyArray(cx_));
942 if (!result_) {
943 return false;
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)) {
951 return false;
954 return 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)) {
964 return false;
967 if (!referent.match(matcher)) {
968 return false;
971 args.rval().setObject(*result);
972 return true;
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)) {
981 return false;
984 if (!referent.match(matcher)) {
985 return false;
988 args.rval().setObject(*result);
989 return true;
992 class DebuggerScript::GetOffsetMetadataMatcher {
993 JSContext* cx_;
994 size_t offset_;
995 MutableHandle<PlainObject*> result_;
997 public:
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));
1004 if (!script) {
1005 return false;
1008 if (!EnsureScriptOffsetIsValid(cx_, script, offset_)) {
1009 return false;
1012 result_.set(NewPlainObject(cx_));
1013 if (!result_) {
1014 return false;
1017 BytecodeRangeWithPosition r(cx_, script);
1018 while (!r.empty() && r.frontOffset() < offset_) {
1019 r.popFront();
1022 RootedValue value(cx_, NumberValue(r.frontLineNumber()));
1023 if (!DefineDataProperty(cx_, result_, cx_->names().lineNumber, value)) {
1024 return false;
1027 value = NumberValue(r.frontColumnNumber().oneOriginValue());
1028 if (!DefineDataProperty(cx_, result_, cx_->names().columnNumber, value)) {
1029 return false;
1032 value = BooleanValue(r.frontIsBreakablePoint());
1033 if (!DefineDataProperty(cx_, result_, cx_->names().isBreakpoint, value)) {
1034 return false;
1037 value = BooleanValue(r.frontIsBreakableStepPoint());
1038 if (!DefineDataProperty(cx_, result_, cx_->names().isStepStart, value)) {
1039 return false;
1042 return true;
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);
1049 return false;
1052 uint32_t lineno;
1053 JS::LimitedColumnNumberOneOrigin column;
1054 if (!instance.debug().getOffsetLocation(offset_, &lineno, &column)) {
1055 JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr,
1056 JSMSG_DEBUG_BAD_OFFSET);
1057 return false;
1060 result_.set(NewPlainObject(cx_));
1061 if (!result_) {
1062 return false;
1065 RootedValue value(cx_, NumberValue(lineno));
1066 if (!DefineDataProperty(cx_, result_, cx_->names().lineNumber, value)) {
1067 return false;
1070 value = NumberValue(column.oneOriginValue());
1071 if (!DefineDataProperty(cx_, result_, cx_->names().columnNumber, value)) {
1072 return false;
1075 value.setBoolean(true);
1076 if (!DefineDataProperty(cx_, result_, cx_->names().isBreakpoint, value)) {
1077 return false;
1080 value.setBoolean(true);
1081 if (!DefineDataProperty(cx_, result_, cx_->names().isStepStart, value)) {
1082 return false;
1085 return true;
1089 bool DebuggerScript::CallData::getOffsetMetadata() {
1090 if (!args.requireAtLeast(cx, "Debugger.Script.getOffsetMetadata", 1)) {
1091 return false;
1093 size_t offset;
1094 if (!ScriptOffset(cx, args[0], &offset)) {
1095 return false;
1098 Rooted<PlainObject*> result(cx);
1099 GetOffsetMetadataMatcher matcher(cx, offset, &result);
1100 if (!referent.match(matcher)) {
1101 return false;
1104 args.rval().setObject(*result);
1105 return true;
1108 namespace {
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
1116 * following cases:
1117 * - hasNoEdges:
1118 * The instruction cannot be reached, so the instruction is not an entry
1119 * point for the line it is on.
1120 * - hasSingleEdge:
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:
1128 * - hasNoEdges:
1129 * The instruction cannot be reached, so the instruction is not an entry
1130 * point for the position it is on.
1131 * - hasSingleEdge:
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 {
1137 public:
1138 class Entry {
1139 public:
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,
1145 uint32_t column) {
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_);
1178 private:
1179 Entry(uint32_t lineno, uint32_t column)
1180 : lineno_(lineno), column_(column) {}
1182 // Line number (1-origin).
1183 // Line_HasNoEdge for no edge.
1184 uint32_t lineno_;
1186 // Column number in UTF-16 code units (1-origin).
1187 // Column_HasMultipleEdge for multiple edge.
1188 uint32_t column_;
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())) {
1197 return false;
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);
1241 pc += step;
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;
1272 prevOp = op;
1275 return true;
1278 private:
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 {
1301 JSContext* cx_;
1302 size_t offset_;
1303 MutableHandle<PlainObject*> result_;
1305 public:
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));
1312 if (!script) {
1313 return false;
1316 if (!EnsureScriptOffsetIsValid(cx_, script, offset_)) {
1317 return false;
1320 FlowGraphSummary flowData(cx_);
1321 if (!flowData.populate(cx_, script)) {
1322 return false;
1325 result_.set(NewPlainObject(cx_));
1326 if (!result_) {
1327 return false;
1330 BytecodeRangeWithPosition r(cx_, script);
1331 while (!r.empty() && r.frontOffset() < offset_) {
1332 r.popFront();
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()) {
1343 r.popFront();
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
1349 // edge position.
1350 uint32_t lineno;
1351 JS::LimitedColumnNumberOneOrigin column;
1352 if (r.frontIsEntryPoint()) {
1353 lineno = r.frontLineNumber();
1354 column = r.frontColumnNumber();
1355 } else {
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)) {
1363 return false;
1366 value = NumberValue(column.oneOriginValue());
1367 if (!DefineDataProperty(cx_, result_, cx_->names().columnNumber, value)) {
1368 return false;
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)) {
1378 return false;
1381 return true;
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);
1388 return false;
1391 uint32_t lineno;
1392 JS::LimitedColumnNumberOneOrigin column;
1393 if (!instance.debug().getOffsetLocation(offset_, &lineno, &column)) {
1394 JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr,
1395 JSMSG_DEBUG_BAD_OFFSET);
1396 return false;
1399 result_.set(NewPlainObject(cx_));
1400 if (!result_) {
1401 return false;
1404 RootedValue value(cx_, NumberValue(lineno));
1405 if (!DefineDataProperty(cx_, result_, cx_->names().lineNumber, value)) {
1406 return false;
1409 value = NumberValue(column.oneOriginValue());
1410 if (!DefineDataProperty(cx_, result_, cx_->names().columnNumber, value)) {
1411 return false;
1414 value.setBoolean(true);
1415 if (!DefineDataProperty(cx_, result_, cx_->names().isEntryPoint, value)) {
1416 return false;
1419 return true;
1423 bool DebuggerScript::CallData::getOffsetLocation() {
1424 if (!args.requireAtLeast(cx, "Debugger.Script.getOffsetLocation", 1)) {
1425 return false;
1427 size_t offset;
1428 if (!ScriptOffset(cx, args[0], &offset)) {
1429 return false;
1432 Rooted<PlainObject*> result(cx);
1433 GetOffsetLocationMatcher matcher(cx, offset, &result);
1434 if (!referent.match(matcher)) {
1435 return false;
1438 args.rval().setObject(*result);
1439 return true;
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);
1450 switch (op) {
1451 case JSOp::SetProp:
1452 case JSOp::StrictSetProp:
1453 case JSOp::SetPropSuper:
1454 case JSOp::StrictSetPropSuper:
1455 case JSOp::SetElem:
1456 case JSOp::StrictSetElem:
1457 case JSOp::SetElemSuper:
1458 case JSOp::StrictSetElemSuper:
1459 case JSOp::SetName:
1460 case JSOp::StrictSetName:
1461 case JSOp::SetGName:
1462 case JSOp::StrictSetGName:
1463 case JSOp::DelProp:
1464 case JSOp::StrictDelProp:
1465 case JSOp::DelElem:
1466 case JSOp::StrictDelElem:
1467 case JSOp::DelName:
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:
1477 case JSOp::Yield:
1478 case JSOp::Await:
1479 case JSOp::CanSkipAwait:
1480 return true;
1482 case JSOp::Nop:
1483 case JSOp::NopDestructuring:
1484 case JSOp::NopIsAssignOp:
1485 case JSOp::TryDestructuring:
1486 case JSOp::Lineno:
1487 case JSOp::JumpTarget:
1488 case JSOp::Undefined:
1489 case JSOp::JumpIfTrue:
1490 case JSOp::JumpIfFalse:
1491 case JSOp::Return:
1492 case JSOp::RetRval:
1493 case JSOp::And:
1494 case JSOp::Or:
1495 case JSOp::Coalesce:
1496 case JSOp::Try:
1497 case JSOp::Throw:
1498 case JSOp::ThrowWithStack:
1499 case JSOp::Goto:
1500 case JSOp::TableSwitch:
1501 case JSOp::Case:
1502 case JSOp::Default:
1503 case JSOp::BitNot:
1504 case JSOp::BitAnd:
1505 case JSOp::BitOr:
1506 case JSOp::BitXor:
1507 case JSOp::Lsh:
1508 case JSOp::Rsh:
1509 case JSOp::Ursh:
1510 case JSOp::Add:
1511 case JSOp::Sub:
1512 case JSOp::Mul:
1513 case JSOp::Div:
1514 case JSOp::Mod:
1515 case JSOp::Pow:
1516 case JSOp::Pos:
1517 case JSOp::ToNumeric:
1518 case JSOp::Neg:
1519 case JSOp::Inc:
1520 case JSOp::Dec:
1521 case JSOp::ToString:
1522 case JSOp::Eq:
1523 case JSOp::Ne:
1524 case JSOp::StrictEq:
1525 case JSOp::StrictNe:
1526 case JSOp::Lt:
1527 case JSOp::Le:
1528 case JSOp::Gt:
1529 case JSOp::Ge:
1530 case JSOp::Double:
1531 case JSOp::BigInt:
1532 case JSOp::String:
1533 case JSOp::Symbol:
1534 case JSOp::Zero:
1535 case JSOp::One:
1536 case JSOp::Null:
1537 case JSOp::Void:
1538 case JSOp::Hole:
1539 case JSOp::False:
1540 case JSOp::True:
1541 case JSOp::Arguments:
1542 case JSOp::Rest:
1543 case JSOp::GetArg:
1544 case JSOp::GetFrameArg:
1545 case JSOp::SetArg:
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:
1555 case JSOp::Pop:
1556 case JSOp::PopN:
1557 case JSOp::DupAt:
1558 case JSOp::NewArray:
1559 case JSOp::NewInit:
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:
1578 case JSOp::Call:
1579 case JSOp::CallContent:
1580 case JSOp::CallIgnoresRv:
1581 case JSOp::CallIter:
1582 case JSOp::CallContentIter:
1583 case JSOp::New:
1584 case JSOp::NewContent:
1585 case JSOp::Eval:
1586 case JSOp::StrictEval:
1587 case JSOp::Int8:
1588 case JSOp::Uint16:
1589 case JSOp::ResumeKind:
1590 case JSOp::GetGName:
1591 case JSOp::GetName:
1592 case JSOp::GetIntrinsic:
1593 case JSOp::GetImport:
1594 case JSOp::BindGName:
1595 case JSOp::BindName:
1596 case JSOp::BindVar:
1597 case JSOp::Dup:
1598 case JSOp::Dup2:
1599 case JSOp::Swap:
1600 case JSOp::Pick:
1601 case JSOp::Unpick:
1602 case JSOp::GetAliasedDebugVar:
1603 case JSOp::GetAliasedVar:
1604 case JSOp::Uint24:
1605 case JSOp::Int32:
1606 case JSOp::LoopHead:
1607 case JSOp::GetElem:
1608 case JSOp::Not:
1609 case JSOp::FunctionThis:
1610 case JSOp::GlobalThis:
1611 case JSOp::NonSyntacticGlobalThis:
1612 case JSOp::Callee:
1613 case JSOp::EnvCallee:
1614 case JSOp::SuperBase:
1615 case JSOp::GetPropSuper:
1616 case JSOp::GetElemSuper:
1617 case JSOp::GetProp:
1618 case JSOp::RegExp:
1619 case JSOp::CallSiteObj:
1620 case JSOp::Object:
1621 case JSOp::Typeof:
1622 case JSOp::TypeofExpr:
1623 case JSOp::ToAsyncIter:
1624 case JSOp::ToPropertyKey:
1625 case JSOp::Lambda:
1626 case JSOp::PushLexicalEnv:
1627 case JSOp::PopLexicalEnv:
1628 case JSOp::FreshenLexicalEnv:
1629 case JSOp::RecreateLexicalEnv:
1630 case JSOp::PushClassBodyEnv:
1631 case JSOp::Iter:
1632 case JSOp::MoreIter:
1633 case JSOp::IsNoIter:
1634 case JSOp::EndIter:
1635 case JSOp::CloseIter:
1636 case JSOp::OptimizeGetIterator:
1637 case JSOp::IsNullOrUndefined:
1638 case JSOp::In:
1639 case JSOp::HasOwn:
1640 case JSOp::CheckPrivateField:
1641 case JSOp::NewPrivateName:
1642 case JSOp::SetRval:
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:
1675 case JSOp::Resume:
1676 case JSOp::CheckResumeKind:
1677 case JSOp::AfterYield:
1678 case JSOp::MaybeExtractAwaitValue:
1679 case JSOp::Generator:
1680 case JSOp::AsyncAwait:
1681 case JSOp::AsyncResolve:
1682 case JSOp::Finally:
1683 case JSOp::GetRval:
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:
1694 #endif
1695 return false;
1697 case JSOp::InitAliasedLexical: {
1698 uint32_t hops = EnvironmentCoordinate(pc).hops();
1699 if (hops == 0) {
1700 // Initializing aliased lexical in the current scope is almost same
1701 // as JSOp::InitLexical.
1702 return false;
1705 // Otherwise this can touch an environment outside of the current scope.
1706 return true;
1710 MOZ_ASSERT_UNREACHABLE("Invalid opcode");
1711 return false;
1714 bool DebuggerScript::CallData::getEffectfulOffsets() {
1715 if (!ensureScript()) {
1716 return false;
1719 RootedObject result(cx, NewDenseEmptyArray(cx));
1720 if (!result) {
1721 return false;
1723 for (BytecodeRange r(cx, script); !r.empty(); r.popFront()) {
1724 size_t offset = r.frontOffset();
1725 if (!BytecodeIsEffectful(script, offset)) {
1726 continue;
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.
1734 continue;
1737 if (!NewbornArrayPush(cx, result, NumberValue(offset))) {
1738 return false;
1742 args.rval().setObject(*result);
1743 return true;
1746 bool DebuggerScript::CallData::getAllOffsets() {
1747 if (!ensureScript()) {
1748 return false;
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)) {
1755 return false;
1758 // Second pass: build the result array.
1759 RootedObject result(cx, NewDenseEmptyArray(cx));
1760 if (!result) {
1761 return false;
1763 for (BytecodeRangeWithPosition r(cx, script); !r.empty(); r.popFront()) {
1764 if (!r.frontIsEntryPoint()) {
1765 continue;
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
1772 // line.
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));
1780 bool found;
1781 if (!HasOwnProperty(cx, result, id, &found)) {
1782 return false;
1784 if (found && !GetProperty(cx, result, result, id, &offsetsv)) {
1785 return false;
1788 if (offsetsv.isObject()) {
1789 offsets = &offsetsv.toObject();
1790 } else {
1791 MOZ_ASSERT(offsetsv.isUndefined());
1793 // Create an empty offsets array for this line.
1794 // Store it in the result array.
1795 RootedId id(cx);
1796 RootedValue v(cx, NumberValue(lineno));
1797 offsets = NewDenseEmptyArray(cx);
1798 if (!offsets || !PrimitiveValueToId<CanGC>(cx, v, &id)) {
1799 return false;
1802 RootedValue value(cx, ObjectValue(*offsets));
1803 if (!DefineDataProperty(cx, result, id, value)) {
1804 return false;
1808 // Append the current offset to the offsets array.
1809 if (!NewbornArrayPush(cx, offsets, NumberValue(offset))) {
1810 return false;
1815 args.rval().setObject(*result);
1816 return true;
1819 class DebuggerScript::GetAllColumnOffsetsMatcher {
1820 JSContext* cx_;
1821 MutableHandleObject result_;
1823 bool appendColumnOffsetEntry(uint32_t lineno,
1824 JS::LimitedColumnNumberOneOrigin column,
1825 size_t offset) {
1826 Rooted<PlainObject*> entry(cx_, NewPlainObject(cx_));
1827 if (!entry) {
1828 return false;
1831 RootedValue value(cx_, NumberValue(lineno));
1832 if (!DefineDataProperty(cx_, entry, cx_->names().lineNumber, value)) {
1833 return false;
1836 value = NumberValue(column.oneOriginValue());
1837 if (!DefineDataProperty(cx_, entry, cx_->names().columnNumber, value)) {
1838 return false;
1841 value = NumberValue(offset);
1842 if (!DefineDataProperty(cx_, entry, cx_->names().offset, value)) {
1843 return false;
1846 return NewbornArrayPush(cx_, result_, ObjectValue(*entry));
1849 public:
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));
1855 if (!script) {
1856 return false;
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)) {
1863 return false;
1866 // Second pass: build the result array.
1867 result_.set(NewDenseEmptyArray(cx_));
1868 if (!result_) {
1869 return false;
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)) {
1883 return false;
1887 return true;
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)) {
1895 return false;
1898 result_.set(NewDenseEmptyArray(cx_));
1899 if (!result_) {
1900 return false;
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)) {
1908 return false;
1911 return true;
1915 bool DebuggerScript::CallData::getAllColumnOffsets() {
1916 RootedObject result(cx);
1917 GetAllColumnOffsetsMatcher matcher(cx, &result);
1918 if (!referent.match(matcher)) {
1919 return false;
1922 args.rval().setObject(*result);
1923 return true;
1926 class DebuggerScript::GetLineOffsetsMatcher {
1927 JSContext* cx_;
1928 uint32_t lineno_;
1929 MutableHandleObject result_;
1931 public:
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));
1938 if (!script) {
1939 return false;
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)) {
1946 return false;
1949 result_.set(NewDenseEmptyArray(cx_));
1950 if (!result_) {
1951 return false;
1954 // Second pass: build the result array.
1955 for (BytecodeRangeWithPosition r(cx_, script); !r.empty(); r.popFront()) {
1956 if (!r.frontIsEntryPoint()) {
1957 continue;
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))) {
1966 return false;
1971 return true;
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)) {
1979 return false;
1982 result_.set(NewDenseEmptyArray(cx_));
1983 if (!result_) {
1984 return false;
1987 for (uint32_t i = 0; i < offsets.length(); i++) {
1988 if (!NewbornArrayPush(cx_, result_, NumberValue(offsets[i]))) {
1989 return false;
1992 return true;
1996 bool DebuggerScript::CallData::getLineOffsets() {
1997 if (!args.requireAtLeast(cx, "Debugger.Script.getLineOffsets", 1)) {
1998 return false;
2001 // Parse lineno argument.
2002 RootedValue linenoValue(cx, args[0]);
2003 uint32_t lineno;
2004 if (!ToNumber(cx, &linenoValue)) {
2005 return false;
2008 double d = linenoValue.toNumber();
2009 lineno = uint32_t(d);
2010 if (lineno != d) {
2011 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2012 JSMSG_DEBUG_BAD_LINE);
2013 return false;
2017 RootedObject result(cx);
2018 GetLineOffsetsMatcher matcher(cx, lineno, &result);
2019 if (!referent.match(matcher)) {
2020 return false;
2023 args.rval().setObject(*result);
2024 return true;
2027 struct DebuggerScript::SetBreakpointMatcher {
2028 JSContext* cx_;
2029 Debugger* dbg_;
2030 size_t offset_;
2031 RootedObject handler_;
2032 RootedObject debuggerObject_;
2034 bool wrapCrossCompartmentEdges() {
2035 if (!cx_->compartment()->wrap(cx_, &handler_) ||
2036 !cx_->compartment()->wrap(cx_, &debuggerObject_)) {
2037 return false;
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
2042 // failure.
2043 if (IsDeadProxyObject(handler_) || IsDeadProxyObject(debuggerObject_)) {
2044 ReportAccessDenied(cx_);
2045 return false;
2048 return true;
2051 public:
2052 explicit SetBreakpointMatcher(JSContext* cx, Debugger* dbg, size_t offset,
2053 HandleObject handler)
2054 : cx_(cx),
2055 dbg_(dbg),
2056 offset_(offset),
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));
2064 if (!script) {
2065 return false;
2068 if (!dbg_->observesScript(script)) {
2069 JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr,
2070 JSMSG_DEBUG_NOT_DEBUGGING);
2071 return false;
2074 if (!EnsureScriptOffsetIsValid(cx_, script, offset_)) {
2075 return false;
2078 if (!EnsureBreakpointIsAllowed(cx_, script, offset_)) {
2079 return false;
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)) {
2087 return false;
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()) {
2094 return false;
2097 jsbytecode* pc = script->offsetToPC(offset_);
2098 JSBreakpointSite* site =
2099 DebugScript::getOrCreateBreakpointSite(cx_, script, pc);
2100 if (!site) {
2101 return false;
2104 if (!cx_->zone()->new_<Breakpoint>(dbg_, debuggerObject_, site, handler_)) {
2105 site->destroyIfEmpty(cx_->runtime()->gcContext());
2106 return false;
2108 AddCellMemory(script, sizeof(Breakpoint), MemoryUse::Breakpoint);
2110 return true;
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);
2118 return false;
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()) {
2125 return false;
2128 WasmBreakpointSite* site = instance.getOrCreateBreakpointSite(cx_, offset_);
2129 if (!site) {
2130 return false;
2133 if (!cx_->zone()->new_<Breakpoint>(dbg_, debuggerObject_, site, handler_)) {
2134 site->destroyIfEmpty(cx_->runtime()->gcContext());
2135 return false;
2137 AddCellMemory(wasmInstance, sizeof(Breakpoint), MemoryUse::Breakpoint);
2139 return true;
2143 bool DebuggerScript::CallData::setBreakpoint() {
2144 if (!args.requireAtLeast(cx, "Debugger.Script.setBreakpoint", 2)) {
2145 return false;
2147 Debugger* dbg = obj->owner();
2149 size_t offset;
2150 if (!ScriptOffset(cx, args[0], &offset)) {
2151 return false;
2154 RootedObject handler(cx, RequireObject(cx, args[1]));
2155 if (!handler) {
2156 return false;
2159 SetBreakpointMatcher matcher(cx, dbg, offset, handler);
2160 if (!referent.match(matcher)) {
2161 return false;
2163 args.rval().setUndefined();
2164 return true;
2167 bool DebuggerScript::CallData::getBreakpoints() {
2168 if (!ensureScript()) {
2169 return false;
2171 Debugger* dbg = obj->owner();
2173 jsbytecode* pc;
2174 if (args.length() > 0) {
2175 size_t offset;
2176 if (!ScriptOffset(cx, args[0], &offset) ||
2177 !EnsureScriptOffsetIsValid(cx, script, offset)) {
2178 return false;
2180 pc = script->offsetToPC(offset);
2181 } else {
2182 pc = nullptr;
2185 RootedObject arr(cx, NewDenseEmptyArray(cx));
2186 if (!arr) {
2187 return false;
2190 for (unsigned i = 0; i < script->length(); i++) {
2191 JSBreakpointSite* site =
2192 DebugScript::getBreakpointSite(script, script->offsetToPC(i));
2193 if (!site) {
2194 continue;
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))) {
2203 return false;
2209 args.rval().setObject(*arr);
2210 return true;
2213 class DebuggerScript::ClearBreakpointMatcher {
2214 JSContext* cx_;
2215 Debugger* dbg_;
2216 RootedObject handler_;
2218 public:
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));
2225 if (!script) {
2226 return false;
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
2233 // search for.
2234 AutoRealm ar(cx_, script);
2235 if (!cx_->compartment()->wrap(cx_, &handler_)) {
2236 return false;
2239 DebugScript::clearBreakpointsIn(cx_->runtime()->gcContext(), script, dbg_,
2240 handler_);
2241 return true;
2243 ReturnType match(Handle<WasmInstanceObject*> instanceObj) {
2244 wasm::Instance& instance = instanceObj->instance();
2245 if (!instance.debugEnabled()) {
2246 return true;
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
2253 // to search for.
2254 AutoRealm ar(cx_, instanceObj);
2255 if (!cx_->compartment()->wrap(cx_, &handler_)) {
2256 return false;
2259 instance.debug().clearBreakpointsIn(cx_->runtime()->gcContext(),
2260 instanceObj, dbg_, handler_);
2261 return true;
2265 bool DebuggerScript::CallData::clearBreakpoint() {
2266 if (!args.requireAtLeast(cx, "Debugger.Script.clearBreakpoint", 1)) {
2267 return false;
2269 Debugger* dbg = obj->owner();
2271 JSObject* handler = RequireObject(cx, args[0]);
2272 if (!handler) {
2273 return false;
2276 ClearBreakpointMatcher matcher(cx, dbg, handler);
2277 if (!referent.match(matcher)) {
2278 return false;
2281 args.rval().setUndefined();
2282 return true;
2285 bool DebuggerScript::CallData::clearAllBreakpoints() {
2286 Debugger* dbg = obj->owner();
2287 ClearBreakpointMatcher matcher(cx, dbg, nullptr);
2288 if (!referent.match(matcher)) {
2289 return false;
2291 args.rval().setUndefined();
2292 return true;
2295 class DebuggerScript::IsInCatchScopeMatcher {
2296 JSContext* cx_;
2297 size_t offset_;
2298 bool isInCatch_;
2300 public:
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));
2309 if (!script) {
2310 return false;
2313 if (!EnsureScriptOffsetIsValid(cx_, script, offset_)) {
2314 return false;
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) {
2321 isInCatch_ = true;
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) {
2329 isInCatch_ = false;
2330 continue;
2332 return true;
2336 return true;
2338 ReturnType match(Handle<WasmInstanceObject*> instance) {
2339 isInCatch_ = false;
2340 return true;
2344 bool DebuggerScript::CallData::isInCatchScope() {
2345 if (!args.requireAtLeast(cx, "Debugger.Script.isInCatchScope", 1)) {
2346 return false;
2349 size_t offset;
2350 if (!ScriptOffset(cx, args[0], &offset)) {
2351 return false;
2354 IsInCatchScopeMatcher matcher(cx, offset);
2355 if (!referent.match(matcher)) {
2356 return false;
2358 args.rval().setBoolean(matcher.isInCatch());
2359 return true;
2362 bool DebuggerScript::CallData::getOffsetsCoverage() {
2363 if (!ensureScript()) {
2364 return false;
2367 Debugger* dbg = obj->owner();
2368 if (dbg->observesCoverage() != Debugger::Observing) {
2369 args.rval().setNull();
2370 return true;
2373 // If the script has no coverage information, then skip this and return null
2374 // instead.
2375 if (!script->hasScriptCounts()) {
2376 args.rval().setNull();
2377 return true;
2380 ScriptCounts* sc = &script->getScriptCounts();
2382 // If the main ever got visited, then assume that any code before main got
2383 // visited once.
2384 uint64_t hits = 0;
2385 const PCCounts* counts =
2386 sc->maybeGetPCCounts(script->pcToOffset(script->main()));
2387 if (counts->numExec()) {
2388 hits = 1;
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));
2397 if (!result) {
2398 return false;
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
2418 // instruction.
2419 counts = sc->maybeGetPCCounts(offset);
2420 if (counts) {
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))) {
2437 return false;
2440 // If the current instruction has thrown, then decrement the hit counts
2441 // with the number of throws.
2442 counts = sc->maybeGetThrowCounts(offset);
2443 if (counts) {
2444 hits -= counts->numExec();
2448 args.rval().setObject(*result);
2449 return true;
2452 /* static */
2453 bool DebuggerScript::construct(JSContext* cx, unsigned argc, Value* vp) {
2454 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR,
2455 "Debugger.Script");
2456 return false;
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),
2476 JS_PS_END};
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};