Bug Summary

File:var/lib/jenkins/workspace/firefox-scan-build/js/src/debugger/Script.cpp
Warning:line 1248, column 9
Value stored to 'pc' is never read

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-pc-linux-gnu -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name Unified_cpp_js_src_debugger1.cpp -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -analyzer-config-compatibility-mode=true -mrelocation-model pic -pic-level 2 -fhalf-no-semantic-interposition -mframe-pointer=all -relaxed-aliasing -ffp-contract=off -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/js/src/debugger -fcoverage-compilation-dir=/var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/js/src/debugger -resource-dir /usr/lib/llvm-18/lib/clang/18 -include /var/lib/jenkins/workspace/firefox-scan-build/config/gcc_hidden.h -include /var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/mozilla-config.h -I /var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/dist/stl_wrappers -I /var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/dist/system_wrappers -U _FORTIFY_SOURCE -D _FORTIFY_SOURCE=2 -D DEBUG=1 -D WASM_SUPPORTS_HUGE_MEMORY -D JS_CACHEIR_SPEW -D JS_STRUCTURED_SPEW -D JS_HAS_CTYPES -D FFI_BUILDING -D EXPORT_JS_API -D MOZ_HAS_MOZGLUE -D MOZ_SUPPORT_LEAKCHECKING -I /var/lib/jenkins/workspace/firefox-scan-build/js/src/debugger -I /var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/js/src/debugger -I /var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/js/src -I /var/lib/jenkins/workspace/firefox-scan-build/js/src -I /var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/dist/include -I /var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/dist/include/nspr -I /var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/dist/include/nss -D MOZILLA_CLIENT -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/x86_64-linux-gnu/c++/14 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/backward -internal-isystem /usr/lib/llvm-18/lib/clang/18/include -internal-isystem /usr/local/include -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/14/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -O3 -Wno-error=tautological-type-limit-compare -Wno-invalid-offsetof -Wno-range-loop-analysis -Wno-deprecated-anon-enum-enum-conversion -Wno-deprecated-enum-enum-conversion -Wno-deprecated-this-capture -Wno-inline-new-delete -Wno-error=deprecated-declarations -Wno-error=array-bounds -Wno-error=free-nonheap-object -Wno-error=atomic-alignment -Wno-error=deprecated-builtins -Wno-psabi -Wno-error=builtin-macro-redefined -Wno-vla-cxx-extension -Wno-unknown-warning-option -fdeprecated-macro -ferror-limit 19 -stack-protector 2 -fstack-clash-protection -ftrivial-auto-var-init=pattern -fno-rtti -fgnuc-version=4.2.1 -fno-aligned-allocation -vectorize-loops -vectorize-slp -analyzer-checker optin.performance.Padding -analyzer-output=html -analyzer-config stable-report-filename=true -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /tmp/scan-build-2024-07-27-022226-2793976-1 -x c++ Unified_cpp_js_src_debugger1.cpp
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, UINT32_MAX, SIZE_MAX, int32_t
15
16#include "jsnum.h" // for ToNumber
17#include "NamespaceImports.h" // for CallArgs, RootedValue
18
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
52
53#include "gc/Marking-inl.h" // for MaybeForwardedObjectIs
54#include "vm/BytecodeUtil-inl.h" // for BytecodeRangeWithPosition
55#include "vm/JSAtomUtils-inl.h" // for PrimitiveValueToId
56#include "vm/JSObject-inl.h" // for NewBuiltinClassInstance, NewObjectWithGivenProto, NewTenuredObjectWithGivenProto
57#include "vm/JSScript-inl.h" // for JSScript::global
58#include "vm/ObjectOperations-inl.h" // for GetProperty
59#include "vm/Realm-inl.h" // for AutoRealm::AutoRealm
60
61using namespace js;
62
63using mozilla::Maybe;
64using mozilla::Some;
65
66const JSClassOps DebuggerScript::classOps_ = {
67 nullptr, // addProperty
68 nullptr, // delProperty
69 nullptr, // enumerate
70 nullptr, // newEnumerate
71 nullptr, // resolve
72 nullptr, // mayResolve
73 nullptr, // finalize
74 nullptr, // call
75 nullptr, // construct
76 CallTraceMethod<DebuggerScript>, // trace
77};
78
79const JSClass DebuggerScript::class_ = {
80 "Script", JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS), &classOps_};
81
82void DebuggerScript::trace(JSTracer* trc) {
83 // This comes from a private pointer, so no barrier needed.
84 gc::Cell* cell = getReferentCell();
85 if (cell) {
86 if (cell->is<BaseScript>()) {
87 BaseScript* script = cell->as<BaseScript>();
88 TraceManuallyBarrieredCrossCompartmentEdge(
89 trc, this, &script, "Debugger.Script script referent");
90 if (script != cell->as<BaseScript>()) {
91 setReservedSlotGCThingAsPrivateUnbarriered(SCRIPT_SLOT, script);
92 }
93 } else {
94 JSObject* wasm = cell->as<JSObject>();
95 TraceManuallyBarrieredCrossCompartmentEdge(
96 trc, this, &wasm, "Debugger.Script wasm referent");
97 if (wasm != cell->as<JSObject>()) {
98 MOZ_ASSERT(gc::MaybeForwardedObjectIs<WasmInstanceObject>(wasm))do { static_assert( mozilla::detail::AssertionConditionType<
decltype(gc::MaybeForwardedObjectIs<WasmInstanceObject>
(wasm))>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(gc::MaybeForwardedObjectIs<WasmInstanceObject>
(wasm)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("gc::MaybeForwardedObjectIs<WasmInstanceObject>(wasm)"
, "/var/lib/jenkins/workspace/firefox-scan-build/js/src/debugger/Script.cpp"
, 98); AnnotateMozCrashReason("MOZ_ASSERT" "(" "gc::MaybeForwardedObjectIs<WasmInstanceObject>(wasm)"
")"); do { *((volatile int*)__null) = 98; __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
99 setReservedSlotGCThingAsPrivateUnbarriered(SCRIPT_SLOT, wasm);
100 }
101 }
102 }
103}
104
105/* static */
106NativeObject* DebuggerScript::initClass(JSContext* cx,
107 Handle<GlobalObject*> global,
108 HandleObject debugCtor) {
109 return InitClass(cx, debugCtor, nullptr, nullptr, "Script", construct, 0,
110 properties_, methods_, nullptr, nullptr);
111}
112
113/* static */
114DebuggerScript* DebuggerScript::create(JSContext* cx, HandleObject proto,
115 Handle<DebuggerScriptReferent> referent,
116 Handle<NativeObject*> debugger) {
117 DebuggerScript* scriptobj =
118 NewTenuredObjectWithGivenProto<DebuggerScript>(cx, proto);
119 if (!scriptobj) {
120 return nullptr;
121 }
122
123 scriptobj->setReservedSlot(DebuggerScript::OWNER_SLOT,
124 ObjectValue(*debugger));
125 referent.get().match([&](auto& scriptHandle) {
126 scriptobj->setReservedSlotGCThingAsPrivate(SCRIPT_SLOT, scriptHandle);
127 });
128
129 return scriptobj;
130}
131
132static JSScript* DelazifyScript(JSContext* cx, Handle<BaseScript*> script) {
133 if (script->hasBytecode()) {
134 return script->asJSScript();
135 }
136 MOZ_ASSERT(script->isFunction())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(script->isFunction())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(script->isFunction()))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("script->isFunction()"
, "/var/lib/jenkins/workspace/firefox-scan-build/js/src/debugger/Script.cpp"
, 136); AnnotateMozCrashReason("MOZ_ASSERT" "(" "script->isFunction()"
")"); do { *((volatile int*)__null) = 136; __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
137
138 // JSFunction::getOrCreateScript requires an enclosing scope. This requires
139 // the enclosing script to be non-lazy.
140 if (script->hasEnclosingScript()) {
141 Rooted<BaseScript*> enclosingScript(cx, script->enclosingScript());
142 if (!DelazifyScript(cx, enclosingScript)) {
143 return nullptr;
144 }
145
146 if (!script->isReadyForDelazification()) {
147 // It didn't work! Delazifying the enclosing script still didn't
148 // delazify this script. This happens when the function
149 // corresponding to this script was removed by constant folding.
150 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
151 JSMSG_DEBUG_OPTIMIZED_OUT_FUN);
152 return nullptr;
153 }
154 }
155
156 MOZ_ASSERT(script->enclosingScope())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(script->enclosingScope())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(script->enclosingScope())
)), 0))) { do { } while (false); MOZ_ReportAssertionFailure("script->enclosingScope()"
, "/var/lib/jenkins/workspace/firefox-scan-build/js/src/debugger/Script.cpp"
, 156); AnnotateMozCrashReason("MOZ_ASSERT" "(" "script->enclosingScope()"
")"); do { *((volatile int*)__null) = 156; __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
157
158 RootedFunction fun(cx, script->function());
159 AutoRealm ar(cx, fun);
160 return JSFunction::getOrCreateScript(cx, fun);
161}
162
163/* static */
164DebuggerScript* DebuggerScript::check(JSContext* cx, HandleValue v) {
165 JSObject* thisobj = RequireObject(cx, v);
166 if (!thisobj) {
167 return nullptr;
168 }
169 if (!thisobj->is<DebuggerScript>()) {
170 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
171 JSMSG_INCOMPATIBLE_PROTO, "Debugger.Script",
172 "method", thisobj->getClass()->name);
173 return nullptr;
174 }
175
176 return &thisobj->as<DebuggerScript>();
177}
178
179struct MOZ_STACK_CLASS DebuggerScript::CallData {
180 JSContext* cx;
181 const CallArgs& args;
182
183 Handle<DebuggerScript*> obj;
184 Rooted<DebuggerScriptReferent> referent;
185 RootedScript script;
186
187 CallData(JSContext* cx, const CallArgs& args, Handle<DebuggerScript*> obj)
188 : cx(cx),
189 args(args),
190 obj(obj),
191 referent(cx, obj->getReferent()),
192 script(cx) {}
193
194 [[nodiscard]] bool ensureScriptMaybeLazy() {
195 if (!referent.is<BaseScript*>()) {
196 ReportValueError(cx, JSMSG_DEBUG_BAD_REFERENT, JSDVG_SEARCH_STACK1,
197 args.thisv(), nullptr, "a JS script");
198 return false;
199 }
200 return true;
201 }
202
203 [[nodiscard]] bool ensureScript() {
204 if (!ensureScriptMaybeLazy()) {
205 return false;
206 }
207 script = DelazifyScript(cx, referent.as<BaseScript*>());
208 if (!script) {
209 return false;
210 }
211 return true;
212 }
213
214 bool getIsGeneratorFunction();
215 bool getIsAsyncFunction();
216 bool getIsFunction();
217 bool getIsModule();
218 bool getDisplayName();
219 bool getParameterNames();
220 bool getUrl();
221 bool getStartLine();
222 bool getStartColumn();
223 bool getLineCount();
224 bool getSource();
225 bool getSourceStart();
226 bool getSourceLength();
227 bool getMainOffset();
228 bool getGlobal();
229 bool getFormat();
230 bool getChildScripts();
231 bool getPossibleBreakpoints();
232 bool getPossibleBreakpointOffsets();
233 bool getOffsetMetadata();
234 bool getOffsetLocation();
235 bool getEffectfulOffsets();
236 bool getAllOffsets();
237 bool getAllColumnOffsets();
238 bool getLineOffsets();
239 bool setBreakpoint();
240 bool getBreakpoints();
241 bool clearBreakpoint();
242 bool clearAllBreakpoints();
243 bool isInCatchScope();
244 bool getOffsetsCoverage();
245
246 using Method = bool (CallData::*)();
247
248 template <Method MyMethod>
249 static bool ToNative(JSContext* cx, unsigned argc, Value* vp);
250};
251
252template <DebuggerScript::CallData::Method MyMethod>
253/* static */
254bool DebuggerScript::CallData::ToNative(JSContext* cx, unsigned argc,
255 Value* vp) {
256 CallArgs args = CallArgsFromVp(argc, vp);
257
258 Rooted<DebuggerScript*> obj(cx, DebuggerScript::check(cx, args.thisv()));
259 if (!obj) {
260 return false;
261 }
262
263 CallData data(cx, args, obj);
264 return (data.*MyMethod)();
265}
266
267bool DebuggerScript::CallData::getIsGeneratorFunction() {
268 if (!ensureScriptMaybeLazy()) {
269 return false;
270 }
271 args.rval().setBoolean(obj->getReferentScript()->isGenerator());
272 return true;
273}
274
275bool DebuggerScript::CallData::getIsAsyncFunction() {
276 if (!ensureScriptMaybeLazy()) {
277 return false;
278 }
279 args.rval().setBoolean(obj->getReferentScript()->isAsync());
280 return true;
281}
282
283bool DebuggerScript::CallData::getIsFunction() {
284 if (!ensureScriptMaybeLazy()) {
285 return false;
286 }
287
288 args.rval().setBoolean(obj->getReferentScript()->function());
289 return true;
290}
291
292bool DebuggerScript::CallData::getIsModule() {
293 if (!ensureScriptMaybeLazy()) {
294 return false;
295 }
296 BaseScript* script = referent.as<BaseScript*>();
297
298 args.rval().setBoolean(script->isModule());
299 return true;
300}
301
302bool DebuggerScript::CallData::getDisplayName() {
303 if (!ensureScriptMaybeLazy()) {
304 return false;
305 }
306
307 JSFunction* func = obj->getReferentScript()->function();
308 if (!func) {
309 args.rval().setUndefined();
310 return true;
311 }
312
313 JSAtom* name = func->fullDisplayAtom();
314 if (!name) {
315 args.rval().setUndefined();
316 return true;
317 }
318
319 RootedValue namev(cx, StringValue(name));
320 Debugger* dbg = obj->owner();
321 if (!dbg->wrapDebuggeeValue(cx, &namev)) {
322 return false;
323 }
324 args.rval().set(namev);
325 return true;
326}
327
328bool DebuggerScript::CallData::getParameterNames() {
329 if (!ensureScript()) {
330 return false;
331 }
332
333 RootedFunction fun(cx, referent.as<BaseScript*>()->function());
334 if (!fun) {
335 args.rval().setUndefined();
336 return true;
337 }
338
339 ArrayObject* arr = GetFunctionParameterNamesArray(cx, fun);
340 if (!arr) {
341 return false;
342 }
343
344 args.rval().setObject(*arr);
345 return true;
346}
347
348bool DebuggerScript::CallData::getUrl() {
349 if (!ensureScriptMaybeLazy()) {
350 return false;
351 }
352
353 Rooted<BaseScript*> script(cx, referent.as<BaseScript*>());
354
355 if (script->filename()) {
356 JSString* str;
357 if (const char* introducer = script->scriptSource()->introducerFilename()) {
358 str =
359 NewStringCopyUTF8N(cx, JS::UTF8Chars(introducer, strlen(introducer)));
360 } else {
361 const char* filename = script->filename();
362 str = NewStringCopyUTF8N(cx, JS::UTF8Chars(filename, strlen(filename)));
363 }
364 if (!str) {
365 return false;
366 }
367 args.rval().setString(str);
368 } else {
369 args.rval().setNull();
370 }
371 return true;
372}
373
374bool DebuggerScript::CallData::getStartLine() {
375 args.rval().setNumber(
376 referent.get().match([](BaseScript*& s) { return s->lineno(); },
377 [](WasmInstanceObject*&) { return (uint32_t)1; }));
378 return true;
379}
380
381bool DebuggerScript::CallData::getStartColumn() {
382 JS::LimitedColumnNumberOneOrigin column = referent.get().match(
383 [](BaseScript*& s) { return s->column(); },
384 [](WasmInstanceObject*&) {
385 return JS::LimitedColumnNumberOneOrigin(
386 JS::WasmFunctionIndex::DefaultBinarySourceColumnNumberOneOrigin);
387 });
388 args.rval().setNumber(column.oneOriginValue());
389 return true;
390}
391
392struct DebuggerScript::GetLineCountMatcher {
393 JSContext* cx_;
394 double totalLines;
395
396 explicit GetLineCountMatcher(JSContext* cx) : cx_(cx), totalLines(0.0) {}
397 using ReturnType = bool;
398
399 ReturnType match(Handle<BaseScript*> base) {
400 RootedScript script(cx_, DelazifyScript(cx_, base));
401 if (!script) {
402 return false;
403 }
404 totalLines = double(GetScriptLineExtent(script));
405 return true;
406 }
407 ReturnType match(Handle<WasmInstanceObject*> instanceObj) {
408 wasm::Instance& instance = instanceObj->instance();
409 if (instance.debugEnabled()) {
410 totalLines = double(instance.debug().bytecode().length());
411 } else {
412 totalLines = 0;
413 }
414 return true;
415 }
416};
417
418bool DebuggerScript::CallData::getLineCount() {
419 GetLineCountMatcher matcher(cx);
420 if (!referent.match(matcher)) {
421 return false;
422 }
423 args.rval().setNumber(matcher.totalLines);
424 return true;
425}
426
427class DebuggerScript::GetSourceMatcher {
428 JSContext* cx_;
429 Debugger* dbg_;
430
431 public:
432 GetSourceMatcher(JSContext* cx, Debugger* dbg) : cx_(cx), dbg_(dbg) {}
433
434 using ReturnType = DebuggerSource*;
435
436 ReturnType match(Handle<BaseScript*> script) {
437 Rooted<ScriptSourceObject*> source(cx_, script->sourceObject());
438 return dbg_->wrapSource(cx_, source);
439 }
440 ReturnType match(Handle<WasmInstanceObject*> wasmInstance) {
441 return dbg_->wrapWasmSource(cx_, wasmInstance);
442 }
443};
444
445bool DebuggerScript::CallData::getSource() {
446 Debugger* dbg = obj->owner();
447
448 GetSourceMatcher matcher(cx, dbg);
449 Rooted<DebuggerSource*> sourceObject(cx, referent.match(matcher));
450 if (!sourceObject) {
451 return false;
452 }
453
454 args.rval().setObject(*sourceObject);
455 return true;
456}
457
458bool DebuggerScript::CallData::getSourceStart() {
459 if (!ensureScriptMaybeLazy()) {
460 return false;
461 }
462 args.rval().setNumber(uint32_t(obj->getReferentScript()->sourceStart()));
463 return true;
464}
465
466bool DebuggerScript::CallData::getSourceLength() {
467 if (!ensureScriptMaybeLazy()) {
468 return false;
469 }
470 args.rval().setNumber(uint32_t(obj->getReferentScript()->sourceLength()));
471 return true;
472}
473
474bool DebuggerScript::CallData::getMainOffset() {
475 if (!ensureScript()) {
476 return false;
477 }
478 args.rval().setNumber(uint32_t(script->mainOffset()));
479 return true;
480}
481
482bool DebuggerScript::CallData::getGlobal() {
483 if (!ensureScript()) {
484 return false;
485 }
486 Debugger* dbg = obj->owner();
487
488 RootedValue v(cx, ObjectValue(script->global()));
489 if (!dbg->wrapDebuggeeValue(cx, &v)) {
490 return false;
491 }
492 args.rval().set(v);
493 return true;
494}
495
496bool DebuggerScript::CallData::getFormat() {
497 args.rval().setString(referent.get().match(
498 [this](BaseScript*&) { return cx->names().js.get(); },
499 [this](WasmInstanceObject*&) { return cx->names().wasm.get(); }));
500 return true;
501}
502
503static bool PushFunctionScript(JSContext* cx, Debugger* dbg, HandleFunction fun,
504 HandleObject array) {
505 // Ignore asm.js natives.
506 if (!IsInterpretedNonSelfHostedFunction(fun)) {
507 return true;
508 }
509
510 Rooted<BaseScript*> script(cx, fun->baseScript());
511 MOZ_ASSERT(script)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(script)>::isValid, "invalid assertion condition")
; if ((__builtin_expect(!!(!(!!(script))), 0))) { do { } while
(false); MOZ_ReportAssertionFailure("script", "/var/lib/jenkins/workspace/firefox-scan-build/js/src/debugger/Script.cpp"
, 511); AnnotateMozCrashReason("MOZ_ASSERT" "(" "script" ")")
; do { *((volatile int*)__null) = 511; __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
512 if (!script) {
513 // If the function doesn't have script, ignore it.
514 return true;
515 }
516 RootedObject wrapped(cx, dbg->wrapScript(cx, script));
517 if (!wrapped) {
518 return false;
519 }
520
521 return NewbornArrayPush(cx, array, ObjectValue(*wrapped));
522}
523
524static bool PushInnerFunctions(JSContext* cx, Debugger* dbg, HandleObject array,
525 mozilla::Span<const JS::GCCellPtr> gcThings) {
526 RootedFunction fun(cx);
527
528 for (JS::GCCellPtr gcThing : gcThings) {
529 if (!gcThing.is<JSObject>()) {
530 continue;
531 }
532
533 JSObject* obj = &gcThing.as<JSObject>();
534 if (obj->is<JSFunction>()) {
535 fun = &obj->as<JSFunction>();
536
537 // Ignore any delazification placeholder functions. These should not be
538 // exposed to debugger in any way.
539 if (fun->isGhost()) {
540 continue;
541 }
542
543 if (!PushFunctionScript(cx, dbg, fun, array)) {
544 return false;
545 }
546 }
547 }
548
549 return true;
550}
551
552bool DebuggerScript::CallData::getChildScripts() {
553 if (!ensureScriptMaybeLazy()) {
554 return false;
555 }
556 Debugger* dbg = obj->owner();
557
558 RootedObject result(cx, NewDenseEmptyArray(cx));
559 if (!result) {
560 return false;
561 }
562
563 Rooted<BaseScript*> script(cx, obj->getReferent().as<BaseScript*>());
564 if (!PushInnerFunctions(cx, dbg, result, script->gcthings())) {
565 return false;
566 }
567
568 args.rval().setObject(*result);
569 return true;
570}
571
572static bool ScriptOffset(JSContext* cx, const Value& v, size_t* offsetp) {
573 double d;
574 size_t off;
575
576 bool ok = v.isNumber();
577 if (ok) {
578 d = v.toNumber();
579 off = size_t(d);
580 }
581 if (!ok || off != d) {
582 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
583 JSMSG_DEBUG_BAD_OFFSET);
584 return false;
585 }
586 *offsetp = off;
587 return true;
588}
589
590static bool EnsureScriptOffsetIsValid(JSContext* cx, JSScript* script,
591 size_t offset) {
592 if (IsValidBytecodeOffset(cx, script, offset)) {
593 return true;
594 }
595 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
596 JSMSG_DEBUG_BAD_OFFSET);
597 return false;
598}
599
600static bool IsGeneratorSlotInitialization(JSScript* script, size_t offset,
601 JSContext* cx) {
602 jsbytecode* pc = script->offsetToPC(offset);
603 if (JSOp(*pc) != JSOp::SetAliasedVar) {
604 return false;
605 }
606
607 PropertyName* name = EnvironmentCoordinateNameSlow(script, pc);
608 return name == cx->names().dot_generator_;
609}
610
611static bool EnsureBreakpointIsAllowed(JSContext* cx, JSScript* script,
612 size_t offset) {
613 // Disallow breakpoint for `JSOp::SetAliasedVar` after `JSOp::Generator`.
614 // Those 2 instructions are supposed to be atomic, and nothing should happen
615 // in between them.
616 //
617 // Hitting a breakpoint there breaks the assumption around the existence of
618 // the frame's `GeneratorInfo`.
619 // (see `DebugAPI::slowPathOnNewGenerator` and `DebuggerFrame::create`)
620 if (IsGeneratorSlotInitialization(script, offset, cx)) {
621 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
622 JSMSG_DEBUG_BREAKPOINT_NOT_ALLOWED);
623 return false;
624 }
625
626 return true;
627}
628
629template <bool OnlyOffsets>
630class DebuggerScript::GetPossibleBreakpointsMatcher {
631 JSContext* cx_;
632 MutableHandleObject result_;
633
634 Maybe<size_t> minOffset;
635 Maybe<size_t> maxOffset;
636
637 Maybe<uint32_t> minLine;
638 JS::LimitedColumnNumberOneOrigin minColumn;
639 Maybe<uint32_t> maxLine;
640 JS::LimitedColumnNumberOneOrigin maxColumn;
641
642 bool passesQuery(size_t offset, uint32_t lineno,
643 JS::LimitedColumnNumberOneOrigin colno) {
644 // [minOffset, maxOffset) - Inclusive minimum and exclusive maximum.
645 if ((minOffset && offset < *minOffset) ||
646 (maxOffset && offset >= *maxOffset)) {
647 return false;
648 }
649
650 if (minLine) {
651 if (lineno < *minLine || (lineno == *minLine && colno < minColumn)) {
652 return false;
653 }
654 }
655
656 if (maxLine) {
657 if (lineno > *maxLine || (lineno == *maxLine && colno >= maxColumn)) {
658 return false;
659 }
660 }
661
662 return true;
663 }
664
665 bool maybeAppendEntry(size_t offset, uint32_t lineno,
666 JS::LimitedColumnNumberOneOrigin colno,
667 bool isStepStart) {
668 if (!passesQuery(offset, lineno, colno)) {
669 return true;
670 }
671
672 if (OnlyOffsets) {
673 if (!NewbornArrayPush(cx_, result_, NumberValue(offset))) {
674 return false;
675 }
676
677 return true;
678 }
679
680 Rooted<PlainObject*> entry(cx_, NewPlainObject(cx_));
681 if (!entry) {
682 return false;
683 }
684
685 RootedValue value(cx_, NumberValue(offset));
686 if (!DefineDataProperty(cx_, entry, cx_->names().offset, value)) {
687 return false;
688 }
689
690 value = NumberValue(lineno);
691 if (!DefineDataProperty(cx_, entry, cx_->names().lineNumber, value)) {
692 return false;
693 }
694
695 value = NumberValue(colno.oneOriginValue());
696 if (!DefineDataProperty(cx_, entry, cx_->names().columnNumber, value)) {
697 return false;
698 }
699
700 value = BooleanValue(isStepStart);
701 if (!DefineDataProperty(cx_, entry, cx_->names().isStepStart, value)) {
702 return false;
703 }
704
705 if (!NewbornArrayPush(cx_, result_, ObjectValue(*entry))) {
706 return false;
707 }
708 return true;
709 }
710
711 template <typename T>
712 bool parseIntValueImpl(HandleValue value, T* result) {
713 if (!value.isNumber()) {
714 return false;
715 }
716
717 double doubleOffset = value.toNumber();
718 if (doubleOffset < 0 || (unsigned int)doubleOffset != doubleOffset) {
719 return false;
720 }
721
722 *result = doubleOffset;
723 return true;
724 }
725
726 bool parseUint32Value(HandleValue value, uint32_t* result) {
727 return parseIntValueImpl(value, result);
728 }
729 bool parseColumnValue(HandleValue value,
730 JS::LimitedColumnNumberOneOrigin* result) {
731 uint32_t tmp;
732 if (!parseIntValueImpl(value, &tmp)) {
733 return false;
734 }
735 if (tmp == 0) {
736 return false;
737 }
738 *result->addressOfValueForTranscode() = tmp;
739 return true;
740 }
741 bool parseSizeTValue(HandleValue value, size_t* result) {
742 return parseIntValueImpl(value, result);
743 }
744
745 template <typename T>
746 bool parseIntValueMaybeImpl(HandleValue value, Maybe<T>* result) {
747 T result_;
748 if (!parseIntValueImpl(value, &result_)) {
749 return false;
750 }
751
752 *result = Some(result_);
753 return true;
754 }
755
756 bool parseUint32Value(HandleValue value, Maybe<uint32_t>* result) {
757 return parseIntValueMaybeImpl(value, result);
758 }
759 bool parseSizeTValue(HandleValue value, Maybe<size_t>* result) {
760 return parseIntValueMaybeImpl(value, result);
761 }
762
763 public:
764 explicit GetPossibleBreakpointsMatcher(JSContext* cx,
765 MutableHandleObject result)
766 : cx_(cx), result_(result) {}
767
768 bool parseQuery(HandleObject query) {
769 RootedValue lineValue(cx_);
770 if (!GetProperty(cx_, query, query, cx_->names().line, &lineValue)) {
771 return false;
772 }
773
774 RootedValue minLineValue(cx_);
775 if (!GetProperty(cx_, query, query, cx_->names().minLine, &minLineValue)) {
776 return false;
777 }
778
779 RootedValue minColumnValue(cx_);
780 if (!GetProperty(cx_, query, query, cx_->names().minColumn,
781 &minColumnValue)) {
782 return false;
783 }
784
785 RootedValue minOffsetValue(cx_);
786 if (!GetProperty(cx_, query, query, cx_->names().minOffset,
787 &minOffsetValue)) {
788 return false;
789 }
790
791 RootedValue maxLineValue(cx_);
792 if (!GetProperty(cx_, query, query, cx_->names().maxLine, &maxLineValue)) {
793 return false;
794 }
795
796 RootedValue maxColumnValue(cx_);
797 if (!GetProperty(cx_, query, query, cx_->names().maxColumn,
798 &maxColumnValue)) {
799 return false;
800 }
801
802 RootedValue maxOffsetValue(cx_);
803 if (!GetProperty(cx_, query, query, cx_->names().maxOffset,
804 &maxOffsetValue)) {
805 return false;
806 }
807
808 if (!minOffsetValue.isUndefined()) {
809 if (!parseSizeTValue(minOffsetValue, &minOffset)) {
810 JS_ReportErrorNumberASCII(
811 cx_, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
812 "getPossibleBreakpoints' 'minOffset'", "not an integer");
813 return false;
814 }
815 }
816 if (!maxOffsetValue.isUndefined()) {
817 if (!parseSizeTValue(maxOffsetValue, &maxOffset)) {
818 JS_ReportErrorNumberASCII(
819 cx_, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
820 "getPossibleBreakpoints' 'maxOffset'", "not an integer");
821 return false;
822 }
823 }
824
825 if (!lineValue.isUndefined()) {
826 if (!minLineValue.isUndefined() || !maxLineValue.isUndefined()) {
827 JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr,
828 JSMSG_UNEXPECTED_TYPE,
829 "getPossibleBreakpoints' 'line'",
830 "not allowed alongside 'minLine'/'maxLine'");
831 return false;
832 }
833
834 uint32_t line;
835 if (!parseUint32Value(lineValue, &line)) {
836 JS_ReportErrorNumberASCII(
837 cx_, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
838 "getPossibleBreakpoints' 'line'", "not an integer");
839 return false;
840 }
841
842 // If no end column is given, we use the default of 0 and wrap to
843 // the next line.
844 minLine = Some(line);
845 maxLine = Some(line + (maxColumnValue.isUndefined() ? 1 : 0));
846 }
847
848 if (!minLineValue.isUndefined()) {
849 if (!parseUint32Value(minLineValue, &minLine)) {
850 JS_ReportErrorNumberASCII(
851 cx_, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
852 "getPossibleBreakpoints' 'minLine'", "not an integer");
853 return false;
854 }
855 }
856
857 if (!minColumnValue.isUndefined()) {
858 if (!minLine) {
859 JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr,
860 JSMSG_UNEXPECTED_TYPE,
861 "getPossibleBreakpoints' 'minColumn'",
862 "not allowed without 'line' or 'minLine'");
863 return false;
864 }
865
866 if (!parseColumnValue(minColumnValue, &minColumn)) {
867 JS_ReportErrorNumberASCII(
868 cx_, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
869 "getPossibleBreakpoints' 'minColumn'", "not a positive integer");
870 return false;
871 }
872 }
873
874 if (!maxLineValue.isUndefined()) {
875 if (!parseUint32Value(maxLineValue, &maxLine)) {
876 JS_ReportErrorNumberASCII(
877 cx_, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
878 "getPossibleBreakpoints' 'maxLine'", "not an integer");
879 return false;
880 }
881 }
882
883 if (!maxColumnValue.isUndefined()) {
884 if (!maxLine) {
885 JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr,
886 JSMSG_UNEXPECTED_TYPE,
887 "getPossibleBreakpoints' 'maxColumn'",
888 "not allowed without 'line' or 'maxLine'");
889 return false;
890 }
891
892 if (!parseColumnValue(maxColumnValue, &maxColumn)) {
893 JS_ReportErrorNumberASCII(
894 cx_, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
895 "getPossibleBreakpoints' 'maxColumn'", "not a positive integer");
896 return false;
897 }
898 }
899
900 return true;
901 }
902
903 using ReturnType = bool;
904 ReturnType match(Handle<BaseScript*> base) {
905 RootedScript script(cx_, DelazifyScript(cx_, base));
906 if (!script) {
907 return false;
908 }
909
910 // Second pass: build the result array.
911 result_.set(NewDenseEmptyArray(cx_));
912 if (!result_) {
913 return false;
914 }
915
916 for (BytecodeRangeWithPosition r(cx_, script); !r.empty(); r.popFront()) {
917 if (!r.frontIsBreakablePoint()) {
918 continue;
919 }
920
921 size_t offset = r.frontOffset();
922 uint32_t lineno = r.frontLineNumber();
923 JS::LimitedColumnNumberOneOrigin colno = r.frontColumnNumber();
924
925 if (!maybeAppendEntry(offset, lineno, colno,
926 r.frontIsBreakableStepPoint())) {
927 return false;
928 }
929 }
930
931 return true;
932 }
933 ReturnType match(Handle<WasmInstanceObject*> instanceObj) {
934 wasm::Instance& instance = instanceObj->instance();
935
936 Vector<wasm::ExprLoc> offsets(cx_);
937 if (instance.debugEnabled() &&
938 !instance.debug().getAllColumnOffsets(&offsets)) {
939 return false;
940 }
941
942 result_.set(NewDenseEmptyArray(cx_));
943 if (!result_) {
944 return false;
945 }
946
947 for (uint32_t i = 0; i < offsets.length(); i++) {
948 uint32_t lineno = offsets[i].lineno;
949 JS::LimitedColumnNumberOneOrigin column(offsets[i].column);
950 size_t offset = offsets[i].offset;
951 if (!maybeAppendEntry(offset, lineno, column, true)) {
952 return false;
953 }
954 }
955 return true;
956 }
957};
958
959bool DebuggerScript::CallData::getPossibleBreakpoints() {
960 RootedObject result(cx);
961 GetPossibleBreakpointsMatcher<false> matcher(cx, &result);
962 if (args.length() >= 1 && !args[0].isUndefined()) {
963 RootedObject queryObject(cx, RequireObject(cx, args[0]));
964 if (!queryObject || !matcher.parseQuery(queryObject)) {
965 return false;
966 }
967 }
968 if (!referent.match(matcher)) {
969 return false;
970 }
971
972 args.rval().setObject(*result);
973 return true;
974}
975
976bool DebuggerScript::CallData::getPossibleBreakpointOffsets() {
977 RootedObject result(cx);
978 GetPossibleBreakpointsMatcher<true> matcher(cx, &result);
979 if (args.length() >= 1 && !args[0].isUndefined()) {
980 RootedObject queryObject(cx, RequireObject(cx, args[0]));
981 if (!queryObject || !matcher.parseQuery(queryObject)) {
982 return false;
983 }
984 }
985 if (!referent.match(matcher)) {
986 return false;
987 }
988
989 args.rval().setObject(*result);
990 return true;
991}
992
993class DebuggerScript::GetOffsetMetadataMatcher {
994 JSContext* cx_;
995 size_t offset_;
996 MutableHandle<PlainObject*> result_;
997
998 public:
999 explicit GetOffsetMetadataMatcher(JSContext* cx, size_t offset,
1000 MutableHandle<PlainObject*> result)
1001 : cx_(cx), offset_(offset), result_(result) {}
1002 using ReturnType = bool;
1003 ReturnType match(Handle<BaseScript*> base) {
1004 RootedScript script(cx_, DelazifyScript(cx_, base));
1005 if (!script) {
1006 return false;
1007 }
1008
1009 if (!EnsureScriptOffsetIsValid(cx_, script, offset_)) {
1010 return false;
1011 }
1012
1013 result_.set(NewPlainObject(cx_));
1014 if (!result_) {
1015 return false;
1016 }
1017
1018 BytecodeRangeWithPosition r(cx_, script);
1019 while (!r.empty() && r.frontOffset() < offset_) {
1020 r.popFront();
1021 }
1022
1023 RootedValue value(cx_, NumberValue(r.frontLineNumber()));
1024 if (!DefineDataProperty(cx_, result_, cx_->names().lineNumber, value)) {
1025 return false;
1026 }
1027
1028 value = NumberValue(r.frontColumnNumber().oneOriginValue());
1029 if (!DefineDataProperty(cx_, result_, cx_->names().columnNumber, value)) {
1030 return false;
1031 }
1032
1033 value = BooleanValue(r.frontIsBreakablePoint());
1034 if (!DefineDataProperty(cx_, result_, cx_->names().isBreakpoint, value)) {
1035 return false;
1036 }
1037
1038 value = BooleanValue(r.frontIsBreakableStepPoint());
1039 if (!DefineDataProperty(cx_, result_, cx_->names().isStepStart, value)) {
1040 return false;
1041 }
1042
1043 return true;
1044 }
1045 ReturnType match(Handle<WasmInstanceObject*> instanceObj) {
1046 wasm::Instance& instance = instanceObj->instance();
1047 if (!instance.debugEnabled()) {
1048 JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr,
1049 JSMSG_DEBUG_BAD_OFFSET);
1050 return false;
1051 }
1052
1053 uint32_t lineno;
1054 JS::LimitedColumnNumberOneOrigin column;
1055 if (!instance.debug().getOffsetLocation(offset_, &lineno, &column)) {
1056 JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr,
1057 JSMSG_DEBUG_BAD_OFFSET);
1058 return false;
1059 }
1060
1061 result_.set(NewPlainObject(cx_));
1062 if (!result_) {
1063 return false;
1064 }
1065
1066 RootedValue value(cx_, NumberValue(lineno));
1067 if (!DefineDataProperty(cx_, result_, cx_->names().lineNumber, value)) {
1068 return false;
1069 }
1070
1071 value = NumberValue(column.oneOriginValue());
1072 if (!DefineDataProperty(cx_, result_, cx_->names().columnNumber, value)) {
1073 return false;
1074 }
1075
1076 value.setBoolean(true);
1077 if (!DefineDataProperty(cx_, result_, cx_->names().isBreakpoint, value)) {
1078 return false;
1079 }
1080
1081 value.setBoolean(true);
1082 if (!DefineDataProperty(cx_, result_, cx_->names().isStepStart, value)) {
1083 return false;
1084 }
1085
1086 return true;
1087 }
1088};
1089
1090bool DebuggerScript::CallData::getOffsetMetadata() {
1091 if (!args.requireAtLeast(cx, "Debugger.Script.getOffsetMetadata", 1)) {
1092 return false;
1093 }
1094 size_t offset;
1095 if (!ScriptOffset(cx, args[0], &offset)) {
1096 return false;
1097 }
1098
1099 Rooted<PlainObject*> result(cx);
1100 GetOffsetMetadataMatcher matcher(cx, offset, &result);
1101 if (!referent.match(matcher)) {
1102 return false;
1103 }
1104
1105 args.rval().setObject(*result);
1106 return true;
1107}
1108
1109namespace {
1110
1111/*
1112 * FlowGraphSummary::populate(cx, script) computes a summary of script's
1113 * control flow graph used by DebuggerScript_{getAllOffsets,getLineOffsets}.
1114 *
1115 * An instruction on a given line is an entry point for that line if it can be
1116 * reached from (an instruction on) a different line. We distinguish between the
1117 * following cases:
1118 * - hasNoEdges:
1119 * The instruction cannot be reached, so the instruction is not an entry
1120 * point for the line it is on.
1121 * - hasSingleEdge:
1122 * The instruction can be reached from a single line. If this line is
1123 * different from the line the instruction is on, the instruction is an
1124 * entry point for that line.
1125 *
1126 * Similarly, an instruction on a given position (line/column pair) is an
1127 * entry point for that position if it can be reached from (an instruction on) a
1128 * different position. Again, we distinguish between the following cases:
1129 * - hasNoEdges:
1130 * The instruction cannot be reached, so the instruction is not an entry
1131 * point for the position it is on.
1132 * - hasSingleEdge:
1133 * The instruction can be reached from a single position. If this line is
1134 * different from the position the instruction is on, the instruction is
1135 * an entry point for that position.
1136 */
1137class FlowGraphSummary {
1138 public:
1139 class Entry {
1140 public:
1141 static constexpr uint32_t Line_HasNoEdge = UINT32_MAX(4294967295U);
1142 static constexpr uint32_t Column_HasMultipleEdge = UINT32_MAX(4294967295U);
1143
1144 // NOTE: column can be Column_HasMultipleEdge.
1145 static Entry createWithSingleEdgeOrMultipleEdge(uint32_t lineno,
1146 uint32_t column) {
1147 return Entry(lineno, column);
1148 }
1149
1150 static Entry createWithMultipleEdgesFromSingleLine(uint32_t lineno) {
1151 return Entry(lineno, Column_HasMultipleEdge);
1152 }
1153
1154 static Entry createWithMultipleEdgesFromMultipleLines() {
1155 return Entry(Line_HasNoEdge, Column_HasMultipleEdge);
1156 }
1157
1158 Entry() : lineno_(Line_HasNoEdge), column_(1) {}
1159
1160 bool hasNoEdges() const {
1161 return lineno_ == Line_HasNoEdge && column_ != Column_HasMultipleEdge;
1162 }
1163
1164 bool hasSingleEdge() const {
1165 return lineno_ != Line_HasNoEdge && column_ != Column_HasMultipleEdge;
1166 }
1167
1168 uint32_t lineno() const { return lineno_; }
1169
1170 // Returns 1-origin column number or the sentinel value
1171 // Column_HasMultipleEdge.
1172 uint32_t columnOrSentinel() const { return column_; }
1173
1174 JS::LimitedColumnNumberOneOrigin column() const {
1175 MOZ_ASSERT(column_ != Column_HasMultipleEdge)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(column_ != Column_HasMultipleEdge)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(column_ != Column_HasMultipleEdge
))), 0))) { do { } while (false); MOZ_ReportAssertionFailure(
"column_ != Column_HasMultipleEdge", "/var/lib/jenkins/workspace/firefox-scan-build/js/src/debugger/Script.cpp"
, 1175); AnnotateMozCrashReason("MOZ_ASSERT" "(" "column_ != Column_HasMultipleEdge"
")"); do { *((volatile int*)__null) = 1175; __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
1176 return JS::LimitedColumnNumberOneOrigin(column_);
1177 }
1178
1179 private:
1180 Entry(uint32_t lineno, uint32_t column)
1181 : lineno_(lineno), column_(column) {}
1182
1183 // Line number (1-origin).
1184 // Line_HasNoEdge for no edge.
1185 uint32_t lineno_;
1186
1187 // Column number in UTF-16 code units (1-origin).
1188 // Column_HasMultipleEdge for multiple edge.
1189 uint32_t column_;
1190 };
1191
1192 explicit FlowGraphSummary(JSContext* cx) : entries_(cx) {}
1193
1194 Entry& operator[](size_t index) { return entries_[index]; }
1195
1196 bool populate(JSContext* cx, JSScript* script) {
1197 if (!entries_.growBy(script->length())) {
1198 return false;
1199 }
1200 unsigned mainOffset = script->pcToOffset(script->main());
1201 entries_[mainOffset] = Entry::createWithMultipleEdgesFromMultipleLines();
1202
1203 // The following code uses uint32_t for column numbers.
1204 // The value is either 1-origin column number,
1205 // or Entry::Column_HasMultipleEdge.
1206
1207 uint32_t prevLineno = script->lineno();
1208 uint32_t prevColumn = 1;
1209 JSOp prevOp = JSOp::Nop;
1210 for (BytecodeRangeWithPosition r(cx, script); !r.empty(); r.popFront()) {
1211 uint32_t lineno = prevLineno;
1212 uint32_t column = prevColumn;
1213 JSOp op = r.frontOpcode();
1214
1215 if (BytecodeFallsThrough(prevOp)) {
1216 addEdge(prevLineno, prevColumn, r.frontOffset());
1217 }
1218
1219 // If we visit the branch target before we visit the
1220 // branch op itself, just reuse the previous location.
1221 // This is reasonable for the time being because this
1222 // situation can currently only arise from loop heads,
1223 // where this assumption holds.
1224 if (BytecodeIsJumpTarget(op) && !entries_[r.frontOffset()].hasNoEdges()) {
1225 lineno = entries_[r.frontOffset()].lineno();
1226 column = entries_[r.frontOffset()].columnOrSentinel();
1227 }
1228
1229 if (r.frontIsEntryPoint()) {
1230 lineno = r.frontLineNumber();
1231 column = r.frontColumnNumber().oneOriginValue();
1232 }
1233
1234 if (IsJumpOpcode(op)) {
1235 addEdge(lineno, column, r.frontOffset() + GET_JUMP_OFFSET(r.frontPC()));
1236 } else if (op == JSOp::TableSwitch) {
1237 jsbytecode* const switchPC = r.frontPC();
1238 jsbytecode* pc = switchPC;
1239 size_t offset = r.frontOffset();
1240 ptrdiff_t step = JUMP_OFFSET_LEN;
1241 size_t defaultOffset = offset + GET_JUMP_OFFSET(pc);
1242 pc += step;
1243 addEdge(lineno, column, defaultOffset);
1244
1245 int32_t low = GET_JUMP_OFFSET(pc);
1246 pc += JUMP_OFFSET_LEN;
1247 int ncases = GET_JUMP_OFFSET(pc) - low + 1;
1248 pc += JUMP_OFFSET_LEN;
Value stored to 'pc' is never read
1249
1250 for (int i = 0; i < ncases; i++) {
1251 size_t target = script->tableSwitchCaseOffset(switchPC, i);
1252 addEdge(lineno, column, target);
1253 }
1254 } else if (op == JSOp::Try) {
1255 // As there is no literal incoming edge into the catch block, we
1256 // make a fake one by copying the JSOp::Try location, as-if this
1257 // was an incoming edge of the catch block. This is needed
1258 // because we only report offsets of entry points which have
1259 // valid incoming edges.
1260 for (const TryNote& tn : script->trynotes()) {
1261 if (tn.start == r.frontOffset() + JSOpLength_Try) {
1262 uint32_t catchOffset = tn.start + tn.length;
1263 if (tn.kind() == TryNoteKind::Catch ||
1264 tn.kind() == TryNoteKind::Finally) {
1265 addEdge(lineno, column, catchOffset);
1266 }
1267 }
1268 }
1269 }
1270
1271 prevLineno = lineno;
1272 prevColumn = column;
1273 prevOp = op;
1274 }
1275
1276 return true;
1277 }
1278
1279 private:
1280 // sourceColumn is either 1-origin column number,
1281 // or Entry::Column_HasMultipleEdge.
1282 void addEdge(uint32_t sourceLineno, uint32_t sourceColumn,
1283 size_t targetOffset) {
1284 if (entries_[targetOffset].hasNoEdges()) {
1285 entries_[targetOffset] =
1286 Entry::createWithSingleEdgeOrMultipleEdge(sourceLineno, sourceColumn);
1287 } else if (entries_[targetOffset].lineno() != sourceLineno) {
1288 entries_[targetOffset] =
1289 Entry::createWithMultipleEdgesFromMultipleLines();
1290 } else if (entries_[targetOffset].columnOrSentinel() != sourceColumn) {
1291 entries_[targetOffset] =
1292 Entry::createWithMultipleEdgesFromSingleLine(sourceLineno);
1293 }
1294 }
1295
1296 Vector<Entry> entries_;
1297};
1298
1299} /* anonymous namespace */
1300
1301class DebuggerScript::GetOffsetLocationMatcher {
1302 JSContext* cx_;
1303 size_t offset_;
1304 MutableHandle<PlainObject*> result_;
1305
1306 public:
1307 explicit GetOffsetLocationMatcher(JSContext* cx, size_t offset,
1308 MutableHandle<PlainObject*> result)
1309 : cx_(cx), offset_(offset), result_(result) {}
1310 using ReturnType = bool;
1311 ReturnType match(Handle<BaseScript*> base) {
1312 RootedScript script(cx_, DelazifyScript(cx_, base));
1313 if (!script) {
1314 return false;
1315 }
1316
1317 if (!EnsureScriptOffsetIsValid(cx_, script, offset_)) {
1318 return false;
1319 }
1320
1321 FlowGraphSummary flowData(cx_);
1322 if (!flowData.populate(cx_, script)) {
1323 return false;
1324 }
1325
1326 result_.set(NewPlainObject(cx_));
1327 if (!result_) {
1328 return false;
1329 }
1330
1331 BytecodeRangeWithPosition r(cx_, script);
1332 while (!r.empty() && r.frontOffset() < offset_) {
1333 r.popFront();
1334 }
1335
1336 size_t offset = r.frontOffset();
1337 bool isEntryPoint = r.frontIsEntryPoint();
1338
1339 // Line numbers are only correctly defined on entry points. Thus looks
1340 // either for the next valid offset in the flowData, being the last entry
1341 // point flowing into the current offset, or for the next valid entry point.
1342 while (!r.frontIsEntryPoint() &&
1343 !flowData[r.frontOffset()].hasSingleEdge()) {
1344 r.popFront();
1345 MOZ_ASSERT(!r.empty())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!r.empty())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!r.empty()))), 0))) { do { }
while (false); MOZ_ReportAssertionFailure("!r.empty()", "/var/lib/jenkins/workspace/firefox-scan-build/js/src/debugger/Script.cpp"
, 1345); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!r.empty()"
")"); do { *((volatile int*)__null) = 1345; __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
1346 }
1347
1348 // If this is an entry point, take the line number associated with the entry
1349 // point, otherwise settle on the next instruction and take the incoming
1350 // edge position.
1351 uint32_t lineno;
1352 JS::LimitedColumnNumberOneOrigin column;
1353 if (r.frontIsEntryPoint()) {
1354 lineno = r.frontLineNumber();
1355 column = r.frontColumnNumber();
1356 } else {
1357 MOZ_ASSERT(flowData[r.frontOffset()].hasSingleEdge())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(flowData[r.frontOffset()].hasSingleEdge())>::isValid
, "invalid assertion condition"); if ((__builtin_expect(!!(!(
!!(flowData[r.frontOffset()].hasSingleEdge()))), 0))) { do { }
while (false); MOZ_ReportAssertionFailure("flowData[r.frontOffset()].hasSingleEdge()"
, "/var/lib/jenkins/workspace/firefox-scan-build/js/src/debugger/Script.cpp"
, 1357); AnnotateMozCrashReason("MOZ_ASSERT" "(" "flowData[r.frontOffset()].hasSingleEdge()"
")"); do { *((volatile int*)__null) = 1357; __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
1358 lineno = flowData[r.frontOffset()].lineno();
1359 column = flowData[r.frontOffset()].column();
1360 }
1361
1362 RootedValue value(cx_, NumberValue(lineno));
1363 if (!DefineDataProperty(cx_, result_, cx_->names().lineNumber, value)) {
1364 return false;
1365 }
1366
1367 value = NumberValue(column.oneOriginValue());
1368 if (!DefineDataProperty(cx_, result_, cx_->names().columnNumber, value)) {
1369 return false;
1370 }
1371
1372 // The same entry point test that is used by getAllColumnOffsets.
1373 isEntryPoint = (isEntryPoint && !flowData[offset].hasNoEdges() &&
1374 (flowData[offset].lineno() != r.frontLineNumber() ||
1375 flowData[offset].columnOrSentinel() !=
1376 r.frontColumnNumber().oneOriginValue()));
1377 value.setBoolean(isEntryPoint);
1378 if (!DefineDataProperty(cx_, result_, cx_->names().isEntryPoint, value)) {
1379 return false;
1380 }
1381
1382 return true;
1383 }
1384 ReturnType match(Handle<WasmInstanceObject*> instanceObj) {
1385 wasm::Instance& instance = instanceObj->instance();
1386 if (!instance.debugEnabled()) {
1387 JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr,
1388 JSMSG_DEBUG_BAD_OFFSET);
1389 return false;
1390 }
1391
1392 uint32_t lineno;
1393 JS::LimitedColumnNumberOneOrigin column;
1394 if (!instance.debug().getOffsetLocation(offset_, &lineno, &column)) {
1395 JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr,
1396 JSMSG_DEBUG_BAD_OFFSET);
1397 return false;
1398 }
1399
1400 result_.set(NewPlainObject(cx_));
1401 if (!result_) {
1402 return false;
1403 }
1404
1405 RootedValue value(cx_, NumberValue(lineno));
1406 if (!DefineDataProperty(cx_, result_, cx_->names().lineNumber, value)) {
1407 return false;
1408 }
1409
1410 value = NumberValue(column.oneOriginValue());
1411 if (!DefineDataProperty(cx_, result_, cx_->names().columnNumber, value)) {
1412 return false;
1413 }
1414
1415 value.setBoolean(true);
1416 if (!DefineDataProperty(cx_, result_, cx_->names().isEntryPoint, value)) {
1417 return false;
1418 }
1419
1420 return true;
1421 }
1422};
1423
1424bool DebuggerScript::CallData::getOffsetLocation() {
1425 if (!args.requireAtLeast(cx, "Debugger.Script.getOffsetLocation", 1)) {
1426 return false;
1427 }
1428 size_t offset;
1429 if (!ScriptOffset(cx, args[0], &offset)) {
1430 return false;
1431 }
1432
1433 Rooted<PlainObject*> result(cx);
1434 GetOffsetLocationMatcher matcher(cx, offset, &result);
1435 if (!referent.match(matcher)) {
1436 return false;
1437 }
1438
1439 args.rval().setObject(*result);
1440 return true;
1441}
1442
1443// Return whether an opcode is considered effectful: it can have direct side
1444// effects that can be observed outside of the current frame. Opcodes are not
1445// effectful if they only modify the current frame's state, modify objects
1446// created by the current frame, or can potentially call other scripts or
1447// natives which could have side effects.
1448static bool BytecodeIsEffectful(JSScript* script, size_t offset) {
1449 jsbytecode* pc = script->offsetToPC(offset);
1450 JSOp op = JSOp(*pc);
1451 switch (op) {
1452 case JSOp::SetProp:
1453 case JSOp::StrictSetProp:
1454 case JSOp::SetPropSuper:
1455 case JSOp::StrictSetPropSuper:
1456 case JSOp::SetElem:
1457 case JSOp::StrictSetElem:
1458 case JSOp::SetElemSuper:
1459 case JSOp::StrictSetElemSuper:
1460 case JSOp::SetName:
1461 case JSOp::StrictSetName:
1462 case JSOp::SetGName:
1463 case JSOp::StrictSetGName:
1464 case JSOp::DelProp:
1465 case JSOp::StrictDelProp:
1466 case JSOp::DelElem:
1467 case JSOp::StrictDelElem:
1468 case JSOp::DelName:
1469 case JSOp::SetAliasedVar:
1470 case JSOp::InitHomeObject:
1471 case JSOp::SetIntrinsic:
1472 case JSOp::InitGLexical:
1473 case JSOp::GlobalOrEvalDeclInstantiation:
1474 case JSOp::SetFunName:
1475 case JSOp::MutateProto:
1476 case JSOp::DynamicImport:
1477 case JSOp::InitialYield:
1478 case JSOp::Yield:
1479 case JSOp::Await:
1480 case JSOp::CanSkipAwait:
1481#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
1482 case JSOp::AddDisposable:
1483 case JSOp::DisposeDisposables:
1484#endif
1485 return true;
1486
1487 case JSOp::Nop:
1488 case JSOp::NopDestructuring:
1489 case JSOp::NopIsAssignOp:
1490 case JSOp::TryDestructuring:
1491 case JSOp::Lineno:
1492 case JSOp::JumpTarget:
1493 case JSOp::Undefined:
1494 case JSOp::JumpIfTrue:
1495 case JSOp::JumpIfFalse:
1496 case JSOp::Return:
1497 case JSOp::RetRval:
1498 case JSOp::And:
1499 case JSOp::Or:
1500 case JSOp::Coalesce:
1501 case JSOp::Try:
1502 case JSOp::Throw:
1503 case JSOp::ThrowWithStack:
1504#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
1505 case JSOp::ThrowWithStackWithoutJump:
1506#endif
1507 case JSOp::Goto:
1508 case JSOp::TableSwitch:
1509 case JSOp::Case:
1510 case JSOp::Default:
1511 case JSOp::BitNot:
1512 case JSOp::BitAnd:
1513 case JSOp::BitOr:
1514 case JSOp::BitXor:
1515 case JSOp::Lsh:
1516 case JSOp::Rsh:
1517 case JSOp::Ursh:
1518 case JSOp::Add:
1519 case JSOp::Sub:
1520 case JSOp::Mul:
1521 case JSOp::Div:
1522 case JSOp::Mod:
1523 case JSOp::Pow:
1524 case JSOp::Pos:
1525 case JSOp::ToNumeric:
1526 case JSOp::Neg:
1527 case JSOp::Inc:
1528 case JSOp::Dec:
1529 case JSOp::ToString:
1530 case JSOp::Eq:
1531 case JSOp::Ne:
1532 case JSOp::StrictEq:
1533 case JSOp::StrictNe:
1534 case JSOp::Lt:
1535 case JSOp::Le:
1536 case JSOp::Gt:
1537 case JSOp::Ge:
1538 case JSOp::Double:
1539 case JSOp::BigInt:
1540 case JSOp::String:
1541 case JSOp::Symbol:
1542 case JSOp::Zero:
1543 case JSOp::One:
1544 case JSOp::Null:
1545 case JSOp::Void:
1546 case JSOp::Hole:
1547 case JSOp::False:
1548 case JSOp::True:
1549 case JSOp::Arguments:
1550 case JSOp::Rest:
1551 case JSOp::GetArg:
1552 case JSOp::GetFrameArg:
1553 case JSOp::SetArg:
1554 case JSOp::GetLocal:
1555 case JSOp::SetLocal:
1556 case JSOp::GetActualArg:
1557 case JSOp::ArgumentsLength:
1558 case JSOp::ThrowSetConst:
1559 case JSOp::CheckLexical:
1560 case JSOp::CheckAliasedLexical:
1561 case JSOp::InitLexical:
1562 case JSOp::Uninitialized:
1563 case JSOp::Pop:
1564 case JSOp::PopN:
1565 case JSOp::DupAt:
1566 case JSOp::NewArray:
1567 case JSOp::NewInit:
1568 case JSOp::NewObject:
1569 case JSOp::InitElem:
1570 case JSOp::InitHiddenElem:
1571 case JSOp::InitLockedElem:
1572 case JSOp::InitElemInc:
1573 case JSOp::InitElemArray:
1574 case JSOp::InitProp:
1575 case JSOp::InitLockedProp:
1576 case JSOp::InitHiddenProp:
1577 case JSOp::InitPropGetter:
1578 case JSOp::InitHiddenPropGetter:
1579 case JSOp::InitPropSetter:
1580 case JSOp::InitHiddenPropSetter:
1581 case JSOp::InitElemGetter:
1582 case JSOp::InitHiddenElemGetter:
1583 case JSOp::InitElemSetter:
1584 case JSOp::InitHiddenElemSetter:
1585 case JSOp::SpreadCall:
1586 case JSOp::Call:
1587 case JSOp::CallContent:
1588 case JSOp::CallIgnoresRv:
1589 case JSOp::CallIter:
1590 case JSOp::CallContentIter:
1591 case JSOp::New:
1592 case JSOp::NewContent:
1593 case JSOp::Eval:
1594 case JSOp::StrictEval:
1595 case JSOp::Int8:
1596 case JSOp::Uint16:
1597 case JSOp::ResumeKind:
1598 case JSOp::GetGName:
1599 case JSOp::GetName:
1600 case JSOp::GetIntrinsic:
1601 case JSOp::GetImport:
1602 case JSOp::BindGName:
1603 case JSOp::BindName:
1604 case JSOp::BindVar:
1605 case JSOp::Dup:
1606 case JSOp::Dup2:
1607 case JSOp::Swap:
1608 case JSOp::Pick:
1609 case JSOp::Unpick:
1610 case JSOp::GetAliasedDebugVar:
1611 case JSOp::GetAliasedVar:
1612 case JSOp::Uint24:
1613 case JSOp::Int32:
1614 case JSOp::LoopHead:
1615 case JSOp::GetElem:
1616 case JSOp::Not:
1617 case JSOp::FunctionThis:
1618 case JSOp::GlobalThis:
1619 case JSOp::NonSyntacticGlobalThis:
1620 case JSOp::Callee:
1621 case JSOp::EnvCallee:
1622 case JSOp::SuperBase:
1623 case JSOp::GetPropSuper:
1624 case JSOp::GetElemSuper:
1625 case JSOp::GetProp:
1626 case JSOp::RegExp:
1627 case JSOp::CallSiteObj:
1628 case JSOp::Object:
1629 case JSOp::Typeof:
1630 case JSOp::TypeofExpr:
1631 case JSOp::TypeofEq:
1632 case JSOp::ToAsyncIter:
1633 case JSOp::ToPropertyKey:
1634 case JSOp::Lambda:
1635 case JSOp::PushLexicalEnv:
1636 case JSOp::PopLexicalEnv:
1637 case JSOp::FreshenLexicalEnv:
1638 case JSOp::RecreateLexicalEnv:
1639 case JSOp::PushClassBodyEnv:
1640 case JSOp::Iter:
1641 case JSOp::MoreIter:
1642 case JSOp::IsNoIter:
1643 case JSOp::EndIter:
1644 case JSOp::CloseIter:
1645 case JSOp::OptimizeGetIterator:
1646 case JSOp::IsNullOrUndefined:
1647 case JSOp::In:
1648 case JSOp::HasOwn:
1649 case JSOp::CheckPrivateField:
1650 case JSOp::NewPrivateName:
1651 case JSOp::SetRval:
1652 case JSOp::Instanceof:
1653 case JSOp::DebugLeaveLexicalEnv:
1654 case JSOp::Debugger:
1655 case JSOp::ImplicitThis:
1656 case JSOp::NewTarget:
1657 case JSOp::CheckIsObj:
1658 case JSOp::CheckObjCoercible:
1659 case JSOp::DebugCheckSelfHosted:
1660 case JSOp::IsConstructing:
1661 case JSOp::OptimizeSpreadCall:
1662 case JSOp::ImportMeta:
1663 case JSOp::EnterWith:
1664 case JSOp::LeaveWith:
1665 case JSOp::SpreadNew:
1666 case JSOp::SpreadEval:
1667 case JSOp::StrictSpreadEval:
1668 case JSOp::CheckClassHeritage:
1669 case JSOp::FunWithProto:
1670 case JSOp::ObjWithProto:
1671 case JSOp::BuiltinObject:
1672 case JSOp::CheckThis:
1673 case JSOp::CheckReturn:
1674 case JSOp::CheckThisReinit:
1675 case JSOp::SuperFun:
1676 case JSOp::SpreadSuperCall:
1677 case JSOp::SuperCall:
1678 case JSOp::PushVarEnv:
1679 case JSOp::GetBoundName:
1680 case JSOp::Exception:
1681 case JSOp::ExceptionAndStack:
1682 case JSOp::IsGenClosing:
1683 case JSOp::FinalYieldRval:
1684 case JSOp::Resume:
1685 case JSOp::CheckResumeKind:
1686 case JSOp::AfterYield:
1687 case JSOp::MaybeExtractAwaitValue:
1688 case JSOp::Generator:
1689 case JSOp::AsyncAwait:
1690 case JSOp::AsyncResolve:
1691 case JSOp::AsyncReject:
1692 case JSOp::Finally:
1693 case JSOp::GetRval:
1694 case JSOp::ThrowMsg:
1695 case JSOp::ForceInterpreter:
1696#ifdef ENABLE_RECORD_TUPLE
1697 case JSOp::InitRecord:
1698 case JSOp::AddRecordProperty:
1699 case JSOp::AddRecordSpread:
1700 case JSOp::FinishRecord:
1701 case JSOp::InitTuple:
1702 case JSOp::AddTupleElement:
1703 case JSOp::FinishTuple:
1704#endif
1705 return false;
1706
1707 case JSOp::InitAliasedLexical: {
1708 uint32_t hops = EnvironmentCoordinate(pc).hops();
1709 if (hops == 0) {
1710 // Initializing aliased lexical in the current scope is almost same
1711 // as JSOp::InitLexical.
1712 return false;
1713 }
1714
1715 // Otherwise this can touch an environment outside of the current scope.
1716 return true;
1717 }
1718 }
1719
1720 MOZ_ASSERT_UNREACHABLE("Invalid opcode")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(false)>::isValid, "invalid assertion condition");
if ((__builtin_expect(!!(!(!!(false))), 0))) { do { } while (
false); MOZ_ReportAssertionFailure("false" " (" "MOZ_ASSERT_UNREACHABLE: "
"Invalid opcode" ")", "/var/lib/jenkins/workspace/firefox-scan-build/js/src/debugger/Script.cpp"
, 1720); AnnotateMozCrashReason("MOZ_ASSERT" "(" "false" ") ("
"MOZ_ASSERT_UNREACHABLE: " "Invalid opcode" ")"); do { *((volatile
int*)__null) = 1720; __attribute__((nomerge)) ::abort(); } while
(false); } } while (false)
;
1721 return false;
1722}
1723
1724bool DebuggerScript::CallData::getEffectfulOffsets() {
1725 if (!ensureScript()) {
1726 return false;
1727 }
1728
1729 RootedObject result(cx, NewDenseEmptyArray(cx));
1730 if (!result) {
1731 return false;
1732 }
1733 for (BytecodeRange r(cx, script); !r.empty(); r.popFront()) {
1734 size_t offset = r.frontOffset();
1735 if (!BytecodeIsEffectful(script, offset)) {
1736 continue;
1737 }
1738
1739 if (IsGeneratorSlotInitialization(script, offset, cx)) {
1740 // This is engine-internal operation and not visible outside the
1741 // currently executing frame.
1742 //
1743 // Also this offset is not allowed for setting breakpoint.
1744 continue;
1745 }
1746
1747 if (!NewbornArrayPush(cx, result, NumberValue(offset))) {
1748 return false;
1749 }
1750 }
1751
1752 args.rval().setObject(*result);
1753 return true;
1754}
1755
1756bool DebuggerScript::CallData::getAllOffsets() {
1757 if (!ensureScript()) {
1758 return false;
1759 }
1760
1761 // First pass: determine which offsets in this script are jump targets and
1762 // which line numbers jump to them.
1763 FlowGraphSummary flowData(cx);
1764 if (!flowData.populate(cx, script)) {
1765 return false;
1766 }
1767
1768 // Second pass: build the result array.
1769 RootedObject result(cx, NewDenseEmptyArray(cx));
1770 if (!result) {
1771 return false;
1772 }
1773 for (BytecodeRangeWithPosition r(cx, script); !r.empty(); r.popFront()) {
1774 if (!r.frontIsEntryPoint()) {
1775 continue;
1776 }
1777
1778 size_t offset = r.frontOffset();
1779 uint32_t lineno = r.frontLineNumber();
1780
1781 // Make a note, if the current instruction is an entry point for the current
1782 // line.
1783 if (!flowData[offset].hasNoEdges() && flowData[offset].lineno() != lineno) {
1784 // Get the offsets array for this line.
1785 RootedObject offsets(cx);
1786 RootedValue offsetsv(cx);
1787
1788 RootedId id(cx, PropertyKey::Int(lineno));
1789
1790 bool found;
1791 if (!HasOwnProperty(cx, result, id, &found)) {
1792 return false;
1793 }
1794 if (found && !GetProperty(cx, result, result, id, &offsetsv)) {
1795 return false;
1796 }
1797
1798 if (offsetsv.isObject()) {
1799 offsets = &offsetsv.toObject();
1800 } else {
1801 MOZ_ASSERT(offsetsv.isUndefined())do { static_assert( mozilla::detail::AssertionConditionType<
decltype(offsetsv.isUndefined())>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(offsetsv.isUndefined()))), 0
))) { do { } while (false); MOZ_ReportAssertionFailure("offsetsv.isUndefined()"
, "/var/lib/jenkins/workspace/firefox-scan-build/js/src/debugger/Script.cpp"
, 1801); AnnotateMozCrashReason("MOZ_ASSERT" "(" "offsetsv.isUndefined()"
")"); do { *((volatile int*)__null) = 1801; __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
1802
1803 // Create an empty offsets array for this line.
1804 // Store it in the result array.
1805 RootedId id(cx);
1806 RootedValue v(cx, NumberValue(lineno));
1807 offsets = NewDenseEmptyArray(cx);
1808 if (!offsets || !PrimitiveValueToId<CanGC>(cx, v, &id)) {
1809 return false;
1810 }
1811
1812 RootedValue value(cx, ObjectValue(*offsets));
1813 if (!DefineDataProperty(cx, result, id, value)) {
1814 return false;
1815 }
1816 }
1817
1818 // Append the current offset to the offsets array.
1819 if (!NewbornArrayPush(cx, offsets, NumberValue(offset))) {
1820 return false;
1821 }
1822 }
1823 }
1824
1825 args.rval().setObject(*result);
1826 return true;
1827}
1828
1829class DebuggerScript::GetAllColumnOffsetsMatcher {
1830 JSContext* cx_;
1831 MutableHandleObject result_;
1832
1833 bool appendColumnOffsetEntry(uint32_t lineno,
1834 JS::LimitedColumnNumberOneOrigin column,
1835 size_t offset) {
1836 Rooted<PlainObject*> entry(cx_, NewPlainObject(cx_));
1837 if (!entry) {
1838 return false;
1839 }
1840
1841 RootedValue value(cx_, NumberValue(lineno));
1842 if (!DefineDataProperty(cx_, entry, cx_->names().lineNumber, value)) {
1843 return false;
1844 }
1845
1846 value = NumberValue(column.oneOriginValue());
1847 if (!DefineDataProperty(cx_, entry, cx_->names().columnNumber, value)) {
1848 return false;
1849 }
1850
1851 value = NumberValue(offset);
1852 if (!DefineDataProperty(cx_, entry, cx_->names().offset, value)) {
1853 return false;
1854 }
1855
1856 return NewbornArrayPush(cx_, result_, ObjectValue(*entry));
1857 }
1858
1859 public:
1860 explicit GetAllColumnOffsetsMatcher(JSContext* cx, MutableHandleObject result)
1861 : cx_(cx), result_(result) {}
1862 using ReturnType = bool;
1863 ReturnType match(Handle<BaseScript*> base) {
1864 RootedScript script(cx_, DelazifyScript(cx_, base));
1865 if (!script) {
1866 return false;
1867 }
1868
1869 // First pass: determine which offsets in this script are jump targets
1870 // and which positions jump to them.
1871 FlowGraphSummary flowData(cx_);
1872 if (!flowData.populate(cx_, script)) {
1873 return false;
1874 }
1875
1876 // Second pass: build the result array.
1877 result_.set(NewDenseEmptyArray(cx_));
1878 if (!result_) {
1879 return false;
1880 }
1881
1882 for (BytecodeRangeWithPosition r(cx_, script); !r.empty(); r.popFront()) {
1883 uint32_t lineno = r.frontLineNumber();
1884 JS::LimitedColumnNumberOneOrigin column = r.frontColumnNumber();
1885 size_t offset = r.frontOffset();
1886
1887 // Make a note, if the current instruction is an entry point for
1888 // the current position.
1889 if (r.frontIsEntryPoint() && !flowData[offset].hasNoEdges() &&
1890 (flowData[offset].lineno() != lineno ||
1891 flowData[offset].columnOrSentinel() != column.oneOriginValue())) {
1892 if (!appendColumnOffsetEntry(lineno, column, offset)) {
1893 return false;
1894 }
1895 }
1896 }
1897 return true;
1898 }
1899 ReturnType match(Handle<WasmInstanceObject*> instanceObj) {
1900 wasm::Instance& instance = instanceObj->instance();
1901
1902 Vector<wasm::ExprLoc> offsets(cx_);
1903 if (instance.debugEnabled() &&
1904 !instance.debug().getAllColumnOffsets(&offsets)) {
1905 return false;
1906 }
1907
1908 result_.set(NewDenseEmptyArray(cx_));
1909 if (!result_) {
1910 return false;
1911 }
1912
1913 for (uint32_t i = 0; i < offsets.length(); i++) {
1914 uint32_t lineno = offsets[i].lineno;
1915 JS::LimitedColumnNumberOneOrigin column(offsets[i].column);
1916 size_t offset = offsets[i].offset;
1917 if (!appendColumnOffsetEntry(lineno, column, offset)) {
1918 return false;
1919 }
1920 }
1921 return true;
1922 }
1923};
1924
1925bool DebuggerScript::CallData::getAllColumnOffsets() {
1926 RootedObject result(cx);
1927 GetAllColumnOffsetsMatcher matcher(cx, &result);
1928 if (!referent.match(matcher)) {
1929 return false;
1930 }
1931
1932 args.rval().setObject(*result);
1933 return true;
1934}
1935
1936class DebuggerScript::GetLineOffsetsMatcher {
1937 JSContext* cx_;
1938 uint32_t lineno_;
1939 MutableHandleObject result_;
1940
1941 public:
1942 explicit GetLineOffsetsMatcher(JSContext* cx, uint32_t lineno,
1943 MutableHandleObject result)
1944 : cx_(cx), lineno_(lineno), result_(result) {}
1945 using ReturnType = bool;
1946 ReturnType match(Handle<BaseScript*> base) {
1947 RootedScript script(cx_, DelazifyScript(cx_, base));
1948 if (!script) {
1949 return false;
1950 }
1951
1952 // First pass: determine which offsets in this script are jump targets and
1953 // which line numbers jump to them.
1954 FlowGraphSummary flowData(cx_);
1955 if (!flowData.populate(cx_, script)) {
1956 return false;
1957 }
1958
1959 result_.set(NewDenseEmptyArray(cx_));
1960 if (!result_) {
1961 return false;
1962 }
1963
1964 // Second pass: build the result array.
1965 for (BytecodeRangeWithPosition r(cx_, script); !r.empty(); r.popFront()) {
1966 if (!r.frontIsEntryPoint()) {
1967 continue;
1968 }
1969
1970 size_t offset = r.frontOffset();
1971
1972 // If the op at offset is an entry point, append offset to result.
1973 if (r.frontLineNumber() == lineno_ && !flowData[offset].hasNoEdges() &&
1974 flowData[offset].lineno() != lineno_) {
1975 if (!NewbornArrayPush(cx_, result_, NumberValue(offset))) {
1976 return false;
1977 }
1978 }
1979 }
1980
1981 return true;
1982 }
1983 ReturnType match(Handle<WasmInstanceObject*> instanceObj) {
1984 wasm::Instance& instance = instanceObj->instance();
1985
1986 Vector<uint32_t> offsets(cx_);
1987 if (instance.debugEnabled() &&
1988 !instance.debug().getLineOffsets(lineno_, &offsets)) {
1989 return false;
1990 }
1991
1992 result_.set(NewDenseEmptyArray(cx_));
1993 if (!result_) {
1994 return false;
1995 }
1996
1997 for (uint32_t i = 0; i < offsets.length(); i++) {
1998 if (!NewbornArrayPush(cx_, result_, NumberValue(offsets[i]))) {
1999 return false;
2000 }
2001 }
2002 return true;
2003 }
2004};
2005
2006bool DebuggerScript::CallData::getLineOffsets() {
2007 if (!args.requireAtLeast(cx, "Debugger.Script.getLineOffsets", 1)) {
2008 return false;
2009 }
2010
2011 // Parse lineno argument.
2012 RootedValue linenoValue(cx, args[0]);
2013 uint32_t lineno;
2014 if (!ToNumber(cx, &linenoValue)) {
2015 return false;
2016 }
2017 {
2018 double d = linenoValue.toNumber();
2019 lineno = uint32_t(d);
2020 if (lineno != d) {
2021 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2022 JSMSG_DEBUG_BAD_LINE);
2023 return false;
2024 }
2025 }
2026
2027 RootedObject result(cx);
2028 GetLineOffsetsMatcher matcher(cx, lineno, &result);
2029 if (!referent.match(matcher)) {
2030 return false;
2031 }
2032
2033 args.rval().setObject(*result);
2034 return true;
2035}
2036
2037struct DebuggerScript::SetBreakpointMatcher {
2038 JSContext* cx_;
2039 Debugger* dbg_;
2040 size_t offset_;
2041 RootedObject handler_;
2042 RootedObject debuggerObject_;
2043
2044 bool wrapCrossCompartmentEdges() {
2045 if (!cx_->compartment()->wrap(cx_, &handler_) ||
2046 !cx_->compartment()->wrap(cx_, &debuggerObject_)) {
2047 return false;
2048 }
2049
2050 // If the Debugger's compartment has killed incoming wrappers, we may not
2051 // have gotten usable results from the 'wrap' calls. Treat it as a
2052 // failure.
2053 if (IsDeadProxyObject(handler_) || IsDeadProxyObject(debuggerObject_)) {
2054 ReportAccessDenied(cx_);
2055 return false;
2056 }
2057
2058 return true;
2059 }
2060
2061 public:
2062 explicit SetBreakpointMatcher(JSContext* cx, Debugger* dbg, size_t offset,
2063 HandleObject handler)
2064 : cx_(cx),
2065 dbg_(dbg),
2066 offset_(offset),
2067 handler_(cx, handler),
2068 debuggerObject_(cx_, dbg_->toJSObject()) {}
2069
2070 using ReturnType = bool;
2071
2072 ReturnType match(Handle<BaseScript*> base) {
2073 RootedScript script(cx_, DelazifyScript(cx_, base));
2074 if (!script) {
2075 return false;
2076 }
2077
2078 if (!dbg_->observesScript(script)) {
2079 JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr,
2080 JSMSG_DEBUG_NOT_DEBUGGING);
2081 return false;
2082 }
2083
2084 if (!EnsureScriptOffsetIsValid(cx_, script, offset_)) {
2085 return false;
2086 }
2087
2088 if (!EnsureBreakpointIsAllowed(cx_, script, offset_)) {
2089 return false;
2090 }
2091
2092 // Ensure observability *before* setting the breakpoint. If the script is
2093 // not already a debuggee, trying to ensure observability after setting
2094 // the breakpoint (and thus marking the script as a debuggee) will skip
2095 // actually ensuring observability.
2096 if (!dbg_->ensureExecutionObservabilityOfScript(cx_, script)) {
2097 return false;
2098 }
2099
2100 // A Breakpoint belongs logically to its script's compartment, so its
2101 // references to its Debugger and handler must be properly wrapped.
2102 AutoRealm ar(cx_, script);
2103 if (!wrapCrossCompartmentEdges()) {
2104 return false;
2105 }
2106
2107 jsbytecode* pc = script->offsetToPC(offset_);
2108 JSBreakpointSite* site =
2109 DebugScript::getOrCreateBreakpointSite(cx_, script, pc);
2110 if (!site) {
2111 return false;
2112 }
2113
2114 if (!cx_->zone()->new_<Breakpoint>(dbg_, debuggerObject_, site, handler_)) {
2115 site->destroyIfEmpty(cx_->runtime()->gcContext());
2116 return false;
2117 }
2118 AddCellMemory(script, sizeof(Breakpoint), MemoryUse::Breakpoint);
2119
2120 return true;
2121 }
2122 ReturnType match(Handle<WasmInstanceObject*> wasmInstance) {
2123 wasm::Instance& instance = wasmInstance->instance();
2124 if (!instance.debugEnabled() ||
2125 !instance.debug().hasBreakpointTrapAtOffset(offset_)) {
2126 JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr,
2127 JSMSG_DEBUG_BAD_OFFSET);
2128 return false;
2129 }
2130
2131 // A Breakpoint belongs logically to its Instance's compartment, so its
2132 // references to its Debugger and handler must be properly wrapped.
2133 AutoRealm ar(cx_, wasmInstance);
2134 if (!wrapCrossCompartmentEdges()) {
2135 return false;
2136 }
2137
2138 WasmBreakpointSite* site = instance.getOrCreateBreakpointSite(cx_, offset_);
2139 if (!site) {
2140 return false;
2141 }
2142
2143 if (!cx_->zone()->new_<Breakpoint>(dbg_, debuggerObject_, site, handler_)) {
2144 site->destroyIfEmpty(cx_->runtime()->gcContext());
2145 return false;
2146 }
2147 AddCellMemory(wasmInstance, sizeof(Breakpoint), MemoryUse::Breakpoint);
2148
2149 return true;
2150 }
2151};
2152
2153bool DebuggerScript::CallData::setBreakpoint() {
2154 if (!args.requireAtLeast(cx, "Debugger.Script.setBreakpoint", 2)) {
2155 return false;
2156 }
2157 Debugger* dbg = obj->owner();
2158
2159 size_t offset;
2160 if (!ScriptOffset(cx, args[0], &offset)) {
2161 return false;
2162 }
2163
2164 RootedObject handler(cx, RequireObject(cx, args[1]));
2165 if (!handler) {
2166 return false;
2167 }
2168
2169 SetBreakpointMatcher matcher(cx, dbg, offset, handler);
2170 if (!referent.match(matcher)) {
2171 return false;
2172 }
2173 args.rval().setUndefined();
2174 return true;
2175}
2176
2177bool DebuggerScript::CallData::getBreakpoints() {
2178 if (!ensureScript()) {
2179 return false;
2180 }
2181 Debugger* dbg = obj->owner();
2182
2183 jsbytecode* pc;
2184 if (args.length() > 0) {
2185 size_t offset;
2186 if (!ScriptOffset(cx, args[0], &offset) ||
2187 !EnsureScriptOffsetIsValid(cx, script, offset)) {
2188 return false;
2189 }
2190 pc = script->offsetToPC(offset);
2191 } else {
2192 pc = nullptr;
2193 }
2194
2195 RootedObject arr(cx, NewDenseEmptyArray(cx));
2196 if (!arr) {
2197 return false;
2198 }
2199
2200 for (unsigned i = 0; i < script->length(); i++) {
2201 JSBreakpointSite* site =
2202 DebugScript::getBreakpointSite(script, script->offsetToPC(i));
2203 if (!site) {
2204 continue;
2205 }
2206 if (!pc || site->pc == pc) {
2207 for (Breakpoint* bp = site->firstBreakpoint(); bp;
2208 bp = bp->nextInSite()) {
2209 if (bp->debugger == dbg) {
2210 RootedObject handler(cx, bp->getHandler());
2211 if (!cx->compartment()->wrap(cx, &handler) ||
2212 !NewbornArrayPush(cx, arr, ObjectValue(*handler))) {
2213 return false;
2214 }
2215 }
2216 }
2217 }
2218 }
2219 args.rval().setObject(*arr);
2220 return true;
2221}
2222
2223class DebuggerScript::ClearBreakpointMatcher {
2224 JSContext* cx_;
2225 Debugger* dbg_;
2226 RootedObject handler_;
2227
2228 public:
2229 ClearBreakpointMatcher(JSContext* cx, Debugger* dbg, JSObject* handler)
2230 : cx_(cx), dbg_(dbg), handler_(cx, handler) {}
2231 using ReturnType = bool;
2232
2233 ReturnType match(Handle<BaseScript*> base) {
2234 RootedScript script(cx_, DelazifyScript(cx_, base));
2235 if (!script) {
2236 return false;
2237 }
2238
2239 // A Breakpoint belongs logically to its script's compartment, so it holds
2240 // its handler via a cross-compartment wrapper. But the handler passed to
2241 // `clearBreakpoint` is same-compartment with the Debugger. Wrap it here,
2242 // so that `DebugScript::clearBreakpointsIn` gets the right value to
2243 // search for.
2244 AutoRealm ar(cx_, script);
2245 if (!cx_->compartment()->wrap(cx_, &handler_)) {
2246 return false;
2247 }
2248
2249 DebugScript::clearBreakpointsIn(cx_->runtime()->gcContext(), script, dbg_,
2250 handler_);
2251 return true;
2252 }
2253 ReturnType match(Handle<WasmInstanceObject*> instanceObj) {
2254 wasm::Instance& instance = instanceObj->instance();
2255 if (!instance.debugEnabled()) {
2256 return true;
2257 }
2258
2259 // A Breakpoint belongs logically to its instance's compartment, so it
2260 // holds its handler via a cross-compartment wrapper. But the handler
2261 // passed to `clearBreakpoint` is same-compartment with the Debugger. Wrap
2262 // it here, so that `DebugState::clearBreakpointsIn` gets the right value
2263 // to search for.
2264 AutoRealm ar(cx_, instanceObj);
2265 if (!cx_->compartment()->wrap(cx_, &handler_)) {
2266 return false;
2267 }
2268
2269 instance.debug().clearBreakpointsIn(cx_->runtime()->gcContext(),
2270 instanceObj, dbg_, handler_);
2271 return true;
2272 }
2273};
2274
2275bool DebuggerScript::CallData::clearBreakpoint() {
2276 if (!args.requireAtLeast(cx, "Debugger.Script.clearBreakpoint", 1)) {
2277 return false;
2278 }
2279 Debugger* dbg = obj->owner();
2280
2281 JSObject* handler = RequireObject(cx, args[0]);
2282 if (!handler) {
2283 return false;
2284 }
2285
2286 ClearBreakpointMatcher matcher(cx, dbg, handler);
2287 if (!referent.match(matcher)) {
2288 return false;
2289 }
2290
2291 args.rval().setUndefined();
2292 return true;
2293}
2294
2295bool DebuggerScript::CallData::clearAllBreakpoints() {
2296 Debugger* dbg = obj->owner();
2297 ClearBreakpointMatcher matcher(cx, dbg, nullptr);
2298 if (!referent.match(matcher)) {
2299 return false;
2300 }
2301 args.rval().setUndefined();
2302 return true;
2303}
2304
2305class DebuggerScript::IsInCatchScopeMatcher {
2306 JSContext* cx_;
2307 size_t offset_;
2308 bool isInCatch_;
2309
2310 public:
2311 explicit IsInCatchScopeMatcher(JSContext* cx, size_t offset)
2312 : cx_(cx), offset_(offset), isInCatch_(false) {}
2313 using ReturnType = bool;
2314
2315 inline bool isInCatch() const { return isInCatch_; }
2316
2317 ReturnType match(Handle<BaseScript*> base) {
2318 RootedScript script(cx_, DelazifyScript(cx_, base));
2319 if (!script) {
2320 return false;
2321 }
2322
2323 if (!EnsureScriptOffsetIsValid(cx_, script, offset_)) {
2324 return false;
2325 }
2326
2327 MOZ_ASSERT(!isInCatch_)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(!isInCatch_)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(!isInCatch_))), 0))) { do { }
while (false); MOZ_ReportAssertionFailure("!isInCatch_", "/var/lib/jenkins/workspace/firefox-scan-build/js/src/debugger/Script.cpp"
, 2327); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!isInCatch_"
")"); do { *((volatile int*)__null) = 2327; __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2328 for (const TryNote& tn : script->trynotes()) {
2329 bool inRange = tn.start <= offset_ && offset_ < tn.start + tn.length;
2330 if (inRange && tn.kind() == TryNoteKind::Catch) {
2331 isInCatch_ = true;
2332 } else if (isInCatch_) {
2333 // For-of loops generate a synthetic catch block to handle
2334 // closing the iterator when throwing an exception. The
2335 // debugger should ignore these synthetic catch blocks, so
2336 // we skip any Catch trynote that is immediately followed
2337 // by a ForOf trynote.
2338 if (inRange && tn.kind() == TryNoteKind::ForOf) {
2339 isInCatch_ = false;
2340 continue;
2341 }
2342 return true;
2343 }
2344 }
2345
2346 return true;
2347 }
2348 ReturnType match(Handle<WasmInstanceObject*> instance) {
2349 isInCatch_ = false;
2350 return true;
2351 }
2352};
2353
2354bool DebuggerScript::CallData::isInCatchScope() {
2355 if (!args.requireAtLeast(cx, "Debugger.Script.isInCatchScope", 1)) {
2356 return false;
2357 }
2358
2359 size_t offset;
2360 if (!ScriptOffset(cx, args[0], &offset)) {
2361 return false;
2362 }
2363
2364 IsInCatchScopeMatcher matcher(cx, offset);
2365 if (!referent.match(matcher)) {
2366 return false;
2367 }
2368 args.rval().setBoolean(matcher.isInCatch());
2369 return true;
2370}
2371
2372bool DebuggerScript::CallData::getOffsetsCoverage() {
2373 if (!ensureScript()) {
2374 return false;
2375 }
2376
2377 Debugger* dbg = obj->owner();
2378 if (dbg->observesCoverage() != Debugger::Observing) {
2379 args.rval().setNull();
2380 return true;
2381 }
2382
2383 // If the script has no coverage information, then skip this and return null
2384 // instead.
2385 if (!script->hasScriptCounts()) {
2386 args.rval().setNull();
2387 return true;
2388 }
2389
2390 ScriptCounts* sc = &script->getScriptCounts();
2391
2392 // If the main ever got visited, then assume that any code before main got
2393 // visited once.
2394 uint64_t hits = 0;
2395 const PCCounts* counts =
2396 sc->maybeGetPCCounts(script->pcToOffset(script->main()));
2397 if (counts->numExec()) {
2398 hits = 1;
2399 }
2400
2401 // Build an array of objects which are composed of 4 properties:
2402 // - offset PC offset of the current opcode.
2403 // - lineNumber Line of the current opcode.
2404 // - columnNumber Column of the current opcode.
2405 // - count Number of times the instruction got executed.
2406 RootedObject result(cx, NewDenseEmptyArray(cx));
2407 if (!result) {
2408 return false;
2409 }
2410
2411 RootedId offsetId(cx, NameToId(cx->names().offset));
2412 RootedId lineNumberId(cx, NameToId(cx->names().lineNumber));
2413 RootedId columnNumberId(cx, NameToId(cx->names().columnNumber));
2414 RootedId countId(cx, NameToId(cx->names().count));
2415
2416 RootedObject item(cx);
2417 RootedValue offsetValue(cx);
2418 RootedValue lineNumberValue(cx);
2419 RootedValue columnNumberValue(cx);
2420 RootedValue countValue(cx);
2421
2422 // Iterate linearly over the bytecode.
2423 for (BytecodeRangeWithPosition r(cx, script); !r.empty(); r.popFront()) {
2424 size_t offset = r.frontOffset();
2425
2426 // The beginning of each non-branching sequences of instruction set the
2427 // number of execution of the current instruction and any following
2428 // instruction.
2429 counts = sc->maybeGetPCCounts(offset);
2430 if (counts) {
2431 hits = counts->numExec();
2432 }
2433
2434 offsetValue.setNumber(double(offset));
2435 lineNumberValue.setNumber(double(r.frontLineNumber()));
2436 columnNumberValue.setNumber(double(r.frontColumnNumber().oneOriginValue()));
2437 countValue.setNumber(double(hits));
2438
2439 // Create a new object with the offset, line number, column number, the
2440 // number of hit counts, and append it to the array.
2441 item = NewPlainObjectWithProto(cx, nullptr);
2442 if (!item || !DefineDataProperty(cx, item, offsetId, offsetValue) ||
2443 !DefineDataProperty(cx, item, lineNumberId, lineNumberValue) ||
2444 !DefineDataProperty(cx, item, columnNumberId, columnNumberValue) ||
2445 !DefineDataProperty(cx, item, countId, countValue) ||
2446 !NewbornArrayPush(cx, result, ObjectValue(*item))) {
2447 return false;
2448 }
2449
2450 // If the current instruction has thrown, then decrement the hit counts
2451 // with the number of throws.
2452 counts = sc->maybeGetThrowCounts(offset);
2453 if (counts) {
2454 hits -= counts->numExec();
2455 }
2456 }
2457
2458 args.rval().setObject(*result);
2459 return true;
2460}
2461
2462/* static */
2463bool DebuggerScript::construct(JSContext* cx, unsigned argc, Value* vp) {
2464 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR,
2465 "Debugger.Script");
2466 return false;
2467}
2468
2469const JSPropertySpec DebuggerScript::properties_[] = {
2470 JS_DEBUG_PSG("isGeneratorFunction", getIsGeneratorFunction)JSPropertySpec::nativeAccessors("isGeneratorFunction", CheckAccessorAttrs
<0>(), CallData::ToNative<&CallData::getIsGeneratorFunction
>, nullptr)
,
2471 JS_DEBUG_PSG("isAsyncFunction", getIsAsyncFunction)JSPropertySpec::nativeAccessors("isAsyncFunction", CheckAccessorAttrs
<0>(), CallData::ToNative<&CallData::getIsAsyncFunction
>, nullptr)
,
2472 JS_DEBUG_PSG("isFunction", getIsFunction)JSPropertySpec::nativeAccessors("isFunction", CheckAccessorAttrs
<0>(), CallData::ToNative<&CallData::getIsFunction
>, nullptr)
,
2473 JS_DEBUG_PSG("isModule", getIsModule)JSPropertySpec::nativeAccessors("isModule", CheckAccessorAttrs
<0>(), CallData::ToNative<&CallData::getIsModule
>, nullptr)
,
2474 JS_DEBUG_PSG("displayName", getDisplayName)JSPropertySpec::nativeAccessors("displayName", CheckAccessorAttrs
<0>(), CallData::ToNative<&CallData::getDisplayName
>, nullptr)
,
2475 JS_DEBUG_PSG("parameterNames", getParameterNames)JSPropertySpec::nativeAccessors("parameterNames", CheckAccessorAttrs
<0>(), CallData::ToNative<&CallData::getParameterNames
>, nullptr)
,
2476 JS_DEBUG_PSG("url", getUrl)JSPropertySpec::nativeAccessors("url", CheckAccessorAttrs<
0>(), CallData::ToNative<&CallData::getUrl>, nullptr
)
,
2477 JS_DEBUG_PSG("startLine", getStartLine)JSPropertySpec::nativeAccessors("startLine", CheckAccessorAttrs
<0>(), CallData::ToNative<&CallData::getStartLine
>, nullptr)
,
2478 JS_DEBUG_PSG("startColumn", getStartColumn)JSPropertySpec::nativeAccessors("startColumn", CheckAccessorAttrs
<0>(), CallData::ToNative<&CallData::getStartColumn
>, nullptr)
,
2479 JS_DEBUG_PSG("lineCount", getLineCount)JSPropertySpec::nativeAccessors("lineCount", CheckAccessorAttrs
<0>(), CallData::ToNative<&CallData::getLineCount
>, nullptr)
,
2480 JS_DEBUG_PSG("source", getSource)JSPropertySpec::nativeAccessors("source", CheckAccessorAttrs<
0>(), CallData::ToNative<&CallData::getSource>, nullptr
)
,
2481 JS_DEBUG_PSG("sourceStart", getSourceStart)JSPropertySpec::nativeAccessors("sourceStart", CheckAccessorAttrs
<0>(), CallData::ToNative<&CallData::getSourceStart
>, nullptr)
,
2482 JS_DEBUG_PSG("sourceLength", getSourceLength)JSPropertySpec::nativeAccessors("sourceLength", CheckAccessorAttrs
<0>(), CallData::ToNative<&CallData::getSourceLength
>, nullptr)
,
2483 JS_DEBUG_PSG("mainOffset", getMainOffset)JSPropertySpec::nativeAccessors("mainOffset", CheckAccessorAttrs
<0>(), CallData::ToNative<&CallData::getMainOffset
>, nullptr)
,
2484 JS_DEBUG_PSG("global", getGlobal)JSPropertySpec::nativeAccessors("global", CheckAccessorAttrs<
0>(), CallData::ToNative<&CallData::getGlobal>, nullptr
)
,
2485 JS_DEBUG_PSG("format", getFormat)JSPropertySpec::nativeAccessors("format", CheckAccessorAttrs<
0>(), CallData::ToNative<&CallData::getFormat>, nullptr
)
,
2486 JS_PS_ENDJSPropertySpec::sentinel()};
2487
2488const JSFunctionSpec DebuggerScript::methods_[] = {
2489 JS_DEBUG_FN("getChildScripts", getChildScripts, 0){JSFunctionSpec::Name("getChildScripts"), {CallData::ToNative
<&CallData::getChildScripts>, nullptr}, 0, 0, nullptr
}
,
2490 JS_DEBUG_FN("getPossibleBreakpoints", getPossibleBreakpoints, 0){JSFunctionSpec::Name("getPossibleBreakpoints"), {CallData::ToNative
<&CallData::getPossibleBreakpoints>, nullptr}, 0, 0
, nullptr}
,
2491 JS_DEBUG_FN("getPossibleBreakpointOffsets", getPossibleBreakpointOffsets,{JSFunctionSpec::Name("getPossibleBreakpointOffsets"), {CallData
::ToNative<&CallData::getPossibleBreakpointOffsets>
, nullptr}, 0, 0, nullptr}
2492 0){JSFunctionSpec::Name("getPossibleBreakpointOffsets"), {CallData
::ToNative<&CallData::getPossibleBreakpointOffsets>
, nullptr}, 0, 0, nullptr}
,
2493 JS_DEBUG_FN("setBreakpoint", setBreakpoint, 2){JSFunctionSpec::Name("setBreakpoint"), {CallData::ToNative<
&CallData::setBreakpoint>, nullptr}, 2, 0, nullptr}
,
2494 JS_DEBUG_FN("getBreakpoints", getBreakpoints, 1){JSFunctionSpec::Name("getBreakpoints"), {CallData::ToNative<
&CallData::getBreakpoints>, nullptr}, 1, 0, nullptr}
,
2495 JS_DEBUG_FN("clearBreakpoint", clearBreakpoint, 1){JSFunctionSpec::Name("clearBreakpoint"), {CallData::ToNative
<&CallData::clearBreakpoint>, nullptr}, 1, 0, nullptr
}
,
2496 JS_DEBUG_FN("clearAllBreakpoints", clearAllBreakpoints, 0){JSFunctionSpec::Name("clearAllBreakpoints"), {CallData::ToNative
<&CallData::clearAllBreakpoints>, nullptr}, 0, 0, nullptr
}
,
2497 JS_DEBUG_FN("isInCatchScope", isInCatchScope, 1){JSFunctionSpec::Name("isInCatchScope"), {CallData::ToNative<
&CallData::isInCatchScope>, nullptr}, 1, 0, nullptr}
,
2498 JS_DEBUG_FN("getOffsetMetadata", getOffsetMetadata, 1){JSFunctionSpec::Name("getOffsetMetadata"), {CallData::ToNative
<&CallData::getOffsetMetadata>, nullptr}, 1, 0, nullptr
}
,
2499 JS_DEBUG_FN("getOffsetsCoverage", getOffsetsCoverage, 0){JSFunctionSpec::Name("getOffsetsCoverage"), {CallData::ToNative
<&CallData::getOffsetsCoverage>, nullptr}, 0, 0, nullptr
}
,
2500 JS_DEBUG_FN("getEffectfulOffsets", getEffectfulOffsets, 1){JSFunctionSpec::Name("getEffectfulOffsets"), {CallData::ToNative
<&CallData::getEffectfulOffsets>, nullptr}, 1, 0, nullptr
}
,
2501
2502 // The following APIs are deprecated due to their reliance on the
2503 // under-defined 'entrypoint' concept. Make use of getPossibleBreakpoints,
2504 // getPossibleBreakpointOffsets, or getOffsetMetadata instead.
2505 JS_DEBUG_FN("getAllOffsets", getAllOffsets, 0){JSFunctionSpec::Name("getAllOffsets"), {CallData::ToNative<
&CallData::getAllOffsets>, nullptr}, 0, 0, nullptr}
,
2506 JS_DEBUG_FN("getAllColumnOffsets", getAllColumnOffsets, 0){JSFunctionSpec::Name("getAllColumnOffsets"), {CallData::ToNative
<&CallData::getAllColumnOffsets>, nullptr}, 0, 0, nullptr
}
,
2507 JS_DEBUG_FN("getLineOffsets", getLineOffsets, 1){JSFunctionSpec::Name("getLineOffsets"), {CallData::ToNative<
&CallData::getLineOffsets>, nullptr}, 1, 0, nullptr}
,
2508 JS_DEBUG_FN("getOffsetLocation", getOffsetLocation, 0){JSFunctionSpec::Name("getOffsetLocation"), {CallData::ToNative
<&CallData::getOffsetLocation>, nullptr}, 0, 0, nullptr
}
, JS_FS_END{JSFunctionSpec::Name(nullptr), {nullptr, nullptr}, 0, 0, nullptr
}
};