BytecodeUtil.cpp (84999B)
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 /* 8 * JS bytecode descriptors, disassemblers, and (expression) decompilers. 9 */ 10 11 #include "vm/BytecodeUtil-inl.h" 12 13 #define __STDC_FORMAT_MACROS 14 15 #include "mozilla/Maybe.h" 16 #include "mozilla/ReverseIterator.h" 17 #include "mozilla/Sprintf.h" 18 19 #include <inttypes.h> 20 #include <stdio.h> 21 #include <string.h> 22 23 #include "jsapi.h" 24 #include "jstypes.h" 25 26 #include "gc/PublicIterators.h" 27 #include "jit/IonScript.h" // IonBlockCounts 28 #include "js/CharacterEncoding.h" 29 #include "js/ColumnNumber.h" // JS::LimitedColumnNumberOneOrigin 30 #include "js/experimental/CodeCoverage.h" 31 #include "js/experimental/PCCountProfiling.h" // JS::{Start,Stop}PCCountProfiling, JS::PurgePCCounts, JS::GetPCCountScript{Count,Summary,Contents} 32 #include "js/friend/DumpFunctions.h" // js::DumpPC, js::DumpScript 33 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* 34 #include "js/Printer.h" 35 #include "js/Printf.h" 36 #include "js/Symbol.h" 37 #include "util/DifferentialTesting.h" 38 #include "util/Identifier.h" // IsIdentifier 39 #include "util/Memory.h" 40 #include "util/Text.h" 41 #include "vm/BuiltinObjectKind.h" 42 #include "vm/BytecodeIterator.h" // for AllBytecodesIterable 43 #include "vm/BytecodeLocation.h" 44 #include "vm/CodeCoverage.h" 45 #include "vm/ConstantCompareOperand.h" 46 #include "vm/EnvironmentObject.h" 47 #include "vm/FrameIter.h" // js::{,Script}FrameIter 48 #include "vm/JSAtomUtils.h" // AtomToPrintableString, Atomize 49 #include "vm/JSContext.h" 50 #include "vm/JSFunction.h" 51 #include "vm/JSObject.h" 52 #include "vm/JSONPrinter.h" 53 #include "vm/JSScript.h" 54 #include "vm/Opcodes.h" 55 #include "vm/Realm.h" 56 #include "vm/Shape.h" 57 #include "vm/ToSource.h" // js::ValueToSource 58 #include "vm/TypeofEqOperand.h" // TypeofEqOperand 59 60 #include "gc/GC-inl.h" 61 #include "vm/BytecodeIterator-inl.h" 62 #include "vm/JSContext-inl.h" 63 #include "vm/JSScript-inl.h" 64 #include "vm/Realm-inl.h" 65 66 using namespace js; 67 68 /* 69 * Index limit must stay within 32 bits. 70 */ 71 static_assert(sizeof(uint32_t) * CHAR_BIT >= INDEX_LIMIT_LOG2 + 1); 72 73 const JSCodeSpec js::CodeSpecTable[] = { 74 #define MAKE_CODESPEC(op, op_snake, token, length, nuses, ndefs, format) \ 75 {length, nuses, ndefs, format}, 76 FOR_EACH_OPCODE(MAKE_CODESPEC) 77 #undef MAKE_CODESPEC 78 }; 79 80 /* 81 * Each element of the array is either a source literal associated with JS 82 * bytecode or null. 83 */ 84 static const char* const CodeToken[] = { 85 #define TOKEN(op, op_snake, token, ...) token, 86 FOR_EACH_OPCODE(TOKEN) 87 #undef TOKEN 88 }; 89 90 /* 91 * Array of JS bytecode names used by PC count JSON, DEBUG-only Disassemble 92 * and JIT debug spew. 93 */ 94 const char* const js::CodeNameTable[] = { 95 #define OPNAME(op, ...) #op, 96 FOR_EACH_OPCODE(OPNAME) 97 #undef OPNAME 98 }; 99 100 /************************************************************************/ 101 102 static bool DecompileArgumentFromStack(JSContext* cx, int formalIndex, 103 UniqueChars* res); 104 105 /* static */ const char PCCounts::numExecName[] = "interp"; 106 107 [[nodiscard]] static bool DumpIonScriptCounts(StringPrinter* sp, 108 HandleScript script, 109 jit::IonScriptCounts* ionCounts) { 110 sp->printf("IonScript [%zu blocks]:\n", ionCounts->numBlocks()); 111 112 for (size_t i = 0; i < ionCounts->numBlocks(); i++) { 113 const jit::IonBlockCounts& block = ionCounts->block(i); 114 unsigned lineNumber = 0; 115 JS::LimitedColumnNumberOneOrigin columnNumber; 116 lineNumber = PCToLineNumber(script, script->offsetToPC(block.offset()), 117 &columnNumber); 118 sp->printf("BB #%" PRIu32 " [%05u,%u,%u]", block.id(), block.offset(), 119 lineNumber, columnNumber.oneOriginValue()); 120 if (block.description()) { 121 sp->printf(" [inlined %s]", block.description()); 122 } 123 for (size_t j = 0; j < block.numSuccessors(); j++) { 124 sp->printf(" -> #%" PRIu32, block.successor(j)); 125 } 126 sp->printf(" :: %" PRIu64 " hits\n", block.hitCount()); 127 sp->printf("%s\n", block.code()); 128 } 129 130 return true; 131 } 132 133 [[nodiscard]] static bool DumpPCCounts(JSContext* cx, HandleScript script, 134 StringPrinter* sp) { 135 // In some edge cases Disassemble1 can end up invoking JS code, so ensure 136 // script counts haven't been discarded. 137 if (!script->hasScriptCounts()) { 138 return true; 139 } 140 141 #ifdef DEBUG 142 jsbytecode* pc = script->code(); 143 while (pc < script->codeEnd()) { 144 jsbytecode* next = GetNextPc(pc); 145 146 if (!Disassemble1(cx, script, pc, script->pcToOffset(pc), true, sp)) { 147 return false; 148 } 149 150 sp->put(" {"); 151 if (script->hasScriptCounts()) { 152 PCCounts* counts = script->maybeGetPCCounts(pc); 153 if (double val = counts ? counts->numExec() : 0.0) { 154 sp->printf("\"%s\": %.0f", PCCounts::numExecName, val); 155 } 156 } 157 sp->put("}\n"); 158 159 pc = next; 160 } 161 #endif 162 163 if (!script->hasScriptCounts()) { 164 return true; 165 } 166 167 jit::IonScriptCounts* ionCounts = script->getIonCounts(); 168 while (ionCounts) { 169 if (!DumpIonScriptCounts(sp, script, ionCounts)) { 170 return false; 171 } 172 173 ionCounts = ionCounts->previous(); 174 } 175 176 return true; 177 } 178 179 bool js::DumpRealmPCCounts(JSContext* cx) { 180 Rooted<GCVector<JSScript*>> scripts(cx, GCVector<JSScript*>(cx)); 181 for (auto base = cx->zone()->cellIter<BaseScript>(); !base.done(); 182 base.next()) { 183 if (base->realm() != cx->realm()) { 184 continue; 185 } 186 MOZ_ASSERT_IF(base->hasScriptCounts(), base->hasBytecode()); 187 if (base->hasScriptCounts()) { 188 if (!scripts.append(base->asJSScript())) { 189 return false; 190 } 191 } 192 } 193 194 for (uint32_t i = 0; i < scripts.length(); i++) { 195 HandleScript script = scripts[i]; 196 Sprinter sprinter(cx); 197 if (!sprinter.init()) { 198 return false; 199 } 200 201 const char* filename = script->filename(); 202 if (!filename) { 203 filename = "(unknown)"; 204 } 205 fprintf(stdout, "--- SCRIPT %s:%u ---\n", filename, script->lineno()); 206 if (!DumpPCCounts(cx, script, &sprinter)) { 207 return false; 208 } 209 JS::UniqueChars out = sprinter.release(); 210 if (!out) { 211 return false; 212 } 213 fputs(out.get(), stdout); 214 fprintf(stdout, "--- END SCRIPT %s:%u ---\n", filename, script->lineno()); 215 } 216 217 return true; 218 } 219 220 ///////////////////////////////////////////////////////////////////// 221 // Bytecode Parser 222 ///////////////////////////////////////////////////////////////////// 223 224 // Stores the information about the stack slot, where the value comes from. 225 // Elements of BytecodeParser::Bytecode.{offsetStack,offsetStackAfter} arrays. 226 class OffsetAndDefIndex { 227 // The offset of the PC that pushed the value for this slot. 228 uint32_t offset_; 229 230 // The index in `ndefs` for the PC (0-origin) 231 uint8_t defIndex_; 232 233 enum : uint8_t { 234 Normal = 0, 235 236 // Ignored this value in the expression decompilation. 237 // Used by JSOp::NopDestructuring. See BytecodeParser::simulateOp. 238 Ignored, 239 240 // The value in this slot comes from 2 or more paths. 241 // offset_ and defIndex_ holds the information for the path that 242 // reaches here first. 243 Merged, 244 } type_; 245 246 public: 247 uint32_t offset() const { 248 MOZ_ASSERT(!isSpecial()); 249 return offset_; 250 }; 251 uint32_t specialOffset() const { 252 MOZ_ASSERT(isSpecial()); 253 return offset_; 254 }; 255 256 uint8_t defIndex() const { 257 MOZ_ASSERT(!isSpecial()); 258 return defIndex_; 259 } 260 uint8_t specialDefIndex() const { 261 MOZ_ASSERT(isSpecial()); 262 return defIndex_; 263 } 264 265 bool isSpecial() const { return type_ != Normal; } 266 bool isMerged() const { return type_ == Merged; } 267 bool isIgnored() const { return type_ == Ignored; } 268 269 void set(uint32_t aOffset, uint8_t aDefIndex) { 270 offset_ = aOffset; 271 defIndex_ = aDefIndex; 272 type_ = Normal; 273 } 274 275 // Keep offset_ and defIndex_ values for stack dump. 276 void setMerged() { type_ = Merged; } 277 void setIgnored() { type_ = Ignored; } 278 279 bool operator==(const OffsetAndDefIndex& rhs) const { 280 return offset_ == rhs.offset_ && defIndex_ == rhs.defIndex_; 281 } 282 283 bool operator!=(const OffsetAndDefIndex& rhs) const { 284 return !(*this == rhs); 285 } 286 }; 287 288 namespace { 289 290 class BytecodeParser { 291 public: 292 enum class JumpKind { 293 Simple, 294 SwitchCase, 295 SwitchDefault, 296 TryCatch, 297 TryFinally 298 }; 299 300 private: 301 class Bytecode { 302 public: 303 explicit Bytecode(const LifoAllocPolicy<Fallible>& alloc) 304 : parsed(false), 305 stackDepth(0), 306 offsetStack(nullptr) 307 #if defined(DEBUG) || defined(JS_JITSPEW) 308 , 309 stackDepthAfter(0), 310 offsetStackAfter(nullptr), 311 jumpOrigins(alloc) 312 #endif /* defined(DEBUG) || defined(JS_JITSPEW) */ 313 { 314 } 315 316 // Whether this instruction has been analyzed to get its output defines 317 // and stack. 318 bool parsed; 319 320 // Stack depth before this opcode. 321 uint32_t stackDepth; 322 323 // Pointer to array of |stackDepth| offsets. An element at position N 324 // in the array is the offset of the opcode that defined the 325 // corresponding stack slot. The top of the stack is at position 326 // |stackDepth - 1|. 327 OffsetAndDefIndex* offsetStack; 328 329 #if defined(DEBUG) || defined(JS_JITSPEW) 330 // stack depth after this opcode. 331 uint32_t stackDepthAfter; 332 333 // Pointer to array of |stackDepthAfter| offsets. 334 OffsetAndDefIndex* offsetStackAfter; 335 336 struct JumpInfo { 337 uint32_t from; 338 JumpKind kind; 339 340 JumpInfo(uint32_t from_, JumpKind kind_) : from(from_), kind(kind_) {} 341 }; 342 343 // A list of offsets of the bytecode that jumps to this bytecode, 344 // exclusing previous bytecode. 345 Vector<JumpInfo, 0, LifoAllocPolicy<Fallible>> jumpOrigins; 346 #endif /* defined(DEBUG) || defined(JS_JITSPEW) */ 347 348 bool captureOffsetStack(LifoAlloc& alloc, const OffsetAndDefIndex* stack, 349 uint32_t depth) { 350 stackDepth = depth; 351 if (stackDepth) { 352 offsetStack = alloc.newArray<OffsetAndDefIndex>(stackDepth); 353 if (!offsetStack) { 354 return false; 355 } 356 for (uint32_t n = 0; n < stackDepth; n++) { 357 offsetStack[n] = stack[n]; 358 } 359 } 360 return true; 361 } 362 363 #if defined(DEBUG) || defined(JS_JITSPEW) 364 bool captureOffsetStackAfter(LifoAlloc& alloc, 365 const OffsetAndDefIndex* stack, 366 uint32_t depth) { 367 stackDepthAfter = depth; 368 if (stackDepthAfter) { 369 offsetStackAfter = alloc.newArray<OffsetAndDefIndex>(stackDepthAfter); 370 if (!offsetStackAfter) { 371 return false; 372 } 373 for (uint32_t n = 0; n < stackDepthAfter; n++) { 374 offsetStackAfter[n] = stack[n]; 375 } 376 } 377 return true; 378 } 379 380 bool addJump(uint32_t from, JumpKind kind) { 381 return jumpOrigins.append(JumpInfo(from, kind)); 382 } 383 #endif /* defined(DEBUG) || defined(JS_JITSPEW) */ 384 385 // When control-flow merges, intersect the stacks, marking slots that 386 // are defined by different offsets and/or defIndices merged. 387 // This is sufficient for forward control-flow. It doesn't grok loops 388 // -- for that you would have to iterate to a fixed point -- but there 389 // shouldn't be operands on the stack at a loop back-edge anyway. 390 void mergeOffsetStack(const OffsetAndDefIndex* stack, uint32_t depth) { 391 MOZ_ASSERT(depth == stackDepth); 392 for (uint32_t n = 0; n < stackDepth; n++) { 393 if (stack[n].isIgnored()) { 394 continue; 395 } 396 if (offsetStack[n].isIgnored()) { 397 offsetStack[n] = stack[n]; 398 } 399 if (offsetStack[n] != stack[n]) { 400 offsetStack[n].setMerged(); 401 } 402 } 403 } 404 }; 405 406 JSContext* cx_; 407 LifoAlloc& alloc_; 408 RootedScript script_; 409 410 Bytecode** codeArray_; 411 412 #if defined(DEBUG) || defined(JS_JITSPEW) 413 // Dedicated mode for stack dump. 414 // Capture stack after each opcode, and also enable special handling for 415 // some opcodes to make stack transition clearer. 416 bool isStackDump = false; 417 #endif 418 419 public: 420 BytecodeParser(JSContext* cx, LifoAlloc& alloc, JSScript* script) 421 : cx_(cx), alloc_(alloc), script_(cx, script), codeArray_(nullptr) {} 422 423 bool parse(); 424 425 #if defined(DEBUG) || defined(JS_JITSPEW) 426 bool isReachable(const jsbytecode* pc) const { return maybeCode(pc); } 427 #endif 428 429 uint32_t stackDepthAtPC(uint32_t offset) const { 430 // Sometimes the code generator in debug mode asks about the stack depth 431 // of unreachable code (bug 932180 comment 22). Assume that unreachable 432 // code has no operands on the stack. 433 return getCode(offset).stackDepth; 434 } 435 uint32_t stackDepthAtPC(const jsbytecode* pc) const { 436 return stackDepthAtPC(script_->pcToOffset(pc)); 437 } 438 439 #if defined(DEBUG) || defined(JS_JITSPEW) 440 uint32_t stackDepthAfterPC(uint32_t offset) const { 441 return getCode(offset).stackDepthAfter; 442 } 443 uint32_t stackDepthAfterPC(const jsbytecode* pc) const { 444 return stackDepthAfterPC(script_->pcToOffset(pc)); 445 } 446 #endif 447 448 const OffsetAndDefIndex& offsetForStackOperand(uint32_t offset, 449 int operand) const { 450 Bytecode& code = getCode(offset); 451 if (operand < 0) { 452 operand += code.stackDepth; 453 MOZ_ASSERT(operand >= 0); 454 } 455 MOZ_ASSERT(uint32_t(operand) < code.stackDepth); 456 return code.offsetStack[operand]; 457 } 458 jsbytecode* pcForStackOperand(jsbytecode* pc, int operand, 459 uint8_t* defIndex) const { 460 size_t offset = script_->pcToOffset(pc); 461 const OffsetAndDefIndex& offsetAndDefIndex = 462 offsetForStackOperand(offset, operand); 463 if (offsetAndDefIndex.isSpecial()) { 464 return nullptr; 465 } 466 *defIndex = offsetAndDefIndex.defIndex(); 467 return script_->offsetToPC(offsetAndDefIndex.offset()); 468 } 469 470 #if defined(DEBUG) || defined(JS_JITSPEW) 471 const OffsetAndDefIndex& offsetForStackOperandAfterPC(uint32_t offset, 472 int operand) const { 473 Bytecode& code = getCode(offset); 474 if (operand < 0) { 475 operand += code.stackDepthAfter; 476 MOZ_ASSERT(operand >= 0); 477 } 478 MOZ_ASSERT(uint32_t(operand) < code.stackDepthAfter); 479 return code.offsetStackAfter[operand]; 480 } 481 482 template <typename Callback> 483 bool forEachJumpOrigins(jsbytecode* pc, Callback callback) const { 484 Bytecode& code = getCode(script_->pcToOffset(pc)); 485 486 for (Bytecode::JumpInfo& info : code.jumpOrigins) { 487 if (!callback(script_->offsetToPC(info.from), info.kind)) { 488 return false; 489 } 490 } 491 492 return true; 493 } 494 495 void setStackDump() { isStackDump = true; } 496 #endif /* defined(DEBUG) || defined(JS_JITSPEW) */ 497 498 private: 499 LifoAlloc& alloc() { return alloc_; } 500 501 void reportOOM() { ReportOutOfMemory(cx_); } 502 503 uint32_t maximumStackDepth() const { 504 return script_->nslots() - script_->nfixed(); 505 } 506 507 Bytecode& getCode(uint32_t offset) const { 508 MOZ_ASSERT(offset < script_->length()); 509 MOZ_ASSERT(codeArray_[offset]); 510 return *codeArray_[offset]; 511 } 512 513 Bytecode* maybeCode(uint32_t offset) const { 514 MOZ_ASSERT(offset < script_->length()); 515 return codeArray_[offset]; 516 } 517 518 #if defined(DEBUG) || defined(JS_JITSPEW) 519 Bytecode* maybeCode(const jsbytecode* pc) const { 520 return maybeCode(script_->pcToOffset(pc)); 521 } 522 #endif 523 524 uint32_t simulateOp(JSOp op, uint32_t offset, OffsetAndDefIndex* offsetStack, 525 uint32_t stackDepth); 526 527 inline bool recordBytecode(uint32_t offset, 528 const OffsetAndDefIndex* offsetStack, 529 uint32_t stackDepth); 530 531 inline bool addJump(uint32_t offset, uint32_t stackDepth, 532 const OffsetAndDefIndex* offsetStack, jsbytecode* pc, 533 JumpKind kind); 534 }; 535 536 } // anonymous namespace 537 538 uint32_t BytecodeParser::simulateOp(JSOp op, uint32_t offset, 539 OffsetAndDefIndex* offsetStack, 540 uint32_t stackDepth) { 541 jsbytecode* pc = script_->offsetToPC(offset); 542 uint32_t nuses = GetUseCount(pc); 543 uint32_t ndefs = GetDefCount(pc); 544 545 MOZ_RELEASE_ASSERT(stackDepth >= nuses); 546 stackDepth -= nuses; 547 MOZ_RELEASE_ASSERT(stackDepth + ndefs <= maximumStackDepth()); 548 549 #ifdef DEBUG 550 if (isStackDump) { 551 // Opcodes that modifies the object but keeps it on the stack while 552 // initialization should be listed here instead of switch below. 553 // For error message, they shouldn't be shown as the original object 554 // after adding properties. 555 // For stack dump, keeping the input is better. 556 switch (op) { 557 case JSOp::InitHiddenProp: 558 case JSOp::InitHiddenPropGetter: 559 case JSOp::InitHiddenPropSetter: 560 case JSOp::InitLockedProp: 561 case JSOp::InitProp: 562 case JSOp::InitPropGetter: 563 case JSOp::InitPropSetter: 564 case JSOp::MutateProto: 565 case JSOp::SetFunName: 566 // Keep the second value. 567 MOZ_ASSERT(nuses == 2); 568 MOZ_ASSERT(ndefs == 1); 569 goto end; 570 571 case JSOp::InitElem: 572 case JSOp::InitElemGetter: 573 case JSOp::InitElemSetter: 574 case JSOp::InitHiddenElem: 575 case JSOp::InitHiddenElemGetter: 576 case JSOp::InitHiddenElemSetter: 577 case JSOp::InitLockedElem: 578 // Keep the third value. 579 MOZ_ASSERT(nuses == 3); 580 MOZ_ASSERT(ndefs == 1); 581 goto end; 582 583 default: 584 break; 585 } 586 } 587 #endif /* DEBUG */ 588 589 // Mark the current offset as defining its values on the offset stack, 590 // unless it just reshuffles the stack. In that case we want to preserve 591 // the opcode that generated the original value. 592 switch (op) { 593 default: 594 for (uint32_t n = 0; n != ndefs; ++n) { 595 offsetStack[stackDepth + n].set(offset, n); 596 } 597 break; 598 599 case JSOp::NopDestructuring: 600 // Poison the last offset to not obfuscate the error message. 601 offsetStack[stackDepth - 1].setIgnored(); 602 break; 603 604 case JSOp::Case: 605 // Keep the switch value. 606 MOZ_ASSERT(ndefs == 1); 607 break; 608 609 case JSOp::Dup: 610 MOZ_ASSERT(ndefs == 2); 611 offsetStack[stackDepth + 1] = offsetStack[stackDepth]; 612 break; 613 614 case JSOp::Dup2: 615 MOZ_ASSERT(ndefs == 4); 616 offsetStack[stackDepth + 2] = offsetStack[stackDepth]; 617 offsetStack[stackDepth + 3] = offsetStack[stackDepth + 1]; 618 break; 619 620 case JSOp::DupAt: { 621 MOZ_ASSERT(ndefs == 1); 622 unsigned n = GET_UINT24(pc); 623 MOZ_ASSERT(n < stackDepth); 624 offsetStack[stackDepth] = offsetStack[stackDepth - 1 - n]; 625 break; 626 } 627 628 case JSOp::Swap: { 629 MOZ_ASSERT(ndefs == 2); 630 OffsetAndDefIndex tmp = offsetStack[stackDepth + 1]; 631 offsetStack[stackDepth + 1] = offsetStack[stackDepth]; 632 offsetStack[stackDepth] = tmp; 633 break; 634 } 635 636 case JSOp::Pick: { 637 unsigned n = GET_UINT8(pc); 638 MOZ_ASSERT(ndefs == n + 1); 639 uint32_t top = stackDepth + n; 640 OffsetAndDefIndex tmp = offsetStack[stackDepth]; 641 for (uint32_t i = stackDepth; i < top; i++) { 642 offsetStack[i] = offsetStack[i + 1]; 643 } 644 offsetStack[top] = tmp; 645 break; 646 } 647 648 case JSOp::Unpick: { 649 unsigned n = GET_UINT8(pc); 650 MOZ_ASSERT(ndefs == n + 1); 651 uint32_t top = stackDepth + n; 652 OffsetAndDefIndex tmp = offsetStack[top]; 653 for (uint32_t i = top; i > stackDepth; i--) { 654 offsetStack[i] = offsetStack[i - 1]; 655 } 656 offsetStack[stackDepth] = tmp; 657 break; 658 } 659 660 case JSOp::And: 661 case JSOp::CheckIsObj: 662 case JSOp::CheckObjCoercible: 663 case JSOp::CheckThis: 664 case JSOp::CheckThisReinit: 665 case JSOp::CheckClassHeritage: 666 case JSOp::DebugCheckSelfHosted: 667 case JSOp::InitGLexical: 668 case JSOp::InitLexical: 669 case JSOp::Or: 670 case JSOp::Coalesce: 671 case JSOp::SetAliasedVar: 672 case JSOp::SetArg: 673 case JSOp::SetIntrinsic: 674 case JSOp::SetLocal: 675 case JSOp::InitAliasedLexical: 676 case JSOp::CheckLexical: 677 case JSOp::CheckAliasedLexical: 678 // Keep the top value. 679 MOZ_ASSERT(nuses == 1); 680 MOZ_ASSERT(ndefs == 1); 681 break; 682 683 case JSOp::InitHomeObject: 684 // Pop the top value, keep the other value. 685 MOZ_ASSERT(nuses == 2); 686 MOZ_ASSERT(ndefs == 1); 687 break; 688 689 case JSOp::CheckResumeKind: 690 // Pop the top two values, keep the other value. 691 MOZ_ASSERT(nuses == 3); 692 MOZ_ASSERT(ndefs == 1); 693 break; 694 695 case JSOp::SetGName: 696 case JSOp::SetName: 697 case JSOp::SetProp: 698 case JSOp::StrictSetGName: 699 case JSOp::StrictSetName: 700 case JSOp::StrictSetProp: 701 // Keep the top value, removing other 1 value. 702 MOZ_ASSERT(nuses == 2); 703 MOZ_ASSERT(ndefs == 1); 704 offsetStack[stackDepth] = offsetStack[stackDepth + 1]; 705 break; 706 707 case JSOp::SetPropSuper: 708 case JSOp::StrictSetPropSuper: 709 // Keep the top value, removing other 2 values. 710 MOZ_ASSERT(nuses == 3); 711 MOZ_ASSERT(ndefs == 1); 712 offsetStack[stackDepth] = offsetStack[stackDepth + 2]; 713 break; 714 715 case JSOp::SetElemSuper: 716 case JSOp::StrictSetElemSuper: 717 // Keep the top value, removing other 3 values. 718 MOZ_ASSERT(nuses == 4); 719 MOZ_ASSERT(ndefs == 1); 720 offsetStack[stackDepth] = offsetStack[stackDepth + 3]; 721 break; 722 723 case JSOp::IsGenClosing: 724 case JSOp::IsNoIter: 725 case JSOp::IsNullOrUndefined: 726 case JSOp::MoreIter: 727 case JSOp::CanSkipAwait: 728 // Keep the top value and push one more value. 729 MOZ_ASSERT(nuses == 1); 730 MOZ_ASSERT(ndefs == 2); 731 offsetStack[stackDepth + 1].set(offset, 1); 732 break; 733 734 case JSOp::MaybeExtractAwaitValue: 735 // Keep the top value and replace the second to top value. 736 MOZ_ASSERT(nuses == 2); 737 MOZ_ASSERT(ndefs == 2); 738 offsetStack[stackDepth].set(offset, 0); 739 break; 740 741 case JSOp::CheckPrivateField: 742 // Keep the top two values, and push one new value. 743 MOZ_ASSERT(nuses == 2); 744 MOZ_ASSERT(ndefs == 3); 745 offsetStack[stackDepth + 2].set(offset, 2); 746 break; 747 } 748 749 #ifdef DEBUG 750 end: 751 #endif /* DEBUG */ 752 753 stackDepth += ndefs; 754 return stackDepth; 755 } 756 757 bool BytecodeParser::recordBytecode(uint32_t offset, 758 const OffsetAndDefIndex* offsetStack, 759 uint32_t stackDepth) { 760 MOZ_RELEASE_ASSERT(offset < script_->length()); 761 MOZ_RELEASE_ASSERT(stackDepth <= maximumStackDepth()); 762 763 Bytecode*& code = codeArray_[offset]; 764 if (!code) { 765 code = alloc().new_<Bytecode>(alloc()); 766 if (!code || !code->captureOffsetStack(alloc(), offsetStack, stackDepth)) { 767 reportOOM(); 768 return false; 769 } 770 } else { 771 code->mergeOffsetStack(offsetStack, stackDepth); 772 } 773 774 return true; 775 } 776 777 bool BytecodeParser::addJump(uint32_t offset, uint32_t stackDepth, 778 const OffsetAndDefIndex* offsetStack, 779 jsbytecode* pc, JumpKind kind) { 780 if (!recordBytecode(offset, offsetStack, stackDepth)) { 781 return false; 782 } 783 784 #ifdef DEBUG 785 uint32_t currentOffset = script_->pcToOffset(pc); 786 if (isStackDump) { 787 if (!codeArray_[offset]->addJump(currentOffset, kind)) { 788 reportOOM(); 789 return false; 790 } 791 } 792 793 // If this is a backedge, assert we parsed the target JSOp::LoopHead. 794 MOZ_ASSERT_IF(offset < currentOffset, codeArray_[offset]->parsed); 795 #endif /* DEBUG */ 796 797 return true; 798 } 799 800 bool BytecodeParser::parse() { 801 MOZ_ASSERT(!codeArray_); 802 803 uint32_t length = script_->length(); 804 codeArray_ = alloc().newArray<Bytecode*>(length); 805 806 if (!codeArray_) { 807 reportOOM(); 808 return false; 809 } 810 811 mozilla::PodZero(codeArray_, length); 812 813 // Fill in stack depth and definitions at initial bytecode. 814 Bytecode* startcode = alloc().new_<Bytecode>(alloc()); 815 if (!startcode) { 816 reportOOM(); 817 return false; 818 } 819 820 // Fill in stack depth and definitions at initial bytecode. 821 OffsetAndDefIndex* offsetStack = 822 alloc().newArray<OffsetAndDefIndex>(maximumStackDepth()); 823 if (maximumStackDepth() && !offsetStack) { 824 reportOOM(); 825 return false; 826 } 827 828 startcode->stackDepth = 0; 829 codeArray_[0] = startcode; 830 831 for (uint32_t offset = 0, nextOffset = 0; offset < length; 832 offset = nextOffset) { 833 Bytecode* code = maybeCode(offset); 834 jsbytecode* pc = script_->offsetToPC(offset); 835 836 // Next bytecode to analyze. 837 nextOffset = offset + GetBytecodeLength(pc); 838 839 MOZ_RELEASE_ASSERT(*pc < JSOP_LIMIT); 840 JSOp op = JSOp(*pc); 841 842 if (!code) { 843 // Haven't found a path by which this bytecode is reachable. 844 continue; 845 } 846 847 // On a jump target, we reload the offsetStack saved for the current 848 // bytecode, as it contains either the original offset stack, or the 849 // merged offset stack. 850 if (BytecodeIsJumpTarget(op)) { 851 for (uint32_t n = 0; n < code->stackDepth; ++n) { 852 offsetStack[n] = code->offsetStack[n]; 853 } 854 } 855 856 if (code->parsed) { 857 // No need to reparse. 858 continue; 859 } 860 861 code->parsed = true; 862 863 uint32_t stackDepth = simulateOp(op, offset, offsetStack, code->stackDepth); 864 865 #if defined(DEBUG) || defined(JS_JITSPEW) 866 if (isStackDump) { 867 if (!code->captureOffsetStackAfter(alloc(), offsetStack, stackDepth)) { 868 reportOOM(); 869 return false; 870 } 871 } 872 #endif /* defined(DEBUG) || defined(JS_JITSPEW) */ 873 874 switch (op) { 875 case JSOp::TableSwitch: { 876 uint32_t defaultOffset = offset + GET_JUMP_OFFSET(pc); 877 jsbytecode* pc2 = pc + JUMP_OFFSET_LEN; 878 int32_t low = GET_JUMP_OFFSET(pc2); 879 pc2 += JUMP_OFFSET_LEN; 880 int32_t high = GET_JUMP_OFFSET(pc2); 881 pc2 += JUMP_OFFSET_LEN; 882 883 if (!addJump(defaultOffset, stackDepth, offsetStack, pc, 884 JumpKind::SwitchDefault)) { 885 return false; 886 } 887 888 uint32_t ncases = high - low + 1; 889 890 for (uint32_t i = 0; i < ncases; i++) { 891 uint32_t targetOffset = script_->tableSwitchCaseOffset(pc, i); 892 if (targetOffset != defaultOffset) { 893 if (!addJump(targetOffset, stackDepth, offsetStack, pc, 894 JumpKind::SwitchCase)) { 895 return false; 896 } 897 } 898 } 899 break; 900 } 901 902 case JSOp::Try: { 903 // Everything between a try and corresponding catch or finally is 904 // conditional. Note that there is no problem with code which is skipped 905 // by a thrown exception but is not caught by a later handler in the 906 // same function: no more code will execute, and it does not matter what 907 // is defined. 908 for (const TryNote& tn : script_->trynotes()) { 909 if (tn.start == offset + JSOpLength_Try) { 910 uint32_t catchOffset = tn.start + tn.length; 911 if (tn.kind() == TryNoteKind::Catch) { 912 if (!addJump(catchOffset, stackDepth, offsetStack, pc, 913 JumpKind::TryCatch)) { 914 return false; 915 } 916 } else if (tn.kind() == TryNoteKind::Finally) { 917 // Three additional values will be on the stack at the beginning 918 // of the finally block: the exception/resume index, the exception 919 // stack, and the |throwing| value. For the benefit of the 920 // decompiler, point them at this Try. 921 offsetStack[stackDepth].set(offset, 0); 922 offsetStack[stackDepth + 1].set(offset, 1); 923 offsetStack[stackDepth + 2].set(offset, 2); 924 if (!addJump(catchOffset, stackDepth + 3, offsetStack, pc, 925 JumpKind::TryFinally)) { 926 return false; 927 } 928 } 929 } 930 } 931 break; 932 } 933 934 default: 935 break; 936 } 937 938 // Check basic jump opcodes, which may or may not have a fallthrough. 939 if (IsJumpOpcode(op)) { 940 // Case instructions do not push the lvalue back when branching. 941 uint32_t newStackDepth = stackDepth; 942 if (op == JSOp::Case) { 943 newStackDepth--; 944 } 945 946 uint32_t targetOffset = offset + GET_JUMP_OFFSET(pc); 947 if (!addJump(targetOffset, newStackDepth, offsetStack, pc, 948 JumpKind::Simple)) { 949 return false; 950 } 951 } 952 953 // Handle any fallthrough from this opcode. 954 if (BytecodeFallsThrough(op)) { 955 if (!recordBytecode(nextOffset, offsetStack, stackDepth)) { 956 return false; 957 } 958 } 959 } 960 961 return true; 962 } 963 964 #if defined(DEBUG) || defined(JS_JITSPEW) 965 966 bool js::ReconstructStackDepth(JSContext* cx, JSScript* script, jsbytecode* pc, 967 uint32_t* depth, bool* reachablePC) { 968 LifoAllocScope allocScope(&cx->tempLifoAlloc()); 969 BytecodeParser parser(cx, allocScope.alloc(), script); 970 if (!parser.parse()) { 971 return false; 972 } 973 974 *reachablePC = parser.isReachable(pc); 975 976 if (*reachablePC) { 977 *depth = parser.stackDepthAtPC(pc); 978 } 979 980 return true; 981 } 982 983 static unsigned Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc, 984 unsigned loc, bool lines, 985 const BytecodeParser* parser, StringPrinter* sp); 986 987 /* 988 * If pc != nullptr, include a prefix indicating whether the PC is at the 989 * current line. If showAll is true, include the entry stack depth. 990 */ 991 [[nodiscard]] static bool DisassembleAtPC( 992 JSContext* cx, JSScript* scriptArg, bool lines, const jsbytecode* pc, 993 bool showAll, StringPrinter* sp, 994 DisassembleSkeptically skeptically = DisassembleSkeptically::No) { 995 LifoAllocScope allocScope(&cx->tempLifoAlloc()); 996 RootedScript script(cx, scriptArg); 997 mozilla::Maybe<BytecodeParser> parser; 998 999 if (skeptically == DisassembleSkeptically::No) { 1000 parser.emplace(cx, allocScope.alloc(), script); 1001 parser->setStackDump(); 1002 if (!parser->parse()) { 1003 return false; 1004 } 1005 } 1006 1007 if (showAll) { 1008 sp->printf("%s:%u\n", script->filename(), unsigned(script->lineno())); 1009 } 1010 1011 if (pc != nullptr) { 1012 sp->put(" "); 1013 } 1014 if (showAll) { 1015 sp->put("sn stack "); 1016 } 1017 sp->put("loc "); 1018 if (lines) { 1019 sp->put("line"); 1020 } 1021 sp->put(" op\n"); 1022 1023 if (pc != nullptr) { 1024 sp->put(" "); 1025 } 1026 if (showAll) { 1027 sp->put("-- ----- "); 1028 } 1029 sp->put("----- "); 1030 if (lines) { 1031 sp->put("----"); 1032 } 1033 sp->put(" --\n"); 1034 1035 jsbytecode* next = script->code(); 1036 jsbytecode* end = script->codeEnd(); 1037 while (next < end) { 1038 if (next == script->main()) { 1039 sp->put("main:\n"); 1040 } 1041 if (pc != nullptr) { 1042 sp->put(pc == next ? "--> " : " "); 1043 } 1044 if (showAll) { 1045 if (parser && parser->isReachable(next)) { 1046 sp->printf("%05u ", parser->stackDepthAtPC(next)); 1047 } else { 1048 sp->put(" "); 1049 } 1050 } 1051 unsigned len = Disassemble1(cx, script, next, script->pcToOffset(next), 1052 lines, parser.ptrOr(nullptr), sp); 1053 if (!len) { 1054 return false; 1055 } 1056 1057 next += len; 1058 } 1059 1060 return true; 1061 } 1062 1063 bool js::Disassemble(JSContext* cx, HandleScript script, bool lines, 1064 StringPrinter* sp, DisassembleSkeptically skeptically) { 1065 return DisassembleAtPC(cx, script, lines, nullptr, false, sp, skeptically); 1066 } 1067 1068 JS_PUBLIC_API bool js::DumpPC(JSContext* cx, FILE* fp) { 1069 gc::AutoSuppressGC suppressGC(cx); 1070 Sprinter sprinter(cx); 1071 if (!sprinter.init()) { 1072 return false; 1073 } 1074 ScriptFrameIter iter(cx); 1075 if (iter.done()) { 1076 fprintf(fp, "Empty stack.\n"); 1077 return true; 1078 } 1079 RootedScript script(cx, iter.script()); 1080 bool ok = DisassembleAtPC(cx, script, true, iter.pc(), false, &sprinter); 1081 JS::UniqueChars out = sprinter.release(); 1082 if (!out) { 1083 return false; 1084 } 1085 fprintf(fp, "%s", out.get()); 1086 return ok; 1087 } 1088 1089 JS_PUBLIC_API bool js::DumpScript(JSContext* cx, JSScript* scriptArg, 1090 FILE* fp) { 1091 gc::AutoSuppressGC suppressGC(cx); 1092 Sprinter sprinter(cx); 1093 if (!sprinter.init()) { 1094 return false; 1095 } 1096 RootedScript script(cx, scriptArg); 1097 bool ok = Disassemble(cx, script, true, &sprinter); 1098 JS::UniqueChars out = sprinter.release(); 1099 if (!out) { 1100 return false; 1101 } 1102 fprintf(fp, "%s", out.get()); 1103 return ok; 1104 } 1105 1106 UniqueChars js::ToDisassemblySource(JSContext* cx, HandleValue v) { 1107 if (v.isString()) { 1108 return QuoteString(cx, v.toString(), '"'); 1109 } 1110 1111 if (JS::RuntimeHeapIsBusy()) { 1112 return DuplicateString(cx, "<value>"); 1113 } 1114 1115 if (v.isObject()) { 1116 JSObject& obj = v.toObject(); 1117 1118 if (obj.is<JSFunction>()) { 1119 RootedFunction fun(cx, &obj.as<JSFunction>()); 1120 JSString* str = JS_DecompileFunction(cx, fun); 1121 if (!str) { 1122 return nullptr; 1123 } 1124 return QuoteString(cx, str); 1125 } 1126 1127 if (obj.is<RegExpObject>()) { 1128 Rooted<RegExpObject*> reobj(cx, &obj.as<RegExpObject>()); 1129 JSString* source = RegExpObject::toString(cx, reobj); 1130 if (!source) { 1131 return nullptr; 1132 } 1133 return QuoteString(cx, source); 1134 } 1135 } 1136 1137 JSString* str = ValueToSource(cx, v); 1138 if (!str) { 1139 return nullptr; 1140 } 1141 return QuoteString(cx, str); 1142 } 1143 1144 static bool ToDisassemblySource(JSContext* cx, Handle<Scope*> scope, 1145 UniqueChars* bytes) { 1146 UniqueChars source = JS_smprintf("%s {", ScopeKindString(scope->kind())); 1147 if (!source) { 1148 ReportOutOfMemory(cx); 1149 return false; 1150 } 1151 1152 for (Rooted<BindingIter> bi(cx, BindingIter(scope)); bi; bi++) { 1153 UniqueChars nameBytes = AtomToPrintableString(cx, bi.name()); 1154 if (!nameBytes) { 1155 return false; 1156 } 1157 1158 source = JS_sprintf_append(std::move(source), "%s: ", nameBytes.get()); 1159 if (!source) { 1160 ReportOutOfMemory(cx); 1161 return false; 1162 } 1163 1164 BindingLocation loc = bi.location(); 1165 switch (loc.kind()) { 1166 case BindingLocation::Kind::Global: 1167 source = JS_sprintf_append(std::move(source), "global"); 1168 break; 1169 1170 case BindingLocation::Kind::Frame: 1171 source = 1172 JS_sprintf_append(std::move(source), "frame slot %u", loc.slot()); 1173 break; 1174 1175 case BindingLocation::Kind::Environment: 1176 source = 1177 JS_sprintf_append(std::move(source), "env slot %u", loc.slot()); 1178 break; 1179 1180 case BindingLocation::Kind::Argument: 1181 source = 1182 JS_sprintf_append(std::move(source), "arg slot %u", loc.slot()); 1183 break; 1184 1185 case BindingLocation::Kind::NamedLambdaCallee: 1186 source = JS_sprintf_append(std::move(source), "named lambda callee"); 1187 break; 1188 1189 case BindingLocation::Kind::Import: 1190 source = JS_sprintf_append(std::move(source), "import"); 1191 break; 1192 } 1193 1194 if (!source) { 1195 ReportOutOfMemory(cx); 1196 return false; 1197 } 1198 1199 if (!bi.isLast()) { 1200 source = JS_sprintf_append(std::move(source), ", "); 1201 if (!source) { 1202 ReportOutOfMemory(cx); 1203 return false; 1204 } 1205 } 1206 } 1207 1208 source = JS_sprintf_append(std::move(source), "}"); 1209 if (!source) { 1210 ReportOutOfMemory(cx); 1211 return false; 1212 } 1213 1214 *bytes = std::move(source); 1215 return true; 1216 } 1217 1218 static bool DumpJumpOrigins(HandleScript script, jsbytecode* pc, 1219 const BytecodeParser* parser, StringPrinter* sp) { 1220 bool called = false; 1221 auto callback = [&script, &sp, &called](jsbytecode* pc, 1222 BytecodeParser::JumpKind kind) { 1223 if (!called) { 1224 called = true; 1225 sp->put("\n# "); 1226 } else { 1227 sp->put(", "); 1228 } 1229 1230 switch (kind) { 1231 case BytecodeParser::JumpKind::Simple: 1232 break; 1233 1234 case BytecodeParser::JumpKind::SwitchCase: 1235 sp->put("switch-case "); 1236 break; 1237 1238 case BytecodeParser::JumpKind::SwitchDefault: 1239 sp->put("switch-default "); 1240 break; 1241 1242 case BytecodeParser::JumpKind::TryCatch: 1243 sp->put("try-catch "); 1244 break; 1245 1246 case BytecodeParser::JumpKind::TryFinally: 1247 sp->put("try-finally "); 1248 break; 1249 } 1250 1251 sp->printf("from %s @ %05u", CodeName(JSOp(*pc)), 1252 unsigned(script->pcToOffset(pc))); 1253 1254 return true; 1255 }; 1256 if (!parser->forEachJumpOrigins(pc, callback)) { 1257 return false; 1258 } 1259 if (called) { 1260 sp->put("\n"); 1261 } 1262 1263 return true; 1264 } 1265 1266 static bool DecompileAtPCForStackDump( 1267 JSContext* cx, HandleScript script, 1268 const OffsetAndDefIndex& offsetAndDefIndex, StringPrinter* sp); 1269 1270 static bool PrintShapeProperties(JSContext* cx, StringPrinter* sp, 1271 SharedShape* shape) { 1272 // Add all property keys to a vector to allow printing them in property 1273 // definition order. 1274 Vector<PropertyKey> props(cx); 1275 for (SharedShapePropertyIter<NoGC> iter(shape); !iter.done(); iter++) { 1276 if (!props.append(iter->key())) { 1277 return false; 1278 } 1279 } 1280 1281 sp->put("{"); 1282 1283 for (size_t i = props.length(); i > 0; i--) { 1284 PropertyKey key = props[i - 1]; 1285 RootedValue keyv(cx, IdToValue(key)); 1286 JSString* str = ToString<NoGC>(cx, keyv); 1287 if (!str) { 1288 ReportOutOfMemory(cx); 1289 return false; 1290 } 1291 sp->putString(cx, str); 1292 if (i > 1) { 1293 sp->put(", "); 1294 } 1295 } 1296 1297 sp->put("}"); 1298 return true; 1299 } 1300 1301 static unsigned Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc, 1302 unsigned loc, bool lines, 1303 const BytecodeParser* parser, StringPrinter* sp) { 1304 if (parser && parser->isReachable(pc)) { 1305 if (!DumpJumpOrigins(script, pc, parser, sp)) { 1306 return 0; 1307 } 1308 } 1309 1310 size_t before = sp->length(); 1311 bool stackDumped = false; 1312 auto dumpStack = [&cx, &script, &pc, &parser, &sp, &before, &stackDumped]() { 1313 if (!parser) { 1314 return true; 1315 } 1316 if (stackDumped) { 1317 return true; 1318 } 1319 stackDumped = true; 1320 1321 size_t after = sp->length(); 1322 MOZ_ASSERT(after >= before); 1323 1324 static const size_t stack_column = 40; 1325 for (size_t i = after - before; i < stack_column - 1; i++) { 1326 sp->put(" "); 1327 } 1328 1329 sp->put(" # "); 1330 1331 if (!parser->isReachable(pc)) { 1332 sp->put("!!! UNREACHABLE !!!"); 1333 } else { 1334 uint32_t depth = parser->stackDepthAfterPC(pc); 1335 1336 for (uint32_t i = 0; i < depth; i++) { 1337 if (i) { 1338 sp->put(" "); 1339 } 1340 1341 const OffsetAndDefIndex& offsetAndDefIndex = 1342 parser->offsetForStackOperandAfterPC(script->pcToOffset(pc), i); 1343 // This will decompile the stack for the same PC many times. 1344 // We'll avoid optimizing it since this is a testing function 1345 // and it won't be worth managing cached expression here. 1346 if (!DecompileAtPCForStackDump(cx, script, offsetAndDefIndex, sp)) { 1347 return false; 1348 } 1349 } 1350 } 1351 1352 return true; 1353 }; 1354 1355 if (*pc >= JSOP_LIMIT) { 1356 char numBuf1[12], numBuf2[12]; 1357 SprintfLiteral(numBuf1, "%d", int(*pc)); 1358 SprintfLiteral(numBuf2, "%d", JSOP_LIMIT); 1359 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1360 JSMSG_BYTECODE_TOO_BIG, numBuf1, numBuf2); 1361 return 0; 1362 } 1363 JSOp op = JSOp(*pc); 1364 const JSCodeSpec& cs = CodeSpec(op); 1365 const unsigned len = cs.length; 1366 sp->printf("%05u:", loc); 1367 if (lines) { 1368 sp->printf("%4u", PCToLineNumber(script, pc)); 1369 } 1370 sp->printf(" %s", CodeName(op)); 1371 1372 int i; 1373 switch (JOF_TYPE(cs.format)) { 1374 case JOF_BYTE: 1375 break; 1376 1377 case JOF_JUMP: { 1378 ptrdiff_t off = GET_JUMP_OFFSET(pc); 1379 sp->printf(" %u (%+d)", unsigned(loc + int(off)), int(off)); 1380 break; 1381 } 1382 1383 case JOF_SCOPE: { 1384 Rooted<Scope*> scope(cx, script->getScope(pc)); 1385 UniqueChars bytes; 1386 if (!ToDisassemblySource(cx, scope, &bytes)) { 1387 return 0; 1388 } 1389 sp->printf(" %s", bytes.get()); 1390 break; 1391 } 1392 1393 case JOF_ENVCOORD: { 1394 RootedValue v(cx, StringValue(EnvironmentCoordinateNameSlow(script, pc))); 1395 UniqueChars bytes = ToDisassemblySource(cx, v); 1396 if (!bytes) { 1397 return 0; 1398 } 1399 EnvironmentCoordinate ec(pc); 1400 sp->printf(" %s (hops = %u, slot = %u)", bytes.get(), ec.hops(), 1401 ec.slot()); 1402 break; 1403 } 1404 case JOF_DEBUGCOORD: { 1405 EnvironmentCoordinate ec(pc); 1406 sp->printf("(hops = %u, slot = %u)", ec.hops(), ec.slot()); 1407 break; 1408 } 1409 case JOF_ATOM: { 1410 RootedValue v(cx, StringValue(script->getAtom(pc))); 1411 UniqueChars bytes = ToDisassemblySource(cx, v); 1412 if (!bytes) { 1413 return 0; 1414 } 1415 sp->printf(" %s", bytes.get()); 1416 break; 1417 } 1418 case JOF_STRING: { 1419 RootedValue v(cx, StringValue(script->getString(pc))); 1420 UniqueChars bytes = ToDisassemblySource(cx, v); 1421 if (!bytes) { 1422 return 0; 1423 } 1424 sp->printf(" %s", bytes.get()); 1425 break; 1426 } 1427 1428 case JOF_DOUBLE: { 1429 double d = GET_INLINE_VALUE(pc).toDouble(); 1430 sp->printf(" %lf", d); 1431 break; 1432 } 1433 1434 case JOF_BIGINT: { 1435 RootedValue v(cx, BigIntValue(script->getBigInt(pc))); 1436 UniqueChars bytes = ToDisassemblySource(cx, v); 1437 if (!bytes) { 1438 return 0; 1439 } 1440 sp->printf(" %s", bytes.get()); 1441 break; 1442 } 1443 1444 case JOF_OBJECT: { 1445 JSObject* obj = script->getObject(pc); 1446 { 1447 RootedValue v(cx, ObjectValue(*obj)); 1448 UniqueChars bytes = ToDisassemblySource(cx, v); 1449 if (!bytes) { 1450 return 0; 1451 } 1452 sp->printf(" %s", bytes.get()); 1453 } 1454 break; 1455 } 1456 1457 case JOF_SHAPE: { 1458 SharedShape* shape = script->getShape(pc); 1459 sp->put(" "); 1460 if (!PrintShapeProperties(cx, sp, shape)) { 1461 return 0; 1462 } 1463 break; 1464 } 1465 1466 case JOF_REGEXP: { 1467 js::RegExpObject* obj = script->getRegExp(pc); 1468 RootedValue v(cx, ObjectValue(*obj)); 1469 UniqueChars bytes = ToDisassemblySource(cx, v); 1470 if (!bytes) { 1471 return 0; 1472 } 1473 sp->printf(" %s", bytes.get()); 1474 break; 1475 } 1476 1477 case JOF_TABLESWITCH: { 1478 int32_t i, low, high; 1479 1480 ptrdiff_t off = GET_JUMP_OFFSET(pc); 1481 jsbytecode* pc2 = pc + JUMP_OFFSET_LEN; 1482 low = GET_JUMP_OFFSET(pc2); 1483 pc2 += JUMP_OFFSET_LEN; 1484 high = GET_JUMP_OFFSET(pc2); 1485 pc2 += JUMP_OFFSET_LEN; 1486 sp->printf(" defaultOffset %d low %d high %d", int(off), low, high); 1487 1488 // Display stack dump before diplaying the offsets for each case. 1489 if (!dumpStack()) { 1490 return 0; 1491 } 1492 1493 for (i = low; i <= high; i++) { 1494 off = 1495 script->tableSwitchCaseOffset(pc, i - low) - script->pcToOffset(pc); 1496 sp->printf("\n\t%d: %d", i, int(off)); 1497 } 1498 break; 1499 } 1500 1501 case JOF_QARG: 1502 sp->printf(" %u", GET_ARGNO(pc)); 1503 break; 1504 1505 case JOF_LOCAL: 1506 sp->printf(" %u", GET_LOCALNO(pc)); 1507 break; 1508 1509 case JOF_GCTHING: 1510 sp->printf(" %u", unsigned(GET_GCTHING_INDEX(pc))); 1511 break; 1512 1513 case JOF_UINT32: 1514 sp->printf(" %u", GET_UINT32(pc)); 1515 break; 1516 1517 case JOF_ICINDEX: 1518 sp->printf(" (ic: %u)", GET_ICINDEX(pc)); 1519 break; 1520 1521 case JOF_LOOPHEAD: 1522 sp->printf(" (ic: %u, depthHint: %u)", GET_ICINDEX(pc), 1523 LoopHeadDepthHint(pc)); 1524 break; 1525 1526 case JOF_TWO_UINT8: { 1527 int one = (int)GET_UINT8(pc); 1528 int two = (int)GET_UINT8(pc + 1); 1529 1530 sp->printf(" %d", one); 1531 sp->printf(" %d", two); 1532 break; 1533 } 1534 1535 case JOF_ARGC: 1536 case JOF_UINT16: 1537 i = (int)GET_UINT16(pc); 1538 goto print_int; 1539 1540 case JOF_RESUMEINDEX: 1541 case JOF_UINT24: 1542 MOZ_ASSERT(len == 4); 1543 i = (int)GET_UINT24(pc); 1544 goto print_int; 1545 1546 case JOF_UINT8: 1547 i = GET_UINT8(pc); 1548 goto print_int; 1549 1550 case JOF_INT8: 1551 i = GET_INT8(pc); 1552 goto print_int; 1553 1554 case JOF_INT32: 1555 MOZ_ASSERT(op == JSOp::Int32); 1556 i = GET_INT32(pc); 1557 print_int: 1558 sp->printf(" %d", i); 1559 break; 1560 1561 default: { 1562 char numBuf[12]; 1563 SprintfLiteral(numBuf, "%x", cs.format); 1564 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1565 JSMSG_UNKNOWN_FORMAT, numBuf); 1566 return 0; 1567 } 1568 } 1569 1570 if (!dumpStack()) { 1571 return 0; 1572 } 1573 1574 sp->put("\n"); 1575 return len; 1576 } 1577 1578 unsigned js::Disassemble1(JSContext* cx, JS::Handle<JSScript*> script, 1579 jsbytecode* pc, unsigned loc, bool lines, 1580 StringPrinter* sp) { 1581 return Disassemble1(cx, script, pc, loc, lines, nullptr, sp); 1582 } 1583 1584 #endif /* defined(DEBUG) || defined(JS_JITSPEW) */ 1585 1586 namespace { 1587 /* 1588 * The expression decompiler is invoked by error handling code to produce a 1589 * string representation of the erroring expression. As it's only a debugging 1590 * tool, it only supports basic expressions. For anything complicated, it simply 1591 * puts "(intermediate value)" into the error result. 1592 * 1593 * Here's the basic algorithm: 1594 * 1595 * 1. Find the stack location of the value whose expression we wish to 1596 * decompile. The error handler can explicitly pass this as an 1597 * argument. Otherwise, we search backwards down the stack for the offending 1598 * value. 1599 * 1600 * 2. Instantiate and run a BytecodeParser for the current frame. This creates a 1601 * stack of pcs parallel to the interpreter stack; given an interpreter stack 1602 * location, the corresponding pc stack location contains the opcode that pushed 1603 * the value in the interpreter. Now, with the result of step 1, we have the 1604 * opcode responsible for pushing the value we want to decompile. 1605 * 1606 * 3. Pass the opcode to decompilePC. decompilePC is the main decompiler 1607 * routine, responsible for a string representation of the expression that 1608 * generated a certain stack location. decompilePC looks at one opcode and 1609 * returns the JS source equivalent of that opcode. 1610 * 1611 * 4. Expressions can, of course, contain subexpressions. For example, the 1612 * literals "4" and "5" are subexpressions of the addition operator in "4 + 1613 * 5". If we need to decompile a subexpression, we call decompilePC (step 2) 1614 * recursively on the operands' pcs. The result is a depth-first traversal of 1615 * the expression tree. 1616 * 1617 */ 1618 struct ExpressionDecompiler { 1619 JSContext* cx; 1620 RootedScript script; 1621 const BytecodeParser& parser; 1622 Sprinter sprinter; 1623 1624 #if defined(DEBUG) || defined(JS_JITSPEW) 1625 // Dedicated mode for stack dump. 1626 // Generates an expression for stack dump, including internal state, 1627 // and also disables special handling for self-hosted code. 1628 bool isStackDump; 1629 #endif 1630 1631 ExpressionDecompiler(JSContext* cx, JSScript* script, 1632 const BytecodeParser& parser) 1633 : cx(cx), 1634 script(cx, script), 1635 parser(parser), 1636 sprinter(cx) 1637 #if defined(DEBUG) || defined(JS_JITSPEW) 1638 , 1639 isStackDump(false) 1640 #endif 1641 { 1642 } 1643 bool init(); 1644 bool decompilePCForStackOperand(jsbytecode* pc, int i); 1645 bool decompilePC(jsbytecode* pc, uint8_t defIndex); 1646 bool decompilePC(const OffsetAndDefIndex& offsetAndDefIndex); 1647 JSAtom* getArg(unsigned slot); 1648 JSAtom* loadAtom(jsbytecode* pc); 1649 JSString* loadString(jsbytecode* pc); 1650 bool quote(JSString* s, char quote); 1651 bool write(const char* s); 1652 bool write(JSString* str); 1653 bool write(ConstantCompareOperand* operand); 1654 UniqueChars getOutput(); 1655 #if defined(DEBUG) || defined(JS_JITSPEW) 1656 void setStackDump() { isStackDump = true; } 1657 #endif 1658 }; 1659 1660 bool ExpressionDecompiler::decompilePCForStackOperand(jsbytecode* pc, int i) { 1661 return decompilePC(parser.offsetForStackOperand(script->pcToOffset(pc), i)); 1662 } 1663 1664 bool ExpressionDecompiler::decompilePC(jsbytecode* pc, uint8_t defIndex) { 1665 MOZ_ASSERT(script->containsPC(pc)); 1666 1667 JSOp op = (JSOp)*pc; 1668 1669 if (const char* token = CodeToken[uint8_t(op)]) { 1670 MOZ_ASSERT(defIndex == 0); 1671 MOZ_ASSERT(CodeSpec(op).ndefs == 1); 1672 1673 // Handle simple cases of binary and unary operators. 1674 switch (CodeSpec(op).nuses) { 1675 case 2: { 1676 const char* extra = ""; 1677 1678 MOZ_ASSERT(pc + 1 < script->codeEnd(), 1679 "binary opcode shouldn't be the last opcode in the script"); 1680 if (CodeSpec(op).length == 1 && 1681 (JSOp)(*(pc + 1)) == JSOp::NopIsAssignOp) { 1682 extra = "="; 1683 } 1684 1685 return write("(") && decompilePCForStackOperand(pc, -2) && write(" ") && 1686 write(token) && write(extra) && write(" ") && 1687 decompilePCForStackOperand(pc, -1) && write(")"); 1688 break; 1689 } 1690 case 1: 1691 return write("(") && write(token) && 1692 decompilePCForStackOperand(pc, -1) && write(")"); 1693 default: 1694 break; 1695 } 1696 } 1697 1698 switch (op) { 1699 case JSOp::DelName: 1700 return write("(delete ") && write(loadAtom(pc)) && write(")"); 1701 1702 case JSOp::GetGName: 1703 case JSOp::GetName: 1704 case JSOp::GetIntrinsic: 1705 return write(loadAtom(pc)); 1706 case JSOp::GetArg: { 1707 unsigned slot = GET_ARGNO(pc); 1708 1709 // For self-hosted scripts that are called from non-self-hosted code, 1710 // decompiling the parameter name in the self-hosted script is 1711 // unhelpful. Decompile the argument name instead. 1712 if (script->selfHosted() 1713 #ifdef DEBUG 1714 // For stack dump, argument name is not necessary. 1715 && !isStackDump 1716 #endif /* DEBUG */ 1717 ) { 1718 UniqueChars result; 1719 if (!DecompileArgumentFromStack(cx, slot, &result)) { 1720 return false; 1721 } 1722 1723 // Note that decompiling the argument in the parent frame might 1724 // not succeed. 1725 if (result) { 1726 return write(result.get()); 1727 } 1728 1729 // If it fails, do not return parameter name and let the caller 1730 // fallback. 1731 return write("(intermediate value)"); 1732 } 1733 1734 JSAtom* atom = getArg(slot); 1735 if (!atom) { 1736 return false; 1737 } 1738 return write(atom); 1739 } 1740 case JSOp::GetLocal: { 1741 JSAtom* atom = FrameSlotName(script, pc); 1742 MOZ_ASSERT(atom); 1743 return write(atom); 1744 } 1745 case JSOp::GetAliasedVar: { 1746 JSAtom* atom = EnvironmentCoordinateNameSlow(script, pc); 1747 MOZ_ASSERT(atom); 1748 return write(atom); 1749 } 1750 1751 case JSOp::DelProp: 1752 case JSOp::StrictDelProp: 1753 case JSOp::GetProp: 1754 case JSOp::GetBoundName: { 1755 bool hasDelete = op == JSOp::DelProp || op == JSOp::StrictDelProp; 1756 Rooted<JSAtom*> prop(cx, loadAtom(pc)); 1757 MOZ_ASSERT(prop); 1758 return (hasDelete ? write("(delete ") : true) && 1759 decompilePCForStackOperand(pc, -1) && 1760 (IsIdentifier(prop) 1761 ? write(".") && quote(prop, '\0') 1762 : write("[") && quote(prop, '\'') && write("]")) && 1763 (hasDelete ? write(")") : true); 1764 } 1765 case JSOp::GetPropSuper: { 1766 Rooted<JSAtom*> prop(cx, loadAtom(pc)); 1767 return write("super.") && quote(prop, '\0'); 1768 } 1769 case JSOp::SetElem: 1770 case JSOp::StrictSetElem: 1771 // NOTE: We don't show the right hand side of the operation because 1772 // it's used in error messages like: "a[0] is not readable". 1773 // 1774 // We could though. 1775 return decompilePCForStackOperand(pc, -3) && write("[") && 1776 decompilePCForStackOperand(pc, -2) && write("]"); 1777 1778 case JSOp::DelElem: 1779 case JSOp::StrictDelElem: 1780 case JSOp::GetElem: { 1781 bool hasDelete = (op == JSOp::DelElem || op == JSOp::StrictDelElem); 1782 return (hasDelete ? write("(delete ") : true) && 1783 decompilePCForStackOperand(pc, -2) && write("[") && 1784 decompilePCForStackOperand(pc, -1) && write("]") && 1785 (hasDelete ? write(")") : true); 1786 } 1787 1788 case JSOp::GetElemSuper: 1789 return write("super[") && decompilePCForStackOperand(pc, -2) && 1790 write("]"); 1791 case JSOp::Null: 1792 return write("null"); 1793 case JSOp::True: 1794 return write("true"); 1795 case JSOp::False: 1796 return write("false"); 1797 case JSOp::Zero: 1798 case JSOp::One: 1799 case JSOp::Int8: 1800 case JSOp::Uint16: 1801 case JSOp::Uint24: 1802 case JSOp::Int32: 1803 sprinter.printf("%d", GetBytecodeInteger(pc)); 1804 return true; 1805 case JSOp::String: 1806 return quote(loadString(pc), '"'); 1807 case JSOp::Symbol: { 1808 unsigned i = uint8_t(pc[1]); 1809 MOZ_ASSERT(i < JS::WellKnownSymbolLimit); 1810 if (i < JS::WellKnownSymbolLimit) { 1811 return write(cx->names().wellKnownSymbolDescriptions()[i]); 1812 } 1813 break; 1814 } 1815 case JSOp::Undefined: 1816 return write("undefined"); 1817 case JSOp::GlobalThis: 1818 case JSOp::NonSyntacticGlobalThis: 1819 // |this| could convert to a very long object initialiser, so cite it by 1820 // its keyword name. 1821 return write("this"); 1822 case JSOp::NewTarget: 1823 return write("new.target"); 1824 case JSOp::ImportMeta: 1825 return write("import.meta"); 1826 case JSOp::Call: 1827 case JSOp::CallContent: 1828 case JSOp::CallIgnoresRv: 1829 case JSOp::CallIter: 1830 case JSOp::CallContentIter: { 1831 uint16_t argc = GET_ARGC(pc); 1832 return decompilePCForStackOperand(pc, -int32_t(argc + 2)) && 1833 write(argc ? "(...)" : "()"); 1834 } 1835 case JSOp::SpreadCall: 1836 return decompilePCForStackOperand(pc, -3) && write("(...)"); 1837 case JSOp::NewArray: 1838 return write("[]"); 1839 case JSOp::RegExp: { 1840 Rooted<RegExpObject*> obj(cx, &script->getObject(pc)->as<RegExpObject>()); 1841 JSString* str = RegExpObject::toString(cx, obj); 1842 if (!str) { 1843 return false; 1844 } 1845 return write(str); 1846 } 1847 case JSOp::Object: { 1848 JSObject* obj = script->getObject(pc); 1849 RootedValue objv(cx, ObjectValue(*obj)); 1850 JSString* str = ValueToSource(cx, objv); 1851 if (!str) { 1852 return false; 1853 } 1854 return write(str); 1855 } 1856 case JSOp::Void: 1857 return write("(void ") && decompilePCForStackOperand(pc, -1) && 1858 write(")"); 1859 1860 case JSOp::SuperCall: 1861 if (GET_ARGC(pc) == 0) { 1862 return write("super()"); 1863 } 1864 [[fallthrough]]; 1865 case JSOp::SpreadSuperCall: 1866 return write("super(...)"); 1867 case JSOp::SuperFun: 1868 return write("super"); 1869 1870 case JSOp::Eval: 1871 case JSOp::SpreadEval: 1872 case JSOp::StrictEval: 1873 case JSOp::StrictSpreadEval: 1874 return write("eval(...)"); 1875 1876 case JSOp::New: 1877 case JSOp::NewContent: { 1878 uint16_t argc = GET_ARGC(pc); 1879 return write("(new ") && 1880 decompilePCForStackOperand(pc, -int32_t(argc + 3)) && 1881 write(argc ? "(...))" : "())"); 1882 } 1883 1884 case JSOp::SpreadNew: 1885 return write("(new ") && decompilePCForStackOperand(pc, -4) && 1886 write("(...))"); 1887 1888 case JSOp::DynamicImport: 1889 return write("import(...)"); 1890 1891 case JSOp::Typeof: 1892 case JSOp::TypeofExpr: 1893 return write("(typeof ") && decompilePCForStackOperand(pc, -1) && 1894 write(")"); 1895 1896 case JSOp::TypeofEq: { 1897 auto operand = TypeofEqOperand::fromRawValue(GET_UINT8(pc)); 1898 JSType type = operand.type(); 1899 JSOp compareOp = operand.compareOp(); 1900 1901 return write("(typeof ") && decompilePCForStackOperand(pc, -1) && 1902 write(compareOp == JSOp::Ne ? " != \"" : " == \"") && 1903 write(JSTypeToString(type)) && write("\")"); 1904 } 1905 1906 case JSOp::StrictConstantEq: 1907 case JSOp::StrictConstantNe: { 1908 auto operand = ConstantCompareOperand::fromRawValue(GET_UINT16(pc)); 1909 return write("(") && decompilePCForStackOperand(pc, -1) && write(" ") && 1910 write(op == JSOp::StrictConstantEq ? "===" : "!==") && 1911 write(" ") && write(&operand) && write(")"); 1912 } 1913 1914 case JSOp::InitElemArray: 1915 return write("[...]"); 1916 1917 case JSOp::InitElemInc: 1918 if (defIndex == 0) { 1919 return write("[...]"); 1920 } 1921 MOZ_ASSERT(defIndex == 1); 1922 #ifdef DEBUG 1923 // INDEX won't be be exposed to error message. 1924 if (isStackDump) { 1925 return write("INDEX"); 1926 } 1927 #endif 1928 break; 1929 1930 case JSOp::ToNumeric: 1931 return write("(tonumeric ") && decompilePCForStackOperand(pc, -1) && 1932 write(")"); 1933 1934 case JSOp::Inc: 1935 return write("(inc ") && decompilePCForStackOperand(pc, -1) && write(")"); 1936 1937 case JSOp::Dec: 1938 return write("(dec ") && decompilePCForStackOperand(pc, -1) && write(")"); 1939 1940 case JSOp::BigInt: 1941 #if defined(DEBUG) || defined(JS_JITSPEW) 1942 // BigInt::dumpLiteral() only available in this configuration. 1943 script->getBigInt(pc)->dumpLiteral(sprinter); 1944 return true; 1945 #else 1946 return write("[bigint]"); 1947 #endif 1948 1949 case JSOp::BuiltinObject: { 1950 auto kind = BuiltinObjectKind(GET_UINT8(pc)); 1951 return write(BuiltinObjectName(kind)); 1952 } 1953 1954 default: 1955 break; 1956 } 1957 1958 #ifdef DEBUG 1959 if (isStackDump) { 1960 // Special decompilation for stack dump. 1961 switch (op) { 1962 case JSOp::Arguments: 1963 return write("arguments"); 1964 1965 case JSOp::ArgumentsLength: 1966 return write("arguments.length"); 1967 1968 case JSOp::GetFrameArg: 1969 sprinter.printf("arguments[%u]", GET_ARGNO(pc)); 1970 return true; 1971 1972 case JSOp::GetActualArg: 1973 return write("arguments[") && decompilePCForStackOperand(pc, -1) && 1974 write("]"); 1975 1976 case JSOp::BindUnqualifiedGName: 1977 return write("GLOBAL"); 1978 1979 case JSOp::BindName: 1980 case JSOp::BindUnqualifiedName: 1981 case JSOp::BindVar: 1982 return write("ENV"); 1983 1984 case JSOp::Callee: 1985 return write("CALLEE"); 1986 1987 case JSOp::EnvCallee: 1988 return write("ENVCALLEE"); 1989 1990 case JSOp::CallSiteObj: 1991 return write("OBJ"); 1992 1993 case JSOp::Double: 1994 sprinter.printf("%lf", GET_INLINE_VALUE(pc).toDouble()); 1995 return true; 1996 1997 case JSOp::Exception: 1998 return write("EXCEPTION"); 1999 2000 case JSOp::ExceptionAndStack: 2001 if (defIndex == 0) { 2002 return write("EXCEPTION"); 2003 } 2004 MOZ_ASSERT(defIndex == 1); 2005 return write("STACK"); 2006 2007 case JSOp::Try: 2008 // Used for the values live on entry to the finally block. 2009 // See TryNoteKind::Finally above. 2010 if (defIndex == 0) { 2011 return write("PC"); 2012 } 2013 if (defIndex == 1) { 2014 return write("STACK"); 2015 } 2016 MOZ_ASSERT(defIndex == 2); 2017 return write("THROWING"); 2018 2019 case JSOp::FunctionThis: 2020 case JSOp::ImplicitThis: 2021 return write("THIS"); 2022 2023 case JSOp::FunWithProto: 2024 return write("FUN"); 2025 2026 case JSOp::Generator: 2027 return write("GENERATOR"); 2028 2029 case JSOp::GetImport: 2030 return write("VAL"); 2031 2032 case JSOp::GetRval: 2033 return write("RVAL"); 2034 2035 case JSOp::Hole: 2036 return write("HOLE"); 2037 2038 case JSOp::IsGenClosing: 2039 // For stack dump, defIndex == 0 is not used. 2040 MOZ_ASSERT(defIndex == 1); 2041 return write("ISGENCLOSING"); 2042 2043 case JSOp::IsNoIter: 2044 // For stack dump, defIndex == 0 is not used. 2045 MOZ_ASSERT(defIndex == 1); 2046 return write("ISNOITER"); 2047 2048 case JSOp::IsConstructing: 2049 return write("JS_IS_CONSTRUCTING"); 2050 2051 case JSOp::IsNullOrUndefined: 2052 return write("IS_NULL_OR_UNDEF"); 2053 2054 case JSOp::Iter: 2055 return write("ITER"); 2056 2057 case JSOp::Lambda: 2058 return write("FUN"); 2059 2060 case JSOp::ToAsyncIter: 2061 return write("ASYNCITER"); 2062 2063 case JSOp::MoreIter: 2064 // For stack dump, defIndex == 0 is not used. 2065 MOZ_ASSERT(defIndex == 1); 2066 return write("MOREITER"); 2067 2068 case JSOp::NewInit: 2069 case JSOp::NewObject: 2070 case JSOp::ObjWithProto: 2071 return write("OBJ"); 2072 2073 case JSOp::OptimizeGetIterator: 2074 case JSOp::OptimizeSpreadCall: 2075 return write("OPTIMIZED"); 2076 2077 case JSOp::Rest: 2078 return write("REST"); 2079 2080 case JSOp::Resume: 2081 return write("RVAL"); 2082 2083 case JSOp::SuperBase: 2084 return write("HOMEOBJECTPROTO"); 2085 2086 case JSOp::ToPropertyKey: 2087 return write("TOPROPERTYKEY(") && decompilePCForStackOperand(pc, -1) && 2088 write(")"); 2089 case JSOp::ToString: 2090 return write("TOSTRING(") && decompilePCForStackOperand(pc, -1) && 2091 write(")"); 2092 2093 case JSOp::Uninitialized: 2094 return write("UNINITIALIZED"); 2095 2096 case JSOp::InitialYield: 2097 case JSOp::Await: 2098 case JSOp::Yield: 2099 // Printing "yield SOMETHING" is confusing since the operand doesn't 2100 // match to the syntax, since the stack operand for "yield 10" is 2101 // the result object, not 10. 2102 if (defIndex == 0) { 2103 return write("RVAL"); 2104 } 2105 if (defIndex == 1) { 2106 return write("GENERATOR"); 2107 } 2108 MOZ_ASSERT(defIndex == 2); 2109 return write("RESUMEKIND"); 2110 2111 case JSOp::ResumeKind: 2112 return write("RESUMEKIND"); 2113 2114 case JSOp::AsyncAwait: 2115 case JSOp::AsyncResolve: 2116 case JSOp::AsyncReject: 2117 return write("PROMISE"); 2118 2119 case JSOp::CanSkipAwait: 2120 // For stack dump, defIndex == 0 is not used. 2121 MOZ_ASSERT(defIndex == 1); 2122 return write("CAN_SKIP_AWAIT"); 2123 2124 case JSOp::MaybeExtractAwaitValue: 2125 // For stack dump, defIndex == 1 is not used. 2126 MOZ_ASSERT(defIndex == 0); 2127 return write("MAYBE_RESOLVED(") && decompilePCForStackOperand(pc, -2) && 2128 write(")"); 2129 2130 case JSOp::CheckPrivateField: 2131 return write("HasPrivateField"); 2132 2133 case JSOp::NewPrivateName: 2134 return write("PRIVATENAME"); 2135 2136 case JSOp::CheckReturn: 2137 return write("RVAL"); 2138 2139 case JSOp::HasOwn: 2140 return write("HasOwn(") && decompilePCForStackOperand(pc, -2) && 2141 write(", ") && decompilePCForStackOperand(pc, -1) && write(")"); 2142 2143 # ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT 2144 case JSOp::AddDisposable: 2145 return decompilePCForStackOperand(pc, -1); 2146 2147 case JSOp::TakeDisposeCapability: 2148 if (defIndex == 0) { 2149 return write("DISPOSECAPABILITY"); 2150 } 2151 MOZ_ASSERT(defIndex == 1); 2152 return write("COUNT"); 2153 # endif 2154 2155 default: 2156 break; 2157 } 2158 return write("<unknown>"); 2159 } 2160 #endif /* DEBUG */ 2161 2162 return write("(intermediate value)"); 2163 } 2164 2165 bool ExpressionDecompiler::decompilePC( 2166 const OffsetAndDefIndex& offsetAndDefIndex) { 2167 if (offsetAndDefIndex.isSpecial()) { 2168 #ifdef DEBUG 2169 if (isStackDump) { 2170 if (offsetAndDefIndex.isMerged()) { 2171 if (!write("merged<")) { 2172 return false; 2173 } 2174 } else if (offsetAndDefIndex.isIgnored()) { 2175 if (!write("ignored<")) { 2176 return false; 2177 } 2178 } 2179 2180 if (!decompilePC(script->offsetToPC(offsetAndDefIndex.specialOffset()), 2181 offsetAndDefIndex.specialDefIndex())) { 2182 return false; 2183 } 2184 2185 if (!write(">")) { 2186 return false; 2187 } 2188 2189 return true; 2190 } 2191 #endif /* DEBUG */ 2192 return write("(intermediate value)"); 2193 } 2194 2195 return decompilePC(script->offsetToPC(offsetAndDefIndex.offset()), 2196 offsetAndDefIndex.defIndex()); 2197 } 2198 2199 bool ExpressionDecompiler::init() { 2200 cx->check(script); 2201 return sprinter.init(); 2202 } 2203 2204 bool ExpressionDecompiler::write(const char* s) { 2205 sprinter.put(s); 2206 return true; 2207 } 2208 2209 bool ExpressionDecompiler::write(JSString* str) { 2210 if (str == cx->names().dot_this_) { 2211 return write("this"); 2212 } 2213 if (str == cx->names().dot_newTarget_) { 2214 return write("new.target"); 2215 } 2216 sprinter.putString(cx, str); 2217 return true; 2218 } 2219 2220 bool ExpressionDecompiler::quote(JSString* s, char quote) { 2221 QuoteString(&sprinter, s, quote); 2222 return true; 2223 } 2224 2225 JSAtom* ExpressionDecompiler::loadAtom(jsbytecode* pc) { 2226 return script->getAtom(pc); 2227 } 2228 2229 bool ExpressionDecompiler::write(ConstantCompareOperand* operand) { 2230 switch (operand->type()) { 2231 case ConstantCompareOperand::EncodedType::Int32: { 2232 sprinter.printf("%d", operand->toInt32()); 2233 return true; 2234 } 2235 case ConstantCompareOperand::EncodedType::Boolean: { 2236 return write(operand->toBoolean() ? "true" : "false"); 2237 } 2238 case ConstantCompareOperand::EncodedType::Null: { 2239 return write("null"); 2240 } 2241 case ConstantCompareOperand::EncodedType::Undefined: { 2242 return write("undefined"); 2243 } 2244 } 2245 MOZ_CRASH("Unknown constant compare operand type"); 2246 } 2247 2248 JSString* ExpressionDecompiler::loadString(jsbytecode* pc) { 2249 return script->getString(pc); 2250 } 2251 2252 JSAtom* ExpressionDecompiler::getArg(unsigned slot) { 2253 MOZ_ASSERT(script->isFunction()); 2254 MOZ_ASSERT(slot < script->numArgs()); 2255 2256 for (PositionalFormalParameterIter fi(script); fi; fi++) { 2257 if (fi.argumentSlot() == slot) { 2258 if (!fi.isDestructured()) { 2259 return fi.name(); 2260 } 2261 2262 // Destructured arguments have no single binding name. 2263 static const char destructuredParam[] = "(destructured parameter)"; 2264 return Atomize(cx, destructuredParam, strlen(destructuredParam)); 2265 } 2266 } 2267 2268 MOZ_CRASH("No binding"); 2269 } 2270 2271 UniqueChars ExpressionDecompiler::getOutput() { return sprinter.release(); } 2272 2273 } // anonymous namespace 2274 2275 #if defined(DEBUG) || defined(JS_JITSPEW) 2276 static bool DecompileAtPCForStackDump( 2277 JSContext* cx, HandleScript script, 2278 const OffsetAndDefIndex& offsetAndDefIndex, StringPrinter* sp) { 2279 // The expression decompiler asserts the script is in the current realm. 2280 AutoRealm ar(cx, script); 2281 2282 LifoAllocScope allocScope(&cx->tempLifoAlloc()); 2283 BytecodeParser parser(cx, allocScope.alloc(), script); 2284 parser.setStackDump(); 2285 if (!parser.parse()) { 2286 return false; 2287 } 2288 2289 ExpressionDecompiler ed(cx, script, parser); 2290 ed.setStackDump(); 2291 if (!ed.init()) { 2292 return false; 2293 } 2294 2295 if (!ed.decompilePC(offsetAndDefIndex)) { 2296 return false; 2297 } 2298 2299 UniqueChars result = ed.getOutput(); 2300 if (!result) { 2301 return false; 2302 } 2303 2304 sp->put(result.get()); 2305 return true; 2306 } 2307 #endif /* defined(DEBUG) || defined(JS_JITSPEW) */ 2308 2309 static bool FindStartPC(JSContext* cx, const FrameIter& iter, 2310 const BytecodeParser& parser, int spindex, 2311 int skipStackHits, const Value& v, jsbytecode** valuepc, 2312 uint8_t* defIndex) { 2313 jsbytecode* current = *valuepc; 2314 *valuepc = nullptr; 2315 *defIndex = 0; 2316 2317 if (spindex < 0 && spindex + int(parser.stackDepthAtPC(current)) < 0) { 2318 spindex = JSDVG_SEARCH_STACK; 2319 } 2320 2321 if (spindex == JSDVG_SEARCH_STACK) { 2322 size_t index = iter.numFrameSlots(); 2323 2324 // The decompiler may be called from inside functions that are not 2325 // called from script, but via the C++ API directly, such as 2326 // Invoke. In that case, the youngest script frame may have a 2327 // completely unrelated pc and stack depth, so we give up. 2328 if (index < size_t(parser.stackDepthAtPC(current))) { 2329 return true; 2330 } 2331 2332 // We search from fp->sp to base to find the most recently calculated 2333 // value matching v under assumption that it is the value that caused 2334 // the exception. 2335 int stackHits = 0; 2336 Value s; 2337 do { 2338 if (!index) { 2339 return true; 2340 } 2341 s = iter.frameSlotValue(--index); 2342 } while (s != v || stackHits++ != skipStackHits); 2343 2344 // If the current PC has fewer values on the stack than the index we are 2345 // looking for, the blamed value must be one pushed by the current 2346 // bytecode (e.g. JSOp::MoreIter), so restore *valuepc. 2347 if (index < size_t(parser.stackDepthAtPC(current))) { 2348 *valuepc = parser.pcForStackOperand(current, index, defIndex); 2349 } else { 2350 *valuepc = current; 2351 *defIndex = index - size_t(parser.stackDepthAtPC(current)); 2352 } 2353 } else { 2354 *valuepc = parser.pcForStackOperand(current, spindex, defIndex); 2355 } 2356 return true; 2357 } 2358 2359 static bool DecompileExpressionFromStack(JSContext* cx, int spindex, 2360 int skipStackHits, HandleValue v, 2361 UniqueChars* res) { 2362 MOZ_ASSERT(spindex < 0 || spindex == JSDVG_IGNORE_STACK || 2363 spindex == JSDVG_SEARCH_STACK); 2364 2365 *res = nullptr; 2366 2367 /* 2368 * Give up if we need deterministic behavior for differential testing. 2369 * IonMonkey doesn't use InterpreterFrames and this ensures we get the same 2370 * error messages. 2371 */ 2372 if (js::SupportDifferentialTesting()) { 2373 return true; 2374 } 2375 2376 if (spindex == JSDVG_IGNORE_STACK) { 2377 return true; 2378 } 2379 2380 FrameIter frameIter(cx); 2381 2382 if (frameIter.done() || !frameIter.hasScript() || 2383 frameIter.realm() != cx->realm() || frameIter.inPrologue()) { 2384 return true; 2385 } 2386 2387 /* 2388 * FIXME: Fall back if iter.isIon(), since the stack snapshot may be for the 2389 * previous pc (see bug 831120). 2390 */ 2391 if (frameIter.isIon()) { 2392 return true; 2393 } 2394 2395 RootedScript script(cx, frameIter.script()); 2396 jsbytecode* valuepc = frameIter.pc(); 2397 2398 MOZ_ASSERT(script->containsPC(valuepc)); 2399 2400 LifoAllocScope allocScope(&cx->tempLifoAlloc()); 2401 BytecodeParser parser(cx, allocScope.alloc(), frameIter.script()); 2402 if (!parser.parse()) { 2403 return false; 2404 } 2405 2406 uint8_t defIndex; 2407 if (!FindStartPC(cx, frameIter, parser, spindex, skipStackHits, v, &valuepc, 2408 &defIndex)) { 2409 return false; 2410 } 2411 if (!valuepc) { 2412 return true; 2413 } 2414 2415 ExpressionDecompiler ed(cx, script, parser); 2416 if (!ed.init()) { 2417 return false; 2418 } 2419 if (!ed.decompilePC(valuepc, defIndex)) { 2420 return false; 2421 } 2422 2423 *res = ed.getOutput(); 2424 return *res != nullptr; 2425 } 2426 2427 UniqueChars js::DecompileValueGenerator(JSContext* cx, int spindex, 2428 HandleValue v, HandleString fallbackArg, 2429 int skipStackHits) { 2430 RootedString fallback(cx, fallbackArg); 2431 { 2432 UniqueChars result; 2433 if (!DecompileExpressionFromStack(cx, spindex, skipStackHits, v, &result)) { 2434 return nullptr; 2435 } 2436 if (result && strcmp(result.get(), "(intermediate value)")) { 2437 return result; 2438 } 2439 } 2440 if (!fallback) { 2441 if (v.isUndefined()) { 2442 return DuplicateString(cx, "undefined"); // Prevent users from seeing 2443 // "(void 0)" 2444 } 2445 fallback = ValueToSource(cx, v); 2446 if (!fallback) { 2447 return nullptr; 2448 } 2449 } 2450 2451 return StringToNewUTF8CharsZ(cx, *fallback); 2452 } 2453 2454 static bool DecompileArgumentFromStack(JSContext* cx, int formalIndex, 2455 UniqueChars* res) { 2456 MOZ_ASSERT(formalIndex >= 0); 2457 2458 *res = nullptr; 2459 2460 /* See note in DecompileExpressionFromStack. */ 2461 if (js::SupportDifferentialTesting()) { 2462 return true; 2463 } 2464 2465 /* 2466 * Settle on the nearest script frame, which should be the builtin that 2467 * called the intrinsic. 2468 */ 2469 FrameIter frameIter(cx); 2470 MOZ_ASSERT(!frameIter.done()); 2471 MOZ_ASSERT(frameIter.script()->selfHosted()); 2472 2473 /* 2474 * Get the second-to-top frame, the non-self-hosted caller of the builtin 2475 * that called the intrinsic. 2476 */ 2477 ++frameIter; 2478 if (frameIter.done() || !frameIter.hasScript() || 2479 frameIter.script()->selfHosted() || frameIter.realm() != cx->realm()) { 2480 return true; 2481 } 2482 2483 RootedScript script(cx, frameIter.script()); 2484 jsbytecode* current = frameIter.pc(); 2485 2486 MOZ_ASSERT(script->containsPC(current)); 2487 2488 if (current < script->main()) { 2489 return true; 2490 } 2491 2492 /* Don't handle getters, setters or calls from fun.call/fun.apply. */ 2493 JSOp op = JSOp(*current); 2494 if (op != JSOp::Call && op != JSOp::CallContent && 2495 op != JSOp::CallIgnoresRv && op != JSOp::New && op != JSOp::NewContent) { 2496 return true; 2497 } 2498 2499 if (static_cast<unsigned>(formalIndex) >= GET_ARGC(current)) { 2500 return true; 2501 } 2502 2503 LifoAllocScope allocScope(&cx->tempLifoAlloc()); 2504 BytecodeParser parser(cx, allocScope.alloc(), script); 2505 if (!parser.parse()) { 2506 return false; 2507 } 2508 2509 bool pushedNewTarget = op == JSOp::New || op == JSOp::NewContent; 2510 int formalStackIndex = parser.stackDepthAtPC(current) - GET_ARGC(current) - 2511 pushedNewTarget + formalIndex; 2512 MOZ_ASSERT(formalStackIndex >= 0); 2513 if (uint32_t(formalStackIndex) >= parser.stackDepthAtPC(current)) { 2514 return true; 2515 } 2516 2517 ExpressionDecompiler ed(cx, script, parser); 2518 if (!ed.init()) { 2519 return false; 2520 } 2521 if (!ed.decompilePCForStackOperand(current, formalStackIndex)) { 2522 return false; 2523 } 2524 2525 *res = ed.getOutput(); 2526 return *res != nullptr; 2527 } 2528 2529 JSString* js::DecompileArgument(JSContext* cx, int formalIndex, HandleValue v) { 2530 { 2531 UniqueChars result; 2532 if (!DecompileArgumentFromStack(cx, formalIndex, &result)) { 2533 return nullptr; 2534 } 2535 if (result && strcmp(result.get(), "(intermediate value)")) { 2536 JS::ConstUTF8CharsZ utf8chars(result.get(), strlen(result.get())); 2537 return NewStringCopyUTF8Z(cx, utf8chars); 2538 } 2539 } 2540 if (v.isUndefined()) { 2541 return cx->names().undefined; // Prevent users from seeing "(void 0)" 2542 } 2543 2544 return ValueToSource(cx, v); 2545 } 2546 2547 extern bool js::IsValidBytecodeOffset(JSContext* cx, JSScript* script, 2548 size_t offset) { 2549 // This could be faster (by following jump instructions if the target 2550 // is <= offset). 2551 for (BytecodeRange r(cx, script); !r.empty(); r.popFront()) { 2552 size_t here = r.frontOffset(); 2553 if (here >= offset) { 2554 return here == offset; 2555 } 2556 } 2557 return false; 2558 } 2559 2560 /* 2561 * There are three possible PCCount profiling states: 2562 * 2563 * 1. None: Neither scripts nor the runtime have count information. 2564 * 2. Profile: Active scripts have count information, the runtime does not. 2565 * 3. Query: Scripts do not have count information, the runtime does. 2566 * 2567 * When starting to profile scripts, counting begins immediately, with all JIT 2568 * code discarded and recompiled with counts as necessary. Active interpreter 2569 * frames will not begin profiling until they begin executing another script 2570 * (via a call or return). 2571 * 2572 * The below API functions manage transitions to new states, according 2573 * to the table below. 2574 * 2575 * Old State 2576 * ------------------------- 2577 * Function None Profile Query 2578 * -------- 2579 * StartPCCountProfiling Profile Profile Profile 2580 * StopPCCountProfiling None Query Query 2581 * PurgePCCounts None None None 2582 */ 2583 2584 static void ReleaseScriptCounts(JSRuntime* rt) { 2585 MOZ_ASSERT(rt->scriptAndCountsVector); 2586 2587 js_delete(rt->scriptAndCountsVector.ref()); 2588 rt->scriptAndCountsVector = nullptr; 2589 } 2590 2591 void JS::StartPCCountProfiling(JSContext* cx) { 2592 JSRuntime* rt = cx->runtime(); 2593 2594 if (rt->profilingScripts) { 2595 return; 2596 } 2597 2598 if (rt->scriptAndCountsVector) { 2599 ReleaseScriptCounts(rt); 2600 } 2601 2602 ReleaseAllJITCode(rt->gcContext()); 2603 2604 rt->profilingScripts = true; 2605 } 2606 2607 void JS::StopPCCountProfiling(JSContext* cx) { 2608 JSRuntime* rt = cx->runtime(); 2609 2610 if (!rt->profilingScripts) { 2611 return; 2612 } 2613 MOZ_ASSERT(!rt->scriptAndCountsVector); 2614 2615 ReleaseAllJITCode(rt->gcContext()); 2616 2617 auto* vec = cx->new_<PersistentRooted<ScriptAndCountsVector>>( 2618 cx, ScriptAndCountsVector()); 2619 if (!vec) { 2620 return; 2621 } 2622 2623 for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) { 2624 for (auto base = zone->cellIter<BaseScript>(); !base.done(); base.next()) { 2625 if (base->hasScriptCounts() && base->hasJitScript()) { 2626 if (!vec->append(base->asJSScript())) { 2627 return; 2628 } 2629 } 2630 } 2631 } 2632 2633 rt->profilingScripts = false; 2634 rt->scriptAndCountsVector = vec; 2635 } 2636 2637 void JS::PurgePCCounts(JSContext* cx) { 2638 JSRuntime* rt = cx->runtime(); 2639 2640 if (!rt->scriptAndCountsVector) { 2641 return; 2642 } 2643 MOZ_ASSERT(!rt->profilingScripts); 2644 2645 ReleaseScriptCounts(rt); 2646 } 2647 2648 size_t JS::GetPCCountScriptCount(JSContext* cx) { 2649 JSRuntime* rt = cx->runtime(); 2650 2651 if (!rt->scriptAndCountsVector) { 2652 return 0; 2653 } 2654 2655 return rt->scriptAndCountsVector->length(); 2656 } 2657 2658 [[nodiscard]] static bool JSONStringProperty(StringPrinter& sp, 2659 JSONPrinter& json, 2660 const char* name, JSString* str) { 2661 json.beginStringProperty(name); 2662 JSONQuoteString(&sp, str); 2663 json.endStringProperty(); 2664 return true; 2665 } 2666 2667 JSString* JS::GetPCCountScriptSummary(JSContext* cx, size_t index) { 2668 JSRuntime* rt = cx->runtime(); 2669 2670 if (!rt->scriptAndCountsVector || 2671 index >= rt->scriptAndCountsVector->length()) { 2672 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 2673 JSMSG_BUFFER_TOO_SMALL); 2674 return nullptr; 2675 } 2676 2677 const ScriptAndCounts& sac = (*rt->scriptAndCountsVector)[index]; 2678 RootedScript script(cx, sac.script); 2679 2680 JSSprinter sp(cx); 2681 if (!sp.init()) { 2682 return nullptr; 2683 } 2684 2685 JSONPrinter json(sp, false); 2686 2687 json.beginObject(); 2688 2689 Rooted<JSString*> filenameStr(cx); 2690 if (const char* filename = script->filename()) { 2691 filenameStr = 2692 JS_NewStringCopyUTF8N(cx, JS::UTF8Chars(filename, strlen(filename))); 2693 } else { 2694 filenameStr = JS_GetEmptyString(cx); 2695 } 2696 if (!filenameStr) { 2697 return nullptr; 2698 } 2699 if (!JSONStringProperty(sp, json, "file", filenameStr)) { 2700 return nullptr; 2701 } 2702 json.property("line", script->lineno()); 2703 2704 if (JSFunction* fun = script->function()) { 2705 if (JSAtom* atom = fun->fullDisplayAtom()) { 2706 if (!JSONStringProperty(sp, json, "name", atom)) { 2707 return nullptr; 2708 } 2709 } 2710 } 2711 2712 uint64_t total = 0; 2713 2714 AllBytecodesIterable iter(script); 2715 for (BytecodeLocation loc : iter) { 2716 if (const PCCounts* counts = sac.maybeGetPCCounts(loc.toRawBytecode())) { 2717 total += counts->numExec(); 2718 } 2719 } 2720 2721 json.beginObjectProperty("totals"); 2722 2723 json.property(PCCounts::numExecName, total); 2724 2725 uint64_t ionActivity = 0; 2726 jit::IonScriptCounts* ionCounts = sac.getIonCounts(); 2727 while (ionCounts) { 2728 for (size_t i = 0; i < ionCounts->numBlocks(); i++) { 2729 ionActivity += ionCounts->block(i).hitCount(); 2730 } 2731 ionCounts = ionCounts->previous(); 2732 } 2733 if (ionActivity) { 2734 json.property("ion", ionActivity); 2735 } 2736 2737 json.endObject(); 2738 2739 json.endObject(); 2740 2741 return sp.release(cx); 2742 } 2743 2744 static bool GetPCCountJSON(JSContext* cx, const ScriptAndCounts& sac, 2745 StringPrinter& sp) { 2746 JSONPrinter json(sp, false); 2747 2748 RootedScript script(cx, sac.script); 2749 2750 LifoAllocScope allocScope(&cx->tempLifoAlloc()); 2751 BytecodeParser parser(cx, allocScope.alloc(), script); 2752 if (!parser.parse()) { 2753 return false; 2754 } 2755 2756 json.beginObject(); 2757 2758 JSString* str = JS_DecompileScript(cx, script); 2759 if (!str) { 2760 return false; 2761 } 2762 2763 if (!JSONStringProperty(sp, json, "text", str)) { 2764 return false; 2765 } 2766 2767 json.property("line", script->lineno()); 2768 2769 json.beginListProperty("opcodes"); 2770 2771 uint64_t hits = 0; 2772 for (BytecodeRangeWithPosition range(cx, script, SkipPrologueOps::Yes); 2773 !range.empty(); range.popFront()) { 2774 jsbytecode* pc = range.frontPC(); 2775 size_t offset = script->pcToOffset(pc); 2776 JSOp op = JSOp(*pc); 2777 2778 // If the current instruction is a jump target, 2779 // then update the number of hits. 2780 if (const PCCounts* counts = sac.maybeGetPCCounts(pc)) { 2781 hits = counts->numExec(); 2782 } 2783 2784 json.beginObject(); 2785 2786 json.property("id", offset); 2787 json.property("line", range.frontLineNumber()); 2788 json.property("name", CodeName(op)); 2789 2790 { 2791 ExpressionDecompiler ed(cx, script, parser); 2792 if (!ed.init()) { 2793 return false; 2794 } 2795 // defIndex passed here is not used. 2796 if (!ed.decompilePC(pc, /* defIndex = */ 0)) { 2797 return false; 2798 } 2799 UniqueChars text = ed.getOutput(); 2800 if (!text) { 2801 return false; 2802 } 2803 2804 JS::ConstUTF8CharsZ utf8chars(text.get(), strlen(text.get())); 2805 JSString* str = NewStringCopyUTF8Z(cx, utf8chars); 2806 if (!str) { 2807 return false; 2808 } 2809 2810 if (!JSONStringProperty(sp, json, "text", str)) { 2811 return false; 2812 } 2813 } 2814 2815 json.beginObjectProperty("counts"); 2816 if (hits > 0) { 2817 json.property(PCCounts::numExecName, hits); 2818 } 2819 json.endObject(); 2820 2821 json.endObject(); 2822 2823 // If the current instruction has thrown, 2824 // then decrement the hit counts with the number of throws. 2825 if (const PCCounts* counts = sac.maybeGetThrowCounts(pc)) { 2826 hits -= counts->numExec(); 2827 } 2828 } 2829 2830 json.endList(); 2831 2832 if (jit::IonScriptCounts* ionCounts = sac.getIonCounts()) { 2833 json.beginListProperty("ion"); 2834 2835 while (ionCounts) { 2836 json.beginList(); 2837 for (size_t i = 0; i < ionCounts->numBlocks(); i++) { 2838 const jit::IonBlockCounts& block = ionCounts->block(i); 2839 2840 json.beginObject(); 2841 json.property("id", block.id()); 2842 json.property("offset", block.offset()); 2843 2844 json.beginListProperty("successors"); 2845 for (size_t j = 0; j < block.numSuccessors(); j++) { 2846 json.value(block.successor(j)); 2847 } 2848 json.endList(); 2849 2850 json.property("hits", block.hitCount()); 2851 2852 JSString* str = NewStringCopyZ<CanGC>(cx, block.code()); 2853 if (!str) { 2854 return false; 2855 } 2856 2857 if (!JSONStringProperty(sp, json, "code", str)) { 2858 return false; 2859 } 2860 2861 json.endObject(); 2862 } 2863 json.endList(); 2864 2865 ionCounts = ionCounts->previous(); 2866 } 2867 2868 json.endList(); 2869 } 2870 2871 json.endObject(); 2872 2873 return true; 2874 } 2875 2876 JSString* JS::GetPCCountScriptContents(JSContext* cx, size_t index) { 2877 JSRuntime* rt = cx->runtime(); 2878 2879 if (!rt->scriptAndCountsVector || 2880 index >= rt->scriptAndCountsVector->length()) { 2881 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 2882 JSMSG_BUFFER_TOO_SMALL); 2883 return nullptr; 2884 } 2885 2886 const ScriptAndCounts& sac = (*rt->scriptAndCountsVector)[index]; 2887 JSScript* script = sac.script; 2888 2889 JSSprinter sp(cx); 2890 if (!sp.init()) { 2891 return nullptr; 2892 } 2893 2894 { 2895 AutoRealm ar(cx, &script->global()); 2896 if (!GetPCCountJSON(cx, sac, sp)) { 2897 return nullptr; 2898 } 2899 } 2900 2901 return sp.release(cx); 2902 } 2903 2904 struct CollectedScripts { 2905 MutableHandle<ScriptVector> scripts; 2906 bool ok = true; 2907 2908 explicit CollectedScripts(MutableHandle<ScriptVector> scripts) 2909 : scripts(scripts) {} 2910 2911 static void consider(JSRuntime* rt, void* data, BaseScript* script, 2912 const JS::AutoRequireNoGC& nogc) { 2913 auto self = static_cast<CollectedScripts*>(data); 2914 if (!script->filename()) { 2915 return; 2916 } 2917 if (!self->scripts.append(script->asJSScript())) { 2918 self->ok = false; 2919 } 2920 } 2921 }; 2922 2923 static bool GenerateLcovInfo(JSContext* cx, JS::Realm* realm, 2924 GenericPrinter& out) { 2925 AutoRealmUnchecked ar(cx, realm); 2926 2927 // Collect the list of scripts which are part of the current realm. 2928 2929 MOZ_RELEASE_ASSERT( 2930 coverage::IsLCovEnabled(), 2931 "Coverage must be enabled for process before generating LCov info"); 2932 2933 // Hold the scripts that we have already flushed, to avoid flushing them 2934 // twice. 2935 using JSScriptSet = GCHashSet<JSScript*>; 2936 Rooted<JSScriptSet> scriptsDone(cx, JSScriptSet(cx)); 2937 2938 Rooted<ScriptVector> queue(cx, ScriptVector(cx)); 2939 2940 { 2941 CollectedScripts result(&queue); 2942 IterateScripts(cx, realm, &result, &CollectedScripts::consider); 2943 if (!result.ok) { 2944 ReportOutOfMemory(cx); 2945 return false; 2946 } 2947 } 2948 2949 if (queue.length() == 0) { 2950 return true; 2951 } 2952 2953 // Ensure the LCovRealm exists to collect info into. 2954 coverage::LCovRealm* lcovRealm = realm->lcovRealm(); 2955 if (!lcovRealm) { 2956 return false; 2957 } 2958 2959 // Collect code coverage info for one realm. 2960 do { 2961 RootedScript script(cx, queue.popCopy()); 2962 RootedFunction fun(cx); 2963 2964 JSScriptSet::AddPtr entry = scriptsDone.lookupForAdd(script); 2965 if (entry) { 2966 continue; 2967 } 2968 2969 if (!coverage::CollectScriptCoverage(script, false)) { 2970 ReportOutOfMemory(cx); 2971 return false; 2972 } 2973 2974 script->resetScriptCounts(); 2975 2976 if (!scriptsDone.add(entry, script)) { 2977 return false; 2978 } 2979 2980 if (!script->isTopLevel()) { 2981 continue; 2982 } 2983 2984 // Iterate from the last to the first object in order to have 2985 // the functions them visited in the opposite order when popping 2986 // elements from the stack of remaining scripts, such that the 2987 // functions are more-less listed with increasing line numbers. 2988 auto gcthings = script->gcthings(); 2989 for (JS::GCCellPtr gcThing : mozilla::Reversed(gcthings)) { 2990 if (!gcThing.is<JSObject>()) { 2991 continue; 2992 } 2993 JSObject* obj = &gcThing.as<JSObject>(); 2994 2995 if (!obj->is<JSFunction>()) { 2996 continue; 2997 } 2998 fun = &obj->as<JSFunction>(); 2999 3000 // Ignore asm.js functions 3001 if (!fun->isInterpreted()) { 3002 continue; 3003 } 3004 3005 // Queue the script in the list of script associated to the 3006 // current source. 3007 JSScript* childScript = JSFunction::getOrCreateScript(cx, fun); 3008 if (!childScript || !queue.append(childScript)) { 3009 return false; 3010 } 3011 } 3012 } while (!queue.empty()); 3013 3014 bool isEmpty = true; 3015 lcovRealm->exportInto(out, &isEmpty); 3016 return true; 3017 } 3018 3019 JS_PUBLIC_API UniqueChars js::GetCodeCoverageSummaryAll(JSContext* cx, 3020 size_t* length) { 3021 Sprinter out(cx); 3022 if (!out.init()) { 3023 return nullptr; 3024 } 3025 3026 for (RealmsIter realm(cx->runtime()); !realm.done(); realm.next()) { 3027 if (!GenerateLcovInfo(cx, realm, out)) { 3028 return nullptr; 3029 } 3030 } 3031 3032 *length = out.length(); 3033 return out.release(); 3034 } 3035 3036 JS_PUBLIC_API UniqueChars js::GetCodeCoverageSummary(JSContext* cx, 3037 size_t* length) { 3038 Sprinter out(cx); 3039 if (!out.init()) { 3040 return nullptr; 3041 } 3042 3043 if (!GenerateLcovInfo(cx, cx->realm(), out)) { 3044 return nullptr; 3045 } 3046 3047 *length = out.length(); 3048 return out.release(); 3049 }