bool SavedStacks::getLocation(JSContext *cx, const FrameIter &iter, MutableHandleLocationValue locationp) { // We should only ever be caching location values for scripts in this // compartment. Otherwise, we would get dead cross-compartment scripts in // the cache because our compartment's sweep method isn't called when their // compartment gets collected. assertSameCompartment(cx, this, iter.compartment()); // When we have a |JSScript| for this frame, use a potentially memoized // location from our PCLocationMap and copy it into |locationp|. When we do // not have a |JSScript| for this frame (asm.js frames), we take a slow path // that doesn't employ memoization, and update |locationp|'s slots directly. if (!iter.hasScript()) { if (const char16_t *displayURL = iter.scriptDisplayURL()) { locationp->source = AtomizeChars(cx, displayURL, js_strlen(displayURL)); } else { const char *filename = iter.scriptFilename() ? iter.scriptFilename() : ""; locationp->source = Atomize(cx, filename, strlen(filename)); } if (!locationp->source) return false; locationp->line = iter.computeLine(&locationp->column); return true; } RootedScript script(cx, iter.script()); jsbytecode *pc = iter.pc(); PCKey key(script, pc); PCLocationMap::AddPtr p = pcLocationMap.lookupForAdd(key); if (!p) { RootedAtom source(cx); if (const char16_t *displayURL = iter.scriptDisplayURL()) { source = AtomizeChars(cx, displayURL, js_strlen(displayURL)); } else { const char *filename = script->filename() ? script->filename() : ""; source = Atomize(cx, filename, strlen(filename)); } if (!source) return false; uint32_t column; uint32_t line = PCToLineNumber(script, pc, &column); LocationValue value(source, line, column); if (!pcLocationMap.add(p, key, value)) return false; } locationp.set(p->value()); return true; }
static JS::UniqueChars FormatFrame(JSContext* cx, const FrameIter& iter, JS::UniqueChars&& inBuf, int num, bool showArgs, bool showLocals, bool showThisProps) { MOZ_ASSERT(!cx->isExceptionPending()); RootedScript script(cx, iter.script()); jsbytecode* pc = iter.pc(); RootedObject envChain(cx, iter.environmentChain(cx)); JSAutoCompartment ac(cx, envChain); const char* filename = script->filename(); unsigned lineno = PCToLineNumber(script, pc); RootedFunction fun(cx, iter.maybeCallee(cx)); RootedString funname(cx); if (fun) funname = fun->displayAtom(); RootedValue thisVal(cx); if (iter.hasUsableAbstractFramePtr() && iter.isFunctionFrame() && fun && !fun->isArrow() && !fun->isDerivedClassConstructor() && !(fun->isBoundFunction() && iter.isConstructing())) { if (!GetFunctionThis(cx, iter.abstractFramePtr(), &thisVal)) return nullptr; } // print the frame number and function name JS::UniqueChars buf(Move(inBuf)); if (funname) { JSAutoByteString funbytes; char* str = funbytes.encodeLatin1(cx, funname); if (!str) return nullptr; buf = sprintf_append(cx, Move(buf), "%d %s(", num, str); } else if (fun) { buf = sprintf_append(cx, Move(buf), "%d anonymous(", num); } else { buf = sprintf_append(cx, Move(buf), "%d <TOP LEVEL>", num); } if (!buf) return nullptr; if (showArgs && iter.hasArgs()) { PositionalFormalParameterIter fi(script); bool first = true; for (unsigned i = 0; i < iter.numActualArgs(); i++) { RootedValue arg(cx); if (i < iter.numFormalArgs() && fi.closedOver()) { arg = iter.callObj(cx).aliasedBinding(fi); } else if (iter.hasUsableAbstractFramePtr()) { if (script->analyzedArgsUsage() && script->argsObjAliasesFormals() && iter.hasArgsObj()) { arg = iter.argsObj().arg(i); } else { arg = iter.unaliasedActual(i, DONT_CHECK_ALIASING); } } else { arg = MagicValue(JS_OPTIMIZED_OUT); } JSAutoByteString valueBytes; const char* value = FormatValue(cx, arg, valueBytes); if (!value) { if (cx->isThrowingOutOfMemory()) return nullptr; cx->clearPendingException(); } JSAutoByteString nameBytes; const char* name = nullptr; if (i < iter.numFormalArgs()) { MOZ_ASSERT(fi.argumentSlot() == i); if (!fi.isDestructured()) { name = nameBytes.encodeLatin1(cx, fi.name()); if (!name) return nullptr; } else { name = "(destructured parameter)"; } fi++; } if (value) { buf = sprintf_append(cx, Move(buf), "%s%s%s%s%s%s", !first ? ", " : "", name ? name :"", name ? " = " : "", arg.isString() ? "\"" : "", value, arg.isString() ? "\"" : ""); if (!buf) return nullptr; first = false; } else { buf = sprintf_append(cx, Move(buf), " <Failed to get argument while inspecting stack frame>\n"); if (!buf) return nullptr; } } } // print filename and line number buf = sprintf_append(cx, Move(buf), "%s [\"%s\":%d]\n", fun ? ")" : "", filename ? filename : "<unknown>", lineno); if (!buf) return nullptr; // 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()) { JSAutoByteString thisValBytes; RootedString thisValStr(cx, ToString<CanGC>(cx, thisVal)); if (!thisValStr) { if (cx->isThrowingOutOfMemory()) return nullptr; cx->clearPendingException(); } if (thisValStr) { const char* str = thisValBytes.encodeLatin1(cx, thisValStr); if (!str) return nullptr; buf = sprintf_append(cx, Move(buf), " this = %s\n", str); } else { buf = sprintf_append(cx, Move(buf), " <failed to get 'this' value>\n"); } if (!buf) return nullptr; } } if (showThisProps && thisVal.isObject()) { RootedObject obj(cx, &thisVal.toObject()); AutoIdVector keys(cx); if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY, &keys)) { if (cx->isThrowingOutOfMemory()) return nullptr; cx->clearPendingException(); } RootedId id(cx); for (size_t i = 0; i < keys.length(); i++) { RootedId id(cx, keys[i]); RootedValue key(cx, IdToValue(id)); RootedValue v(cx); if (!GetProperty(cx, obj, obj, id, &v)) { if (cx->isThrowingOutOfMemory()) return nullptr; cx->clearPendingException(); buf = sprintf_append(cx, Move(buf), " <Failed to fetch property while inspecting stack frame>\n"); if (!buf) return nullptr; continue; } JSAutoByteString nameBytes; const char* name = FormatValue(cx, key, nameBytes); if (!name) { if (cx->isThrowingOutOfMemory()) return nullptr; cx->clearPendingException(); } JSAutoByteString valueBytes; const char* value = FormatValue(cx, v, valueBytes); if (!value) { if (cx->isThrowingOutOfMemory()) return nullptr; cx->clearPendingException(); } if (name && value) { buf = sprintf_append(cx, Move(buf), " this.%s = %s%s%s\n", name, v.isString() ? "\"" : "", value, v.isString() ? "\"" : ""); } else { buf = sprintf_append(cx, Move(buf), " <Failed to format values while inspecting stack frame>\n"); } if (!buf) return nullptr; } } MOZ_ASSERT(!cx->isExceptionPending()); return buf; }
bool SavedStacks::insertFrames(JSContext *cx, FrameIter &iter, MutableHandleSavedFrame frame, unsigned maxFrameCount) { if (iter.done()) { frame.set(nullptr); return true; } // Don't report the over-recursion error because if we are blowing the stack // here, we already blew the stack in JS, reported it, and we are creating // the saved stack for the over-recursion error object. We do this check // here, rather than inside saveCurrentStack, because in some cases we will // pass the check there, despite later failing the check here (for example, // in js/src/jit-test/tests/saved-stacks/bug-1006876-too-much-recursion.js). JS_CHECK_RECURSION_DONT_REPORT(cx, return false); JSPrincipals* principals = iter.compartment()->principals; RootedAtom name(cx, iter.isNonEvalFunctionFrame() ? iter.functionDisplayAtom() : nullptr); // When we have a |JSScript| for this frame, use |getLocation| to get a // potentially memoized location result and copy it into |location|. When we // do not have a |JSScript| for this frame (asm.js frames), we take a slow // path that doesn't employ memoization, and update |location|'s slots // directly. AutoLocationValueRooter location(cx); if (iter.hasScript()) { JSScript *script = iter.script(); jsbytecode *pc = iter.pc(); { AutoCompartment ac(cx, iter.compartment()); if (!cx->compartment()->savedStacks().getLocation(cx, script, pc, &location)) return false; } } else { const char *filename = iter.scriptFilename(); if (!filename) filename = ""; location.get().source = Atomize(cx, filename, strlen(filename)); if (!location.get().source) return false; uint32_t column; location.get().line = iter.computeLine(&column); location.get().column = column; } RootedSavedFrame parentFrame(cx); // If maxFrameCount is zero, then there's no limit on the number of frames. if (maxFrameCount == 0) { if (!insertFrames(cx, ++iter, &parentFrame, 0)) return false; } else if (maxFrameCount == 1) { // Since we were only asked to save one frame, the SavedFrame we're // building here should have no parent, even if there are older frames // on the stack. parentFrame = nullptr; } else { if (!insertFrames(cx, ++iter, &parentFrame, maxFrameCount - 1)) return false; } SavedFrame::AutoLookupRooter lookup(cx, location.get().source, location.get().line, location.get().column, name, parentFrame, principals); frame.set(getOrCreateSavedFrame(cx, lookup)); return frame.get() != nullptr; }