Script.cpp (75529B)
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)); 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()); 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()); 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_STACK, 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); 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 if (!result->valid()) { 743 return false; 744 } 745 return true; 746 } 747 bool parseSizeTValue(HandleValue value, size_t* result) { 748 return parseIntValueImpl(value, result); 749 } 750 751 template <typename T> 752 bool parseIntValueMaybeImpl(HandleValue value, Maybe<T>* result) { 753 T result_; 754 if (!parseIntValueImpl(value, &result_)) { 755 return false; 756 } 757 758 *result = Some(result_); 759 return true; 760 } 761 762 bool parseUint32Value(HandleValue value, Maybe<uint32_t>* result) { 763 return parseIntValueMaybeImpl(value, result); 764 } 765 bool parseSizeTValue(HandleValue value, Maybe<size_t>* result) { 766 return parseIntValueMaybeImpl(value, result); 767 } 768 769 public: 770 explicit GetPossibleBreakpointsMatcher(JSContext* cx, 771 MutableHandleObject result) 772 : cx_(cx), result_(result) {} 773 774 bool parseQuery(HandleObject query) { 775 RootedValue lineValue(cx_); 776 if (!GetProperty(cx_, query, query, cx_->names().line, &lineValue)) { 777 return false; 778 } 779 780 RootedValue minLineValue(cx_); 781 if (!GetProperty(cx_, query, query, cx_->names().minLine, &minLineValue)) { 782 return false; 783 } 784 785 RootedValue minColumnValue(cx_); 786 if (!GetProperty(cx_, query, query, cx_->names().minColumn, 787 &minColumnValue)) { 788 return false; 789 } 790 791 RootedValue minOffsetValue(cx_); 792 if (!GetProperty(cx_, query, query, cx_->names().minOffset, 793 &minOffsetValue)) { 794 return false; 795 } 796 797 RootedValue maxLineValue(cx_); 798 if (!GetProperty(cx_, query, query, cx_->names().maxLine, &maxLineValue)) { 799 return false; 800 } 801 802 RootedValue maxColumnValue(cx_); 803 if (!GetProperty(cx_, query, query, cx_->names().maxColumn, 804 &maxColumnValue)) { 805 return false; 806 } 807 808 RootedValue maxOffsetValue(cx_); 809 if (!GetProperty(cx_, query, query, cx_->names().maxOffset, 810 &maxOffsetValue)) { 811 return false; 812 } 813 814 if (!minOffsetValue.isUndefined()) { 815 if (!parseSizeTValue(minOffsetValue, &minOffset)) { 816 JS_ReportErrorNumberASCII( 817 cx_, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE, 818 "getPossibleBreakpoints' 'minOffset'", "not an integer"); 819 return false; 820 } 821 } 822 if (!maxOffsetValue.isUndefined()) { 823 if (!parseSizeTValue(maxOffsetValue, &maxOffset)) { 824 JS_ReportErrorNumberASCII( 825 cx_, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE, 826 "getPossibleBreakpoints' 'maxOffset'", "not an integer"); 827 return false; 828 } 829 } 830 831 if (!lineValue.isUndefined()) { 832 if (!minLineValue.isUndefined() || !maxLineValue.isUndefined()) { 833 JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr, 834 JSMSG_UNEXPECTED_TYPE, 835 "getPossibleBreakpoints' 'line'", 836 "not allowed alongside 'minLine'/'maxLine'"); 837 return false; 838 } 839 840 uint32_t line; 841 if (!parseUint32Value(lineValue, &line)) { 842 JS_ReportErrorNumberASCII( 843 cx_, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE, 844 "getPossibleBreakpoints' 'line'", "not an integer"); 845 return false; 846 } 847 848 // If no end column is given, we use the default of 0 and wrap to 849 // the next line. 850 minLine = Some(line); 851 maxLine = Some(line + (maxColumnValue.isUndefined() ? 1 : 0)); 852 } 853 854 if (!minLineValue.isUndefined()) { 855 if (!parseUint32Value(minLineValue, &minLine)) { 856 JS_ReportErrorNumberASCII( 857 cx_, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE, 858 "getPossibleBreakpoints' 'minLine'", "not an integer"); 859 return false; 860 } 861 } 862 863 if (!minColumnValue.isUndefined()) { 864 if (!minLine) { 865 JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr, 866 JSMSG_UNEXPECTED_TYPE, 867 "getPossibleBreakpoints' 'minColumn'", 868 "not allowed without 'line' or 'minLine'"); 869 return false; 870 } 871 872 if (!parseColumnValue(minColumnValue, &minColumn)) { 873 JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr, 874 JSMSG_UNEXPECTED_TYPE, 875 "getPossibleBreakpoints' 'minColumn'", 876 "not a positive integer in valid range"); 877 return false; 878 } 879 } 880 881 if (!maxLineValue.isUndefined()) { 882 if (!parseUint32Value(maxLineValue, &maxLine)) { 883 JS_ReportErrorNumberASCII( 884 cx_, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE, 885 "getPossibleBreakpoints' 'maxLine'", "not an integer"); 886 return false; 887 } 888 } 889 890 if (!maxColumnValue.isUndefined()) { 891 if (!maxLine) { 892 JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr, 893 JSMSG_UNEXPECTED_TYPE, 894 "getPossibleBreakpoints' 'maxColumn'", 895 "not allowed without 'line' or 'maxLine'"); 896 return false; 897 } 898 899 if (!parseColumnValue(maxColumnValue, &maxColumn)) { 900 JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr, 901 JSMSG_UNEXPECTED_TYPE, 902 "getPossibleBreakpoints' 'maxColumn'", 903 "not a positive integer in valid range"); 904 return false; 905 } 906 } 907 908 return true; 909 } 910 911 using ReturnType = bool; 912 ReturnType match(Handle<BaseScript*> base) { 913 RootedScript script(cx_, DelazifyScript(cx_, base)); 914 if (!script) { 915 return false; 916 } 917 918 // Second pass: build the result array. 919 result_.set(NewDenseEmptyArray(cx_)); 920 if (!result_) { 921 return false; 922 } 923 924 for (BytecodeRangeWithPosition r(cx_, script, SkipPrologueOps::Yes); 925 !r.empty(); r.popFront()) { 926 if (!r.frontIsBreakablePoint()) { 927 continue; 928 } 929 930 size_t offset = r.frontOffset(); 931 uint32_t lineno = r.frontLineNumber(); 932 JS::LimitedColumnNumberOneOrigin colno = r.frontColumnNumber(); 933 934 if (!maybeAppendEntry(offset, lineno, colno, 935 r.frontIsBreakableStepPoint())) { 936 return false; 937 } 938 } 939 940 return true; 941 } 942 ReturnType match(Handle<WasmInstanceObject*> instanceObj) { 943 wasm::Instance& instance = instanceObj->instance(); 944 945 Vector<wasm::ExprLoc> offsets(cx_); 946 if (instance.debugEnabled() && 947 !instance.debug().getAllColumnOffsets(&offsets)) { 948 return false; 949 } 950 951 result_.set(NewDenseEmptyArray(cx_)); 952 if (!result_) { 953 return false; 954 } 955 956 for (uint32_t i = 0; i < offsets.length(); i++) { 957 uint32_t lineno = offsets[i].lineno; 958 JS::LimitedColumnNumberOneOrigin column(offsets[i].column); 959 size_t offset = offsets[i].offset; 960 if (!maybeAppendEntry(offset, lineno, column, true)) { 961 return false; 962 } 963 } 964 return true; 965 } 966 }; 967 968 bool DebuggerScript::CallData::getPossibleBreakpoints() { 969 RootedObject result(cx); 970 GetPossibleBreakpointsMatcher<false> matcher(cx, &result); 971 if (args.length() >= 1 && !args[0].isUndefined()) { 972 RootedObject queryObject(cx, RequireObject(cx, args[0])); 973 if (!queryObject || !matcher.parseQuery(queryObject)) { 974 return false; 975 } 976 } 977 if (!referent.match(matcher)) { 978 return false; 979 } 980 981 args.rval().setObject(*result); 982 return true; 983 } 984 985 bool DebuggerScript::CallData::getPossibleBreakpointOffsets() { 986 RootedObject result(cx); 987 GetPossibleBreakpointsMatcher<true> matcher(cx, &result); 988 if (args.length() >= 1 && !args[0].isUndefined()) { 989 RootedObject queryObject(cx, RequireObject(cx, args[0])); 990 if (!queryObject || !matcher.parseQuery(queryObject)) { 991 return false; 992 } 993 } 994 if (!referent.match(matcher)) { 995 return false; 996 } 997 998 args.rval().setObject(*result); 999 return true; 1000 } 1001 1002 class DebuggerScript::GetOffsetMetadataMatcher { 1003 JSContext* cx_; 1004 size_t offset_; 1005 MutableHandle<PlainObject*> result_; 1006 1007 public: 1008 explicit GetOffsetMetadataMatcher(JSContext* cx, size_t offset, 1009 MutableHandle<PlainObject*> result) 1010 : cx_(cx), offset_(offset), result_(result) {} 1011 using ReturnType = bool; 1012 ReturnType match(Handle<BaseScript*> base) { 1013 RootedScript script(cx_, DelazifyScript(cx_, base)); 1014 if (!script) { 1015 return false; 1016 } 1017 1018 if (!EnsureScriptOffsetIsValid(cx_, script, offset_)) { 1019 return false; 1020 } 1021 1022 result_.set(NewPlainObject(cx_)); 1023 if (!result_) { 1024 return false; 1025 } 1026 1027 // Use SkipPrologueOps::No to ensure we return isBreakpoint = false and 1028 // isStepStart = false for prologue ops, instead of the metadata for the 1029 // first 'main' op. 1030 BytecodeRangeWithPosition r(cx_, script, SkipPrologueOps::No); 1031 while (!r.empty() && r.frontOffset() < offset_) { 1032 r.popFront(); 1033 } 1034 MOZ_ASSERT(r.frontOffset() == offset_); 1035 1036 RootedValue value(cx_, NumberValue(r.frontLineNumber())); 1037 if (!DefineDataProperty(cx_, result_, cx_->names().lineNumber, value)) { 1038 return false; 1039 } 1040 1041 value = NumberValue(r.frontColumnNumber().oneOriginValue()); 1042 if (!DefineDataProperty(cx_, result_, cx_->names().columnNumber, value)) { 1043 return false; 1044 } 1045 1046 value = BooleanValue(r.frontIsBreakablePoint()); 1047 if (!DefineDataProperty(cx_, result_, cx_->names().isBreakpoint, value)) { 1048 return false; 1049 } 1050 1051 value = BooleanValue(r.frontIsBreakableStepPoint()); 1052 if (!DefineDataProperty(cx_, result_, cx_->names().isStepStart, value)) { 1053 return false; 1054 } 1055 1056 return true; 1057 } 1058 ReturnType match(Handle<WasmInstanceObject*> instanceObj) { 1059 wasm::Instance& instance = instanceObj->instance(); 1060 if (!instance.debugEnabled()) { 1061 JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr, 1062 JSMSG_DEBUG_BAD_OFFSET); 1063 return false; 1064 } 1065 1066 uint32_t lineno; 1067 JS::LimitedColumnNumberOneOrigin column; 1068 if (!instance.debug().getOffsetLocation(offset_, &lineno, &column)) { 1069 JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr, 1070 JSMSG_DEBUG_BAD_OFFSET); 1071 return false; 1072 } 1073 1074 result_.set(NewPlainObject(cx_)); 1075 if (!result_) { 1076 return false; 1077 } 1078 1079 RootedValue value(cx_, NumberValue(lineno)); 1080 if (!DefineDataProperty(cx_, result_, cx_->names().lineNumber, value)) { 1081 return false; 1082 } 1083 1084 value = NumberValue(column.oneOriginValue()); 1085 if (!DefineDataProperty(cx_, result_, cx_->names().columnNumber, value)) { 1086 return false; 1087 } 1088 1089 value.setBoolean(true); 1090 if (!DefineDataProperty(cx_, result_, cx_->names().isBreakpoint, value)) { 1091 return false; 1092 } 1093 1094 value.setBoolean(true); 1095 if (!DefineDataProperty(cx_, result_, cx_->names().isStepStart, value)) { 1096 return false; 1097 } 1098 1099 return true; 1100 } 1101 }; 1102 1103 bool DebuggerScript::CallData::getOffsetMetadata() { 1104 if (!args.requireAtLeast(cx, "Debugger.Script.getOffsetMetadata", 1)) { 1105 return false; 1106 } 1107 size_t offset; 1108 if (!ScriptOffset(cx, args[0], &offset)) { 1109 return false; 1110 } 1111 1112 Rooted<PlainObject*> result(cx); 1113 GetOffsetMetadataMatcher matcher(cx, offset, &result); 1114 if (!referent.match(matcher)) { 1115 return false; 1116 } 1117 1118 args.rval().setObject(*result); 1119 return true; 1120 } 1121 1122 namespace { 1123 1124 /* 1125 * FlowGraphSummary::populate(cx, script) computes a summary of script's 1126 * control flow graph used by DebuggerScript_{getAllOffsets,getLineOffsets}. 1127 * 1128 * An instruction on a given line is an entry point for that line if it can be 1129 * reached from (an instruction on) a different line. We distinguish between the 1130 * following cases: 1131 * - hasNoEdges: 1132 * The instruction cannot be reached, so the instruction is not an entry 1133 * point for the line it is on. 1134 * - hasSingleEdge: 1135 * The instruction can be reached from a single line. If this line is 1136 * different from the line the instruction is on, the instruction is an 1137 * entry point for that line. 1138 * 1139 * Similarly, an instruction on a given position (line/column pair) is an 1140 * entry point for that position if it can be reached from (an instruction on) a 1141 * different position. Again, we distinguish between the following cases: 1142 * - hasNoEdges: 1143 * The instruction cannot be reached, so the instruction is not an entry 1144 * point for the position it is on. 1145 * - hasSingleEdge: 1146 * The instruction can be reached from a single position. If this line is 1147 * different from the position the instruction is on, the instruction is 1148 * an entry point for that position. 1149 */ 1150 class FlowGraphSummary { 1151 public: 1152 class Entry { 1153 public: 1154 static constexpr uint32_t Line_HasNoEdge = UINT32_MAX; 1155 static constexpr uint32_t Column_HasMultipleEdge = UINT32_MAX; 1156 1157 // NOTE: column can be Column_HasMultipleEdge. 1158 static Entry createWithSingleEdgeOrMultipleEdge(uint32_t lineno, 1159 uint32_t column) { 1160 return Entry(lineno, column); 1161 } 1162 1163 static Entry createWithMultipleEdgesFromSingleLine(uint32_t lineno) { 1164 return Entry(lineno, Column_HasMultipleEdge); 1165 } 1166 1167 static Entry createWithMultipleEdgesFromMultipleLines() { 1168 return Entry(Line_HasNoEdge, Column_HasMultipleEdge); 1169 } 1170 1171 Entry() : lineno_(Line_HasNoEdge), column_(1) {} 1172 1173 bool hasNoEdges() const { 1174 return lineno_ == Line_HasNoEdge && column_ != Column_HasMultipleEdge; 1175 } 1176 1177 bool hasSingleEdge() const { 1178 return lineno_ != Line_HasNoEdge && column_ != Column_HasMultipleEdge; 1179 } 1180 1181 uint32_t lineno() const { return lineno_; } 1182 1183 // Returns 1-origin column number or the sentinel value 1184 // Column_HasMultipleEdge. 1185 uint32_t columnOrSentinel() const { return column_; } 1186 1187 JS::LimitedColumnNumberOneOrigin column() const { 1188 MOZ_ASSERT(column_ != Column_HasMultipleEdge); 1189 return JS::LimitedColumnNumberOneOrigin(column_); 1190 } 1191 1192 private: 1193 Entry(uint32_t lineno, uint32_t column) 1194 : lineno_(lineno), column_(column) {} 1195 1196 // Line number (1-origin). 1197 // Line_HasNoEdge for no edge. 1198 uint32_t lineno_; 1199 1200 // Column number in UTF-16 code units (1-origin). 1201 // Column_HasMultipleEdge for multiple edge. 1202 uint32_t column_; 1203 }; 1204 1205 explicit FlowGraphSummary(JSContext* cx) : entries_(cx) {} 1206 1207 Entry& operator[](size_t index) { return entries_[index]; } 1208 1209 bool populate(JSContext* cx, JSScript* script) { 1210 if (!entries_.growBy(script->length())) { 1211 return false; 1212 } 1213 unsigned mainOffset = script->pcToOffset(script->main()); 1214 entries_[mainOffset] = Entry::createWithMultipleEdgesFromMultipleLines(); 1215 1216 // The following code uses uint32_t for column numbers. 1217 // The value is either 1-origin column number, 1218 // or Entry::Column_HasMultipleEdge. 1219 1220 uint32_t prevLineno = script->lineno(); 1221 uint32_t prevColumn = 1; 1222 JSOp prevOp = JSOp::Nop; 1223 for (BytecodeRangeWithPosition r(cx, script, SkipPrologueOps::Yes); 1224 !r.empty(); r.popFront()) { 1225 uint32_t lineno = prevLineno; 1226 uint32_t column = prevColumn; 1227 JSOp op = r.frontOpcode(); 1228 1229 if (BytecodeFallsThrough(prevOp)) { 1230 addEdge(prevLineno, prevColumn, r.frontOffset()); 1231 } 1232 1233 // If we visit the branch target before we visit the 1234 // branch op itself, just reuse the previous location. 1235 // This is reasonable for the time being because this 1236 // situation can currently only arise from loop heads, 1237 // where this assumption holds. 1238 if (BytecodeIsJumpTarget(op) && !entries_[r.frontOffset()].hasNoEdges()) { 1239 lineno = entries_[r.frontOffset()].lineno(); 1240 column = entries_[r.frontOffset()].columnOrSentinel(); 1241 } 1242 1243 if (r.frontIsEntryPoint()) { 1244 lineno = r.frontLineNumber(); 1245 column = r.frontColumnNumber().oneOriginValue(); 1246 } 1247 1248 if (IsJumpOpcode(op)) { 1249 addEdge(lineno, column, r.frontOffset() + GET_JUMP_OFFSET(r.frontPC())); 1250 } else if (op == JSOp::TableSwitch) { 1251 jsbytecode* const switchPC = r.frontPC(); 1252 jsbytecode* pc = switchPC; 1253 size_t offset = r.frontOffset(); 1254 ptrdiff_t step = JUMP_OFFSET_LEN; 1255 size_t defaultOffset = offset + GET_JUMP_OFFSET(pc); 1256 pc += step; 1257 addEdge(lineno, column, defaultOffset); 1258 1259 int32_t low = GET_JUMP_OFFSET(pc); 1260 pc += JUMP_OFFSET_LEN; 1261 int ncases = GET_JUMP_OFFSET(pc) - low + 1; 1262 pc += JUMP_OFFSET_LEN; 1263 1264 for (int i = 0; i < ncases; i++) { 1265 size_t target = script->tableSwitchCaseOffset(switchPC, i); 1266 addEdge(lineno, column, target); 1267 } 1268 } else if (op == JSOp::Try) { 1269 // As there is no literal incoming edge into the catch block, we 1270 // make a fake one by copying the JSOp::Try location, as-if this 1271 // was an incoming edge of the catch block. This is needed 1272 // because we only report offsets of entry points which have 1273 // valid incoming edges. 1274 for (const TryNote& tn : script->trynotes()) { 1275 if (tn.start == r.frontOffset() + JSOpLength_Try) { 1276 uint32_t catchOffset = tn.start + tn.length; 1277 if (tn.kind() == TryNoteKind::Catch || 1278 tn.kind() == TryNoteKind::Finally) { 1279 addEdge(lineno, column, catchOffset); 1280 } 1281 } 1282 } 1283 } 1284 1285 prevLineno = lineno; 1286 prevColumn = column; 1287 prevOp = op; 1288 } 1289 1290 return true; 1291 } 1292 1293 private: 1294 // sourceColumn is either 1-origin column number, 1295 // or Entry::Column_HasMultipleEdge. 1296 void addEdge(uint32_t sourceLineno, uint32_t sourceColumn, 1297 size_t targetOffset) { 1298 if (entries_[targetOffset].hasNoEdges()) { 1299 entries_[targetOffset] = 1300 Entry::createWithSingleEdgeOrMultipleEdge(sourceLineno, sourceColumn); 1301 } else if (entries_[targetOffset].lineno() != sourceLineno) { 1302 entries_[targetOffset] = 1303 Entry::createWithMultipleEdgesFromMultipleLines(); 1304 } else if (entries_[targetOffset].columnOrSentinel() != sourceColumn) { 1305 entries_[targetOffset] = 1306 Entry::createWithMultipleEdgesFromSingleLine(sourceLineno); 1307 } 1308 } 1309 1310 Vector<Entry> entries_; 1311 }; 1312 1313 } /* anonymous namespace */ 1314 1315 class DebuggerScript::GetOffsetLocationMatcher { 1316 JSContext* cx_; 1317 size_t offset_; 1318 MutableHandle<PlainObject*> result_; 1319 1320 public: 1321 explicit GetOffsetLocationMatcher(JSContext* cx, size_t offset, 1322 MutableHandle<PlainObject*> result) 1323 : cx_(cx), offset_(offset), result_(result) {} 1324 using ReturnType = bool; 1325 ReturnType match(Handle<BaseScript*> base) { 1326 RootedScript script(cx_, DelazifyScript(cx_, base)); 1327 if (!script) { 1328 return false; 1329 } 1330 1331 if (!EnsureScriptOffsetIsValid(cx_, script, offset_)) { 1332 return false; 1333 } 1334 1335 FlowGraphSummary flowData(cx_); 1336 if (!flowData.populate(cx_, script)) { 1337 return false; 1338 } 1339 1340 result_.set(NewPlainObject(cx_)); 1341 if (!result_) { 1342 return false; 1343 } 1344 1345 // Use SkipPrologueOps::No to ensure we return isEntryPoint = false for 1346 // prologue ops, instead of the value for the first 'main' op. 1347 BytecodeRangeWithPosition r(cx_, script, SkipPrologueOps::No); 1348 while (!r.empty() && r.frontOffset() < offset_) { 1349 r.popFront(); 1350 } 1351 MOZ_ASSERT(r.frontOffset() == offset_); 1352 1353 bool isEntryPoint = r.frontIsEntryPoint(); 1354 1355 // Line numbers are only correctly defined on entry points. Thus looks 1356 // either for the next valid offset in the flowData, being the last entry 1357 // point flowing into the current offset, or for the next valid entry point. 1358 while (!r.frontIsEntryPoint() && 1359 !flowData[r.frontOffset()].hasSingleEdge()) { 1360 r.popFront(); 1361 MOZ_ASSERT(!r.empty()); 1362 } 1363 1364 // If this is an entry point, take the line number associated with the entry 1365 // point, otherwise settle on the next instruction and take the incoming 1366 // edge position. 1367 uint32_t lineno; 1368 JS::LimitedColumnNumberOneOrigin column; 1369 if (r.frontIsEntryPoint()) { 1370 lineno = r.frontLineNumber(); 1371 column = r.frontColumnNumber(); 1372 } else { 1373 MOZ_ASSERT(flowData[r.frontOffset()].hasSingleEdge()); 1374 lineno = flowData[r.frontOffset()].lineno(); 1375 column = flowData[r.frontOffset()].column(); 1376 } 1377 1378 RootedValue value(cx_, NumberValue(lineno)); 1379 if (!DefineDataProperty(cx_, result_, cx_->names().lineNumber, value)) { 1380 return false; 1381 } 1382 1383 value = NumberValue(column.oneOriginValue()); 1384 if (!DefineDataProperty(cx_, result_, cx_->names().columnNumber, value)) { 1385 return false; 1386 } 1387 1388 // The same entry point test that is used by getAllColumnOffsets. 1389 isEntryPoint = (isEntryPoint && !flowData[offset_].hasNoEdges() && 1390 (flowData[offset_].lineno() != r.frontLineNumber() || 1391 flowData[offset_].columnOrSentinel() != 1392 r.frontColumnNumber().oneOriginValue())); 1393 value.setBoolean(isEntryPoint); 1394 if (!DefineDataProperty(cx_, result_, cx_->names().isEntryPoint, value)) { 1395 return false; 1396 } 1397 1398 return true; 1399 } 1400 ReturnType match(Handle<WasmInstanceObject*> instanceObj) { 1401 wasm::Instance& instance = instanceObj->instance(); 1402 if (!instance.debugEnabled()) { 1403 JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr, 1404 JSMSG_DEBUG_BAD_OFFSET); 1405 return false; 1406 } 1407 1408 uint32_t lineno; 1409 JS::LimitedColumnNumberOneOrigin column; 1410 if (!instance.debug().getOffsetLocation(offset_, &lineno, &column)) { 1411 JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr, 1412 JSMSG_DEBUG_BAD_OFFSET); 1413 return false; 1414 } 1415 1416 result_.set(NewPlainObject(cx_)); 1417 if (!result_) { 1418 return false; 1419 } 1420 1421 RootedValue value(cx_, NumberValue(lineno)); 1422 if (!DefineDataProperty(cx_, result_, cx_->names().lineNumber, value)) { 1423 return false; 1424 } 1425 1426 value = NumberValue(column.oneOriginValue()); 1427 if (!DefineDataProperty(cx_, result_, cx_->names().columnNumber, value)) { 1428 return false; 1429 } 1430 1431 value.setBoolean(true); 1432 if (!DefineDataProperty(cx_, result_, cx_->names().isEntryPoint, value)) { 1433 return false; 1434 } 1435 1436 return true; 1437 } 1438 }; 1439 1440 bool DebuggerScript::CallData::getOffsetLocation() { 1441 if (!args.requireAtLeast(cx, "Debugger.Script.getOffsetLocation", 1)) { 1442 return false; 1443 } 1444 size_t offset; 1445 if (!ScriptOffset(cx, args[0], &offset)) { 1446 return false; 1447 } 1448 1449 Rooted<PlainObject*> result(cx); 1450 GetOffsetLocationMatcher matcher(cx, offset, &result); 1451 if (!referent.match(matcher)) { 1452 return false; 1453 } 1454 1455 args.rval().setObject(*result); 1456 return true; 1457 } 1458 1459 // Return whether an opcode is considered effectful: it can have direct side 1460 // effects that can be observed outside of the current frame. Opcodes are not 1461 // effectful if they only modify the current frame's state, modify objects 1462 // created by the current frame, or can potentially call other scripts or 1463 // natives which could have side effects. 1464 static bool BytecodeIsEffectful(JSScript* script, size_t offset) { 1465 jsbytecode* pc = script->offsetToPC(offset); 1466 JSOp op = JSOp(*pc); 1467 switch (op) { 1468 case JSOp::SetProp: 1469 case JSOp::StrictSetProp: 1470 case JSOp::SetPropSuper: 1471 case JSOp::StrictSetPropSuper: 1472 case JSOp::SetElem: 1473 case JSOp::StrictSetElem: 1474 case JSOp::SetElemSuper: 1475 case JSOp::StrictSetElemSuper: 1476 case JSOp::SetName: 1477 case JSOp::StrictSetName: 1478 case JSOp::SetGName: 1479 case JSOp::StrictSetGName: 1480 case JSOp::DelProp: 1481 case JSOp::StrictDelProp: 1482 case JSOp::DelElem: 1483 case JSOp::StrictDelElem: 1484 case JSOp::DelName: 1485 case JSOp::SetAliasedVar: 1486 case JSOp::InitHomeObject: 1487 case JSOp::SetIntrinsic: 1488 case JSOp::InitGLexical: 1489 case JSOp::GlobalOrEvalDeclInstantiation: 1490 case JSOp::SetFunName: 1491 case JSOp::MutateProto: 1492 case JSOp::DynamicImport: 1493 case JSOp::InitialYield: 1494 case JSOp::Yield: 1495 case JSOp::Await: 1496 case JSOp::CanSkipAwait: 1497 #ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT 1498 case JSOp::AddDisposable: 1499 #endif 1500 return true; 1501 1502 case JSOp::Nop: 1503 case JSOp::NopDestructuring: 1504 case JSOp::NopIsAssignOp: 1505 case JSOp::TryDestructuring: 1506 case JSOp::Lineno: 1507 case JSOp::JumpTarget: 1508 case JSOp::Undefined: 1509 case JSOp::JumpIfTrue: 1510 case JSOp::JumpIfFalse: 1511 case JSOp::Return: 1512 case JSOp::RetRval: 1513 case JSOp::And: 1514 case JSOp::Or: 1515 case JSOp::Coalesce: 1516 case JSOp::Try: 1517 case JSOp::Throw: 1518 case JSOp::ThrowWithStack: 1519 #ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT 1520 case JSOp::TakeDisposeCapability: 1521 case JSOp::CreateSuppressedError: 1522 #endif 1523 case JSOp::Goto: 1524 case JSOp::TableSwitch: 1525 case JSOp::Case: 1526 case JSOp::Default: 1527 case JSOp::BitNot: 1528 case JSOp::BitAnd: 1529 case JSOp::BitOr: 1530 case JSOp::BitXor: 1531 case JSOp::Lsh: 1532 case JSOp::Rsh: 1533 case JSOp::Ursh: 1534 case JSOp::Add: 1535 case JSOp::Sub: 1536 case JSOp::Mul: 1537 case JSOp::Div: 1538 case JSOp::Mod: 1539 case JSOp::Pow: 1540 case JSOp::Pos: 1541 case JSOp::ToNumeric: 1542 case JSOp::Neg: 1543 case JSOp::Inc: 1544 case JSOp::Dec: 1545 case JSOp::ToString: 1546 case JSOp::Eq: 1547 case JSOp::Ne: 1548 case JSOp::StrictEq: 1549 case JSOp::StrictNe: 1550 case JSOp::StrictConstantEq: 1551 case JSOp::StrictConstantNe: 1552 case JSOp::Lt: 1553 case JSOp::Le: 1554 case JSOp::Gt: 1555 case JSOp::Ge: 1556 case JSOp::Double: 1557 case JSOp::BigInt: 1558 case JSOp::String: 1559 case JSOp::Symbol: 1560 case JSOp::Zero: 1561 case JSOp::One: 1562 case JSOp::Null: 1563 case JSOp::Void: 1564 case JSOp::Hole: 1565 case JSOp::False: 1566 case JSOp::True: 1567 case JSOp::Arguments: 1568 case JSOp::Rest: 1569 case JSOp::GetArg: 1570 case JSOp::GetFrameArg: 1571 case JSOp::SetArg: 1572 case JSOp::GetLocal: 1573 case JSOp::SetLocal: 1574 case JSOp::GetActualArg: 1575 case JSOp::ArgumentsLength: 1576 case JSOp::ThrowSetConst: 1577 case JSOp::CheckLexical: 1578 case JSOp::CheckAliasedLexical: 1579 case JSOp::InitLexical: 1580 case JSOp::Uninitialized: 1581 case JSOp::Pop: 1582 case JSOp::PopN: 1583 case JSOp::DupAt: 1584 case JSOp::NewArray: 1585 case JSOp::NewInit: 1586 case JSOp::NewObject: 1587 case JSOp::InitElem: 1588 case JSOp::InitHiddenElem: 1589 case JSOp::InitLockedElem: 1590 case JSOp::InitElemInc: 1591 case JSOp::InitElemArray: 1592 case JSOp::InitProp: 1593 case JSOp::InitLockedProp: 1594 case JSOp::InitHiddenProp: 1595 case JSOp::InitPropGetter: 1596 case JSOp::InitHiddenPropGetter: 1597 case JSOp::InitPropSetter: 1598 case JSOp::InitHiddenPropSetter: 1599 case JSOp::InitElemGetter: 1600 case JSOp::InitHiddenElemGetter: 1601 case JSOp::InitElemSetter: 1602 case JSOp::InitHiddenElemSetter: 1603 case JSOp::SpreadCall: 1604 case JSOp::Call: 1605 case JSOp::CallContent: 1606 case JSOp::CallIgnoresRv: 1607 case JSOp::CallIter: 1608 case JSOp::CallContentIter: 1609 case JSOp::New: 1610 case JSOp::NewContent: 1611 case JSOp::Eval: 1612 case JSOp::StrictEval: 1613 case JSOp::Int8: 1614 case JSOp::Uint16: 1615 case JSOp::ResumeKind: 1616 case JSOp::GetGName: 1617 case JSOp::GetName: 1618 case JSOp::GetIntrinsic: 1619 case JSOp::GetImport: 1620 case JSOp::BindName: 1621 case JSOp::BindUnqualifiedName: 1622 case JSOp::BindUnqualifiedGName: 1623 case JSOp::BindVar: 1624 case JSOp::Dup: 1625 case JSOp::Dup2: 1626 case JSOp::Swap: 1627 case JSOp::Pick: 1628 case JSOp::Unpick: 1629 case JSOp::GetAliasedDebugVar: 1630 case JSOp::GetAliasedVar: 1631 case JSOp::Uint24: 1632 case JSOp::Int32: 1633 case JSOp::LoopHead: 1634 case JSOp::GetElem: 1635 case JSOp::Not: 1636 case JSOp::FunctionThis: 1637 case JSOp::GlobalThis: 1638 case JSOp::NonSyntacticGlobalThis: 1639 case JSOp::Callee: 1640 case JSOp::EnvCallee: 1641 case JSOp::SuperBase: 1642 case JSOp::GetPropSuper: 1643 case JSOp::GetElemSuper: 1644 case JSOp::GetProp: 1645 case JSOp::RegExp: 1646 case JSOp::CallSiteObj: 1647 case JSOp::Object: 1648 case JSOp::Typeof: 1649 case JSOp::TypeofExpr: 1650 case JSOp::TypeofEq: 1651 case JSOp::ToAsyncIter: 1652 case JSOp::ToPropertyKey: 1653 case JSOp::Lambda: 1654 case JSOp::PushLexicalEnv: 1655 case JSOp::PopLexicalEnv: 1656 case JSOp::FreshenLexicalEnv: 1657 case JSOp::RecreateLexicalEnv: 1658 case JSOp::PushClassBodyEnv: 1659 case JSOp::Iter: 1660 case JSOp::MoreIter: 1661 case JSOp::IsNoIter: 1662 case JSOp::EndIter: 1663 case JSOp::CloseIter: 1664 case JSOp::OptimizeGetIterator: 1665 case JSOp::IsNullOrUndefined: 1666 case JSOp::In: 1667 case JSOp::HasOwn: 1668 case JSOp::CheckPrivateField: 1669 case JSOp::NewPrivateName: 1670 case JSOp::SetRval: 1671 case JSOp::Instanceof: 1672 case JSOp::DebugLeaveLexicalEnv: 1673 case JSOp::Debugger: 1674 case JSOp::ImplicitThis: 1675 case JSOp::NewTarget: 1676 case JSOp::CheckIsObj: 1677 case JSOp::CheckObjCoercible: 1678 case JSOp::DebugCheckSelfHosted: 1679 case JSOp::IsConstructing: 1680 case JSOp::OptimizeSpreadCall: 1681 case JSOp::ImportMeta: 1682 case JSOp::EnterWith: 1683 case JSOp::LeaveWith: 1684 case JSOp::SpreadNew: 1685 case JSOp::SpreadEval: 1686 case JSOp::StrictSpreadEval: 1687 case JSOp::CheckClassHeritage: 1688 case JSOp::FunWithProto: 1689 case JSOp::ObjWithProto: 1690 case JSOp::BuiltinObject: 1691 case JSOp::CheckThis: 1692 case JSOp::CheckReturn: 1693 case JSOp::CheckThisReinit: 1694 case JSOp::SuperFun: 1695 case JSOp::SpreadSuperCall: 1696 case JSOp::SuperCall: 1697 case JSOp::PushVarEnv: 1698 case JSOp::GetBoundName: 1699 case JSOp::Exception: 1700 case JSOp::ExceptionAndStack: 1701 case JSOp::IsGenClosing: 1702 case JSOp::FinalYieldRval: 1703 case JSOp::Resume: 1704 case JSOp::CheckResumeKind: 1705 case JSOp::AfterYield: 1706 case JSOp::MaybeExtractAwaitValue: 1707 case JSOp::Generator: 1708 case JSOp::AsyncAwait: 1709 case JSOp::AsyncResolve: 1710 case JSOp::AsyncReject: 1711 case JSOp::Finally: 1712 case JSOp::GetRval: 1713 case JSOp::ThrowMsg: 1714 case JSOp::ForceInterpreter: 1715 return false; 1716 1717 case JSOp::InitAliasedLexical: { 1718 uint32_t hops = EnvironmentCoordinate(pc).hops(); 1719 if (hops == 0) { 1720 // Initializing aliased lexical in the current scope is almost same 1721 // as JSOp::InitLexical. 1722 return false; 1723 } 1724 1725 // Otherwise this can touch an environment outside of the current scope. 1726 return true; 1727 } 1728 } 1729 1730 MOZ_ASSERT_UNREACHABLE("Invalid opcode"); 1731 return false; 1732 } 1733 1734 bool DebuggerScript::CallData::getEffectfulOffsets() { 1735 if (!ensureScript()) { 1736 return false; 1737 } 1738 1739 RootedObject result(cx, NewDenseEmptyArray(cx)); 1740 if (!result) { 1741 return false; 1742 } 1743 for (BytecodeRange r(cx, script); !r.empty(); r.popFront()) { 1744 size_t offset = r.frontOffset(); 1745 if (!BytecodeIsEffectful(script, offset)) { 1746 continue; 1747 } 1748 1749 if (IsGeneratorSlotInitialization(script, offset, cx)) { 1750 // This is engine-internal operation and not visible outside the 1751 // currently executing frame. 1752 // 1753 // Also this offset is not allowed for setting breakpoint. 1754 continue; 1755 } 1756 1757 if (!NewbornArrayPush(cx, result, NumberValue(offset))) { 1758 return false; 1759 } 1760 } 1761 1762 args.rval().setObject(*result); 1763 return true; 1764 } 1765 1766 bool DebuggerScript::CallData::getAllOffsets() { 1767 if (!ensureScript()) { 1768 return false; 1769 } 1770 1771 // First pass: determine which offsets in this script are jump targets and 1772 // which line numbers jump to them. 1773 FlowGraphSummary flowData(cx); 1774 if (!flowData.populate(cx, script)) { 1775 return false; 1776 } 1777 1778 // Second pass: build the result array. 1779 RootedObject result(cx, NewDenseEmptyArray(cx)); 1780 if (!result) { 1781 return false; 1782 } 1783 for (BytecodeRangeWithPosition r(cx, script, SkipPrologueOps::Yes); 1784 !r.empty(); r.popFront()) { 1785 if (!r.frontIsEntryPoint()) { 1786 continue; 1787 } 1788 1789 size_t offset = r.frontOffset(); 1790 uint32_t lineno = r.frontLineNumber(); 1791 1792 // Make a note, if the current instruction is an entry point for the current 1793 // line. 1794 if (!flowData[offset].hasNoEdges() && flowData[offset].lineno() != lineno) { 1795 // Get the offsets array for this line. 1796 RootedObject offsets(cx); 1797 RootedValue offsetsv(cx); 1798 1799 RootedId id(cx, PropertyKey::Int(lineno)); 1800 1801 bool found; 1802 if (!HasOwnProperty(cx, result, id, &found)) { 1803 return false; 1804 } 1805 if (found && !GetProperty(cx, result, result, id, &offsetsv)) { 1806 return false; 1807 } 1808 1809 if (offsetsv.isObject()) { 1810 offsets = &offsetsv.toObject(); 1811 } else { 1812 MOZ_ASSERT(offsetsv.isUndefined()); 1813 1814 // Create an empty offsets array for this line. 1815 // Store it in the result array. 1816 RootedId id(cx); 1817 RootedValue v(cx, NumberValue(lineno)); 1818 offsets = NewDenseEmptyArray(cx); 1819 if (!offsets || !PrimitiveValueToId<CanGC>(cx, v, &id)) { 1820 return false; 1821 } 1822 1823 RootedValue value(cx, ObjectValue(*offsets)); 1824 if (!DefineDataProperty(cx, result, id, value)) { 1825 return false; 1826 } 1827 } 1828 1829 // Append the current offset to the offsets array. 1830 if (!NewbornArrayPush(cx, offsets, NumberValue(offset))) { 1831 return false; 1832 } 1833 } 1834 } 1835 1836 args.rval().setObject(*result); 1837 return true; 1838 } 1839 1840 class DebuggerScript::GetAllColumnOffsetsMatcher { 1841 JSContext* cx_; 1842 MutableHandleObject result_; 1843 1844 bool appendColumnOffsetEntry(uint32_t lineno, 1845 JS::LimitedColumnNumberOneOrigin column, 1846 size_t offset) { 1847 Rooted<PlainObject*> entry(cx_, NewPlainObject(cx_)); 1848 if (!entry) { 1849 return false; 1850 } 1851 1852 RootedValue value(cx_, NumberValue(lineno)); 1853 if (!DefineDataProperty(cx_, entry, cx_->names().lineNumber, value)) { 1854 return false; 1855 } 1856 1857 value = NumberValue(column.oneOriginValue()); 1858 if (!DefineDataProperty(cx_, entry, cx_->names().columnNumber, value)) { 1859 return false; 1860 } 1861 1862 value = NumberValue(offset); 1863 if (!DefineDataProperty(cx_, entry, cx_->names().offset, value)) { 1864 return false; 1865 } 1866 1867 return NewbornArrayPush(cx_, result_, ObjectValue(*entry)); 1868 } 1869 1870 public: 1871 explicit GetAllColumnOffsetsMatcher(JSContext* cx, MutableHandleObject result) 1872 : cx_(cx), result_(result) {} 1873 using ReturnType = bool; 1874 ReturnType match(Handle<BaseScript*> base) { 1875 RootedScript script(cx_, DelazifyScript(cx_, base)); 1876 if (!script) { 1877 return false; 1878 } 1879 1880 // First pass: determine which offsets in this script are jump targets 1881 // and which positions jump to them. 1882 FlowGraphSummary flowData(cx_); 1883 if (!flowData.populate(cx_, script)) { 1884 return false; 1885 } 1886 1887 // Second pass: build the result array. 1888 result_.set(NewDenseEmptyArray(cx_)); 1889 if (!result_) { 1890 return false; 1891 } 1892 1893 for (BytecodeRangeWithPosition r(cx_, script, SkipPrologueOps::Yes); 1894 !r.empty(); r.popFront()) { 1895 uint32_t lineno = r.frontLineNumber(); 1896 JS::LimitedColumnNumberOneOrigin column = r.frontColumnNumber(); 1897 size_t offset = r.frontOffset(); 1898 1899 // Make a note, if the current instruction is an entry point for 1900 // the current position. 1901 if (r.frontIsEntryPoint() && !flowData[offset].hasNoEdges() && 1902 (flowData[offset].lineno() != lineno || 1903 flowData[offset].columnOrSentinel() != column.oneOriginValue())) { 1904 if (!appendColumnOffsetEntry(lineno, column, offset)) { 1905 return false; 1906 } 1907 } 1908 } 1909 return true; 1910 } 1911 ReturnType match(Handle<WasmInstanceObject*> instanceObj) { 1912 wasm::Instance& instance = instanceObj->instance(); 1913 1914 Vector<wasm::ExprLoc> offsets(cx_); 1915 if (instance.debugEnabled() && 1916 !instance.debug().getAllColumnOffsets(&offsets)) { 1917 return false; 1918 } 1919 1920 result_.set(NewDenseEmptyArray(cx_)); 1921 if (!result_) { 1922 return false; 1923 } 1924 1925 for (uint32_t i = 0; i < offsets.length(); i++) { 1926 uint32_t lineno = offsets[i].lineno; 1927 JS::LimitedColumnNumberOneOrigin column(offsets[i].column); 1928 size_t offset = offsets[i].offset; 1929 if (!appendColumnOffsetEntry(lineno, column, offset)) { 1930 return false; 1931 } 1932 } 1933 return true; 1934 } 1935 }; 1936 1937 bool DebuggerScript::CallData::getAllColumnOffsets() { 1938 RootedObject result(cx); 1939 GetAllColumnOffsetsMatcher matcher(cx, &result); 1940 if (!referent.match(matcher)) { 1941 return false; 1942 } 1943 1944 args.rval().setObject(*result); 1945 return true; 1946 } 1947 1948 class DebuggerScript::GetLineOffsetsMatcher { 1949 JSContext* cx_; 1950 uint32_t lineno_; 1951 MutableHandleObject result_; 1952 1953 public: 1954 explicit GetLineOffsetsMatcher(JSContext* cx, uint32_t lineno, 1955 MutableHandleObject result) 1956 : cx_(cx), lineno_(lineno), result_(result) {} 1957 using ReturnType = bool; 1958 ReturnType match(Handle<BaseScript*> base) { 1959 RootedScript script(cx_, DelazifyScript(cx_, base)); 1960 if (!script) { 1961 return false; 1962 } 1963 1964 // First pass: determine which offsets in this script are jump targets and 1965 // which line numbers jump to them. 1966 FlowGraphSummary flowData(cx_); 1967 if (!flowData.populate(cx_, script)) { 1968 return false; 1969 } 1970 1971 result_.set(NewDenseEmptyArray(cx_)); 1972 if (!result_) { 1973 return false; 1974 } 1975 1976 // Second pass: build the result array. 1977 for (BytecodeRangeWithPosition r(cx_, script, SkipPrologueOps::Yes); 1978 !r.empty(); r.popFront()) { 1979 if (!r.frontIsEntryPoint()) { 1980 continue; 1981 } 1982 1983 size_t offset = r.frontOffset(); 1984 1985 // If the op at offset is an entry point, append offset to result. 1986 if (r.frontLineNumber() == lineno_ && !flowData[offset].hasNoEdges() && 1987 flowData[offset].lineno() != lineno_) { 1988 if (!NewbornArrayPush(cx_, result_, NumberValue(offset))) { 1989 return false; 1990 } 1991 } 1992 } 1993 1994 return true; 1995 } 1996 ReturnType match(Handle<WasmInstanceObject*> instanceObj) { 1997 wasm::Instance& instance = instanceObj->instance(); 1998 1999 Vector<uint32_t> offsets(cx_); 2000 if (instance.debugEnabled() && 2001 !instance.debug().getLineOffsets(lineno_, &offsets)) { 2002 return false; 2003 } 2004 2005 result_.set(NewDenseEmptyArray(cx_)); 2006 if (!result_) { 2007 return false; 2008 } 2009 2010 for (uint32_t i = 0; i < offsets.length(); i++) { 2011 if (!NewbornArrayPush(cx_, result_, NumberValue(offsets[i]))) { 2012 return false; 2013 } 2014 } 2015 return true; 2016 } 2017 }; 2018 2019 bool DebuggerScript::CallData::getLineOffsets() { 2020 if (!args.requireAtLeast(cx, "Debugger.Script.getLineOffsets", 1)) { 2021 return false; 2022 } 2023 2024 // Parse lineno argument. 2025 RootedValue linenoValue(cx, args[0]); 2026 uint32_t lineno; 2027 if (!ToNumber(cx, &linenoValue)) { 2028 return false; 2029 } 2030 { 2031 double d = linenoValue.toNumber(); 2032 lineno = uint32_t(d); 2033 if (lineno != d) { 2034 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 2035 JSMSG_DEBUG_BAD_LINE); 2036 return false; 2037 } 2038 } 2039 2040 RootedObject result(cx); 2041 GetLineOffsetsMatcher matcher(cx, lineno, &result); 2042 if (!referent.match(matcher)) { 2043 return false; 2044 } 2045 2046 args.rval().setObject(*result); 2047 return true; 2048 } 2049 2050 struct DebuggerScript::SetBreakpointMatcher { 2051 JSContext* cx_; 2052 Debugger* dbg_; 2053 size_t offset_; 2054 RootedObject handler_; 2055 RootedObject debuggerObject_; 2056 2057 bool wrapCrossCompartmentEdges() { 2058 if (!cx_->compartment()->wrap(cx_, &handler_) || 2059 !cx_->compartment()->wrap(cx_, &debuggerObject_)) { 2060 return false; 2061 } 2062 2063 // If the Debugger's compartment has killed incoming wrappers, we may not 2064 // have gotten usable results from the 'wrap' calls. Treat it as a 2065 // failure. 2066 if (IsDeadProxyObject(handler_) || IsDeadProxyObject(debuggerObject_)) { 2067 ReportAccessDenied(cx_); 2068 return false; 2069 } 2070 2071 return true; 2072 } 2073 2074 public: 2075 explicit SetBreakpointMatcher(JSContext* cx, Debugger* dbg, size_t offset, 2076 HandleObject handler) 2077 : cx_(cx), 2078 dbg_(dbg), 2079 offset_(offset), 2080 handler_(cx, handler), 2081 debuggerObject_(cx_, dbg_->toJSObject()) {} 2082 2083 using ReturnType = bool; 2084 2085 ReturnType match(Handle<BaseScript*> base) { 2086 RootedScript script(cx_, DelazifyScript(cx_, base)); 2087 if (!script) { 2088 return false; 2089 } 2090 2091 if (!dbg_->observesScript(script)) { 2092 JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr, 2093 JSMSG_DEBUG_NOT_DEBUGGING); 2094 return false; 2095 } 2096 2097 if (!EnsureScriptOffsetIsValid(cx_, script, offset_)) { 2098 return false; 2099 } 2100 2101 if (!EnsureBreakpointIsAllowed(cx_, script, offset_)) { 2102 return false; 2103 } 2104 2105 // Ensure observability *before* setting the breakpoint. If the script is 2106 // not already a debuggee, trying to ensure observability after setting 2107 // the breakpoint (and thus marking the script as a debuggee) will skip 2108 // actually ensuring observability. 2109 if (!dbg_->ensureExecutionObservabilityOfScript(cx_, script)) { 2110 return false; 2111 } 2112 2113 // A Breakpoint belongs logically to its script's compartment, so its 2114 // references to its Debugger and handler must be properly wrapped. 2115 AutoRealm ar(cx_, script); 2116 if (!wrapCrossCompartmentEdges()) { 2117 return false; 2118 } 2119 2120 jsbytecode* pc = script->offsetToPC(offset_); 2121 JSBreakpointSite* site = 2122 DebugScript::getOrCreateBreakpointSite(cx_, script, pc); 2123 if (!site) { 2124 return false; 2125 } 2126 2127 if (!cx_->zone()->new_<Breakpoint>(dbg_, debuggerObject_, site, handler_)) { 2128 site->destroyIfEmpty(cx_->runtime()->gcContext()); 2129 ReportOutOfMemory(cx_); 2130 return false; 2131 } 2132 AddCellMemory(script, sizeof(Breakpoint), MemoryUse::Breakpoint); 2133 2134 return true; 2135 } 2136 ReturnType match(Handle<WasmInstanceObject*> wasmInstance) { 2137 wasm::Instance& instance = wasmInstance->instance(); 2138 if (!instance.debugEnabled() || 2139 !instance.debug().hasBreakpointTrapAtOffset(offset_)) { 2140 JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr, 2141 JSMSG_DEBUG_BAD_OFFSET); 2142 return false; 2143 } 2144 2145 // A Breakpoint belongs logically to its Instance's compartment, so its 2146 // references to its Debugger and handler must be properly wrapped. 2147 AutoRealm ar(cx_, wasmInstance); 2148 if (!wrapCrossCompartmentEdges()) { 2149 return false; 2150 } 2151 2152 WasmBreakpointSite* site = instance.getOrCreateBreakpointSite(cx_, offset_); 2153 if (!site) { 2154 return false; 2155 } 2156 2157 if (!cx_->zone()->new_<Breakpoint>(dbg_, debuggerObject_, site, handler_)) { 2158 site->destroyIfEmpty(cx_->runtime()->gcContext()); 2159 ReportOutOfMemory(cx_); 2160 return false; 2161 } 2162 AddCellMemory(wasmInstance, sizeof(Breakpoint), MemoryUse::Breakpoint); 2163 2164 return true; 2165 } 2166 }; 2167 2168 bool DebuggerScript::CallData::setBreakpoint() { 2169 if (!args.requireAtLeast(cx, "Debugger.Script.setBreakpoint", 2)) { 2170 return false; 2171 } 2172 Debugger* dbg = obj->owner(); 2173 2174 size_t offset; 2175 if (!ScriptOffset(cx, args[0], &offset)) { 2176 return false; 2177 } 2178 2179 RootedObject handler(cx, RequireObject(cx, args[1])); 2180 if (!handler) { 2181 return false; 2182 } 2183 2184 SetBreakpointMatcher matcher(cx, dbg, offset, handler); 2185 if (!referent.match(matcher)) { 2186 return false; 2187 } 2188 args.rval().setUndefined(); 2189 return true; 2190 } 2191 2192 bool DebuggerScript::CallData::getBreakpoints() { 2193 if (!ensureScript()) { 2194 return false; 2195 } 2196 Debugger* dbg = obj->owner(); 2197 2198 jsbytecode* pc; 2199 if (args.length() > 0) { 2200 size_t offset; 2201 if (!ScriptOffset(cx, args[0], &offset) || 2202 !EnsureScriptOffsetIsValid(cx, script, offset)) { 2203 return false; 2204 } 2205 pc = script->offsetToPC(offset); 2206 } else { 2207 pc = nullptr; 2208 } 2209 2210 RootedObject arr(cx, NewDenseEmptyArray(cx)); 2211 if (!arr) { 2212 return false; 2213 } 2214 2215 for (unsigned i = 0; i < script->length(); i++) { 2216 JSBreakpointSite* site = 2217 DebugScript::getBreakpointSite(script, script->offsetToPC(i)); 2218 if (!site) { 2219 continue; 2220 } 2221 if (!pc || site->pc == pc) { 2222 for (Breakpoint* bp = site->firstBreakpoint(); bp; 2223 bp = bp->nextInSite()) { 2224 if (bp->debugger == dbg) { 2225 RootedObject handler(cx, bp->getHandler()); 2226 if (!cx->compartment()->wrap(cx, &handler) || 2227 !NewbornArrayPush(cx, arr, ObjectValue(*handler))) { 2228 return false; 2229 } 2230 } 2231 } 2232 } 2233 } 2234 args.rval().setObject(*arr); 2235 return true; 2236 } 2237 2238 class DebuggerScript::ClearBreakpointMatcher { 2239 JSContext* cx_; 2240 Debugger* dbg_; 2241 RootedObject handler_; 2242 2243 public: 2244 ClearBreakpointMatcher(JSContext* cx, Debugger* dbg, JSObject* handler) 2245 : cx_(cx), dbg_(dbg), handler_(cx, handler) {} 2246 using ReturnType = bool; 2247 2248 ReturnType match(Handle<BaseScript*> base) { 2249 RootedScript script(cx_, DelazifyScript(cx_, base)); 2250 if (!script) { 2251 return false; 2252 } 2253 2254 // A Breakpoint belongs logically to its script's compartment, so it holds 2255 // its handler via a cross-compartment wrapper. But the handler passed to 2256 // `clearBreakpoint` is same-compartment with the Debugger. Wrap it here, 2257 // so that `DebugScript::clearBreakpointsIn` gets the right value to 2258 // search for. 2259 AutoRealm ar(cx_, script); 2260 if (!cx_->compartment()->wrap(cx_, &handler_)) { 2261 return false; 2262 } 2263 2264 DebugScript::clearBreakpointsIn(cx_->runtime()->gcContext(), script, dbg_, 2265 handler_); 2266 return true; 2267 } 2268 ReturnType match(Handle<WasmInstanceObject*> instanceObj) { 2269 wasm::Instance& instance = instanceObj->instance(); 2270 if (!instance.debugEnabled()) { 2271 return true; 2272 } 2273 2274 // A Breakpoint belongs logically to its instance's compartment, so it 2275 // holds its handler via a cross-compartment wrapper. But the handler 2276 // passed to `clearBreakpoint` is same-compartment with the Debugger. Wrap 2277 // it here, so that `DebugState::clearBreakpointsIn` gets the right value 2278 // to search for. 2279 AutoRealm ar(cx_, instanceObj); 2280 if (!cx_->compartment()->wrap(cx_, &handler_)) { 2281 return false; 2282 } 2283 2284 instance.debug().clearBreakpointsIn(cx_->runtime()->gcContext(), 2285 instanceObj, dbg_, handler_); 2286 return true; 2287 } 2288 }; 2289 2290 bool DebuggerScript::CallData::clearBreakpoint() { 2291 if (!args.requireAtLeast(cx, "Debugger.Script.clearBreakpoint", 1)) { 2292 return false; 2293 } 2294 Debugger* dbg = obj->owner(); 2295 2296 JSObject* handler = RequireObject(cx, args[0]); 2297 if (!handler) { 2298 return false; 2299 } 2300 2301 ClearBreakpointMatcher matcher(cx, dbg, handler); 2302 if (!referent.match(matcher)) { 2303 return false; 2304 } 2305 2306 args.rval().setUndefined(); 2307 return true; 2308 } 2309 2310 bool DebuggerScript::CallData::clearAllBreakpoints() { 2311 Debugger* dbg = obj->owner(); 2312 ClearBreakpointMatcher matcher(cx, dbg, nullptr); 2313 if (!referent.match(matcher)) { 2314 return false; 2315 } 2316 args.rval().setUndefined(); 2317 return true; 2318 } 2319 2320 class DebuggerScript::IsInCatchScopeMatcher { 2321 JSContext* cx_; 2322 size_t offset_; 2323 bool isInCatch_; 2324 2325 public: 2326 explicit IsInCatchScopeMatcher(JSContext* cx, size_t offset) 2327 : cx_(cx), offset_(offset), isInCatch_(false) {} 2328 using ReturnType = bool; 2329 2330 inline bool isInCatch() const { return isInCatch_; } 2331 2332 ReturnType match(Handle<BaseScript*> base) { 2333 RootedScript script(cx_, DelazifyScript(cx_, base)); 2334 if (!script) { 2335 return false; 2336 } 2337 2338 if (!EnsureScriptOffsetIsValid(cx_, script, offset_)) { 2339 return false; 2340 } 2341 2342 MOZ_ASSERT(!isInCatch_); 2343 for (const TryNote& tn : script->trynotes()) { 2344 bool inRange = tn.start <= offset_ && offset_ < tn.start + tn.length; 2345 if (inRange && tn.kind() == TryNoteKind::Catch) { 2346 isInCatch_ = true; 2347 } else if (isInCatch_) { 2348 // For-of loops generate a synthetic catch block to handle 2349 // closing the iterator when throwing an exception. The 2350 // debugger should ignore these synthetic catch blocks, so 2351 // we skip any Catch trynote that is immediately followed 2352 // by a ForOf trynote. 2353 if (inRange && tn.kind() == TryNoteKind::ForOf) { 2354 isInCatch_ = false; 2355 continue; 2356 } 2357 return true; 2358 } 2359 } 2360 2361 return true; 2362 } 2363 ReturnType match(Handle<WasmInstanceObject*> instance) { 2364 isInCatch_ = false; 2365 return true; 2366 } 2367 }; 2368 2369 bool DebuggerScript::CallData::isInCatchScope() { 2370 if (!args.requireAtLeast(cx, "Debugger.Script.isInCatchScope", 1)) { 2371 return false; 2372 } 2373 2374 size_t offset; 2375 if (!ScriptOffset(cx, args[0], &offset)) { 2376 return false; 2377 } 2378 2379 IsInCatchScopeMatcher matcher(cx, offset); 2380 if (!referent.match(matcher)) { 2381 return false; 2382 } 2383 args.rval().setBoolean(matcher.isInCatch()); 2384 return true; 2385 } 2386 2387 bool DebuggerScript::CallData::getOffsetsCoverage() { 2388 if (!ensureScript()) { 2389 return false; 2390 } 2391 2392 Debugger* dbg = obj->owner(); 2393 if (dbg->observesCoverage() != Debugger::Observing) { 2394 args.rval().setNull(); 2395 return true; 2396 } 2397 2398 // If the script has no coverage information, then skip this and return null 2399 // instead. 2400 if (!script->hasScriptCounts()) { 2401 args.rval().setNull(); 2402 return true; 2403 } 2404 2405 ScriptCounts* sc = &script->getScriptCounts(); 2406 2407 // If the main ever got visited, then assume that any code before main got 2408 // visited once. 2409 uint64_t hits = 0; 2410 const PCCounts* counts = 2411 sc->maybeGetPCCounts(script->pcToOffset(script->main())); 2412 if (counts->numExec()) { 2413 hits = 1; 2414 } 2415 2416 // Build an array of objects which are composed of 4 properties: 2417 // - offset PC offset of the current opcode. 2418 // - lineNumber Line of the current opcode. 2419 // - columnNumber Column of the current opcode. 2420 // - count Number of times the instruction got executed. 2421 RootedObject result(cx, NewDenseEmptyArray(cx)); 2422 if (!result) { 2423 return false; 2424 } 2425 2426 RootedId offsetId(cx, NameToId(cx->names().offset)); 2427 RootedId lineNumberId(cx, NameToId(cx->names().lineNumber)); 2428 RootedId columnNumberId(cx, NameToId(cx->names().columnNumber)); 2429 RootedId countId(cx, NameToId(cx->names().count)); 2430 2431 RootedObject item(cx); 2432 RootedValue offsetValue(cx); 2433 RootedValue lineNumberValue(cx); 2434 RootedValue columnNumberValue(cx); 2435 RootedValue countValue(cx); 2436 2437 // Iterate linearly over the bytecode. 2438 for (BytecodeRangeWithPosition r(cx, script, SkipPrologueOps::Yes); 2439 !r.empty(); r.popFront()) { 2440 size_t offset = r.frontOffset(); 2441 2442 // The beginning of each non-branching sequences of instruction set the 2443 // number of execution of the current instruction and any following 2444 // instruction. 2445 counts = sc->maybeGetPCCounts(offset); 2446 if (counts) { 2447 hits = counts->numExec(); 2448 } 2449 2450 offsetValue.setNumber(double(offset)); 2451 lineNumberValue.setNumber(double(r.frontLineNumber())); 2452 columnNumberValue.setNumber(double(r.frontColumnNumber().oneOriginValue())); 2453 countValue.setNumber(double(hits)); 2454 2455 // Create a new object with the offset, line number, column number, the 2456 // number of hit counts, and append it to the array. 2457 item = NewPlainObjectWithProto(cx, nullptr); 2458 if (!item || !DefineDataProperty(cx, item, offsetId, offsetValue) || 2459 !DefineDataProperty(cx, item, lineNumberId, lineNumberValue) || 2460 !DefineDataProperty(cx, item, columnNumberId, columnNumberValue) || 2461 !DefineDataProperty(cx, item, countId, countValue) || 2462 !NewbornArrayPush(cx, result, ObjectValue(*item))) { 2463 return false; 2464 } 2465 2466 // If the current instruction has thrown, then decrement the hit counts 2467 // with the number of throws. 2468 counts = sc->maybeGetThrowCounts(offset); 2469 if (counts) { 2470 hits -= counts->numExec(); 2471 } 2472 } 2473 2474 args.rval().setObject(*result); 2475 return true; 2476 } 2477 2478 /* static */ 2479 bool DebuggerScript::construct(JSContext* cx, unsigned argc, Value* vp) { 2480 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR, 2481 "Debugger.Script"); 2482 return false; 2483 } 2484 2485 const JSPropertySpec DebuggerScript::properties_[] = { 2486 JS_DEBUG_PSG("isGeneratorFunction", getIsGeneratorFunction), 2487 JS_DEBUG_PSG("isAsyncFunction", getIsAsyncFunction), 2488 JS_DEBUG_PSG("isFunction", getIsFunction), 2489 JS_DEBUG_PSG("isModule", getIsModule), 2490 JS_DEBUG_PSG("displayName", getDisplayName), 2491 JS_DEBUG_PSG("parameterNames", getParameterNames), 2492 JS_DEBUG_PSG("url", getUrl), 2493 JS_DEBUG_PSG("startLine", getStartLine), 2494 JS_DEBUG_PSG("startColumn", getStartColumn), 2495 JS_DEBUG_PSG("lineCount", getLineCount), 2496 JS_DEBUG_PSG("source", getSource), 2497 JS_DEBUG_PSG("sourceStart", getSourceStart), 2498 JS_DEBUG_PSG("sourceLength", getSourceLength), 2499 JS_DEBUG_PSG("mainOffset", getMainOffset), 2500 JS_DEBUG_PSG("global", getGlobal), 2501 JS_DEBUG_PSG("format", getFormat), 2502 JS_PS_END, 2503 }; 2504 2505 const JSFunctionSpec DebuggerScript::methods_[] = { 2506 JS_DEBUG_FN("getChildScripts", getChildScripts, 0), 2507 JS_DEBUG_FN("getPossibleBreakpoints", getPossibleBreakpoints, 0), 2508 JS_DEBUG_FN("getPossibleBreakpointOffsets", getPossibleBreakpointOffsets, 2509 0), 2510 JS_DEBUG_FN("setBreakpoint", setBreakpoint, 2), 2511 JS_DEBUG_FN("getBreakpoints", getBreakpoints, 1), 2512 JS_DEBUG_FN("clearBreakpoint", clearBreakpoint, 1), 2513 JS_DEBUG_FN("clearAllBreakpoints", clearAllBreakpoints, 0), 2514 JS_DEBUG_FN("isInCatchScope", isInCatchScope, 1), 2515 JS_DEBUG_FN("getOffsetMetadata", getOffsetMetadata, 1), 2516 JS_DEBUG_FN("getOffsetsCoverage", getOffsetsCoverage, 0), 2517 JS_DEBUG_FN("getEffectfulOffsets", getEffectfulOffsets, 1), 2518 2519 // The following APIs are deprecated due to their reliance on the 2520 // under-defined 'entrypoint' concept. Make use of getPossibleBreakpoints, 2521 // getPossibleBreakpointOffsets, or getOffsetMetadata instead. 2522 JS_DEBUG_FN("getAllOffsets", getAllOffsets, 0), 2523 JS_DEBUG_FN("getAllColumnOffsets", getAllColumnOffsets, 0), 2524 JS_DEBUG_FN("getLineOffsets", getLineOffsets, 1), 2525 JS_DEBUG_FN("getOffsetLocation", getOffsetLocation, 0), 2526 JS_FS_END, 2527 };