diff options
Diffstat (limited to 'js/src/util/DumpFunctions.cpp')
-rw-r--r-- | js/src/util/DumpFunctions.cpp | 639 |
1 files changed, 639 insertions, 0 deletions
diff --git a/js/src/util/DumpFunctions.cpp b/js/src/util/DumpFunctions.cpp new file mode 100644 index 0000000000..a4b2dc6c6c --- /dev/null +++ b/js/src/util/DumpFunctions.cpp @@ -0,0 +1,639 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "js/friend/DumpFunctions.h" + +#include "mozilla/Maybe.h" // mozilla::Maybe + +#include <inttypes.h> // PRIu64 +#include <stddef.h> // size_t +#include <stdio.h> // fprintf, fflush + +#include "jsfriendapi.h" // js::WeakMapTracer +#include "jstypes.h" // JS_PUBLIC_API + +#include "gc/Cell.h" // js::gc::Cell, js::gc::TenuredCell +#include "gc/GC.h" // js::TraceRuntimeWithoutEviction +#include "gc/GCEnum.h" // js::CanGC +#include "gc/Heap.h" // js::gc::Arena +#include "gc/Tracer.h" // js::TraceChildren +#include "gc/WeakMap.h" // js::IterateHeapUnbarriered, js::WeakMapBase +#include "js/CallAndConstruct.h" // JS::IsCallable +#include "js/ColumnNumber.h" // JS::LimitedColumnNumberOneOrigin +#include "js/GCAPI.h" // JS::GCReason +#include "js/GCVector.h" // JS::RootedVector +#include "js/HeapAPI.h" // JS::GCCellPtr, js::gc::IsInsideNursery +#include "js/Id.h" // JS::PropertyKey +#include "js/Printer.h" // js::GenericPrinter, js::QuoteString, js::Sprinter +#include "js/RootingAPI.h" // JS::Handle, JS::Rooted +#include "js/TracingAPI.h" // JS::CallbackTracer, JS_GetTraceThingInfo +#include "js/UbiNode.h" // JS::ubi::Node +#include "js/Value.h" // JS::Value +#include "js/Wrapper.h" // js::UncheckedUnwrapWithoutExpose +#include "vm/BigIntType.h" // JS::BigInt::dump +#include "vm/FrameIter.h" // js::AllFramesIter, js::FrameIter +#include "vm/Interpreter.h" // GetFunctionThis +#include "vm/JSContext.h" // JSContext +#include "vm/JSFunction.h" // JSFunction +#include "vm/JSObject.h" // JSObject +#include "vm/JSScript.h" // JSScript +#include "vm/Realm.h" // JS::Realm +#include "vm/Runtime.h" // JSRuntime +#include "vm/Scope.h" // js::PositionalFormalParameterIter +#include "vm/Stack.h" // js::DONT_CHECK_ALIASING +#include "vm/StringType.h" // JSAtom, JSString, js::ToString + +#include "vm/JSObject-inl.h" // js::IsCallable +#include "vm/Realm-inl.h" // js::AutoRealm + +namespace JS { + +class JS_PUBLIC_API AutoRequireNoGC; +class JS_PUBLIC_API Zone; + +} // namespace JS + +using JS::Handle; +using JS::MagicValue; +using JS::PropertyKey; +using JS::Realm; +using JS::Rooted; +using JS::RootedVector; +using JS::UniqueChars; +using JS::Value; +using JS::Zone; + +using js::AllFramesIter; +using js::AutoRealm; +using js::CanGC; +using js::DONT_CHECK_ALIASING; +using js::FrameIter; +using js::PositionalFormalParameterIter; +using js::Sprinter; +using js::ToString; +using js::UncheckedUnwrapWithoutExpose; +using js::WeakMapTracer; + +// We don't want jsfriendapi.h to depend on GenericPrinter, +// so these functions are declared directly in the cpp. + +namespace js { + +class InterpreterFrame; + +extern JS_PUBLIC_API void DumpString(JSString* str, GenericPrinter& out); + +extern JS_PUBLIC_API void DumpAtom(JSAtom* atom, GenericPrinter& out); + +extern JS_PUBLIC_API void DumpObject(JSObject* obj, GenericPrinter& out); + +extern JS_PUBLIC_API void DumpChars(const char16_t* s, size_t n, + GenericPrinter& out); + +extern JS_PUBLIC_API void DumpValue(const JS::Value& val, GenericPrinter& out); + +extern JS_PUBLIC_API void DumpId(PropertyKey id, GenericPrinter& out); + +extern JS_PUBLIC_API void DumpInterpreterFrame( + JSContext* cx, GenericPrinter& out, InterpreterFrame* start = nullptr); + +extern JS_PUBLIC_API void DumpBigInt(JS::BigInt* bi, GenericPrinter& out); + +} // namespace js + +void js::DumpString(JSString* str, GenericPrinter& out) { +#if defined(DEBUG) || defined(JS_JITSPEW) + str->dump(out); +#endif +} + +void js::DumpAtom(JSAtom* atom, GenericPrinter& out) { +#if defined(DEBUG) || defined(JS_JITSPEW) + atom->dump(out); +#endif +} + +void js::DumpChars(const char16_t* s, size_t n, GenericPrinter& out) { +#if defined(DEBUG) || defined(JS_JITSPEW) + if (n == SIZE_MAX) { + n = 0; + while (s[n]) { + n++; + } + } + + out.printf("char16_t * (%p) = \"", (void*)s); + JSString::dumpCharsNoQuote(s, n, out); + out.put("\"\n"); +#endif +} + +void js::DumpObject(JSObject* obj, GenericPrinter& out) { +#if defined(DEBUG) || defined(JS_JITSPEW) + if (!obj) { + out.printf("NULL\n"); + return; + } + obj->dump(out); +#endif +} + +void js::DumpBigInt(JS::BigInt* bi, GenericPrinter& out) { +#if defined(DEBUG) || defined(JS_JITSPEW) + bi->dump(out); +#endif +} + +void js::DumpString(JSString* str, FILE* fp) { +#if defined(DEBUG) || defined(JS_JITSPEW) + Fprinter out(fp); + js::DumpString(str, out); +#endif +} + +void js::DumpAtom(JSAtom* atom, FILE* fp) { +#if defined(DEBUG) || defined(JS_JITSPEW) + Fprinter out(fp); + js::DumpAtom(atom, out); +#endif +} + +void js::DumpChars(const char16_t* s, size_t n, FILE* fp) { +#if defined(DEBUG) || defined(JS_JITSPEW) + Fprinter out(fp); + js::DumpChars(s, n, out); +#endif +} + +void js::DumpObject(JSObject* obj, FILE* fp) { +#if defined(DEBUG) || defined(JS_JITSPEW) + Fprinter out(fp); + js::DumpObject(obj, out); +#endif +} + +void js::DumpBigInt(JS::BigInt* bi, FILE* fp) { +#if defined(DEBUG) || defined(JS_JITSPEW) + Fprinter out(fp); + js::DumpBigInt(bi, out); +#endif +} + +void js::DumpId(PropertyKey id, FILE* fp) { +#if defined(DEBUG) || defined(JS_JITSPEW) + Fprinter out(fp); + js::DumpId(id, out); +#endif +} + +void js::DumpValue(const JS::Value& val, FILE* fp) { +#if defined(DEBUG) || defined(JS_JITSPEW) + Fprinter out(fp); + js::DumpValue(val, out); +#endif +} + +void js::DumpString(JSString* str) { DumpString(str, stderr); } +void js::DumpAtom(JSAtom* atom) { DumpAtom(atom, stderr); } +void js::DumpObject(JSObject* obj) { DumpObject(obj, stderr); } +void js::DumpChars(const char16_t* s, size_t n) { DumpChars(s, n, stderr); } +void js::DumpBigInt(JS::BigInt* bi) { DumpBigInt(bi, stderr); } +void js::DumpValue(const JS::Value& val) { DumpValue(val, stderr); } +void js::DumpId(PropertyKey id) { DumpId(id, stderr); } +void js::DumpInterpreterFrame(JSContext* cx, InterpreterFrame* start) { +#if defined(DEBUG) || defined(JS_JITSPEW) + Fprinter out(stderr); + DumpInterpreterFrame(cx, out, start); +#endif +} + +bool js::DumpPC(JSContext* cx) { +#if defined(DEBUG) || defined(JS_JITSPEW) + return DumpPC(cx, stdout); +#else + return true; +#endif +} + +bool js::DumpScript(JSContext* cx, JSScript* scriptArg) { +#if defined(DEBUG) || defined(JS_JITSPEW) + return DumpScript(cx, scriptArg, stdout); +#else + return true; +#endif +} + +static const char* FormatValue(JSContext* cx, Handle<Value> v, + UniqueChars& bytes) { + if (v.isMagic()) { + MOZ_ASSERT(v.whyMagic() == JS_OPTIMIZED_OUT || + v.whyMagic() == JS_UNINITIALIZED_LEXICAL); + return "[unavailable]"; + } + + if (js::IsCallable(v)) { + return "[function]"; + } + + if (v.isObject() && js::IsCrossCompartmentWrapper(&v.toObject())) { + return "[cross-compartment wrapper]"; + } + + JSString* str; + { + mozilla::Maybe<AutoRealm> ar; + if (v.isObject()) { + ar.emplace(cx, &v.toObject()); + } + + str = ToString<CanGC>(cx, v); + if (!str) { + return nullptr; + } + } + + bytes = js::QuoteString(cx, str, '"'); + return bytes.get(); +} + +static bool FormatFrame(JSContext* cx, const FrameIter& iter, Sprinter& sp, + int num, bool showArgs, bool showLocals, + bool showThisProps) { + MOZ_ASSERT(!cx->isExceptionPending()); + Rooted<JSScript*> script(cx, iter.script()); + jsbytecode* pc = iter.pc(); + + Rooted<JSObject*> envChain(cx, iter.environmentChain(cx)); + JSAutoRealm ar(cx, envChain); + + const char* filename = script->filename(); + JS::LimitedColumnNumberOneOrigin column; + unsigned lineno = PCToLineNumber(script, pc, &column); + Rooted<JSFunction*> fun(cx, iter.maybeCallee(cx)); + Rooted<JSString*> funname(cx); + if (fun) { + funname = fun->fullDisplayAtom(); + } + + Rooted<Value> thisVal(cx); + if (iter.hasUsableAbstractFramePtr() && iter.isFunctionFrame() && fun && + !fun->isArrow() && !fun->isDerivedClassConstructor()) { + if (!GetFunctionThis(cx, iter.abstractFramePtr(), &thisVal)) { + return false; + } + } + + // print the frame number and function name + if (funname) { + UniqueChars funbytes = js::QuoteString(cx, funname); + if (!funbytes) { + return false; + } + sp.printf("%d %s(", num, funbytes.get()); + } else if (fun) { + sp.printf("%d anonymous(", num); + } else { + sp.printf("%d <TOP LEVEL>", num); + } + + if (showArgs && iter.hasArgs()) { + PositionalFormalParameterIter fi(script); + bool first = true; + for (unsigned i = 0; i < iter.numActualArgs(); i++) { + Rooted<Value> arg(cx); + if (i < iter.numFormalArgs() && fi.closedOver()) { + if (iter.hasInitialEnvironment(cx)) { + arg = iter.callObj(cx).aliasedBinding(fi); + } else { + arg = MagicValue(JS_OPTIMIZED_OUT); + } + } else if (iter.hasUsableAbstractFramePtr()) { + if (script->argsObjAliasesFormals() && iter.hasArgsObj()) { + arg = iter.argsObj().arg(i); + } else { + arg = iter.unaliasedActual(i, DONT_CHECK_ALIASING); + } + } else { + arg = MagicValue(JS_OPTIMIZED_OUT); + } + + UniqueChars valueBytes; + const char* value = FormatValue(cx, arg, valueBytes); + if (!value) { + if (cx->isThrowingOutOfMemory()) { + return false; + } + cx->clearPendingException(); + } + + UniqueChars nameBytes; + const char* name = nullptr; + + if (i < iter.numFormalArgs()) { + MOZ_ASSERT(fi.argumentSlot() == i); + if (!fi.isDestructured()) { + nameBytes = StringToNewUTF8CharsZ(cx, *fi.name()); + name = nameBytes.get(); + if (!name) { + return false; + } + } else { + name = "(destructured parameter)"; + } + fi++; + } + + if (value) { + sp.printf("%s%s%s%s%s%s", !first ? ", " : "", name ? name : "", + name ? " = " : "", arg.isString() ? "\"" : "", value, + arg.isString() ? "\"" : ""); + + first = false; + } else { + sp.put( + " <Failed to get argument while inspecting stack " + "frame>\n"); + } + } + } + + // print filename, line number and column + sp.printf("%s [\"%s\":%u:%u]\n", fun ? ")" : "", + filename ? filename : "<unknown>", lineno, column.oneOriginValue()); + + // Note: Right now we don't dump the local variables anymore, because + // that is hard to support across all the JITs etc. + + // print the value of 'this' + if (showLocals) { + if (!thisVal.isUndefined()) { + Rooted<JSString*> thisValStr(cx, ToString<CanGC>(cx, thisVal)); + if (!thisValStr) { + if (cx->isThrowingOutOfMemory()) { + return false; + } + cx->clearPendingException(); + } + if (thisValStr) { + UniqueChars thisValBytes = QuoteString(cx, thisValStr); + if (!thisValBytes) { + return false; + } + sp.printf(" this = %s\n", thisValBytes.get()); + } else { + sp.put(" <failed to get 'this' value>\n"); + } + } + } + + if (showThisProps && thisVal.isObject()) { + Rooted<JSObject*> obj(cx, &thisVal.toObject()); + + RootedVector<PropertyKey> keys(cx); + if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY, &keys)) { + if (cx->isThrowingOutOfMemory()) { + return false; + } + cx->clearPendingException(); + } + + for (size_t i = 0; i < keys.length(); i++) { + Rooted<jsid> id(cx, keys[i]); + Rooted<Value> key(cx, IdToValue(id)); + Rooted<Value> v(cx); + + if (!GetProperty(cx, obj, obj, id, &v)) { + if (cx->isThrowingOutOfMemory()) { + return false; + } + cx->clearPendingException(); + sp.put( + " <Failed to fetch property while inspecting stack " + "frame>\n"); + continue; + } + + UniqueChars nameBytes; + const char* name = FormatValue(cx, key, nameBytes); + if (!name) { + if (cx->isThrowingOutOfMemory()) { + return false; + } + cx->clearPendingException(); + } + + UniqueChars valueBytes; + const char* value = FormatValue(cx, v, valueBytes); + if (!value) { + if (cx->isThrowingOutOfMemory()) { + return false; + } + cx->clearPendingException(); + } + + if (name && value) { + sp.printf(" this.%s = %s%s%s\n", name, v.isString() ? "\"" : "", + value, v.isString() ? "\"" : ""); + } else { + sp.put( + " <Failed to format values while inspecting stack " + "frame>\n"); + } + } + } + + MOZ_ASSERT(!cx->isExceptionPending()); + return true; +} + +static bool FormatWasmFrame(JSContext* cx, const FrameIter& iter, Sprinter& sp, + int num) { + UniqueChars nameStr; + if (JSAtom* functionDisplayAtom = iter.maybeFunctionDisplayAtom()) { + nameStr = StringToNewUTF8CharsZ(cx, *functionDisplayAtom); + if (!nameStr) { + return false; + } + } + + sp.printf("%d %s()", num, nameStr ? nameStr.get() : "<wasm-function>"); + sp.printf(" [\"%s\":wasm-function[%u]:0x%x]\n", + iter.filename() ? iter.filename() : "<unknown>", + iter.wasmFuncIndex(), iter.wasmBytecodeOffset()); + + MOZ_ASSERT(!cx->isExceptionPending()); + return true; +} + +JS::UniqueChars JS::FormatStackDump(JSContext* cx, bool showArgs, + bool showLocals, bool showThisProps) { + int num = 0; + + Sprinter sp(cx); + if (!sp.init()) { + return nullptr; + } + + for (AllFramesIter i(cx); !i.done(); ++i) { + bool ok = i.hasScript() ? FormatFrame(cx, i, sp, num, showArgs, showLocals, + showThisProps) + : FormatWasmFrame(cx, i, sp, num); + if (!ok) { + return nullptr; + } + num++; + } + + if (num == 0) { + sp.put("JavaScript stack is empty\n"); + } + + return sp.release(); +} + +struct DumpHeapTracer final : public JS::CallbackTracer, public WeakMapTracer { + const char* prefix; + FILE* output; + mozilla::MallocSizeOf mallocSizeOf; + + DumpHeapTracer(FILE* fp, JSContext* cx, mozilla::MallocSizeOf mallocSizeOf) + : JS::CallbackTracer(cx, JS::TracerKind::Callback, + JS::WeakMapTraceAction::Skip), + WeakMapTracer(cx->runtime()), + prefix(""), + output(fp), + mallocSizeOf(mallocSizeOf) {} + + private: + void trace(JSObject* map, JS::GCCellPtr key, JS::GCCellPtr value) override { + JSObject* kdelegate = nullptr; + if (key.is<JSObject>()) { + kdelegate = UncheckedUnwrapWithoutExpose(&key.as<JSObject>()); + } + + fprintf(output, "WeakMapEntry map=%p key=%p keyDelegate=%p value=%p\n", map, + key.asCell(), kdelegate, value.asCell()); + } + + void onChild(JS::GCCellPtr thing, const char* name) override; +}; + +static char MarkDescriptor(js::gc::Cell* thing) { + js::gc::TenuredCell* cell = &thing->asTenured(); + if (cell->isMarkedBlack()) { + return 'B'; + } + if (cell->isMarkedGray()) { + return 'G'; + } + if (cell->isMarkedAny()) { + return 'X'; + } + return 'W'; +} + +static void DumpHeapVisitZone(JSRuntime* rt, void* data, Zone* zone, + const JS::AutoRequireNoGC& nogc) { + DumpHeapTracer* dtrc = static_cast<DumpHeapTracer*>(data); + fprintf(dtrc->output, "# zone %p\n", static_cast<void*>(zone)); +} + +static void DumpHeapVisitRealm(JSContext* cx, void* data, Realm* realm, + const JS::AutoRequireNoGC& nogc) { + char name[1024]; + if (auto nameCallback = cx->runtime()->realmNameCallback) { + nameCallback(cx, realm, name, sizeof(name), nogc); + } else { + strcpy(name, "<unknown>"); + } + + DumpHeapTracer* dtrc = static_cast<DumpHeapTracer*>(data); + fprintf(dtrc->output, "# realm %s [in compartment %p, zone %p]\n", name, + static_cast<void*>(realm->compartment()), + static_cast<void*>(realm->zone())); +} + +static void DumpHeapVisitArena(JSRuntime* rt, void* data, js::gc::Arena* arena, + JS::TraceKind traceKind, size_t thingSize, + const JS::AutoRequireNoGC& nogc) { + DumpHeapTracer* dtrc = static_cast<DumpHeapTracer*>(data); + fprintf(dtrc->output, "# arena allockind=%u size=%u\n", + unsigned(arena->getAllocKind()), unsigned(thingSize)); +} + +static void DumpHeapVisitCell(JSRuntime* rt, void* data, JS::GCCellPtr cellptr, + size_t thingSize, + const JS::AutoRequireNoGC& nogc) { + DumpHeapTracer* dtrc = static_cast<DumpHeapTracer*>(data); + char cellDesc[1024 * 32]; + js::gc::GetTraceThingInfo(cellDesc, sizeof(cellDesc), cellptr.asCell(), + cellptr.kind(), true); + + fprintf(dtrc->output, "%p %c %s", cellptr.asCell(), + MarkDescriptor(cellptr.asCell()), cellDesc); + if (dtrc->mallocSizeOf) { + auto size = JS::ubi::Node(cellptr).size(dtrc->mallocSizeOf); + fprintf(dtrc->output, " SIZE:: %" PRIu64 "\n", size); + } else { + fprintf(dtrc->output, "\n"); + } + + JS::TraceChildren(dtrc, cellptr); +} + +void DumpHeapTracer::onChild(JS::GCCellPtr thing, const char* name) { + if (js::gc::IsInsideNursery(thing.asCell())) { + return; + } + + char buffer[1024]; + context().getEdgeName(name, buffer, sizeof(buffer)); + fprintf(output, "%s%p %c %s\n", prefix, thing.asCell(), + MarkDescriptor(thing.asCell()), buffer); +} + +void js::DumpHeap(JSContext* cx, FILE* fp, + DumpHeapNurseryBehaviour nurseryBehaviour, + mozilla::MallocSizeOf mallocSizeOf) { + if (nurseryBehaviour == CollectNurseryBeforeDump) { + cx->runtime()->gc.evictNursery(JS::GCReason::API); + } + + DumpHeapTracer dtrc(fp, cx, mallocSizeOf); + + fprintf(dtrc.output, "# Roots.\n"); + TraceRuntimeWithoutEviction(&dtrc); + + fprintf(dtrc.output, "# Weak maps.\n"); + WeakMapBase::traceAllMappings(&dtrc); + + fprintf(dtrc.output, "==========\n"); + + dtrc.prefix = "> "; + IterateHeapUnbarriered(cx, &dtrc, DumpHeapVisitZone, DumpHeapVisitRealm, + DumpHeapVisitArena, DumpHeapVisitCell); + + fflush(dtrc.output); +} + +void DumpFmtV(FILE* fp, const char* fmt, va_list args) { + js::Fprinter out(fp); + out.vprintf(fmt, args); +} + +void js::DumpFmt(FILE* fp, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + DumpFmtV(fp, fmt, args); + va_end(args); +} + +void js::DumpFmt(const char* fmt, ...) { + va_list args; + va_start(args, fmt); + DumpFmtV(stderr, fmt, args); + va_end(args); +} |