LibJS: Cache the formatted Error.prototype.stack string

Generating the stack trace string is expensive as it involves
formatting each frame with function names, file paths, and line
numbers. Since the stack trace is immutable after error creation,
we can cache the formatted string on first access.

This matches the caching behavior of V8 and JavaScriptCore, which
also return the same cached string on subsequent accesses to the
stack property.
This commit is contained in:
Andreas Kling 2026-01-18 11:48:29 +01:00 committed by Andreas Kling
parent 59e0e331e3
commit b5fc557709
3 changed files with 15 additions and 1 deletions

View file

@ -61,6 +61,7 @@ Error::Error(Object& prototype)
void Error::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_cached_string);
for (auto& frame : m_traceback)
visitor.visit(frame.cached_source_range);
}

View file

@ -48,6 +48,9 @@ public:
Vector<TracebackFrame, 32> const& traceback() const { return m_traceback; }
void set_cached_string(GC::Ref<PrimitiveString> string) { m_cached_string = string; }
GC::Ptr<PrimitiveString> cached_string() const { return m_cached_string; }
protected:
explicit Error(Object& prototype);
@ -58,6 +61,8 @@ private:
void populate_stack();
Vector<TracebackFrame, 32> m_traceback;
GC::Ptr<PrimitiveString> m_cached_string;
};
template<>

View file

@ -83,6 +83,12 @@ JS_DEFINE_NATIVE_FUNCTION(ErrorPrototype::stack_getter)
auto& error = static_cast<Error&>(*this_object);
// OPTIMIZATION: Avoid recomputing the stack string if we already have it cached.
// At least one major engine does this as well, so it's not expected that changing
// the name or message properties updates the stack string.
if (error.cached_string())
return error.cached_string();
// 4. Return ? GetStackString(error).
// NOTE: These steps are not implemented based on the proposal, but to roughly follow behavior of other browsers.
@ -100,7 +106,9 @@ JS_DEFINE_NATIVE_FUNCTION(ErrorPrototype::stack_getter)
? move(name)
: MUST(String::formatted("{}: {}", name, message));
return PrimitiveString::create(vm, MUST(String::formatted("{}\n{}", header, error.stack_string())));
auto string = PrimitiveString::create(vm, MUST(String::formatted("{}\n{}", header, error.stack_string())));
error.set_cached_string(string);
return string;
}
// B.1.2 set Error.prototype.stack ( value ), https://tc39.es/proposal-error-stacks/#sec-set-error.prototype-stack