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