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 |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
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 | |
61 | using namespace js; |
62 | |
63 | using mozilla::Maybe; |
64 | using mozilla::Some; |
65 | |
66 | const 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 | |
79 | const JSClass DebuggerScript::class_ = { |
80 | "Script", JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS), &classOps_}; |
81 | |
82 | void 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 */ |
106 | NativeObject* 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 */ |
114 | DebuggerScript* 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 | |
132 | static 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 */ |
164 | DebuggerScript* 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 | |
179 | struct 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 | |
252 | template <DebuggerScript::CallData::Method MyMethod> |
253 | /* static */ |
254 | bool 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 | |
267 | bool DebuggerScript::CallData::getIsGeneratorFunction() { |
268 | if (!ensureScriptMaybeLazy()) { |
269 | return false; |
270 | } |
271 | args.rval().setBoolean(obj->getReferentScript()->isGenerator()); |
272 | return true; |
273 | } |
274 | |
275 | bool DebuggerScript::CallData::getIsAsyncFunction() { |
276 | if (!ensureScriptMaybeLazy()) { |
277 | return false; |
278 | } |
279 | args.rval().setBoolean(obj->getReferentScript()->isAsync()); |
280 | return true; |
281 | } |
282 | |
283 | bool DebuggerScript::CallData::getIsFunction() { |
284 | if (!ensureScriptMaybeLazy()) { |
285 | return false; |
286 | } |
287 | |
288 | args.rval().setBoolean(obj->getReferentScript()->function()); |
289 | return true; |
290 | } |
291 | |
292 | bool 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 | |
302 | bool 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 | |
328 | bool 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 | |
348 | bool 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 | |
374 | bool 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 | |
381 | bool 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 | |
392 | struct 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 | |
418 | bool 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 | |
427 | class 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 | |
445 | bool 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 | |
458 | bool DebuggerScript::CallData::getSourceStart() { |
459 | if (!ensureScriptMaybeLazy()) { |
460 | return false; |
461 | } |
462 | args.rval().setNumber(uint32_t(obj->getReferentScript()->sourceStart())); |
463 | return true; |
464 | } |
465 | |
466 | bool DebuggerScript::CallData::getSourceLength() { |
467 | if (!ensureScriptMaybeLazy()) { |
468 | return false; |
469 | } |
470 | args.rval().setNumber(uint32_t(obj->getReferentScript()->sourceLength())); |
471 | return true; |
472 | } |
473 | |
474 | bool DebuggerScript::CallData::getMainOffset() { |
475 | if (!ensureScript()) { |
476 | return false; |
477 | } |
478 | args.rval().setNumber(uint32_t(script->mainOffset())); |
479 | return true; |
480 | } |
481 | |
482 | bool 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 | |
496 | bool 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 | |
503 | static 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 | |
524 | static 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 | |
552 | bool 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 | |
572 | static 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 | |
590 | static 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 | |
600 | static 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 | |
611 | static 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 | |
629 | template <bool OnlyOffsets> |
630 | class 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 | |
959 | bool 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 | |
976 | bool 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 | |
993 | class 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 | |
1090 | bool 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 | |
1109 | namespace { |
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 | */ |
1137 | class 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 | |
1301 | class 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 | |
1424 | bool 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. |
1448 | static 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 | |
1724 | bool 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 | |
1756 | bool 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 | |
1829 | class 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 | |
1925 | bool 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 | |
1936 | class 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 | |
2006 | bool 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 | |
2037 | struct 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 | |
2153 | bool 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 | |
2177 | bool 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 | |
2223 | class 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 | |
2275 | bool 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 | |
2295 | bool 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 | |
2305 | class 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 | |
2354 | bool 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 | |
2372 | bool 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 */ |
2463 | bool 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 | |
2469 | const 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 | |
2488 | const 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 }}; |