Bug Summary

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