summaryrefslogtreecommitdiffstats
path: root/js/src/util/DumpFunctions.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/util/DumpFunctions.cpp')
-rw-r--r--js/src/util/DumpFunctions.cpp657
1 files changed, 657 insertions, 0 deletions
diff --git a/js/src/util/DumpFunctions.cpp b/js/src/util/DumpFunctions.cpp
new file mode 100644
index 0000000000..0de9beea89
--- /dev/null
+++ b/js/src/util/DumpFunctions.cpp
@@ -0,0 +1,657 @@
+/* -*- 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/Allocator.h" // js::CanGC
+#include "gc/Cell.h" // js::gc::Cell, js::gc::TenuredCell
+#include "gc/GC.h" // js::TraceRuntimeWithoutEviction
+#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/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)
+ out.printf("char16_t * (%p) = ", (void*)s);
+ JSString::dumpChars(s, n, out);
+ out.putChar('\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();
+ unsigned column = 0;
+ unsigned lineno = PCToLineNumber(script, pc, &column);
+ Rooted<JSFunction*> fun(cx, iter.maybeCallee(cx));
+ Rooted<JSString*> funname(cx);
+ if (fun) {
+ funname = fun->displayAtom();
+ }
+
+ 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;
+ }
+ if (!sp.printf("%d %s(", num, funbytes.get())) {
+ return false;
+ }
+ } else if (fun) {
+ if (!sp.printf("%d anonymous(", num)) {
+ return false;
+ }
+ } else {
+ if (!sp.printf("%d <TOP LEVEL>", num)) {
+ return false;
+ }
+ }
+
+ 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) {
+ if (!sp.printf("%s%s%s%s%s%s", !first ? ", " : "", name ? name : "",
+ name ? " = " : "", arg.isString() ? "\"" : "", value,
+ arg.isString() ? "\"" : "")) {
+ return false;
+ }
+
+ first = false;
+ } else {
+ if (!sp.put(" <Failed to get argument while inspecting stack "
+ "frame>\n")) {
+ return false;
+ }
+ }
+ }
+ }
+
+ // print filename, line number and column
+ if (!sp.printf("%s [\"%s\":%u:%u]\n", fun ? ")" : "",
+ filename ? filename : "<unknown>", lineno, column)) {
+ return false;
+ }
+
+ // 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;
+ }
+ if (!sp.printf(" this = %s\n", thisValBytes.get())) {
+ return false;
+ }
+ } else {
+ if (!sp.put(" <failed to get 'this' value>\n")) {
+ return false;
+ }
+ }
+ }
+ }
+
+ 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();
+ if (!sp.put(" <Failed to fetch property while inspecting stack "
+ "frame>\n")) {
+ return false;
+ }
+ 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) {
+ if (!sp.printf(" this.%s = %s%s%s\n", name, v.isString() ? "\"" : "",
+ value, v.isString() ? "\"" : "")) {
+ return false;
+ }
+ } else {
+ if (!sp.put(" <Failed to format values while inspecting stack "
+ "frame>\n")) {
+ return false;
+ }
+ }
+ }
+ }
+
+ 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;
+ }
+ }
+
+ if (!sp.printf("%d %s()", num, nameStr ? nameStr.get() : "<wasm-function>")) {
+ return false;
+ }
+
+ if (!sp.printf(" [\"%s\":wasm-function[%u]:0x%x]\n",
+ iter.filename() ? iter.filename() : "<unknown>",
+ iter.wasmFuncIndex(), iter.wasmBytecodeOffset())) {
+ return false;
+ }
+
+ 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) {
+ if (!sp.put("JavaScript stack is empty\n")) {
+ return nullptr;
+ }
+ }
+
+ 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);
+}