DumpFunctions.cpp (18796B)
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 "js/friend/DumpFunctions.h" 8 9 #include "mozilla/Maybe.h" // mozilla::Maybe 10 11 #include <inttypes.h> // PRIu64 12 #include <stddef.h> // size_t 13 #include <stdio.h> // fprintf, fflush 14 15 #include "jsfriendapi.h" // js::WeakMapTracer 16 #include "jstypes.h" // JS_PUBLIC_API 17 18 #include "gc/Cell.h" // js::gc::Cell, js::gc::TenuredCell 19 #include "gc/GC.h" // js::TraceRuntimeWithoutEviction 20 #include "gc/GCEnum.h" // js::CanGC 21 #include "gc/Heap.h" // js::gc::Arena 22 #include "gc/Tracer.h" // js::TraceChildren 23 #include "gc/WeakMap.h" // js::IterateHeapUnbarriered, js::WeakMapBase 24 #include "js/CallAndConstruct.h" // JS::IsCallable 25 #include "js/ColumnNumber.h" // JS::LimitedColumnNumberOneOrigin 26 #include "js/GCAPI.h" // JS::GCReason 27 #include "js/GCVector.h" // JS::RootedVector 28 #include "js/HeapAPI.h" // JS::GCCellPtr, js::gc::IsInsideNursery 29 #include "js/Id.h" // JS::PropertyKey 30 #include "js/Printer.h" // js::GenericPrinter, js::QuoteString, js::Sprinter 31 #include "js/RootingAPI.h" // JS::Handle, JS::Rooted 32 #include "js/TracingAPI.h" // JS::CallbackTracer, JS_GetTraceThingInfo 33 #include "js/UbiNode.h" // JS::ubi::Node 34 #include "js/Value.h" // JS::Value 35 #include "js/Wrapper.h" // js::UncheckedUnwrapWithoutExpose 36 #include "vm/BigIntType.h" // JS::BigInt::dump 37 #include "vm/FrameIter.h" // js::AllFramesIter, js::FrameIter 38 #include "vm/Interpreter.h" // GetFunctionThis 39 #include "vm/JSContext.h" // JSContext 40 #include "vm/JSFunction.h" // JSFunction 41 #include "vm/JSObject.h" // JSObject 42 #include "vm/JSScript.h" // JSScript 43 #include "vm/Realm.h" // JS::Realm 44 #include "vm/Runtime.h" // JSRuntime 45 #include "vm/Scope.h" // js::PositionalFormalParameterIter 46 #include "vm/Stack.h" // js::DONT_CHECK_ALIASING 47 #include "vm/StringType.h" // JSAtom, JSString, js::ToString 48 49 #include "vm/JSObject-inl.h" // js::IsCallable 50 #include "vm/Realm-inl.h" // js::AutoRealm 51 52 namespace JS { 53 54 class JS_PUBLIC_API AutoRequireNoGC; 55 class JS_PUBLIC_API Zone; 56 57 } // namespace JS 58 59 using JS::Handle; 60 using JS::MagicValue; 61 using JS::PropertyKey; 62 using JS::Realm; 63 using JS::Rooted; 64 using JS::RootedVector; 65 using JS::UniqueChars; 66 using JS::Value; 67 using JS::Zone; 68 69 using js::AllFramesIter; 70 using js::AutoRealm; 71 using js::CanGC; 72 using js::DONT_CHECK_ALIASING; 73 using js::FrameIter; 74 using js::PositionalFormalParameterIter; 75 using js::Sprinter; 76 using js::ToString; 77 using js::UncheckedUnwrapWithoutExpose; 78 using js::WeakMapTracer; 79 80 // We don't want jsfriendapi.h to depend on GenericPrinter, 81 // so these functions are declared directly in the cpp. 82 83 namespace js { 84 85 class InterpreterFrame; 86 87 extern JS_PUBLIC_API void DumpString(JSString* str, GenericPrinter& out); 88 89 extern JS_PUBLIC_API void DumpAtom(JSAtom* atom, GenericPrinter& out); 90 91 extern JS_PUBLIC_API void DumpObject(JSObject* obj, GenericPrinter& out); 92 93 extern JS_PUBLIC_API void DumpChars(const char16_t* s, size_t n, 94 GenericPrinter& out); 95 96 extern JS_PUBLIC_API void DumpValue(const JS::Value& val, GenericPrinter& out); 97 98 extern JS_PUBLIC_API void DumpId(PropertyKey id, GenericPrinter& out); 99 100 extern JS_PUBLIC_API void DumpInterpreterFrame( 101 JSContext* cx, GenericPrinter& out, InterpreterFrame* start = nullptr); 102 103 extern JS_PUBLIC_API void DumpBigInt(JS::BigInt* bi, GenericPrinter& out); 104 105 } // namespace js 106 107 void js::DumpString(JSString* str, GenericPrinter& out) { 108 #if defined(DEBUG) || defined(JS_JITSPEW) 109 str->dump(out); 110 #endif 111 } 112 113 void js::DumpAtom(JSAtom* atom, GenericPrinter& out) { 114 #if defined(DEBUG) || defined(JS_JITSPEW) 115 atom->dump(out); 116 #endif 117 } 118 119 void js::DumpChars(const char16_t* s, size_t n, GenericPrinter& out) { 120 #if defined(DEBUG) || defined(JS_JITSPEW) 121 if (n == SIZE_MAX) { 122 n = 0; 123 while (s[n]) { 124 n++; 125 } 126 } 127 128 out.printf("char16_t * (%p) = \"", (void*)s); 129 JSString::dumpCharsNoQuote(s, n, out); 130 out.put("\"\n"); 131 #endif 132 } 133 134 void js::DumpObject(JSObject* obj, GenericPrinter& out) { 135 #if defined(DEBUG) || defined(JS_JITSPEW) 136 if (!obj) { 137 out.printf("NULL\n"); 138 return; 139 } 140 obj->dump(out); 141 #endif 142 } 143 144 void js::DumpBigInt(JS::BigInt* bi, GenericPrinter& out) { 145 #if defined(DEBUG) || defined(JS_JITSPEW) 146 bi->dump(out); 147 #endif 148 } 149 150 void js::DumpString(JSString* str, FILE* fp) { 151 #if defined(DEBUG) || defined(JS_JITSPEW) 152 Fprinter out(fp); 153 js::DumpString(str, out); 154 #endif 155 } 156 157 void js::DumpAtom(JSAtom* atom, FILE* fp) { 158 #if defined(DEBUG) || defined(JS_JITSPEW) 159 Fprinter out(fp); 160 js::DumpAtom(atom, out); 161 #endif 162 } 163 164 void js::DumpChars(const char16_t* s, size_t n, FILE* fp) { 165 #if defined(DEBUG) || defined(JS_JITSPEW) 166 Fprinter out(fp); 167 js::DumpChars(s, n, out); 168 #endif 169 } 170 171 void js::DumpObject(JSObject* obj, FILE* fp) { 172 #if defined(DEBUG) || defined(JS_JITSPEW) 173 Fprinter out(fp); 174 js::DumpObject(obj, out); 175 #endif 176 } 177 178 void js::DumpBigInt(JS::BigInt* bi, FILE* fp) { 179 #if defined(DEBUG) || defined(JS_JITSPEW) 180 Fprinter out(fp); 181 js::DumpBigInt(bi, out); 182 #endif 183 } 184 185 void js::DumpId(PropertyKey id, FILE* fp) { 186 #if defined(DEBUG) || defined(JS_JITSPEW) 187 Fprinter out(fp); 188 js::DumpId(id, out); 189 #endif 190 } 191 192 void js::DumpValue(const JS::Value& val, FILE* fp) { 193 #if defined(DEBUG) || defined(JS_JITSPEW) 194 Fprinter out(fp); 195 js::DumpValue(val, out); 196 #endif 197 } 198 199 void js::DumpString(JSString* str) { DumpString(str, stderr); } 200 void js::DumpAtom(JSAtom* atom) { DumpAtom(atom, stderr); } 201 void js::DumpObject(JSObject* obj) { DumpObject(obj, stderr); } 202 void js::DumpChars(const char16_t* s, size_t n) { DumpChars(s, n, stderr); } 203 void js::DumpBigInt(JS::BigInt* bi) { DumpBigInt(bi, stderr); } 204 void js::DumpValue(const JS::Value& val) { DumpValue(val, stderr); } 205 void js::DumpId(PropertyKey id) { DumpId(id, stderr); } 206 void js::DumpInterpreterFrame(JSContext* cx, InterpreterFrame* start) { 207 #if defined(DEBUG) || defined(JS_JITSPEW) 208 Fprinter out(stderr); 209 DumpInterpreterFrame(cx, out, start); 210 #endif 211 } 212 213 bool js::DumpPC(JSContext* cx) { 214 #if defined(DEBUG) || defined(JS_JITSPEW) 215 return DumpPC(cx, stdout); 216 #else 217 return true; 218 #endif 219 } 220 221 bool js::DumpScript(JSContext* cx, JSScript* scriptArg) { 222 #if defined(DEBUG) || defined(JS_JITSPEW) 223 return DumpScript(cx, scriptArg, stdout); 224 #else 225 return true; 226 #endif 227 } 228 229 static const char* FormatValue(JSContext* cx, Handle<Value> v, 230 UniqueChars& bytes) { 231 if (v.isMagic()) { 232 MOZ_ASSERT(v.whyMagic() == JS_OPTIMIZED_OUT || 233 v.whyMagic() == JS_UNINITIALIZED_LEXICAL); 234 return "[unavailable]"; 235 } 236 237 if (js::IsCallable(v)) { 238 return "[function]"; 239 } 240 241 if (v.isObject() && js::IsCrossCompartmentWrapper(&v.toObject())) { 242 return "[cross-compartment wrapper]"; 243 } 244 245 JSString* str; 246 { 247 mozilla::Maybe<AutoRealm> ar; 248 if (v.isObject()) { 249 ar.emplace(cx, &v.toObject()); 250 } 251 252 str = ToString<CanGC>(cx, v); 253 if (!str) { 254 return nullptr; 255 } 256 } 257 258 bytes = js::QuoteString(cx, str, '"'); 259 return bytes.get(); 260 } 261 262 static bool FormatFrame(JSContext* cx, const FrameIter& iter, Sprinter& sp, 263 int num, bool showArgs, bool showLocals, 264 bool showThisProps) { 265 MOZ_ASSERT(!cx->isExceptionPending()); 266 Rooted<JSScript*> script(cx, iter.script()); 267 jsbytecode* pc = iter.pc(); 268 269 Rooted<JSObject*> envChain(cx, iter.environmentChain(cx)); 270 JSAutoRealm ar(cx, envChain); 271 272 const char* filename = script->filename(); 273 JS::LimitedColumnNumberOneOrigin column; 274 unsigned lineno = PCToLineNumber(script, pc, &column); 275 Rooted<JSFunction*> fun(cx, iter.maybeCallee(cx)); 276 Rooted<JSString*> funname(cx); 277 if (fun) { 278 funname = fun->fullDisplayAtom(); 279 } 280 281 Rooted<Value> thisVal(cx); 282 if (iter.hasUsableAbstractFramePtr() && iter.isFunctionFrame() && fun && 283 !fun->isArrow() && !fun->isDerivedClassConstructor()) { 284 if (!GetFunctionThis(cx, iter.abstractFramePtr(), &thisVal)) { 285 return false; 286 } 287 } 288 289 // print the frame number and function name 290 if (funname) { 291 UniqueChars funbytes = js::QuoteString(cx, funname); 292 if (!funbytes) { 293 return false; 294 } 295 sp.printf("%d %s(", num, funbytes.get()); 296 } else if (fun) { 297 sp.printf("%d anonymous(", num); 298 } else { 299 sp.printf("%d <TOP LEVEL>", num); 300 } 301 302 if (showArgs && iter.hasArgs()) { 303 PositionalFormalParameterIter fi(script); 304 bool first = true; 305 for (unsigned i = 0; i < iter.numActualArgs(); i++) { 306 Rooted<Value> arg(cx); 307 if (i < iter.numFormalArgs() && fi.closedOver()) { 308 if (iter.hasInitialEnvironment(cx)) { 309 arg = iter.callObj(cx).aliasedBinding(fi); 310 } else { 311 arg = MagicValue(JS_OPTIMIZED_OUT); 312 } 313 } else if (iter.hasUsableAbstractFramePtr()) { 314 if (script->argsObjAliasesFormals() && iter.hasArgsObj()) { 315 arg = iter.argsObj().arg(i); 316 } else { 317 arg = iter.unaliasedActual(i, DONT_CHECK_ALIASING); 318 } 319 } else { 320 arg = MagicValue(JS_OPTIMIZED_OUT); 321 } 322 323 UniqueChars valueBytes; 324 const char* value = FormatValue(cx, arg, valueBytes); 325 if (!value) { 326 if (cx->isThrowingOutOfMemory()) { 327 return false; 328 } 329 cx->clearPendingException(); 330 } 331 332 UniqueChars nameBytes; 333 const char* name = nullptr; 334 335 if (i < iter.numFormalArgs()) { 336 MOZ_ASSERT(fi.argumentSlot() == i); 337 if (!fi.isDestructured()) { 338 nameBytes = StringToNewUTF8CharsZ(cx, *fi.name()); 339 name = nameBytes.get(); 340 if (!name) { 341 return false; 342 } 343 } else { 344 name = "(destructured parameter)"; 345 } 346 fi++; 347 } 348 349 if (value) { 350 sp.printf("%s%s%s%s%s%s", !first ? ", " : "", name ? name : "", 351 name ? " = " : "", arg.isString() ? "\"" : "", value, 352 arg.isString() ? "\"" : ""); 353 354 first = false; 355 } else { 356 sp.put( 357 " <Failed to get argument while inspecting stack " 358 "frame>\n"); 359 } 360 } 361 } 362 363 // print filename, line number and column 364 sp.printf("%s [\"%s\":%u:%u]\n", fun ? ")" : "", 365 filename ? filename : "<unknown>", lineno, column.oneOriginValue()); 366 367 // Note: Right now we don't dump the local variables anymore, because 368 // that is hard to support across all the JITs etc. 369 370 // print the value of 'this' 371 if (showLocals) { 372 if (!thisVal.isUndefined()) { 373 Rooted<JSString*> thisValStr(cx, ToString<CanGC>(cx, thisVal)); 374 if (!thisValStr) { 375 if (cx->isThrowingOutOfMemory()) { 376 return false; 377 } 378 cx->clearPendingException(); 379 } 380 if (thisValStr) { 381 UniqueChars thisValBytes = QuoteString(cx, thisValStr); 382 if (!thisValBytes) { 383 return false; 384 } 385 sp.printf(" this = %s\n", thisValBytes.get()); 386 } else { 387 sp.put(" <failed to get 'this' value>\n"); 388 } 389 } 390 } 391 392 if (showThisProps && thisVal.isObject()) { 393 Rooted<JSObject*> obj(cx, &thisVal.toObject()); 394 395 RootedVector<PropertyKey> keys(cx); 396 if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY, &keys)) { 397 if (cx->isThrowingOutOfMemory()) { 398 return false; 399 } 400 cx->clearPendingException(); 401 } 402 403 for (size_t i = 0; i < keys.length(); i++) { 404 Rooted<jsid> id(cx, keys[i]); 405 Rooted<Value> key(cx, IdToValue(id)); 406 Rooted<Value> v(cx); 407 408 if (!GetProperty(cx, obj, obj, id, &v)) { 409 if (cx->isThrowingOutOfMemory()) { 410 return false; 411 } 412 cx->clearPendingException(); 413 sp.put( 414 " <Failed to fetch property while inspecting stack " 415 "frame>\n"); 416 continue; 417 } 418 419 UniqueChars nameBytes; 420 const char* name = FormatValue(cx, key, nameBytes); 421 if (!name) { 422 if (cx->isThrowingOutOfMemory()) { 423 return false; 424 } 425 cx->clearPendingException(); 426 } 427 428 UniqueChars valueBytes; 429 const char* value = FormatValue(cx, v, valueBytes); 430 if (!value) { 431 if (cx->isThrowingOutOfMemory()) { 432 return false; 433 } 434 cx->clearPendingException(); 435 } 436 437 if (name && value) { 438 sp.printf(" this.%s = %s%s%s\n", name, v.isString() ? "\"" : "", 439 value, v.isString() ? "\"" : ""); 440 } else { 441 sp.put( 442 " <Failed to format values while inspecting stack " 443 "frame>\n"); 444 } 445 } 446 } 447 448 MOZ_ASSERT(!cx->isExceptionPending()); 449 return true; 450 } 451 452 static bool FormatWasmFrame(JSContext* cx, const FrameIter& iter, Sprinter& sp, 453 int num) { 454 UniqueChars nameStr; 455 if (JSAtom* functionDisplayAtom = iter.maybeFunctionDisplayAtom()) { 456 nameStr = StringToNewUTF8CharsZ(cx, *functionDisplayAtom); 457 if (!nameStr) { 458 return false; 459 } 460 } 461 462 sp.printf("%d %s()", num, nameStr ? nameStr.get() : "<wasm-function>"); 463 sp.printf(" [\"%s\":wasm-function[%u]:0x%x]\n", 464 iter.filename() ? iter.filename() : "<unknown>", 465 iter.wasmFuncIndex(), iter.wasmBytecodeOffset()); 466 467 MOZ_ASSERT(!cx->isExceptionPending()); 468 return true; 469 } 470 471 JS::UniqueChars JS::FormatStackDump(JSContext* cx, bool showArgs, 472 bool showLocals, bool showThisProps) { 473 int num = 0; 474 475 Sprinter sp(cx); 476 if (!sp.init()) { 477 return nullptr; 478 } 479 480 for (AllFramesIter i(cx); !i.done(); ++i) { 481 bool ok = i.hasScript() ? FormatFrame(cx, i, sp, num, showArgs, showLocals, 482 showThisProps) 483 : FormatWasmFrame(cx, i, sp, num); 484 if (!ok) { 485 return nullptr; 486 } 487 num++; 488 } 489 490 if (num == 0) { 491 sp.put("JavaScript stack is empty\n"); 492 } 493 494 return sp.release(); 495 } 496 497 struct DumpHeapTracer final : public JS::CallbackTracer, public WeakMapTracer { 498 const char* prefix; 499 FILE* output; 500 mozilla::MallocSizeOf mallocSizeOf; 501 502 DumpHeapTracer(FILE* fp, JSContext* cx, mozilla::MallocSizeOf mallocSizeOf) 503 : JS::CallbackTracer(cx, JS::TracerKind::Callback, 504 JS::WeakMapTraceAction::Skip), 505 WeakMapTracer(cx->runtime()), 506 prefix(""), 507 output(fp), 508 mallocSizeOf(mallocSizeOf) {} 509 510 private: 511 void trace(JSObject* map, JS::GCCellPtr key, JS::GCCellPtr value) override { 512 JSObject* kdelegate = nullptr; 513 if (key.is<JSObject>()) { 514 kdelegate = UncheckedUnwrapWithoutExpose(&key.as<JSObject>()); 515 } 516 517 fprintf(output, "WeakMapEntry map=%p key=%p keyDelegate=%p value=%p\n", map, 518 key.asCell(), kdelegate, value.asCell()); 519 } 520 521 void onChild(JS::GCCellPtr thing, const char* name) override; 522 }; 523 524 static char MarkDescriptor(js::gc::Cell* thing) { 525 js::gc::TenuredCell* cell = &thing->asTenured(); 526 if (cell->isMarkedBlack()) { 527 return 'B'; 528 } 529 if (cell->isMarkedGray()) { 530 return 'G'; 531 } 532 if (cell->isMarkedAny()) { 533 return 'X'; 534 } 535 return 'W'; 536 } 537 538 static void DumpHeapVisitZone(JSRuntime* rt, void* data, Zone* zone, 539 const JS::AutoRequireNoGC& nogc) { 540 DumpHeapTracer* dtrc = static_cast<DumpHeapTracer*>(data); 541 fprintf(dtrc->output, "# zone %p\n", static_cast<void*>(zone)); 542 } 543 544 static void DumpHeapVisitRealm(JSContext* cx, void* data, Realm* realm, 545 const JS::AutoRequireNoGC& nogc) { 546 char name[1024]; 547 if (auto nameCallback = cx->runtime()->realmNameCallback) { 548 nameCallback(cx, realm, name, sizeof(name), nogc); 549 } else { 550 strcpy(name, "<unknown>"); 551 } 552 553 DumpHeapTracer* dtrc = static_cast<DumpHeapTracer*>(data); 554 fprintf(dtrc->output, "# realm %s [in compartment %p, zone %p]\n", name, 555 static_cast<void*>(realm->compartment()), 556 static_cast<void*>(realm->zone())); 557 } 558 559 static void DumpHeapVisitArena(JSRuntime* rt, void* data, js::gc::Arena* arena, 560 JS::TraceKind traceKind, size_t thingSize, 561 const JS::AutoRequireNoGC& nogc) { 562 DumpHeapTracer* dtrc = static_cast<DumpHeapTracer*>(data); 563 fprintf(dtrc->output, "# arena allockind=%u size=%u\n", 564 unsigned(arena->getAllocKind()), unsigned(thingSize)); 565 } 566 567 static void DumpHeapVisitCell(JSRuntime* rt, void* data, JS::GCCellPtr cellptr, 568 size_t thingSize, 569 const JS::AutoRequireNoGC& nogc) { 570 DumpHeapTracer* dtrc = static_cast<DumpHeapTracer*>(data); 571 char cellDesc[1024 * 32]; 572 js::gc::GetTraceThingInfo(cellDesc, sizeof(cellDesc), cellptr.asCell(), 573 cellptr.kind(), true); 574 575 fprintf(dtrc->output, "%p %c %s", cellptr.asCell(), 576 MarkDescriptor(cellptr.asCell()), cellDesc); 577 if (dtrc->mallocSizeOf) { 578 auto size = JS::ubi::Node(cellptr).size(dtrc->mallocSizeOf); 579 fprintf(dtrc->output, " SIZE:: %" PRIu64 "\n", size); 580 } else { 581 fprintf(dtrc->output, "\n"); 582 } 583 584 JS::TraceChildren(dtrc, cellptr); 585 } 586 587 void DumpHeapTracer::onChild(JS::GCCellPtr thing, const char* name) { 588 if (js::gc::IsInsideNursery(thing.asCell())) { 589 return; 590 } 591 592 char buffer[1024]; 593 context().getEdgeName(name, buffer, sizeof(buffer)); 594 fprintf(output, "%s%p %c %s\n", prefix, thing.asCell(), 595 MarkDescriptor(thing.asCell()), buffer); 596 } 597 598 void js::DumpHeap(JSContext* cx, FILE* fp, 599 DumpHeapNurseryBehaviour nurseryBehaviour, 600 mozilla::MallocSizeOf mallocSizeOf) { 601 if (nurseryBehaviour == CollectNurseryBeforeDump) { 602 cx->runtime()->gc.evictNursery(JS::GCReason::API); 603 } 604 605 DumpHeapTracer dtrc(fp, cx, mallocSizeOf); 606 607 fprintf(dtrc.output, "# Roots.\n"); 608 TraceRuntimeWithoutEviction(&dtrc); 609 610 fprintf(dtrc.output, "# Weak maps.\n"); 611 WeakMapBase::traceAllMappings(&dtrc); 612 613 fprintf(dtrc.output, "==========\n"); 614 615 dtrc.prefix = "> "; 616 gc::AutoPrepareForTracing session(cx); 617 IterateHeapUnbarriered(cx, &dtrc, DumpHeapVisitZone, DumpHeapVisitRealm, 618 DumpHeapVisitArena, DumpHeapVisitCell, session); 619 620 fflush(dtrc.output); 621 } 622 623 void DumpFmtV(FILE* fp, const char* fmt, va_list args) { 624 js::Fprinter out(fp); 625 out.vprintf(fmt, args); 626 } 627 628 void js::DumpFmt(FILE* fp, const char* fmt, ...) { 629 va_list args; 630 va_start(args, fmt); 631 DumpFmtV(fp, fmt, args); 632 va_end(args); 633 } 634 635 void js::DumpFmt(const char* fmt, ...) { 636 va_list args; 637 va_start(args, fmt); 638 DumpFmtV(stderr, fmt, args); 639 va_end(args); 640 }