Source code

Revision control

Other Tools

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/. */
6
7
#include "debugger/Script-inl.h"
8
9
#include "mozilla/Maybe.h" // for Some, Maybe
10
#include "mozilla/Span.h" // for Span
11
#include "mozilla/Vector.h" // for Vector
12
13
#include <stddef.h> // for ptrdiff_t
14
#include <stdint.h> // for uint32_t, SIZE_MAX, int32_t
15
16
#include "jsapi.h" // for CallArgs, Rooted, CallArgsFromVp
17
#include "jsfriendapi.h" // for GetErrorMessage
18
#include "jsnum.h" // for ToNumber
19
#include "NamespaceImports.h" // for CallArgs, RootedValue
20
21
#include "builtin/Array.h" // for NewDenseEmptyArray
22
#include "debugger/Debugger.h" // for DebuggerScriptReferent, Debugger
23
#include "debugger/DebugScript.h" // for DebugScript
24
#include "debugger/Source.h" // for DebuggerSource
25
#include "gc/Barrier.h" // for ImmutablePropertyNamePtr
26
#include "gc/GC.h" // for MemoryUse, MemoryUse::Breakpoint
27
#include "gc/Rooting.h" // for RootedDebuggerScript
28
#include "gc/Tracer.h" // for TraceManuallyBarrieredCrossCompartmentEdge
29
#include "gc/Zone.h" // for Zone
30
#include "gc/ZoneAllocator.h" // for AddCellMemory
31
#include "js/HeapAPI.h" // for GCCellPtr
32
#include "js/Wrapper.h" // for UncheckedUnwrap
33
#include "vm/ArrayObject.h" // for ArrayObject
34
#include "vm/BytecodeUtil.h" // for GET_JUMP_OFFSET
35
#include "vm/GlobalObject.h" // for GlobalObject
36
#include "vm/JSContext.h" // for JSContext, ReportValueError
37
#include "vm/JSFunction.h" // for JSFunction
38
#include "vm/JSObject.h" // for RequireObject, JSObject
39
#include "vm/ObjectGroup.h" // for TenuredObject
40
#include "vm/ObjectOperations.h" // for DefineDataProperty, HasOwnProperty
41
#include "vm/Realm.h" // for AutoRealm
42
#include "vm/Runtime.h" // for JSAtomState, JSRuntime
43
#include "vm/StringType.h" // for NameToId, PropertyName, JSAtom
44
#include "wasm/WasmDebug.h" // for ExprLoc, DebugState
45
#include "wasm/WasmInstance.h" // for Instance
46
#include "wasm/WasmTypes.h" // for Bytes
47
48
#include "vm/BytecodeUtil-inl.h" // for BytecodeRangeWithPosition
49
#include "vm/JSAtom-inl.h" // for ValueToId
50
#include "vm/JSObject-inl.h" // for NewBuiltinClassInstance
51
#include "vm/JSScript-inl.h" // for LazyScript::functionDelazifying
52
#include "vm/ObjectOperations-inl.h" // for GetProperty
53
#include "vm/Realm-inl.h" // for AutoRealm::AutoRealm
54
55
using namespace js;
56
57
using mozilla::Maybe;
58
using mozilla::Some;
59
60
const JSClassOps DebuggerScript::classOps_ = {
61
nullptr, /* addProperty */
62
nullptr, /* delProperty */
63
nullptr, /* enumerate */
64
nullptr, /* newEnumerate */
65
nullptr, /* resolve */
66
nullptr, /* mayResolve */
67
nullptr, /* finalize */
68
nullptr, /* call */
69
nullptr, /* hasInstance */
70
nullptr, /* construct */
71
CallTraceMethod<DebuggerScript>, /* trace */
72
};
73
74
const JSClass DebuggerScript::class_ = {
75
"Script", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS),
76
&classOps_};
77
78
void DebuggerScript::trace(JSTracer* trc) {
79
JSObject* upcast = this;
80
// This comes from a private pointer, so no barrier needed.
81
gc::Cell* cell = getReferentCell();
82
if (cell) {
83
if (cell->is<JSScript>()) {
84
JSScript* script = cell->as<JSScript>();
85
TraceManuallyBarrieredCrossCompartmentEdge(
86
trc, upcast, &script, "Debugger.Script script referent");
87
setPrivateUnbarriered(script);
88
} else if (cell->is<LazyScript>()) {
89
LazyScript* lazyScript = cell->as<LazyScript>();
90
TraceManuallyBarrieredCrossCompartmentEdge(
91
trc, upcast, &lazyScript, "Debugger.Script lazy script referent");
92
setPrivateUnbarriered(lazyScript);
93
} else {
94
JSObject* wasm = cell->as<JSObject>();
95
TraceManuallyBarrieredCrossCompartmentEdge(
96
trc, upcast, &wasm, "Debugger.Script wasm referent");
97
MOZ_ASSERT(wasm->is<WasmInstanceObject>());
98
setPrivateUnbarriered(wasm);
99
}
100
}
101
}
102
103
/* static */
104
NativeObject* DebuggerScript::initClass(JSContext* cx,
105
Handle<GlobalObject*> global,
106
HandleObject debugCtor) {
107
return InitClass(cx, debugCtor, nullptr, &class_, construct, 0, properties_,
108
methods_, nullptr, nullptr);
109
}
110
111
/* static */
112
DebuggerScript* DebuggerScript::create(JSContext* cx, HandleObject proto,
113
Handle<DebuggerScriptReferent> referent,
114
HandleNativeObject debugger) {
115
DebuggerScript* scriptobj =
116
NewObjectWithGivenProto<DebuggerScript>(cx, proto, TenuredObject);
117
if (!scriptobj) {
118
return nullptr;
119
}
120
121
scriptobj->setReservedSlot(DebuggerScript::OWNER_SLOT,
122
ObjectValue(*debugger));
123
referent.get().match(
124
[&](auto& scriptHandle) { scriptobj->setPrivateGCThing(scriptHandle); });
125
126
return scriptobj;
127
}
128
129
static JSScript* DelazifyScript(JSContext* cx, Handle<LazyScript*> lazyScript) {
130
if (lazyScript->maybeScript()) {
131
return lazyScript->maybeScript();
132
}
133
134
// JSFunction::getOrCreateScript requires the enclosing script not to be
135
// lazified.
136
MOZ_ASSERT(lazyScript->hasEnclosingLazyScript() ||
137
lazyScript->hasEnclosingScope());
138
if (lazyScript->hasEnclosingLazyScript()) {
139
Rooted<LazyScript*> enclosingLazyScript(cx,
140
lazyScript->enclosingLazyScript());
141
if (!DelazifyScript(cx, enclosingLazyScript)) {
142
return nullptr;
143
}
144
145
if (!lazyScript->enclosingScriptHasEverBeenCompiled()) {
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;
152
}
153
}
154
MOZ_ASSERT(lazyScript->enclosingScriptHasEverBeenCompiled());
155
156
RootedFunction fun(cx, lazyScript->function());
157
AutoRealm ar(cx, fun);
158
return JSFunction::getOrCreateScript(cx, fun);
159
}
160
161
/* static */
162
DebuggerScript* DebuggerScript::check(JSContext* cx, HandleValue v) {
163
JSObject* thisobj = RequireObject(cx, v);
164
if (!thisobj) {
165
return nullptr;
166
}
167
if (!thisobj->is<DebuggerScript>()) {
168
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
169
JSMSG_INCOMPATIBLE_PROTO, "Debugger.Script",
170
"method", thisobj->getClass()->name);
171
return nullptr;
172
}
173
174
DebuggerScript& scriptObj = thisobj->as<DebuggerScript>();
175
176
// Check for Debugger.Script.prototype, which is of class
177
// DebuggerScript::class but whose script is null.
178
if (!scriptObj.getReferentCell()) {
179
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
180
JSMSG_INCOMPATIBLE_PROTO, "Debugger.Script",
181
"method", "prototype object");
182
return nullptr;
183
}
184
185
return &scriptObj;
186
}
187
188
struct MOZ_STACK_CLASS DebuggerScript::CallData {
189
JSContext* cx;
190
const CallArgs& args;
191
192
HandleDebuggerScript obj;
193
Rooted<DebuggerScriptReferent> referent;
194
RootedScript script;
195
196
CallData(JSContext* cx, const CallArgs& args, HandleDebuggerScript obj)
197
: cx(cx),
198
args(args),
199
obj(obj),
200
referent(cx, obj->getReferent()),
201
script(cx) {}
202
203
MOZ_MUST_USE bool ensureScriptMaybeLazy() {
204
if (!referent.is<JSScript*>() && !referent.is<LazyScript*>()) {
205
ReportValueError(cx, JSMSG_DEBUG_BAD_REFERENT, JSDVG_SEARCH_STACK,
206
args.thisv(), nullptr, "a JS script");
207
return false;
208
}
209
return true;
210
}
211
212
MOZ_MUST_USE bool ensureScript() {
213
if (!ensureScriptMaybeLazy()) {
214
return false;
215
}
216
if (referent.is<JSScript*>()) {
217
script = referent.as<JSScript*>();
218
} else {
219
Rooted<LazyScript*> lazyScript(cx, referent.as<LazyScript*>());
220
script = DelazifyScript(cx, lazyScript);
221
if (!script) {
222
return false;
223
}
224
}
225
return true;
226
}
227
228
bool getIsGeneratorFunction();
229
bool getIsAsyncFunction();
230
bool getIsFunction();
231
bool getIsModule();
232
bool getDisplayName();
233
bool getUrl();
234
bool getStartLine();
235
bool getStartColumn();
236
bool getLineCount();
237
bool getSource();
238
bool getSourceStart();
239
bool getSourceLength();
240
bool getMainOffset();
241
bool getGlobal();
242
bool getFormat();
243
bool getChildScripts();
244
bool getPossibleBreakpoints();
245
bool getPossibleBreakpointOffsets();
246
bool getOffsetMetadata();
247
bool getOffsetLocation();
248
template <bool Successor>
249
bool getSuccessorOrPredecessorOffsets();
250
bool getEffectfulOffsets();
251
bool getAllOffsets();
252
bool getAllColumnOffsets();
253
bool getLineOffsets();
254
bool setBreakpoint();
255
bool getBreakpoints();
256
bool clearBreakpoint();
257
bool clearAllBreakpoints();
258
bool isInCatchScope();
259
bool getOffsetsCoverage();
260
bool setInstrumentationId();
261
262
using Method = bool (CallData::*)();
263
264
template <Method MyMethod>
265
static bool ToNative(JSContext* cx, unsigned argc, Value* vp);
266
};
267
268
template <DebuggerScript::CallData::Method MyMethod>
269
/* static */
270
bool DebuggerScript::CallData::ToNative(JSContext* cx, unsigned argc,
271
Value* vp) {
272
CallArgs args = CallArgsFromVp(argc, vp);
273
274
RootedDebuggerScript obj(cx, DebuggerScript::check(cx, args.thisv()));
275
if (!obj) {
276
return false;
277
}
278
279
CallData data(cx, args, obj);
280
return (data.*MyMethod)();
281
}
282
283
bool DebuggerScript::CallData::getIsGeneratorFunction() {
284
if (!ensureScriptMaybeLazy()) {
285
return false;
286
}
287
args.rval().setBoolean(obj->getReferentScript()->isGenerator());
288
return true;
289
}
290
291
bool DebuggerScript::CallData::getIsAsyncFunction() {
292
if (!ensureScriptMaybeLazy()) {
293
return false;
294
}
295
args.rval().setBoolean(obj->getReferentScript()->isAsync());
296
return true;
297
}
298
299
bool DebuggerScript::CallData::getIsFunction() {
300
if (!ensureScriptMaybeLazy()) {
301
return false;
302
}
303
304
args.rval().setBoolean(obj->getReferentScript()->function());
305
return true;
306
}
307
308
bool DebuggerScript::CallData::getIsModule() {
309
if (!ensureScriptMaybeLazy()) {
310
return false;
311
}
312
args.rval().setBoolean(referent.is<JSScript*>() &&
313
referent.as<JSScript*>()->isModule());
314
return true;
315
}
316
317
bool DebuggerScript::CallData::getDisplayName() {
318
if (!ensureScriptMaybeLazy()) {
319
return false;
320
}
321
JSFunction* func = obj->getReferentScript()->function();
322
Debugger* dbg = Debugger::fromChildJSObject(obj);
323
324
JSString* name = func ? func->displayAtom() : nullptr;
325
if (!name) {
326
args.rval().setUndefined();
327
return true;
328
}
329
330
RootedValue namev(cx, StringValue(name));
331
if (!dbg->wrapDebuggeeValue(cx, &namev)) {
332
return false;
333
}
334
args.rval().set(namev);
335
return true;
336
}
337
338
template <typename T>
339
/* static */
340
bool DebuggerScript::getUrlImpl(JSContext* cx, const CallArgs& args,
341
Handle<T*> script) {
342
if (script->filename()) {
343
JSString* str;
344
if (script->scriptSource()->introducerFilename()) {
345
str = NewStringCopyZ<CanGC>(cx,
346
script->scriptSource()->introducerFilename());
347
} else {
348
str = NewStringCopyZ<CanGC>(cx, script->filename());
349
}
350
if (!str) {
351
return false;
352
}
353
args.rval().setString(str);
354
} else {
355
args.rval().setNull();
356
}
357
return true;
358
}
359
360
bool DebuggerScript::CallData::getUrl() {
361
if (!ensureScriptMaybeLazy()) {
362
return false;
363
}
364
365
if (referent.is<JSScript*>()) {
366
RootedScript script(cx, referent.as<JSScript*>());
367
return getUrlImpl<JSScript>(cx, args, script);
368
}
369
370
Rooted<LazyScript*> lazyScript(cx, referent.as<LazyScript*>());
371
return getUrlImpl<LazyScript>(cx, args, lazyScript);
372
}
373
374
bool DebuggerScript::CallData::getStartLine() {
375
args.rval().setNumber(
376
referent.get().match([](JSScript*& s) { return s->lineno(); },
377
[](LazyScript*& s) { return s->lineno(); },
378
[](WasmInstanceObject*&) { return (uint32_t)1; }));
379
return true;
380
}
381
382
bool DebuggerScript::CallData::getStartColumn() {
383
args.rval().setNumber(
384
referent.get().match([](JSScript*& s) { return s->column(); },
385
[](LazyScript*& s) { return s->column(); },
386
[](WasmInstanceObject*&) { return (uint32_t)0; }));
387
return true;
388
}
389
390
struct DebuggerScript::GetLineCountMatcher {
391
JSContext* cx_;
392
double totalLines;
393
394
explicit GetLineCountMatcher(JSContext* cx) : cx_(cx), totalLines(0.0) {}
395
using ReturnType = bool;
396
397
ReturnType match(HandleScript script) {
398
totalLines = double(GetScriptLineExtent(script));
399
return true;
400
}
401
ReturnType match(Handle<LazyScript*> lazyScript) {
402
RootedScript script(cx_, DelazifyScript(cx_, lazyScript));
403
if (!script) {
404
return false;
405
}
406
return match(script);
407
}
408
ReturnType match(Handle<WasmInstanceObject*> instanceObj) {
409
wasm::Instance& instance = instanceObj->instance();
410
if (instance.debugEnabled()) {
411
totalLines = double(instance.debug().bytecode().length());
412
} else {
413
totalLines = 0;
414
}
415
return true;
416
}
417
};
418
419
bool DebuggerScript::CallData::getLineCount() {
420
GetLineCountMatcher matcher(cx);
421
if (!referent.match(matcher)) {
422
return false;
423
}
424
args.rval().setNumber(matcher.totalLines);
425
return true;
426
}
427
428
class DebuggerScript::GetSourceMatcher {
429
JSContext* cx_;
430
Debugger* dbg_;
431
432
public:
433
GetSourceMatcher(JSContext* cx, Debugger* dbg) : cx_(cx), dbg_(dbg) {}
434
435
using ReturnType = DebuggerSource*;
436
437
ReturnType match(HandleScript script) {
438
// JSScript holds the refefence to possibly wrapped ScriptSourceObject.
439
// It's wrapped when the script is cloned.
440
// See CreateEmptyScriptForClone for more info.
441
RootedScriptSourceObject source(
442
cx_,
443
&UncheckedUnwrap(script->sourceObject())->as<ScriptSourceObject>());
444
return dbg_->wrapSource(cx_, source);
445
}
446
ReturnType match(Handle<LazyScript*> lazyScript) {
447
// LazyScript holds the reference to the unwrapped ScriptSourceObject.
448
RootedScriptSourceObject source(cx_, lazyScript->sourceObject());
449
return dbg_->wrapSource(cx_, source);
450
}
451
ReturnType match(Handle<WasmInstanceObject*> wasmInstance) {
452
return dbg_->wrapWasmSource(cx_, wasmInstance);
453
}
454
};
455
456
bool DebuggerScript::CallData::getSource() {
457
Debugger* dbg = Debugger::fromChildJSObject(obj);
458
459
GetSourceMatcher matcher(cx, dbg);
460
RootedDebuggerSource sourceObject(cx, referent.match(matcher));
461
if (!sourceObject) {
462
return false;
463
}
464
465
args.rval().setObject(*sourceObject);
466
return true;
467
}
468
469
bool DebuggerScript::CallData::getSourceStart() {
470
if (!ensureScriptMaybeLazy()) {
471
return false;
472
}
473
args.rval().setNumber(uint32_t(obj->getReferentScript()->sourceStart()));
474
return true;
475
}
476
477
bool DebuggerScript::CallData::getSourceLength() {
478
if (!ensureScriptMaybeLazy()) {
479
return false;
480
}
481
args.rval().setNumber(uint32_t(obj->getReferentScript()->sourceLength()));
482
return true;
483
}
484
485
bool DebuggerScript::CallData::getMainOffset() {
486
if (!ensureScript()) {
487
return false;
488
}
489
args.rval().setNumber(uint32_t(script->mainOffset()));
490
return true;
491
}
492
493
bool DebuggerScript::CallData::getGlobal() {
494
if (!ensureScript()) {
495
return false;
496
}
497
Debugger* dbg = Debugger::fromChildJSObject(obj);
498
499
RootedValue v(cx, ObjectValue(script->global()));
500
if (!dbg->wrapDebuggeeValue(cx, &v)) {
501
return false;
502
}
503
args.rval().set(v);
504
return true;
505
}
506
507
bool DebuggerScript::CallData::getFormat() {
508
args.rval().setString(referent.get().match(
509
[=](JSScript*&) { return cx->names().js.get(); },
510
[=](LazyScript*&) { return cx->names().js.get(); },
511
[=](WasmInstanceObject*&) { return cx->names().wasm.get(); }));
512
return true;
513
}
514
515
static bool PushFunctionScript(JSContext* cx, Debugger* dbg, HandleFunction fun,
516
HandleObject array) {
517
// Ignore asm.js natives.
518
if (!IsInterpretedNonSelfHostedFunction(fun)) {
519
return true;
520
}
521
522
RootedObject wrapped(cx);
523
524
if (fun->isInterpretedLazy()) {
525
Rooted<LazyScript*> lazy(cx, fun->lazyScript());
526
wrapped = dbg->wrapLazyScript(cx, lazy);
527
} else {
528
RootedScript script(cx, fun->nonLazyScript());
529
wrapped = dbg->wrapScript(cx, script);
530
}
531
532
return wrapped && NewbornArrayPush(cx, array, ObjectValue(*wrapped));
533
}
534
535
static bool PushInnerFunctions(JSContext* cx, Debugger* dbg, HandleObject array,
536
mozilla::Span<const JS::GCCellPtr> gcThings) {
537
RootedFunction fun(cx);
538
539
for (JS::GCCellPtr gcThing : gcThings) {
540
if (!gcThing.is<JSObject>()) {
541
continue;
542
}
543
544
JSObject* obj = &gcThing.as<JSObject>();
545
if (obj->is<JSFunction>()) {
546
fun = &obj->as<JSFunction>();
547
548
if (!PushFunctionScript(cx, dbg, fun, array)) {
549
return false;
550
}
551
}
552
}
553
554
return true;
555
}
556
557
bool DebuggerScript::CallData::getChildScripts() {
558
if (!ensureScriptMaybeLazy()) {
559
return false;
560
}
561
Debugger* dbg = Debugger::fromChildJSObject(obj);
562
563
RootedObject result(cx, NewDenseEmptyArray(cx));
564
if (!result) {
565
return false;
566
}
567
568
if (obj->getReferent().is<JSScript*>()) {
569
RootedScript script(cx, obj->getReferent().as<JSScript*>());
570
if (!PushInnerFunctions(cx, dbg, result, script->gcthings())) {
571
return false;
572
}
573
} else {
574
Rooted<LazyScript*> lazy(cx, obj->getReferent().as<LazyScript*>());
575
if (!PushInnerFunctions(cx, dbg, result, lazy->gcthings())) {
576
return false;
577
}
578
}
579
580
args.rval().setObject(*result);
581
return true;
582
}
583
584
static bool ScriptOffset(JSContext* cx, const Value& v, size_t* offsetp) {
585
double d;
586
size_t off;
587
588
bool ok = v.isNumber();
589
if (ok) {
590
d = v.toNumber();
591
off = size_t(d);
592
}
593
if (!ok || off != d) {
594
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
595
JSMSG_DEBUG_BAD_OFFSET);
596
return false;
597
}
598
*offsetp = off;
599
return true;
600
}
601
602
static bool EnsureScriptOffsetIsValid(JSContext* cx, JSScript* script,
603
size_t offset) {
604
if (IsValidBytecodeOffset(cx, script, offset)) {
605
return true;
606
}
607
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
608
JSMSG_DEBUG_BAD_OFFSET);
609
return false;
610
}
611
612
template <bool OnlyOffsets>
613
class DebuggerScript::GetPossibleBreakpointsMatcher {
614
JSContext* cx_;
615
MutableHandleObject result_;
616
617
Maybe<size_t> minOffset;
618
Maybe<size_t> maxOffset;
619
620
Maybe<size_t> minLine;
621
size_t minColumn;
622
Maybe<size_t> maxLine;
623
size_t maxColumn;
624
625
bool passesQuery(size_t offset, size_t lineno, size_t colno) {
626
// [minOffset, maxOffset) - Inclusive minimum and exclusive maximum.
627
if ((minOffset && offset < *minOffset) ||
628
(maxOffset && offset >= *maxOffset)) {
629
return false;
630
}
631
632
if (minLine) {
633
if (lineno < *minLine || (lineno == *minLine && colno < minColumn)) {
634
return false;
635
}
636
}
637
638
if (maxLine) {
639
if (lineno > *maxLine || (lineno == *maxLine && colno >= maxColumn)) {
640
return false;
641
}
642
}
643
644
return true;
645
}
646
647
bool maybeAppendEntry(size_t offset, size_t lineno, size_t colno,
648
bool isStepStart) {
649
if (!passesQuery(offset, lineno, colno)) {
650
return true;
651
}
652
653
if (OnlyOffsets) {
654
if (!NewbornArrayPush(cx_, result_, NumberValue(offset))) {
655
return false;
656
}
657
658
return true;
659
}
660
661
RootedPlainObject entry(cx_, NewBuiltinClassInstance<PlainObject>(cx_));
662
if (!entry) {
663
return false;
664
}
665
666
RootedValue value(cx_, NumberValue(offset));
667
if (!DefineDataProperty(cx_, entry, cx_->names().offset, value)) {
668
return false;
669
}
670
671
value = NumberValue(lineno);
672
if (!DefineDataProperty(cx_, entry, cx_->names().lineNumber, value)) {
673
return false;
674
}
675
676
value = NumberValue(colno);
677
if (!DefineDataProperty(cx_, entry, cx_->names().columnNumber, value)) {
678
return false;
679
}
680
681
value = BooleanValue(isStepStart);
682
if (!DefineDataProperty(cx_, entry, cx_->names().isStepStart, value)) {
683
return false;
684
}
685
686
if (!NewbornArrayPush(cx_, result_, ObjectValue(*entry))) {
687
return false;
688
}
689
return true;
690
}
691
692
bool parseIntValue(HandleValue value, size_t* result) {
693
if (!value.isNumber()) {
694
return false;
695
}
696
697
double doubleOffset = value.toNumber();
698
if (doubleOffset < 0 || (unsigned int)doubleOffset != doubleOffset) {
699
return false;
700
}
701
702
*result = doubleOffset;
703
return true;
704
}
705
706
bool parseIntValue(HandleValue value, Maybe<size_t>* result) {
707
size_t result_;
708
if (!parseIntValue(value, &result_)) {
709
return false;
710
}
711
712
*result = Some(result_);
713
return true;
714
}
715
716
public:
717
explicit GetPossibleBreakpointsMatcher(JSContext* cx,
718
MutableHandleObject result)
719
: cx_(cx),
720
result_(result),
721
minOffset(),
722
maxOffset(),
723
minLine(),
724
minColumn(0),
725
maxLine(),
726
maxColumn(0) {}
727
728
bool parseQuery(HandleObject query) {
729
RootedValue lineValue(cx_);
730
if (!GetProperty(cx_, query, query, cx_->names().line, &lineValue)) {
731
return false;
732
}
733
734
RootedValue minLineValue(cx_);
735
if (!GetProperty(cx_, query, query, cx_->names().minLine, &minLineValue)) {
736
return false;
737
}
738
739
RootedValue minColumnValue(cx_);
740
if (!GetProperty(cx_, query, query, cx_->names().minColumn,
741
&minColumnValue)) {
742
return false;
743
}
744
745
RootedValue minOffsetValue(cx_);
746
if (!GetProperty(cx_, query, query, cx_->names().minOffset,
747
&minOffsetValue)) {
748
return false;
749
}
750
751
RootedValue maxLineValue(cx_);
752
if (!GetProperty(cx_, query, query, cx_->names().maxLine, &maxLineValue)) {
753
return false;
754
}
755
756
RootedValue maxColumnValue(cx_);
757
if (!GetProperty(cx_, query, query, cx_->names().maxColumn,
758
&maxColumnValue)) {
759
return false;
760
}
761
762
RootedValue maxOffsetValue(cx_);
763
if (!GetProperty(cx_, query, query, cx_->names().maxOffset,
764
&maxOffsetValue)) {
765
return false;
766
}
767
768
if (!minOffsetValue.isUndefined()) {
769
if (!parseIntValue(minOffsetValue, &minOffset)) {
770
JS_ReportErrorNumberASCII(
771
cx_, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
772
"getPossibleBreakpoints' 'minOffset'", "not an integer");
773
return false;
774
}
775
}
776
if (!maxOffsetValue.isUndefined()) {
777
if (!parseIntValue(maxOffsetValue, &maxOffset)) {
778
JS_ReportErrorNumberASCII(
779
cx_, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
780
"getPossibleBreakpoints' 'maxOffset'", "not an integer");
781
return false;
782
}
783
}
784
785
if (!lineValue.isUndefined()) {
786
if (!minLineValue.isUndefined() || !maxLineValue.isUndefined()) {
787
JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr,
788
JSMSG_UNEXPECTED_TYPE,
789
"getPossibleBreakpoints' 'line'",
790
"not allowed alongside 'minLine'/'maxLine'");
791
return false;
792
}
793
794
size_t line;
795
if (!parseIntValue(lineValue, &line)) {
796
JS_ReportErrorNumberASCII(
797
cx_, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
798
"getPossibleBreakpoints' 'line'", "not an integer");
799
return false;
800
}
801
802
// If no end column is given, we use the default of 0 and wrap to
803
// the next line.
804
minLine = Some(line);
805
maxLine = Some(line + (maxColumnValue.isUndefined() ? 1 : 0));
806
}
807
808
if (!minLineValue.isUndefined()) {
809
if (!parseIntValue(minLineValue, &minLine)) {
810
JS_ReportErrorNumberASCII(
811
cx_, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
812
"getPossibleBreakpoints' 'minLine'", "not an integer");
813
return false;
814
}
815
}
816
817
if (!minColumnValue.isUndefined()) {
818
if (!minLine) {
819
JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr,
820
JSMSG_UNEXPECTED_TYPE,
821
"getPossibleBreakpoints' 'minColumn'",
822
"not allowed without 'line' or 'minLine'");
823
return false;
824
}
825
826
if (!parseIntValue(minColumnValue, &minColumn)) {
827
JS_ReportErrorNumberASCII(
828
cx_, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
829
"getPossibleBreakpoints' 'minColumn'", "not an integer");
830
return false;
831
}
832
}
833
834
if (!maxLineValue.isUndefined()) {
835
if (!parseIntValue(maxLineValue, &maxLine)) {
836
JS_ReportErrorNumberASCII(
837
cx_, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
838
"getPossibleBreakpoints' 'maxLine'", "not an integer");
839
return false;
840
}
841
}
842
843
if (!maxColumnValue.isUndefined()) {
844
if (!maxLine) {
845
JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr,
846
JSMSG_UNEXPECTED_TYPE,
847
"getPossibleBreakpoints' 'maxColumn'",
848
"not allowed without 'line' or 'maxLine'");
849
return false;
850
}
851
852
if (!parseIntValue(maxColumnValue, &maxColumn)) {
853
JS_ReportErrorNumberASCII(
854
cx_, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
855
"getPossibleBreakpoints' 'maxColumn'", "not an integer");
856
return false;
857
}
858
}
859
860
return true;
861
}
862
863
using ReturnType = bool;
864
ReturnType match(HandleScript script) {
865
// Second pass: build the result array.
866
result_.set(NewDenseEmptyArray(cx_));
867
if (!result_) {
868
return false;
869
}
870
871
for (BytecodeRangeWithPosition r(cx_, script); !r.empty(); r.popFront()) {
872
if (!r.frontIsBreakablePoint()) {
873
continue;
874
}
875
876
size_t offset = r.frontOffset();
877
size_t lineno = r.frontLineNumber();
878
size_t colno = r.frontColumnNumber();
879
880
if (!maybeAppendEntry(offset, lineno, colno,
881
r.frontIsBreakableStepPoint())) {
882
return false;
883
}
884
}
885
886
return true;
887
}
888
ReturnType match(Handle<LazyScript*> lazyScript) {
889
RootedScript script(cx_, DelazifyScript(cx_, lazyScript));
890
if (!script) {
891
return false;
892
}
893
return match(script);
894
}
895
ReturnType match(Handle<WasmInstanceObject*> instanceObj) {
896
wasm::Instance& instance = instanceObj->instance();
897
898
Vector<wasm::ExprLoc> offsets(cx_);
899
if (instance.debugEnabled() &&
900
!instance.debug().getAllColumnOffsets(&offsets)) {
901
return false;
902
}
903
904
result_.set(NewDenseEmptyArray(cx_));
905
if (!result_) {
906
return false;
907
}
908
909
for (uint32_t i = 0; i < offsets.length(); i++) {
910
size_t lineno = offsets[i].lineno;
911
size_t column = offsets[i].column;
912
size_t offset = offsets[i].offset;
913
if (!maybeAppendEntry(offset, lineno, column, true)) {
914
return false;
915
}
916
}
917
return true;
918
}
919
};
920
921
bool DebuggerScript::CallData::getPossibleBreakpoints() {
922
RootedObject result(cx);
923
GetPossibleBreakpointsMatcher<false> matcher(cx, &result);
924
if (args.length() >= 1 && !args[0].isUndefined()) {
925
RootedObject queryObject(cx, RequireObject(cx, args[0]));
926
if (!queryObject || !matcher.parseQuery(queryObject)) {
927
return false;
928
}
929
}
930
if (!referent.match(matcher)) {
931
return false;
932
}
933
934
args.rval().setObject(*result);
935
return true;
936
}
937
938
bool DebuggerScript::CallData::getPossibleBreakpointOffsets() {
939
RootedObject result(cx);
940
GetPossibleBreakpointsMatcher<true> matcher(cx, &result);
941
if (args.length() >= 1 && !args[0].isUndefined()) {
942
RootedObject queryObject(cx, RequireObject(cx, args[0]));
943
if (!queryObject || !matcher.parseQuery(queryObject)) {
944
return false;
945
}
946
}
947
if (!referent.match(matcher)) {
948
return false;
949
}
950
951
args.rval().setObject(*result);
952
return true;
953
}
954
955
class DebuggerScript::GetOffsetMetadataMatcher {
956
JSContext* cx_;
957
size_t offset_;
958
MutableHandlePlainObject result_;
959
960
public:
961
explicit GetOffsetMetadataMatcher(JSContext* cx, size_t offset,
962
MutableHandlePlainObject result)
963
: cx_(cx), offset_(offset), result_(result) {}
964
using ReturnType = bool;
965
ReturnType match(HandleScript script) {
966
if (!EnsureScriptOffsetIsValid(cx_, script, offset_)) {
967
return false;
968
}
969
970
result_.set(NewBuiltinClassInstance<PlainObject>(cx_));
971
if (!result_) {
972
return false;
973
}
974
975
BytecodeRangeWithPosition r(cx_, script);
976
while (!r.empty() && r.frontOffset() < offset_) {
977
r.popFront();
978
}
979
980
RootedValue value(cx_, NumberValue(r.frontLineNumber()));
981
if (!DefineDataProperty(cx_, result_, cx_->names().lineNumber, value)) {
982
return false;
983
}
984
985
value = NumberValue(r.frontColumnNumber());
986
if (!DefineDataProperty(cx_, result_, cx_->names().columnNumber, value)) {
987
return false;
988
}
989
990
value = BooleanValue(r.frontIsBreakablePoint());
991
if (!DefineDataProperty(cx_, result_, cx_->names().isBreakpoint, value)) {
992
return false;
993
}
994
995
value = BooleanValue(r.frontIsBreakableStepPoint());
996
if (!DefineDataProperty(cx_, result_, cx_->names().isStepStart, value)) {
997
return false;
998
}
999
1000
return true;
1001
}
1002
ReturnType match(Handle<LazyScript*> lazyScript) {
1003
RootedScript script(cx_, DelazifyScript(cx_, lazyScript));
1004
if (!script) {
1005
return false;
1006
}
1007
return match(script);
1008
}
1009
ReturnType match(Handle<WasmInstanceObject*> instanceObj) {
1010
wasm::Instance& instance = instanceObj->instance();
1011
if (!instance.debugEnabled()) {
1012
JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr,
1013
JSMSG_DEBUG_BAD_OFFSET);
1014
return false;
1015
}
1016
1017
size_t lineno;
1018
size_t column;
1019
if (!instance.debug().getOffsetLocation(offset_, &lineno, &column)) {
1020
JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr,
1021
JSMSG_DEBUG_BAD_OFFSET);
1022
return false;
1023
}
1024
1025
result_.set(NewBuiltinClassInstance<PlainObject>(cx_));
1026
if (!result_) {
1027
return false;
1028
}
1029
1030
RootedValue value(cx_, NumberValue(lineno));
1031
if (!DefineDataProperty(cx_, result_, cx_->names().lineNumber, value)) {
1032
return false;
1033
}
1034
1035
value = NumberValue(column);
1036
if (!DefineDataProperty(cx_, result_, cx_->names().columnNumber, value)) {
1037
return false;
1038
}
1039
1040
value.setBoolean(true);
1041
if (!DefineDataProperty(cx_, result_, cx_->names().isBreakpoint, value)) {
1042
return false;
1043
}
1044
1045
value.setBoolean(true);
1046
if (!DefineDataProperty(cx_, result_, cx_->names().isStepStart, value)) {
1047
return false;
1048
}
1049
1050
return true;
1051
}
1052
};
1053
1054
bool DebuggerScript::CallData::getOffsetMetadata() {
1055
if (!args.requireAtLeast(cx, "Debugger.Script.getOffsetMetadata", 1)) {
1056
return false;
1057
}
1058
size_t offset;
1059
if (!ScriptOffset(cx, args[0], &offset)) {
1060
return false;
1061
}
1062
1063
RootedPlainObject result(cx);
1064
GetOffsetMetadataMatcher matcher(cx, offset, &result);
1065
if (!referent.match(matcher)) {
1066
return false;
1067
}
1068
1069
args.rval().setObject(*result);
1070
return true;
1071
}
1072
1073
namespace {
1074
1075
/*
1076
* FlowGraphSummary::populate(cx, script) computes a summary of script's
1077
* control flow graph used by DebuggerScript_{getAllOffsets,getLineOffsets}.
1078
*
1079
* An instruction on a given line is an entry point for that line if it can be
1080
* reached from (an instruction on) a different line. We distinguish between the
1081
* following cases:
1082
* - hasNoEdges:
1083
* The instruction cannot be reached, so the instruction is not an entry
1084
* point for the line it is on.
1085
* - hasSingleEdge:
1086
* The instruction can be reached from a single line. If this line is
1087
* different from the line the instruction is on, the instruction is an
1088
* entry point for that line.
1089
*
1090
* Similarly, an instruction on a given position (line/column pair) is an
1091
* entry point for that position if it can be reached from (an instruction on) a
1092
* different position. Again, we distinguish between the following cases:
1093
* - hasNoEdges:
1094
* The instruction cannot be reached, so the instruction is not an entry
1095
* point for the position it is on.
1096
* - hasSingleEdge:
1097
* The instruction can be reached from a single position. If this line is
1098
* different from the position the instruction is on, the instruction is
1099
* an entry point for that position.
1100
*/
1101
class FlowGraphSummary {
1102
public:
1103
class Entry {
1104
public:
1105
static Entry createWithSingleEdge(size_t lineno, size_t column) {
1106
return Entry(lineno, column);
1107
}
1108
1109
static Entry createWithMultipleEdgesFromSingleLine(size_t lineno) {
1110
return Entry(lineno, SIZE_MAX);
1111
}
1112
1113
static Entry createWithMultipleEdgesFromMultipleLines() {
1114
return Entry(SIZE_MAX, SIZE_MAX);
1115
}
1116
1117
Entry() : lineno_(SIZE_MAX), column_(0) {}
1118
1119
bool hasNoEdges() const {
1120
return lineno_ == SIZE_MAX && column_ != SIZE_MAX;
1121
}
1122
1123
bool hasSingleEdge() const {
1124
return lineno_ != SIZE_MAX && column_ != SIZE_MAX;
1125
}
1126
1127
size_t lineno() const { return lineno_; }
1128
1129
size_t column() const { return column_; }
1130
1131
private:
1132
Entry(size_t lineno, size_t column) : lineno_(lineno), column_(column) {}
1133
1134
size_t lineno_;
1135
size_t column_;
1136
};
1137
1138
explicit FlowGraphSummary(JSContext* cx) : entries_(cx) {}
1139
1140
Entry& operator[](size_t index) { return entries_[index]; }
1141
1142
bool populate(JSContext* cx, JSScript* script) {
1143
if (!entries_.growBy(script->length())) {
1144
return false;
1145
}
1146
unsigned mainOffset = script->pcToOffset(script->main());
1147
entries_[mainOffset] = Entry::createWithMultipleEdgesFromMultipleLines();
1148
1149
size_t prevLineno = script->lineno();
1150
size_t prevColumn = 0;
1151
JSOp prevOp = JSOP_NOP;
1152
for (BytecodeRangeWithPosition r(cx, script); !r.empty(); r.popFront()) {
1153
size_t lineno = prevLineno;
1154
size_t column = prevColumn;
1155
JSOp op = r.frontOpcode();
1156
1157
if (FlowsIntoNext(prevOp)) {
1158
addEdge(prevLineno, prevColumn, r.frontOffset());
1159
}
1160
1161
// If we visit the branch target before we visit the
1162
// branch op itself, just reuse the previous location.
1163
// This is reasonable for the time being because this
1164
// situation can currently only arise from loop heads,
1165
// where this assumption holds.
1166
if (BytecodeIsJumpTarget(op) && !entries_[r.frontOffset()].hasNoEdges()) {
1167
lineno = entries_[r.frontOffset()].lineno();
1168
column = entries_[r.frontOffset()].column();
1169
}
1170
1171
if (r.frontIsEntryPoint()) {
1172
lineno = r.frontLineNumber();
1173
column = r.frontColumnNumber();
1174
}
1175
1176
if (CodeSpec[op].type() == JOF_JUMP) {
1177
addEdge(lineno, column, r.frontOffset() + GET_JUMP_OFFSET(r.frontPC()));
1178
} else if (op == JSOP_TABLESWITCH) {
1179
jsbytecode* const switchPC = r.frontPC();
1180
jsbytecode* pc = switchPC;
1181
size_t offset = r.frontOffset();
1182
ptrdiff_t step = JUMP_OFFSET_LEN;
1183
size_t defaultOffset = offset + GET_JUMP_OFFSET(pc);
1184
pc += step;
1185
addEdge(lineno, column, defaultOffset);
1186
1187
int32_t low = GET_JUMP_OFFSET(pc);
1188
pc += JUMP_OFFSET_LEN;
1189
int ncases = GET_JUMP_OFFSET(pc) - low + 1;
1190
pc += JUMP_OFFSET_LEN;
1191
1192
for (int i = 0; i < ncases; i++) {
1193
size_t target = script->tableSwitchCaseOffset(switchPC, i);
1194
addEdge(lineno, column, target);
1195
}
1196
} else if (op == JSOP_TRY) {
1197
// As there is no literal incoming edge into the catch block, we
1198
// make a fake one by copying the JSOP_TRY location, as-if this
1199
// was an incoming edge of the catch block. This is needed
1200
// because we only report offsets of entry points which have
1201
// valid incoming edges.
1202
for (const JSTryNote& tn : script->trynotes()) {
1203
if (tn.start == r.frontOffset() + 1) {
1204
uint32_t catchOffset = tn.start + tn.length;
1205
if (tn.kind == JSTRY_CATCH || tn.kind == JSTRY_FINALLY) {
1206
addEdge(lineno, column, catchOffset);
1207
}
1208
}
1209
}
1210
}
1211
1212
prevLineno = lineno;
1213
prevColumn = column;
1214
prevOp = op;
1215
}
1216
1217
return true;
1218
}
1219
1220
private:
1221
void addEdge(size_t sourceLineno, size_t sourceColumn, size_t targetOffset) {
1222
if (entries_[targetOffset].hasNoEdges()) {
1223
entries_[targetOffset] =
1224
Entry::createWithSingleEdge(sourceLineno, sourceColumn);
1225
} else if (entries_[targetOffset].lineno() != sourceLineno) {
1226
entries_[targetOffset] =
1227
Entry::createWithMultipleEdgesFromMultipleLines();
1228
} else if (entries_[targetOffset].column() != sourceColumn) {
1229
entries_[targetOffset] =
1230
Entry::createWithMultipleEdgesFromSingleLine(sourceLineno);
1231
}
1232
}
1233
1234
Vector<Entry> entries_;
1235
};
1236
1237
} /* anonymous namespace */
1238
1239
class DebuggerScript::GetOffsetLocationMatcher {
1240
JSContext* cx_;
1241
size_t offset_;
1242
MutableHandlePlainObject result_;
1243
1244
public:
1245
explicit GetOffsetLocationMatcher(JSContext* cx, size_t offset,
1246
MutableHandlePlainObject result)
1247
: cx_(cx), offset_(offset), result_(result) {}
1248
using ReturnType = bool;
1249
ReturnType match(HandleScript script) {
1250
if (!EnsureScriptOffsetIsValid(cx_, script, offset_)) {
1251
return false;
1252
}
1253
1254
FlowGraphSummary flowData(cx_);
1255
if (!flowData.populate(cx_, script)) {
1256
return false;
1257
}
1258
1259
result_.set(NewBuiltinClassInstance<PlainObject>(cx_));
1260
if (!result_) {
1261
return false;
1262
}
1263
1264
BytecodeRangeWithPosition r(cx_, script);
1265
while (!r.empty() && r.frontOffset() < offset_) {
1266
r.popFront();
1267
}
1268
1269
size_t offset = r.frontOffset();
1270
bool isEntryPoint = r.frontIsEntryPoint();
1271
1272
// Line numbers are only correctly defined on entry points. Thus looks
1273
// either for the next valid offset in the flowData, being the last entry
1274
// point flowing into the current offset, or for the next valid entry point.
1275
while (!r.frontIsEntryPoint() &&
1276
!flowData[r.frontOffset()].hasSingleEdge()) {
1277
r.popFront();
1278
MOZ_ASSERT(!r.empty());
1279
}
1280
1281
// If this is an entry point, take the line number associated with the entry
1282
// point, otherwise settle on the next instruction and take the incoming
1283
// edge position.
1284
size_t lineno;
1285
size_t column;
1286
if (r.frontIsEntryPoint()) {
1287
lineno = r.frontLineNumber();
1288
column = r.frontColumnNumber();
1289
} else {
1290
MOZ_ASSERT(flowData[r.frontOffset()].hasSingleEdge());
1291
lineno = flowData[r.frontOffset()].lineno();
1292
column = flowData[r.frontOffset()].column();
1293
}
1294
1295
RootedValue value(cx_, NumberValue(lineno));
1296
if (!DefineDataProperty(cx_, result_, cx_->names().lineNumber, value)) {
1297
return false;
1298
}
1299
1300
value = NumberValue(column);
1301
if (!DefineDataProperty(cx_, result_, cx_->names().columnNumber, value)) {
1302
return false;
1303
}
1304
1305
// The same entry point test that is used by getAllColumnOffsets.
1306
isEntryPoint = (isEntryPoint && !flowData[offset].hasNoEdges() &&
1307
(flowData[offset].lineno() != r.frontLineNumber() ||
1308
flowData[offset].column() != r.frontColumnNumber()));
1309
value.setBoolean(isEntryPoint);
1310
if (!DefineDataProperty(cx_, result_, cx_->names().isEntryPoint, value)) {
1311
return false;
1312
}
1313
1314
return true;
1315
}
1316
ReturnType match(Handle<LazyScript*> lazyScript) {
1317
RootedScript script(cx_, DelazifyScript(cx_, lazyScript));
1318
if (!script) {
1319
return false;
1320
}
1321
return match(script);
1322
}
1323
ReturnType match(Handle<WasmInstanceObject*> instanceObj) {
1324
wasm::Instance& instance = instanceObj->instance();
1325
if (!instance.debugEnabled()) {
1326
JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr,
1327
JSMSG_DEBUG_BAD_OFFSET);
1328
return false;
1329
}
1330
1331
size_t lineno;
1332
size_t column;
1333
if (!instance.debug().getOffsetLocation(offset_, &lineno, &column)) {
1334
JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr,
1335
JSMSG_DEBUG_BAD_OFFSET);
1336
return false;
1337
}
1338
1339
result_.set(NewBuiltinClassInstance<PlainObject>(cx_));
1340
if (!result_) {
1341
return false;
1342
}
1343
1344
RootedValue value(cx_, NumberValue(lineno));
1345
if (!DefineDataProperty(cx_, result_, cx_->names().lineNumber, value)) {
1346
return false;
1347
}
1348
1349
value = NumberValue(column);
1350
if (!DefineDataProperty(cx_, result_, cx_->names().columnNumber, value)) {
1351
return false;
1352
}
1353
1354
value.setBoolean(true);
1355
if (!DefineDataProperty(cx_, result_, cx_->names().isEntryPoint, value)) {
1356
return false;
1357
}
1358
1359
return true;
1360
}
1361
};
1362
1363
bool DebuggerScript::CallData::getOffsetLocation() {
1364
if (!args.requireAtLeast(cx, "Debugger.Script.getOffsetLocation", 1)) {
1365
return false;
1366
}
1367
size_t offset;
1368
if (!ScriptOffset(cx, args[0], &offset)) {
1369
return false;
1370
}
1371
1372
RootedPlainObject result(cx);
1373
GetOffsetLocationMatcher matcher(cx, offset, &result);
1374
if (!referent.match(matcher)) {
1375
return false;
1376
}
1377
1378
args.rval().setObject(*result);
1379
return true;
1380
}
1381
1382
class DebuggerScript::GetSuccessorOrPredecessorOffsetsMatcher {
1383
JSContext* cx_;
1384
size_t offset_;
1385
bool successor_;
1386
MutableHandleObject result_;
1387
1388
public:
1389
GetSuccessorOrPredecessorOffsetsMatcher(JSContext* cx, size_t offset,
1390
bool successor,
1391
MutableHandleObject result)
1392
: cx_(cx), offset_(offset), successor_(successor), result_(result) {}
1393
1394
using ReturnType = bool;
1395
1396
ReturnType match(HandleScript script) {
1397
if (!EnsureScriptOffsetIsValid(cx_, script, offset_)) {
1398
return false;
1399
}
1400
1401
PcVector adjacent;
1402
if (successor_) {
1403
if (!GetSuccessorBytecodes(script, script->code() + offset_, adjacent)) {
1404
ReportOutOfMemory(cx_);
1405
return false;
1406
}
1407
} else {
1408
if (!GetPredecessorBytecodes(script, script->code() + offset_,
1409
adjacent)) {
1410
ReportOutOfMemory(cx_);
1411
return false;
1412
}
1413
}
1414
1415
result_.set(NewDenseEmptyArray(cx_));
1416
if (!result_) {
1417
return false;
1418
}
1419
1420
for (jsbytecode* pc : adjacent) {
1421
if (!NewbornArrayPush(cx_, result_, NumberValue(pc - script->code()))) {
1422
return false;
1423
}
1424
}
1425
return true;
1426
}
1427
1428
ReturnType match(Handle<LazyScript*> lazyScript) {
1429
RootedScript script(cx_, DelazifyScript(cx_, lazyScript));
1430
if (!script) {
1431
return false;
1432
}
1433
return match(script);
1434
}
1435
1436
ReturnType match(Handle<WasmInstanceObject*> instance) {
1437
JS_ReportErrorASCII(
1438
cx_, "getSuccessorOrPredecessorOffsets NYI on wasm instances");
1439
return false;
1440
}
1441
};
1442
1443
template <bool Successor>
1444
bool DebuggerScript::CallData::getSuccessorOrPredecessorOffsets() {
1445
if (!args.requireAtLeast(cx, "successorOrPredecessorOffsets", 1)) {
1446
return false;
1447
}
1448
size_t offset;
1449
if (!ScriptOffset(cx, args[0], &offset)) {
1450
return false;
1451
}
1452
1453
RootedObject result(cx);
1454
GetSuccessorOrPredecessorOffsetsMatcher matcher(cx, offset, Successor,
1455
&result);
1456
if (!referent.match(matcher)) {
1457
return false;
1458
}
1459
1460
args.rval().setObject(*result);
1461
return true;
1462
}
1463
1464
// Return whether an opcode is considered effectful: it can have direct side
1465
// effects that can be observed outside of the current frame. Opcodes are not
1466
// effectful if they only modify the current frame's state, modify objects
1467
// created by the current frame, or can potentially call other scripts or
1468
// natives which could have side effects.
1469
static bool BytecodeIsEffectful(JSOp op) {
1470
switch (op) {
1471
case JSOP_SETPROP:
1472
case JSOP_STRICTSETPROP:
1473
case JSOP_SETPROP_SUPER:
1474
case JSOP_STRICTSETPROP_SUPER:
1475
case JSOP_SETELEM:
1476
case JSOP_STRICTSETELEM:
1477
case JSOP_SETELEM_SUPER:
1478
case JSOP_STRICTSETELEM_SUPER:
1479
case JSOP_SETNAME:
1480
case JSOP_STRICTSETNAME:
1481
case JSOP_SETGNAME:
1482
case JSOP_STRICTSETGNAME:
1483
case JSOP_DELPROP:
1484
case JSOP_STRICTDELPROP:
1485
case JSOP_DELELEM:
1486
case JSOP_STRICTDELELEM:
1487
case JSOP_DELNAME:
1488
case JSOP_SETALIASEDVAR:
1489
case JSOP_INITHOMEOBJECT:
1490
case JSOP_INITALIASEDLEXICAL:
1491
case JSOP_SETINTRINSIC:
1492
case JSOP_INITGLEXICAL:
1493
case JSOP_DEFVAR:
1494
case JSOP_DEFLET:
1495
case JSOP_DEFCONST:
1496
case JSOP_DEFFUN:
1497
case JSOP_SETFUNNAME:
1498
case JSOP_MUTATEPROTO:
1499
case JSOP_DYNAMIC_IMPORT:
1500
// Treat async functions as effectful so that microtask checkpoints
1501
// won't run.
1502
case JSOP_INITIALYIELD:
1503
case JSOP_YIELD:
1504
return true;
1505
1506
case JSOP_NOP:
1507
case JSOP_NOP_DESTRUCTURING:
1508
case JSOP_TRY_DESTRUCTURING:
1509
case JSOP_LINENO:
1510
case JSOP_JUMPTARGET:
1511
case JSOP_UNDEFINED:
1512
case JSOP_IFNE:
1513
case JSOP_IFEQ:
1514
case JSOP_RETURN:
1515
case JSOP_RETRVAL:
1516
case JSOP_AND:
1517
case JSOP_OR:
1518
case JSOP_COALESCE:
1519
case JSOP_TRY:
1520
case JSOP_THROW:
1521
case JSOP_GOTO:
1522
case JSOP_TABLESWITCH:
1523
case JSOP_CASE:
1524
case JSOP_DEFAULT:
1525
case JSOP_BITNOT:
1526
case JSOP_BITAND:
1527
case JSOP_BITOR:
1528
case JSOP_BITXOR:
1529
case JSOP_LSH:
1530
case JSOP_RSH:
1531
case JSOP_URSH:
1532
case JSOP_ADD:
1533
case JSOP_SUB:
1534
case JSOP_MUL:
1535
case JSOP_DIV:
1536
case JSOP_MOD:
1537
case JSOP_POW:
1538
case JSOP_POS:
1539
case JSOP_TONUMERIC:
1540
case JSOP_NEG:
1541
case JSOP_INC:
1542
case JSOP_DEC:
1543
case JSOP_TOSTRING:
1544
case JSOP_EQ:
1545
case JSOP_NE:
1546
case JSOP_STRICTEQ:
1547
case JSOP_STRICTNE:
1548
case JSOP_LT:
1549
case JSOP_LE:
1550
case JSOP_GT:
1551
case JSOP_GE:
1552
case JSOP_DOUBLE:
1553
case JSOP_BIGINT:
1554
case JSOP_STRING:
1555
case JSOP_SYMBOL:
1556
case JSOP_ZERO:
1557
case JSOP_ONE:
1558
case JSOP_NULL:
1559
case JSOP_VOID:
1560
case JSOP_HOLE:
1561
case JSOP_FALSE:
1562
case JSOP_TRUE:
1563
case JSOP_ARGUMENTS:
1564
case JSOP_REST:
1565
case JSOP_GETARG:
1566
case JSOP_SETARG:
1567
case JSOP_GETLOCAL:
1568
case JSOP_SETLOCAL:
1569
case JSOP_THROWSETCONST:
1570
case JSOP_THROWSETALIASEDCONST:
1571
case JSOP_THROWSETCALLEE:
1572
case JSOP_CHECKLEXICAL:
1573
case JSOP_INITLEXICAL:
1574
case JSOP_CHECKALIASEDLEXICAL:
1575
case JSOP_UNINITIALIZED:
1576
case JSOP_POP:
1577
case JSOP_POPN:
1578
case JSOP_DUPAT:
1579
case JSOP_NEWARRAY:
1580
case JSOP_NEWARRAY_COPYONWRITE:
1581
case JSOP_NEWINIT:
1582
case JSOP_NEWOBJECT:
1583
case JSOP_NEWOBJECT_WITHGROUP:
1584
case JSOP_INITELEM:
1585
case JSOP_INITHIDDENELEM:
1586
case JSOP_INITELEM_INC:
1587
case JSOP_INITELEM_ARRAY:
1588
case JSOP_INITPROP:
1589
case JSOP_INITLOCKEDPROP:
1590
case JSOP_INITHIDDENPROP:
1591
case JSOP_INITPROP_GETTER:
1592
case JSOP_INITHIDDENPROP_GETTER:
1593
case JSOP_INITPROP_SETTER:
1594
case JSOP_INITHIDDENPROP_SETTER:
1595
case JSOP_INITELEM_GETTER:
1596
case JSOP_INITHIDDENELEM_GETTER:
1597
case JSOP_INITELEM_SETTER:
1598
case JSOP_INITHIDDENELEM_SETTER:
1599
case JSOP_FUNCALL:
1600
case JSOP_FUNAPPLY:
1601
case JSOP_SPREADCALL:
1602
case JSOP_CALL:
1603
case JSOP_CALL_IGNORES_RV:
1604
case JSOP_CALLITER:
1605
case JSOP_NEW:
1606
case JSOP_EVAL:
1607
case JSOP_STRICTEVAL:
1608
case JSOP_INT8:
1609
case JSOP_UINT16:
1610
case JSOP_GETGNAME:
1611
case JSOP_GETNAME:
1612
case JSOP_GETINTRINSIC:
1613
case JSOP_GETIMPORT:
1614
case JSOP_BINDGNAME:
1615
case JSOP_BINDNAME:
1616
case JSOP_BINDVAR:
1617
case JSOP_DUP:
1618
case JSOP_DUP2:
1619
case JSOP_SWAP:
1620
case JSOP_PICK:
1621
case JSOP_UNPICK:
1622
case JSOP_GETALIASEDVAR:
1623
case JSOP_UINT24:
1624
case JSOP_RESUMEINDEX:
1625
case JSOP_INT32:
1626
case JSOP_LOOPHEAD:
1627
case JSOP_GETELEM:
1628
case JSOP_CALLELEM:
1629
case JSOP_LENGTH:
1630
case JSOP_NOT:
1631
case JSOP_FUNCTIONTHIS:
1632
case JSOP_GLOBALTHIS:
1633
case JSOP_CALLEE:
1634
case JSOP_ENVCALLEE:
1635
case JSOP_SUPERBASE:
1636
case JSOP_GETPROP_SUPER:
1637
case JSOP_GETELEM_SUPER:
1638
case JSOP_GETPROP:
1639
case JSOP_CALLPROP:
1640
case JSOP_REGEXP:
1641
case JSOP_CALLSITEOBJ:
1642
case JSOP_OBJECT:
1643
case JSOP_CLASSCONSTRUCTOR:
1644
case JSOP_TYPEOF:
1645
case JSOP_TYPEOFEXPR:
1646
case JSOP_TOASYNCITER:
1647
case JSOP_TOID:
1648
case JSOP_ITERNEXT:
1649
case JSOP_LAMBDA:
1650
case JSOP_LAMBDA_ARROW:
1651
case JSOP_PUSHLEXICALENV:
1652
case JSOP_POPLEXICALENV:
1653
case JSOP_FRESHENLEXICALENV:
1654
case JSOP_RECREATELEXICALENV:
1655
case JSOP_ITER:
1656
case JSOP_MOREITER:
1657
case JSOP_ISNOITER:
1658
case JSOP_ENDITER:
1659
case JSOP_IN:
1660
case JSOP_HASOWN:
1661
case JSOP_SETRVAL:
1662
case JSOP_INSTANCEOF:
1663
case JSOP_DEBUGLEAVELEXICALENV:
1664
case JSOP_DEBUGGER:
1665
case JSOP_GIMPLICITTHIS:
1666
case JSOP_IMPLICITTHIS:
1667
case JSOP_NEWTARGET:
1668
case JSOP_CHECKISOBJ:
1669
case JSOP_CHECKISCALLABLE:
1670
case JSOP_CHECKOBJCOERCIBLE:
1671
case JSOP_DEBUGCHECKSELFHOSTED:
1672
case JSOP_IS_CONSTRUCTING:
1673
case JSOP_OPTIMIZE_SPREADCALL:
1674
case JSOP_IMPORTMETA:
1675
case JSOP_INSTRUMENTATION_ACTIVE:
1676
case JSOP_INSTRUMENTATION_CALLBACK:
1677
case JSOP_INSTRUMENTATION_SCRIPT_ID:
1678
case JSOP_ENTERWITH:
1679
case JSOP_LEAVEWITH:
1680
case JSOP_SPREADNEW:
1681
case JSOP_SPREADEVAL:
1682
case JSOP_STRICTSPREADEVAL:
1683
case JSOP_CHECKCLASSHERITAGE:
1684
case JSOP_FUNWITHPROTO:
1685
case JSOP_OBJWITHPROTO:
1686
case JSOP_BUILTINPROTO:
1687
case JSOP_DERIVEDCONSTRUCTOR:
1688
case JSOP_CHECKTHIS:
1689
case JSOP_CHECKRETURN:
1690
case JSOP_CHECKTHISREINIT:
1691
case JSOP_SUPERFUN:
1692
case JSOP_SPREADSUPERCALL:
1693
case JSOP_SUPERCALL:
1694
case JSOP_PUSHVARENV:
1695
case JSOP_POPVARENV:
1696
case JSOP_GETBOUNDNAME:
1697
case JSOP_EXCEPTION:
1698
case JSOP_ISGENCLOSING:
1699
case JSOP_FINALYIELDRVAL:
1700
case JSOP_RESUME:
1701
case JSOP_AFTERYIELD:
1702
case JSOP_AWAIT:
1703
case JSOP_TRYSKIPAWAIT:
1704
case JSOP_GENERATOR:
1705
case JSOP_ASYNCAWAIT:
1706
case JSOP_ASYNCRESOLVE:
1707
case JSOP_FINALLY:
1708
case JSOP_GETRVAL:
1709
case JSOP_GOSUB:
1710
case JSOP_RETSUB:
1711
case JSOP_THROWMSG:
1712
case JSOP_FORCEINTERPRETER:
1713
case JSOP_UNUSED71:
1714
case JSOP_UNUSED106:
1715
case JSOP_UNUSED120:
1716
case JSOP_UNUSED149:
1717
case JSOP_UNUSED227:
1718
case JSOP_LIMIT:
1719
return false;
1720
}
1721
1722
MOZ_ASSERT_UNREACHABLE("Invalid opcode");
1723
return false;
1724
}
1725
1726
bool DebuggerScript::CallData::getEffectfulOffsets() {
1727
if (!ensureScript()) {
1728
return false;
1729
}
1730
1731
RootedObject result(cx, NewDenseEmptyArray(cx));
1732
if (!result) {
1733
return false;
1734
}
1735
for (BytecodeRangeWithPosition r(cx, script); !r.empty(); r.popFront()) {
1736
if (BytecodeIsEffectful(r.frontOpcode())) {
1737
if (!NewbornArrayPush(cx, result, NumberValue(r.frontOffset()))) {
1738
return false;
1739
}
1740
}
1741
}
1742
1743
args.rval().setObject(*result);
1744
return true;
1745
}
1746
1747
bool DebuggerScript::CallData::getAllOffsets() {
1748
if (!ensureScript()) {
1749
return false;
1750
}
1751
1752
// First pass: determine which offsets in this script are jump targets and
1753
// which line numbers jump to them.
1754
FlowGraphSummary flowData(cx);
1755
if (!flowData.populate(cx, script)) {
1756
return false;
1757
}
1758
1759
// Second pass: build the result array.
1760
RootedObject result(cx, NewDenseEmptyArray(cx));
1761
if (!result) {
1762
return false;
1763
}
1764
for (BytecodeRangeWithPosition r(cx, script); !r.empty(); r.popFront()) {
1765
if (!r.frontIsEntryPoint()) {
1766
continue;
1767
}
1768
1769
size_t offset = r.frontOffset();
1770
size_t lineno = r.frontLineNumber();
1771
1772
// Make a note, if the current instruction is an entry point for the current
1773
// line.
1774
if (!flowData[offset].hasNoEdges() && flowData[offset].lineno() != lineno) {
1775
// Get the offsets array for this line.
1776
RootedObject offsets(cx);
1777
RootedValue offsetsv(cx);
1778
1779
RootedId id(cx, INT_TO_JSID(lineno));
1780
1781
bool found;
1782
if (!HasOwnProperty(cx, result, id, &found)) {
1783
return false;
1784
}
1785
if (found && !GetProperty(cx, result, result, id, &offsetsv)) {
1786
return false;
1787
}
1788
1789
if (offsetsv.isObject()) {
1790
offsets = &offsetsv.toObject();
1791
} else {
1792
MOZ_ASSERT(offsetsv.isUndefined());
1793
1794
// Create an empty offsets array for this line.
1795
// Store it in the result array.
1796
RootedId id(cx);
1797
RootedValue v(cx, NumberValue(lineno));
1798
offsets = NewDenseEmptyArray(cx);
1799
if (!offsets || !ValueToId<CanGC>(cx,