Fix several bugs when printing exceptions from Error.captureStackTrace (#14548)

This commit is contained in:
Jarred Sumner
2024-10-14 13:43:06 -07:00
committed by GitHub
parent 6dbd679c06
commit 29d287261b
14 changed files with 766 additions and 216 deletions

View File

@@ -2,7 +2,7 @@ option(WEBKIT_VERSION "The version of WebKit to use")
option(WEBKIT_LOCAL "If a local version of WebKit should be used instead of downloading")
if(NOT WEBKIT_VERSION)
set(WEBKIT_VERSION 01ac6a63449713c5b7cf38fb03628283041f63be)
set(WEBKIT_VERSION 12e2f46fb01f7c5cf5a992b9414ddfaab32b7110)
endif()
if(WEBKIT_LOCAL)

View File

@@ -13,42 +13,39 @@
#include <JavaScriptCore/CodeBlock.h>
#include <JavaScriptCore/Operations.h>
#include <JavaScriptCore/JSCInlines.h>
#include <JavaScriptCore/ObjectConstructor.h>
#include <JavaScriptCore/JSBoundFunction.h>
using namespace JSC;
namespace Zig {
static JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncGetThis);
static JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncGetTypeName);
static JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncGetFunction);
static JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncGetFunctionName);
static JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncGetMethodName);
static JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncGetFileName);
static JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncGetLineNumber);
static JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncGetColumnNumber);
static JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncGetEvalOrigin);
static JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncGetScriptNameOrSourceURL);
static JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncIsToplevel);
static JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncIsEval);
static JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncIsNative);
static JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncIsConstructor);
static JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncIsAsync);
static JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncIsPromiseAll);
static JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncGetPromiseIndex);
static JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncToString);
JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncGetThis);
JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncGetTypeName);
JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncGetFunction);
JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncGetFunctionName);
JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncGetMethodName);
JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncGetFileName);
JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncGetLineNumber);
JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncGetColumnNumber);
JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncGetEvalOrigin);
JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncGetScriptNameOrSourceURL);
JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncIsToplevel);
JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncIsEval);
JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncIsNative);
JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncIsConstructor);
JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncIsAsync);
JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncIsPromiseAll);
JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncGetPromiseIndex);
JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncToString);
JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncToJSON);
ALWAYS_INLINE static CallSite* getCallSite(JSGlobalObject* globalObject, JSC::JSValue thisValue)
{
JSC::VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
if (UNLIKELY(!thisValue.isCell())) {
JSC::throwVMError(globalObject, scope, createNotAnObjectError(globalObject, thisValue));
return nullptr;
}
if (LIKELY(thisValue.asCell()->inherits(CallSite::info()))) {
return JSC::jsCast<CallSite*>(thisValue);
if (auto* callSite = JSC::jsDynamicCast<CallSite*>(thisValue)) {
return callSite;
}
throwTypeError(globalObject, scope, "CallSite operation called on non-CallSite object"_s);
@@ -84,6 +81,7 @@ static const HashTableValue CallSitePrototypeTableValues[]
{ "isPromiseAll"_s, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::Function, NoIntrinsic, { HashTableValue::NativeFunctionType, callSiteProtoFuncIsPromiseAll, 0 } },
{ "getPromiseIndex"_s, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::Function, NoIntrinsic, { HashTableValue::NativeFunctionType, callSiteProtoFuncGetPromiseIndex, 0 } },
{ "toString"_s, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::Function, NoIntrinsic, { HashTableValue::NativeFunctionType, callSiteProtoFuncToString, 0 } },
{ "toJSON"_s, JSC::PropertyAttribute::Function | 0, NoIntrinsic, { HashTableValue::NativeFunctionType, callSiteProtoFuncToJSON, 0 } },
};
const JSC::ClassInfo CallSitePrototype::s_info = { "CallSite"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(CallSitePrototype) };
@@ -165,10 +163,29 @@ JSC_DEFINE_HOST_FUNCTION(callSiteProtoFuncIsToplevel, (JSGlobalObject * globalOb
{
ENTER_PROTO_FUNC();
if (JSValue functionValue = callSite->function()) {
if (JSObject* fn = functionValue.getObject()) {
if (JSFunction* function = jsDynamicCast<JSFunction*>(fn)) {
if (function->inherits<JSC::JSBoundFunction>()) {
return JSC::JSValue::encode(JSC::jsBoolean(false));
}
if (function->isHostFunction()) {
return JSC::JSValue::encode(JSC::jsBoolean(true));
}
if (auto* executable = function->jsExecutable()) {
return JSValue::encode(jsBoolean(executable->isProgramExecutable() || executable->isModuleProgramExecutable()));
}
} else if (auto* function = jsDynamicCast<InternalFunction*>(functionValue)) {
return JSC::JSValue::encode(JSC::jsBoolean(true));
}
}
}
JSC::JSValue thisValue = callSite->thisValue();
// This is what v8 does (JSStackFrame::IsToplevel in messages.cc):
if (thisValue.isUndefinedOrNull()) {
return JSC::JSValue::encode(JSC::jsBoolean(true));
}
@@ -237,4 +254,15 @@ JSC_DEFINE_HOST_FUNCTION(callSiteProtoFuncToString, (JSGlobalObject * globalObje
return JSC::JSValue::encode(JSC::JSValue(jsString(vm, sb.toString())));
}
JSC_DEFINE_HOST_FUNCTION(callSiteProtoFuncToJSON, (JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
{
ENTER_PROTO_FUNC();
JSObject* obj = JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), 4);
obj->putDirect(vm, JSC::Identifier::fromString(vm, "sourceURL"_s), callSite->sourceURL());
obj->putDirect(vm, JSC::Identifier::fromString(vm, "lineNumber"_s), jsNumber(callSite->lineNumber().oneBasedInt()));
obj->putDirect(vm, JSC::Identifier::fromString(vm, "columnNumber"_s), jsNumber(callSite->columnNumber().zeroBasedInt()));
obj->putDirect(vm, JSC::Identifier::fromString(vm, "functionName"_s), callSite->functionName());
return JSC::JSValue::encode(obj);
}
}

View File

@@ -22,7 +22,15 @@ void adjustPositionBackwards(ZigStackFramePosition& pos, int amount, CodeBlock*
pos.column_zero_based = pos.column_zero_based - amount;
if (pos.column_zero_based < 0) {
auto source = code->source().provider()->source();
auto* provider = code->source().provider();
if (!provider) {
pos.line_zero_based = 0;
pos.column_zero_based = 0;
pos.byte_position = 0;
return;
}
auto source = provider->source();
if (!source.is8Bit()) {
// Debug-only assertion
// Bun does not yet use 16-bit sources anywhere. The transpiler ensures everything
@@ -75,6 +83,8 @@ ZigStackFramePosition getAdjustedPositionForBytecode(JSC::CodeBlock* code, JSC::
switch (inst->opcodeID()) {
case op_construct:
case op_construct_varargs:
case op_super_construct:
case op_super_construct_varargs:
// The divot by default is pointing at the `(` or the end of the class name.
// We want to point at the `new` keyword, which is conveniently at the
// expression start.

View File

@@ -16,6 +16,8 @@
#include <JavaScriptCore/StackVisitor.h>
#include <JavaScriptCore/NativeCallee.h>
#include <wtf/IterationStatus.h>
#include <JavaScriptCore/CodeBlock.h>
#include <JavaScriptCore/FunctionCodeBlock.h>
#include "ErrorStackFrame.h"
@@ -24,6 +26,69 @@ using namespace WebCore;
namespace Zig {
static ImplementationVisibility getImplementationVisibility(JSC::CodeBlock* codeBlock)
{
if (auto* executable = codeBlock->ownerExecutable()) {
return executable->implementationVisibility();
}
return ImplementationVisibility::Public;
}
static bool isImplementationVisibilityPrivate(JSC::StackVisitor& visitor)
{
ImplementationVisibility implementationVisibility = [&]() -> ImplementationVisibility {
if (visitor->callee().isCell()) {
if (auto* callee = visitor->callee().asCell()) {
if (auto* jsFunction = jsDynamicCast<JSFunction*>(callee)) {
if (auto* executable = jsFunction->executable())
return executable->implementationVisibility();
}
}
}
if (auto* codeBlock = visitor->codeBlock()) {
return getImplementationVisibility(codeBlock);
}
#if ENABLE(WEBASSEMBLY)
if (visitor->isNativeCalleeFrame())
return visitor->callee().asNativeCallee()->implementationVisibility();
#endif
return ImplementationVisibility::Public;
}();
return implementationVisibility != ImplementationVisibility::Public;
}
static bool isImplementationVisibilityPrivate(const JSC::StackFrame& frame)
{
ImplementationVisibility implementationVisibility = [&]() -> ImplementationVisibility {
#if ENABLE(WEBASSEMBLY)
if (frame.isWasmFrame())
return ImplementationVisibility::Public;
#endif
if (auto* callee = frame.callee()) {
if (auto* jsFunction = jsDynamicCast<JSFunction*>(callee)) {
if (auto* executable = jsFunction->executable())
return executable->implementationVisibility();
}
}
if (auto* codeBlock = frame.codeBlock()) {
return getImplementationVisibility(codeBlock);
}
return ImplementationVisibility::Public;
}();
return implementationVisibility != ImplementationVisibility::Public;
}
JSCStackTrace JSCStackTrace::fromExisting(JSC::VM& vm, const WTF::Vector<JSC::StackFrame>& existingFrames)
{
WTF::Vector<JSCStackFrame> newFrames;
@@ -35,41 +100,155 @@ JSCStackTrace JSCStackTrace::fromExisting(JSC::VM& vm, const WTF::Vector<JSC::St
newFrames.reserveInitialCapacity(frameCount);
for (size_t i = 0; i < frameCount; i++) {
newFrames.constructAndAppend(vm, existingFrames.at(i));
if (!isImplementationVisibilityPrivate(existingFrames.at(i))) {
newFrames.constructAndAppend(vm, existingFrames.at(i));
}
}
return JSCStackTrace(newFrames);
}
static bool isImplementationVisibilityPrivate(JSC::StackVisitor& visitor)
void JSCStackTrace::getFramesForCaller(JSC::VM& vm, JSC::CallFrame* callFrame, JSC::JSCell* owner, JSC::JSValue caller, WTF::Vector<JSC::StackFrame>& stackTrace, size_t stackTraceLimit)
{
ImplementationVisibility implementationVisibility = [&]() -> ImplementationVisibility {
if (auto* codeBlock = visitor->codeBlock()) {
if (auto* executable = codeBlock->ownerExecutable()) {
return executable->implementationVisibility();
}
return ImplementationVisibility::Public;
size_t framesCount = 0;
bool belowCaller = false;
int32_t skipFrames = 0;
WTF::String callerName {};
if (JSC::JSFunction* callerFunction = JSC::jsDynamicCast<JSC::JSFunction*>(caller)) {
callerName = callerFunction->name(vm);
if (callerName.isEmpty() && callerFunction->jsExecutable()) {
callerName = callerFunction->jsExecutable()->name().string();
}
}
if (JSC::InternalFunction* callerFunctionInternal = JSC::jsDynamicCast<JSC::InternalFunction*>(caller)) {
callerName = callerFunctionInternal->name();
}
#if ENABLE(WEBASSEMBLY)
if (visitor->isNativeCalleeFrame())
return visitor->callee().asNativeCallee()->implementationVisibility();
#endif
size_t totalFrames = 0;
if (visitor->callee().isCell()) {
if (auto* callee = visitor->callee().asCell()) {
if (auto* jsFunction = jsDynamicCast<JSFunction*>(callee)) {
if (auto* executable = jsFunction->executable())
return executable->implementationVisibility();
return ImplementationVisibility::Public;
if (!callerName.isEmpty()) {
JSC::StackVisitor::visit(callFrame, vm, [&](JSC::StackVisitor& visitor) -> WTF::IterationStatus {
if (isImplementationVisibilityPrivate(visitor)) {
return WTF::IterationStatus::Continue;
}
framesCount += 1;
// skip caller frame and all frames above it
if (!belowCaller) {
skipFrames += 1;
if (visitor->functionName() == callerName) {
belowCaller = true;
return WTF::IterationStatus::Continue;
}
}
totalFrames += 1;
if (totalFrames > stackTraceLimit) {
return WTF::IterationStatus::Done;
}
return WTF::IterationStatus::Continue;
});
} else if (caller && caller.isCell()) {
JSC::StackVisitor::visit(callFrame, vm, [&](JSC::StackVisitor& visitor) -> WTF::IterationStatus {
if (isImplementationVisibilityPrivate(visitor)) {
return WTF::IterationStatus::Continue;
}
framesCount += 1;
// skip caller frame and all frames above it
if (!belowCaller) {
auto callee = visitor->callee();
skipFrames += 1;
if (callee.isCell() && callee.asCell() == caller) {
belowCaller = true;
return WTF::IterationStatus::Continue;
}
}
totalFrames += 1;
if (totalFrames > stackTraceLimit) {
return WTF::IterationStatus::Done;
}
return WTF::IterationStatus::Continue;
});
} else if (caller.isEmpty() || caller.isUndefined()) {
// Skip the first frame.
JSC::StackVisitor::visit(callFrame, vm, [&](JSC::StackVisitor& visitor) -> WTF::IterationStatus {
if (isImplementationVisibilityPrivate(visitor)) {
return WTF::IterationStatus::Continue;
}
framesCount += 1;
if (!belowCaller) {
skipFrames += 1;
belowCaller = true;
}
totalFrames += 1;
if (totalFrames > stackTraceLimit) {
return WTF::IterationStatus::Done;
}
return WTF::IterationStatus::Continue;
});
}
size_t i = 0;
totalFrames = 0;
stackTrace.reserveInitialCapacity(framesCount);
JSC::StackVisitor::visit(callFrame, vm, [&](JSC::StackVisitor& visitor) -> WTF::IterationStatus {
// Skip native frames
if (isImplementationVisibilityPrivate(visitor)) {
return WTF::IterationStatus::Continue;
}
return ImplementationVisibility::Public;
}();
// Skip frames if needed
if (skipFrames > 0) {
skipFrames--;
return WTF::IterationStatus::Continue;
}
return implementationVisibility != ImplementationVisibility::Public;
totalFrames += 1;
if (totalFrames > stackTraceLimit) {
return WTF::IterationStatus::Done;
}
if (visitor->isNativeCalleeFrame()) {
auto* nativeCallee = visitor->callee().asNativeCallee();
switch (nativeCallee->category()) {
case NativeCallee::Category::Wasm: {
stackTrace.append(StackFrame(visitor->wasmFunctionIndexOrName()));
break;
}
case NativeCallee::Category::InlineCache: {
break;
}
}
#if USE(ALLOW_LINE_AND_COLUMN_NUMBER_IN_BUILTINS)
} else if (!!visitor->codeBlock())
#else
} else if (!!visitor->codeBlock() && !visitor->codeBlock()->unlinkedCodeBlock()->isBuiltinFunction())
#endif
stackTrace.append(StackFrame(vm, owner, visitor->callee().asCell(), visitor->codeBlock(), visitor->bytecodeIndex()));
else
stackTrace.append(StackFrame(vm, owner, visitor->callee().asCell()));
i++;
return (i == framesCount) ? WTF::IterationStatus::Done : WTF::IterationStatus::Continue;
});
}
JSCStackTrace JSCStackTrace::captureCurrentJSStackTrace(Zig::GlobalObject* globalObject, JSC::CallFrame* callFrame, size_t frameLimit, JSC::JSValue caller)
@@ -203,6 +382,22 @@ JSCStackTrace JSCStackTrace::getStackTraceForThrownValue(JSC::VM& vm, JSC::JSVal
return fromExisting(vm, *jscStackTrace);
}
static bool isVisibleBuiltinFunction(JSC::CodeBlock* codeBlock)
{
if (!codeBlock->ownerExecutable()) {
return false;
}
const JSC::SourceCode& source = codeBlock->source();
if (auto* provider = source.provider()) {
const auto& url = provider->sourceURL();
if (!url.isEmpty()) {
return true;
}
}
return false;
}
JSCStackFrame::JSCStackFrame(JSC::VM& vm, JSC::StackVisitor& visitor)
: m_vm(vm)
, m_codeBlock(nullptr)
@@ -228,9 +423,18 @@ JSCStackFrame::JSCStackFrame(JSC::VM& vm, JSC::StackVisitor& visitor)
break;
}
}
} else if (!!visitor->codeBlock() && !visitor->codeBlock()->unlinkedCodeBlock()->isBuiltinFunction()) {
m_codeBlock = visitor->codeBlock();
m_bytecodeIndex = visitor->bytecodeIndex();
} else if (auto* codeBlock = visitor->codeBlock()) {
auto* unlinkedCodeBlock = codeBlock->unlinkedCodeBlock();
if (!unlinkedCodeBlock->isBuiltinFunction() || isVisibleBuiltinFunction(codeBlock)) {
m_codeBlock = codeBlock;
m_bytecodeIndex = visitor->bytecodeIndex();
}
}
if (!m_bytecodeIndex && visitor->hasLineAndColumnInfo()) {
auto lineColumn = visitor->computeLineAndColumn();
m_sourcePositions = { OrdinalNumber::fromOneBasedInt(lineColumn.line), OrdinalNumber::fromOneBasedInt(lineColumn.column) };
m_sourcePositionsState = SourcePositionsState::Calculated;
}
}
@@ -250,12 +454,19 @@ JSCStackFrame::JSCStackFrame(JSC::VM& vm, const JSC::StackFrame& frame)
if (frame.isWasmFrame()) {
m_wasmFunctionIndexOrName = frame.wasmFunctionIndexOrName();
m_isWasmFrame = true;
} else {
m_codeBlock = frame.codeBlock();
if (frame.hasBytecodeIndex()) {
} else if (auto* codeBlock = frame.codeBlock()) {
auto* unlinkedCodeBlock = codeBlock->unlinkedCodeBlock();
if (!unlinkedCodeBlock->isBuiltinFunction() || isVisibleBuiltinFunction(codeBlock)) {
m_codeBlock = codeBlock;
m_bytecodeIndex = frame.bytecodeIndex();
}
}
if (!m_codeBlock && frame.hasLineAndColumnInfo()) {
auto lineColumn = frame.computeLineAndColumn();
m_sourcePositions = { OrdinalNumber::fromOneBasedInt(lineColumn.line), OrdinalNumber::fromOneBasedInt(lineColumn.column) };
m_sourcePositionsState = SourcePositionsState::Calculated;
}
}
intptr_t JSCStackFrame::sourceID() const
@@ -308,16 +519,36 @@ ALWAYS_INLINE String JSCStackFrame::retrieveSourceURL()
return String(sourceURLWasmString);
}
if (m_callee && m_callee->isObject()) {
if (auto* jsFunction = jsDynamicCast<JSFunction*>(m_callee)) {
if (auto* executable = jsFunction->executable()) {
if (!executable->isHostFunction()) {
auto* jsExectuable = jsFunction->jsExecutable();
if (jsExectuable) {
const auto* sourceProvider = jsExectuable->source().provider();
if (sourceProvider) {
return sourceProvider->sourceURL();
}
}
}
}
}
}
if (!m_codeBlock) {
return String(sourceURLNativeString);
}
return m_codeBlock->ownerExecutable()->sourceURL();
auto* provider = m_codeBlock->source().provider();
if (provider) {
return provider->sourceURL();
}
return String();
}
ALWAYS_INLINE String JSCStackFrame::retrieveFunctionName()
{
static auto functionNameEvalCodeString = MAKE_STATIC_STRING_IMPL("eval code");
static auto functionNameModuleCodeString = MAKE_STATIC_STRING_IMPL("module code");
static auto functionNameGlobalCodeString = MAKE_STATIC_STRING_IMPL("global code");
@@ -328,7 +559,8 @@ ALWAYS_INLINE String JSCStackFrame::retrieveFunctionName()
if (m_codeBlock) {
switch (m_codeBlock->codeType()) {
case JSC::EvalCode:
return String(functionNameEvalCodeString);
// Node returns null here.
return String();
case JSC::ModuleCode:
return String(functionNameModuleCodeString);
case JSC::FunctionCode:
@@ -340,13 +572,26 @@ ALWAYS_INLINE String JSCStackFrame::retrieveFunctionName()
}
}
String name;
if (m_callee) {
if (m_callee->isObject())
name = getCalculatedDisplayName(m_vm, jsCast<JSObject*>(m_callee)).impl();
if (auto* callee = m_callee->getObject()) {
// Does the code block have a user-defined name property?
JSC::JSValue name = callee->getDirect(m_vm, m_vm.propertyNames->name);
if (name && name.isString()) {
auto scope = DECLARE_CATCH_SCOPE(m_vm);
auto nameString = name.toWTFString(callee->globalObject());
if (scope.exception()) {
scope.clearException();
}
if (!nameString.isEmpty()) {
return nameString;
}
}
return JSC::getCalculatedDisplayName(m_vm, callee);
}
}
return name.isNull() ? emptyString() : name;
return emptyString();
}
ALWAYS_INLINE String JSCStackFrame::retrieveTypeName()

View File

@@ -46,13 +46,13 @@ public:
private:
JSC::VM& m_vm;
JSC::JSCell* m_callee;
JSC::JSCell* m_callee { nullptr };
// May be null
JSC::CallFrame* m_callFrame;
// May be null
JSC::CodeBlock* m_codeBlock;
JSC::CodeBlock* m_codeBlock { nullptr };
JSC::BytecodeIndex m_bytecodeIndex;
// Lazy-initialized
@@ -96,8 +96,40 @@ public:
SourcePositions* getSourcePositions();
bool isWasmFrame() const { return m_isWasmFrame; }
bool isEval() const { return m_codeBlock && (JSC::EvalCode == m_codeBlock->codeType()); }
bool isConstructor() const { return m_codeBlock && (JSC::CodeForConstruct == m_codeBlock->specializationKind()); }
bool isEval()
{
if (m_codeBlock) {
if (m_codeBlock->codeType() == JSC::EvalCode) {
return true;
}
auto* executable = m_codeBlock->ownerExecutable();
if (!executable) {
return false;
}
switch (executable->evalContextType()) {
case JSC::EvalContextType::None: {
return false;
}
case JSC::EvalContextType::FunctionEvalContext:
case JSC::EvalContextType::InstanceFieldEvalContext:
return true;
}
}
if (m_callee && m_callee->inherits<JSC::JSFunction>()) {
auto* function = jsCast<JSC::JSFunction*>(m_callee);
if (function->isHostFunction()) {
return false;
}
}
return false;
}
bool isConstructor() const
{
return m_codeBlock && (JSC::CodeForConstruct == m_codeBlock->specializationKind());
}
private:
ALWAYS_INLINE String retrieveSourceURL();
@@ -130,10 +162,17 @@ public:
{
}
JSCStackTrace(WTF::Vector<JSCStackFrame>&& frames)
: m_frames(WTFMove(frames))
{
}
size_t size() const { return m_frames.size(); }
bool isEmpty() const { return m_frames.isEmpty(); }
JSCStackFrame& at(size_t i) { return m_frames.at(i); }
WTF::Vector<JSCStackFrame>&& frames() { return WTFMove(m_frames); }
static JSCStackTrace fromExisting(JSC::VM& vm, const WTF::Vector<JSC::StackFrame>& existingFrames);
/* This is based on JSC::Interpreter::getStackTrace, but skips native (non js and not wasm)
@@ -145,6 +184,7 @@ public:
*
* Return value must remain stack allocated. */
static JSCStackTrace captureCurrentJSStackTrace(Zig::GlobalObject* globalObject, JSC::CallFrame* callFrame, size_t frameLimit, JSC::JSValue caller);
static void getFramesForCaller(JSC::VM& vm, JSC::CallFrame* callFrame, JSC::JSCell* owner, JSC::JSValue caller, WTF::Vector<JSC::StackFrame>& stackTrace, size_t stackTraceLimit);
/* In JSC, JSC::Exception points to the actual value that was thrown, usually
* a JSC::ErrorInstance (but could be any JSValue). In v8, on the other hand,

View File

@@ -276,27 +276,10 @@ extern "C" void* Bun__getVM();
extern "C" void Bun__setDefaultGlobalObject(Zig::GlobalObject* globalObject);
// Error.captureStackTrace may cause computeErrorInfo to be called twice
// Rather than figure out the plumbing in JSC, we just skip the next call
// TODO: thread_local for workers
static bool skipNextComputeErrorInfo = false;
static JSValue formatStackTraceToJSValue(JSC::VM& vm, Zig::GlobalObject* globalObject, JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSObject* errorObject, JSC::JSArray* callSites, JSValue prepareStackTrace)
static JSValue formatStackTraceToJSValue(JSC::VM& vm, Zig::GlobalObject* globalObject, JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSObject* errorObject, JSC::JSArray* callSites)
{
auto scope = DECLARE_THROW_SCOPE(vm);
auto* errorConstructor = lexicalGlobalObject->m_errorStructure.constructor(globalObject);
if (!prepareStackTrace) {
if (lexicalGlobalObject->inherits<Zig::GlobalObject>()) {
if (auto prepare = globalObject->m_errorConstructorPrepareStackTraceValue.get()) {
prepareStackTrace = prepare;
}
} else {
prepareStackTrace = errorConstructor->getIfPropertyExists(lexicalGlobalObject, JSC::Identifier::fromString(vm, "prepareStackTrace"_s));
}
}
// default formatting
size_t framesCount = callSites->length();
@@ -322,21 +305,20 @@ static JSValue formatStackTraceToJSValue(JSC::VM& vm, Zig::GlobalObject* globalO
CallSite* callSite = JSC::jsDynamicCast<CallSite*>(callSiteValue);
sb.append(" at "_s);
callSite->formatAsString(vm, lexicalGlobalObject, sb);
RETURN_IF_EXCEPTION(scope, {});
if (i != framesCount - 1) {
sb.append("\n"_s);
}
}
bool originalSkipNextComputeErrorInfo = skipNextComputeErrorInfo;
skipNextComputeErrorInfo = true;
if (errorObject->hasProperty(lexicalGlobalObject, vm.propertyNames->stack)) {
skipNextComputeErrorInfo = true;
errorObject->deleteProperty(lexicalGlobalObject, vm.propertyNames->stack);
}
return jsString(vm, sb.toString());
}
skipNextComputeErrorInfo = originalSkipNextComputeErrorInfo;
JSValue stackStringValue = jsString(vm, sb.toString());
static JSValue formatStackTraceToJSValue(JSC::VM& vm, Zig::GlobalObject* globalObject, JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSObject* errorObject, JSC::JSArray* callSites, JSValue prepareStackTrace)
{
auto scope = DECLARE_THROW_SCOPE(vm);
auto stackStringValue = formatStackTraceToJSValue(vm, globalObject, lexicalGlobalObject, errorObject, callSites);
RETURN_IF_EXCEPTION(scope, {});
if (prepareStackTrace && prepareStackTrace.isObject()) {
JSC::CallData prepareStackTraceCallData = JSC::getCallData(prepareStackTrace);
@@ -355,10 +337,10 @@ static JSValue formatStackTraceToJSValue(JSC::VM& vm, Zig::GlobalObject* globalO
JSC::ProfilingReason::Other,
prepareStackTrace,
prepareStackTraceCallData,
errorConstructor,
lexicalGlobalObject->m_errorStructure.constructor(globalObject),
arguments);
RETURN_IF_EXCEPTION(scope, {});
RETURN_IF_EXCEPTION(scope, stackStringValue);
if (result.isUndefinedOrNull()) {
result = jsUndefined();
@@ -371,6 +353,26 @@ static JSValue formatStackTraceToJSValue(JSC::VM& vm, Zig::GlobalObject* globalO
return stackStringValue;
}
static JSValue formatStackTraceToJSValueWithoutPrepareStackTrace(JSC::VM& vm, Zig::GlobalObject* globalObject, JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSObject* errorObject, JSC::JSArray* callSites)
{
JSValue prepareStackTrace = {};
if (lexicalGlobalObject->inherits<Zig::GlobalObject>()) {
if (auto prepare = globalObject->m_errorConstructorPrepareStackTraceValue.get()) {
prepareStackTrace = prepare;
}
} else {
auto scope = DECLARE_CATCH_SCOPE(vm);
auto* errorConstructor = lexicalGlobalObject->m_errorStructure.constructor(globalObject);
prepareStackTrace = errorConstructor->getIfPropertyExists(lexicalGlobalObject, JSC::Identifier::fromString(vm, "prepareStackTrace"_s));
if (scope.exception()) {
scope.clearException();
}
}
return formatStackTraceToJSValue(vm, globalObject, lexicalGlobalObject, errorObject, callSites, prepareStackTrace);
}
WTF::String Bun::formatStackTrace(
JSC::VM& vm,
Zig::GlobalObject* globalObject,
@@ -467,12 +469,13 @@ WTF::String Bun::formatStackTrace(
for (size_t i = 0; i < framesCount; i++) {
StackFrame& frame = stackTrace.at(i);
WTF::String functionName;
bool isBuiltinFunction = false;
sb.append(" at "_s);
WTF::String functionName;
if (auto codeblock = frame.codeBlock()) {
if (codeblock->isConstructor()) {
sb.append("new "_s);
}
@@ -484,11 +487,25 @@ WTF::String Bun::formatStackTrace(
case JSC::CodeType::FunctionCode:
case JSC::CodeType::EvalCode: {
if (auto* callee = frame.callee()) {
if (callee->isObject()) {
JSValue functionNameValue = callee->getObject()->getDirect(vm, vm.propertyNames->name);
if (auto* object = callee->getObject()) {
JSValue functionNameValue = object->getDirect(vm, vm.propertyNames->name);
if (functionNameValue && functionNameValue.isString()) {
functionName = functionNameValue.toWTFString(lexicalGlobalObject);
}
if (functionName.isEmpty()) {
auto catchScope = DECLARE_CATCH_SCOPE(vm);
functionName = JSC::getCalculatedDisplayName(vm, object);
if (catchScope.exception()) {
catchScope.clearException();
}
}
if (auto* unlinkedCodeBlock = codeblock->unlinkedCodeBlock()) {
if (unlinkedCodeBlock->isBuiltinFunction()) {
isBuiltinFunction = true;
}
}
}
}
break;
@@ -544,8 +561,10 @@ WTF::String Bun::formatStackTrace(
}
}
// If it's not a Zig::GlobalObject, don't bother source-mapping it.
if (globalObject == lexicalGlobalObject && globalObject) {
bool isDefinitelyNotRunninginNodeVMGlobalObject = (globalObject == lexicalGlobalObject && globalObject);
bool isDefaultGlobalObjectInAFinalizer = (globalObject && !lexicalGlobalObject && !errorInstance);
if (isDefinitelyNotRunninginNodeVMGlobalObject || isDefaultGlobalObjectInAFinalizer) {
// https://github.com/oven-sh/bun/issues/3595
if (!sourceURLForFrame.isEmpty()) {
remappedFrame.source_url = Bun::toStringRef(sourceURLForFrame);
@@ -572,7 +591,15 @@ WTF::String Bun::formatStackTrace(
}
sb.append(" ("_s);
sb.append(sourceURLForFrame);
if (sourceURLForFrame.isEmpty()) {
if (isBuiltinFunction) {
sb.append("native"_s);
} else {
sb.append("unknown"_s);
}
} else {
sb.append(sourceURLForFrame);
}
sb.append(":"_s);
sb.append(remappedFrame.position.line().oneBasedInt());
sb.append(":"_s);
@@ -623,16 +650,14 @@ static String computeErrorInfoWithoutPrepareStackTrace(
return Bun::formatStackTrace(vm, globalObject, lexicalGlobalObject, name, message, line, column, sourceURL, stackTrace, errorInstance);
}
static String computeErrorInfoWithPrepareStackTrace(JSC::VM& vm, Zig::GlobalObject* globalObject, JSC::JSGlobalObject* lexicalGlobalObject, Vector<StackFrame>& stackFrames, OrdinalNumber& line, OrdinalNumber& column, String& sourceURL, JSObject* errorObject, JSObject* prepareStackTrace)
static JSValue computeErrorInfoWithPrepareStackTrace(JSC::VM& vm, Zig::GlobalObject* globalObject, JSC::JSGlobalObject* lexicalGlobalObject, Vector<StackFrame>& stackFrames, OrdinalNumber& line, OrdinalNumber& column, String& sourceURL, JSObject* errorObject, JSObject* prepareStackTrace)
{
auto scope = DECLARE_THROW_SCOPE(vm);
JSCStackTrace stackTrace = JSCStackTrace::fromExisting(vm, stackFrames);
// Note: we cannot use tryCreateUninitializedRestricted here because we cannot allocate memory inside initializeIndex()
JSC::JSArray* callSites = JSC::JSArray::create(vm,
globalObject->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous),
stackTrace.size());
MarkedArgumentBuffer callSites;
// Create the call sites (one per frame)
GlobalObject::createCallSitesFromFrames(globalObject, lexicalGlobalObject, stackTrace, callSites);
@@ -657,7 +682,7 @@ static String computeErrorInfoWithPrepareStackTrace(JSC::VM& vm, Zig::GlobalObje
Bun__remapStackFramePositions(globalObject, remappedFrames, framesCount);
for (size_t i = 0; i < framesCount; i++) {
JSC::JSValue callSiteValue = callSites->getIndex(lexicalGlobalObject, i);
JSC::JSValue callSiteValue = callSites.at(i);
CallSite* callSite = JSC::jsDynamicCast<CallSite*>(callSiteValue);
if (remappedFrames[i].remapped) {
callSite->setColumnNumber(remappedFrames[i].position.column());
@@ -666,64 +691,85 @@ static String computeErrorInfoWithPrepareStackTrace(JSC::VM& vm, Zig::GlobalObje
}
}
JSValue value = formatStackTraceToJSValue(vm, jsDynamicCast<Zig::GlobalObject*>(lexicalGlobalObject), lexicalGlobalObject, errorObject, callSites, prepareStackTrace);
JSArray* callSitesArray = JSC::constructArray(globalObject, globalObject->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous), callSites);
RETURN_IF_EXCEPTION(scope, String());
if (errorObject && !value.isEmpty()) {
errorObject->putDirect(vm, vm.propertyNames->stack, value, 0);
}
if (value.isString()) {
return value.toWTFString(lexicalGlobalObject);
}
return String();
return formatStackTraceToJSValue(vm, globalObject, lexicalGlobalObject, errorObject, callSitesArray, prepareStackTrace);
}
static String computeErrorInfo(JSC::VM& vm, Vector<StackFrame>& stackTrace, OrdinalNumber& line, OrdinalNumber& column, String& sourceURL, JSObject* errorInstance)
static String computeErrorInfoToString(JSC::VM& vm, Vector<StackFrame>& stackTrace, OrdinalNumber& line, OrdinalNumber& column, String& sourceURL)
{
if (skipNextComputeErrorInfo) {
return String();
}
Zig::GlobalObject* globalObject = nullptr;
JSC::JSGlobalObject* lexicalGlobalObject = nullptr;
if (errorInstance) {
lexicalGlobalObject = errorInstance->globalObject();
globalObject = jsDynamicCast<Zig::GlobalObject*>(lexicalGlobalObject);
return computeErrorInfoWithoutPrepareStackTrace(vm, globalObject, lexicalGlobalObject, stackTrace, line, column, sourceURL, nullptr);
}
// Error.prepareStackTrace - https://v8.dev/docs/stack-trace-api#customizing-stack-traces
if (!globalObject) {
// node:vm will use a different JSGlobalObject
globalObject = defaultGlobalObject();
static JSValue computeErrorInfoToJSValueWithoutSkipping(JSC::VM& vm, Vector<StackFrame>& stackTrace, OrdinalNumber& line, OrdinalNumber& column, String& sourceURL, JSObject* errorInstance)
{
Zig::GlobalObject* globalObject = nullptr;
JSC::JSGlobalObject* lexicalGlobalObject = nullptr;
lexicalGlobalObject = errorInstance->globalObject();
globalObject = jsDynamicCast<Zig::GlobalObject*>(lexicalGlobalObject);
// Error.prepareStackTrace - https://v8.dev/docs/stack-trace-api#customizing-stack-traces
if (!globalObject) {
// node:vm will use a different JSGlobalObject
globalObject = defaultGlobalObject();
if (!globalObject->isInsideErrorPrepareStackTraceCallback) {
auto* errorConstructor = lexicalGlobalObject->m_errorStructure.constructor(lexicalGlobalObject);
if (JSValue prepareStackTrace = errorConstructor->getIfPropertyExists(lexicalGlobalObject, Identifier::fromString(vm, "prepareStackTrace"_s))) {
if (prepareStackTrace.isCell() && prepareStackTrace.isObject() && prepareStackTrace.isCallable()) {
return computeErrorInfoWithPrepareStackTrace(vm, globalObject, lexicalGlobalObject, stackTrace, line, column, sourceURL, errorInstance, prepareStackTrace.getObject());
globalObject->isInsideErrorPrepareStackTraceCallback = true;
auto result = computeErrorInfoWithPrepareStackTrace(vm, globalObject, lexicalGlobalObject, stackTrace, line, column, sourceURL, errorInstance, prepareStackTrace.getObject());
globalObject->isInsideErrorPrepareStackTraceCallback = false;
return result;
}
}
} else {
if (JSValue prepareStackTrace = globalObject->m_errorConstructorPrepareStackTraceValue.get()) {
if (prepareStackTrace.isCell() && prepareStackTrace.isObject() && prepareStackTrace.isCallable()) {
return computeErrorInfoWithPrepareStackTrace(vm, globalObject, lexicalGlobalObject, stackTrace, line, column, sourceURL, errorInstance, prepareStackTrace.getObject());
}
} else if (!globalObject->isInsideErrorPrepareStackTraceCallback) {
if (JSValue prepareStackTrace = globalObject->m_errorConstructorPrepareStackTraceValue.get()) {
if (prepareStackTrace) {
if (prepareStackTrace.isCallable()) {
globalObject->isInsideErrorPrepareStackTraceCallback = true;
auto result = computeErrorInfoWithPrepareStackTrace(vm, globalObject, lexicalGlobalObject, stackTrace, line, column, sourceURL, errorInstance, prepareStackTrace.getObject());
globalObject->isInsideErrorPrepareStackTraceCallback = false;
return result;
}
}
}
}
return computeErrorInfoWithoutPrepareStackTrace(vm, globalObject, lexicalGlobalObject, stackTrace, line, column, sourceURL, errorInstance);
String result = computeErrorInfoWithoutPrepareStackTrace(vm, globalObject, lexicalGlobalObject, stackTrace, line, column, sourceURL, errorInstance);
return jsString(vm, result);
}
static JSValue computeErrorInfoToJSValue(JSC::VM& vm, Vector<StackFrame>& stackTrace, OrdinalNumber& line, OrdinalNumber& column, String& sourceURL, JSObject* errorInstance)
{
return computeErrorInfoToJSValueWithoutSkipping(vm, stackTrace, line, column, sourceURL, errorInstance);
}
// TODO: @paperdave: remove this wrapper and make the WTF::Function from JavaScriptCore expect OrdinalNumber instead of unsigned.
static String computeErrorInfoWrapper(JSC::VM& vm, Vector<StackFrame>& stackTrace, unsigned int& line_in, unsigned int& column_in, String& sourceURL, JSObject* errorInstance)
static String computeErrorInfoWrapperToString(JSC::VM& vm, Vector<StackFrame>& stackTrace, unsigned int& line_in, unsigned int& column_in, String& sourceURL)
{
OrdinalNumber line = OrdinalNumber::fromOneBasedInt(line_in);
OrdinalNumber column = OrdinalNumber::fromOneBasedInt(column_in);
WTF::String result = computeErrorInfo(vm, stackTrace, line, column, sourceURL, errorInstance);
WTF::String result = computeErrorInfoToString(vm, stackTrace, line, column, sourceURL);
line_in = line.oneBasedInt();
column_in = column.oneBasedInt();
return result;
}
static JSValue computeErrorInfoWrapperToJSValue(JSC::VM& vm, Vector<StackFrame>& stackTrace, unsigned int& line_in, unsigned int& column_in, String& sourceURL, JSObject* errorInstance)
{
OrdinalNumber line = OrdinalNumber::fromOneBasedInt(line_in);
OrdinalNumber column = OrdinalNumber::fromOneBasedInt(column_in);
JSValue result = computeErrorInfoToJSValue(vm, stackTrace, line, column, sourceURL, errorInstance);
line_in = line.oneBasedInt();
column_in = column.oneBasedInt();
@@ -820,7 +866,8 @@ extern "C" JSC__JSGlobalObject* Zig__GlobalObject__create(void* console_client,
Bun__setDefaultGlobalObject(globalObject);
JSC::gcProtect(globalObject);
vm.setOnComputeErrorInfo(computeErrorInfoWrapper);
vm.setOnComputeErrorInfo(computeErrorInfoWrapperToString);
vm.setOnComputeErrorInfoJSValue(computeErrorInfoWrapperToJSValue);
vm.setOnEachMicrotaskTick([](JSC::VM& vm) -> void {
auto* globalObject = defaultGlobalObject();
if (auto nextTickQueue = globalObject->m_nextTickQueue.get()) {
@@ -2528,7 +2575,7 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionPerformMicrotaskVariadic, (JSGlobalObject * g
return JSValue::encode(jsUndefined());
}
void GlobalObject::createCallSitesFromFrames(Zig::GlobalObject* globalObject, JSC::JSGlobalObject* lexicalGlobalObject, JSCStackTrace& stackTrace, JSC::JSArray* callSites)
void GlobalObject::createCallSitesFromFrames(Zig::GlobalObject* globalObject, JSC::JSGlobalObject* lexicalGlobalObject, JSCStackTrace& stackTrace, MarkedArgumentBuffer& callSites)
{
/* From v8's "Stack Trace API" (https://github.com/v8/v8/wiki/Stack-Trace-API):
* "To maintain restrictions imposed on strict mode functions, frames that have a
@@ -2543,20 +2590,12 @@ void GlobalObject::createCallSitesFromFrames(Zig::GlobalObject* globalObject, JS
for (size_t i = 0; i < framesCount; i++) {
CallSite* callSite = CallSite::create(lexicalGlobalObject, callSiteStructure, stackTrace.at(i), encounteredStrictFrame);
callSites->putDirectIndex(lexicalGlobalObject, i, callSite);
if (!encounteredStrictFrame) {
encounteredStrictFrame = callSite->isStrict();
}
}
}
void GlobalObject::formatStackTrace(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSObject* errorObject, JSC::JSArray* callSites, JSValue prepareStackTrace)
{
JSValue stackTraceValue = formatStackTraceToJSValue(vm, this, lexicalGlobalObject, errorObject, callSites, prepareStackTrace);
if (!stackTraceValue.isEmpty()) {
errorObject->putDirect(vm, vm.propertyNames->stack, stackTraceValue, 0);
callSites.append(callSite);
}
}
@@ -2606,6 +2645,44 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionDefaultErrorPrepareStackTrace, (JSGlobalObjec
return JSC::JSValue::encode(result);
}
JSC_DEFINE_CUSTOM_GETTER(errorInstanceLazyStackCustomGetter, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, PropertyName))
{
auto& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
auto* errorObject = jsDynamicCast<ErrorInstance*>(JSValue::decode(thisValue));
// This shouldn't be possible.
if (!errorObject) {
return JSValue::encode(jsUndefined());
}
OrdinalNumber line;
OrdinalNumber column;
String sourceURL;
auto stackTrace = errorObject->stackTrace();
if (stackTrace == nullptr) {
return JSValue::encode(jsUndefined());
}
JSValue result = computeErrorInfoToJSValue(vm, *stackTrace, line, column, sourceURL, errorObject);
stackTrace->clear();
errorObject->setStackFrames(vm, {});
RETURN_IF_EXCEPTION(scope, {});
errorObject->putDirect(vm, vm.propertyNames->stack, result, 0);
return JSValue::encode(result);
}
JSC_DEFINE_CUSTOM_SETTER(errorInstanceLazyStackCustomSetter, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue value, PropertyName))
{
auto& vm = globalObject->vm();
JSValue decodedValue = JSValue::decode(thisValue);
if (auto* object = decodedValue.getObject()) {
object->putDirect(vm, vm.propertyNames->stack, JSValue::decode(value), 0);
}
return true;
}
JSC_DEFINE_HOST_FUNCTION(errorConstructorFuncCaptureStackTrace, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame))
{
GlobalObject* globalObject = reinterpret_cast<GlobalObject*>(lexicalGlobalObject);
@@ -2625,56 +2702,30 @@ JSC_DEFINE_HOST_FUNCTION(errorConstructorFuncCaptureStackTrace, (JSC::JSGlobalOb
stackTraceLimit = DEFAULT_ERROR_STACK_TRACE_LIMIT;
}
JSCStackTrace stackTrace = JSCStackTrace::captureCurrentJSStackTrace(globalObject, callFrame, stackTraceLimit, caller);
WTF::Vector<JSC::StackFrame> stackTrace;
JSCStackTrace::getFramesForCaller(vm, callFrame, errorObject, caller, stackTrace, stackTraceLimit);
// Note: we cannot use tryCreateUninitializedRestricted here because we cannot allocate memory inside initializeIndex()
JSC::JSArray* callSites = JSC::JSArray::create(vm,
globalObject->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous),
stackTrace.size());
// Create the call sites (one per frame)
GlobalObject::createCallSitesFromFrames(globalObject, lexicalGlobalObject, stackTrace, callSites);
/* Format the stack trace.
* Note that v8 won't actually format the stack trace here, but will create a "stack" accessor
* on the error object, which will format the stack trace on the first access. For now, since
* we're not being used internally by JSC, we can assume callers of Error.captureStackTrace in
* node are interested in the (formatted) stack. */
size_t framesCount = stackTrace.size();
ZigStackFrame remappedFrames[64];
framesCount = framesCount > 64 ? 64 : framesCount;
for (int i = 0; i < framesCount; i++) {
memset(remappedFrames + i, 0, sizeof(ZigStackFrame));
remappedFrames[i].source_url = Bun::toStringRef(lexicalGlobalObject, stackTrace.at(i).sourceURL());
if (JSCStackFrame::SourcePositions* sourcePositions = stackTrace.at(i).getSourcePositions()) {
remappedFrames[i].position.line_zero_based = sourcePositions->line.zeroBasedInt();
remappedFrames[i].position.column_zero_based = sourcePositions->column.zeroBasedInt();
} else {
remappedFrames[i].position.line_zero_based = -1;
remappedFrames[i].position.column_zero_based = -1;
if (auto* instance = jsDynamicCast<JSC::ErrorInstance*>(errorObject)) {
instance->setStackFrames(vm, WTFMove(stackTrace));
if (instance->hasMaterializedErrorInfo()) {
const auto& propertyName = vm.propertyNames->stack;
VM::DeletePropertyModeScope scope(vm, VM::DeletePropertyMode::IgnoreConfigurable);
DeletePropertySlot slot;
JSObject::deleteProperty(instance, globalObject, propertyName, slot);
if (auto* zigGlobalObject = jsDynamicCast<Zig::GlobalObject*>(globalObject)) {
instance->putDirectCustomAccessor(vm, vm.propertyNames->stack, zigGlobalObject->m_lazyStackCustomGetterSetter.get(zigGlobalObject), JSC::PropertyAttribute::CustomAccessor | 0);
} else {
instance->putDirectCustomAccessor(vm, vm.propertyNames->stack, CustomGetterSetter::create(vm, errorInstanceLazyStackCustomGetter, errorInstanceLazyStackCustomSetter), JSC::PropertyAttribute::CustomAccessor | 0);
}
}
} else {
OrdinalNumber line;
OrdinalNumber column;
String sourceURL;
JSValue result = computeErrorInfoToJSValue(vm, stackTrace, line, column, sourceURL, errorObject);
errorObject->putDirect(vm, vm.propertyNames->stack, result, 0);
}
// remap line and column start to original source
// XXX: this function does not fully populate the fields of ZigStackFrame,
// be careful reading the fields below.
Bun__remapStackFramePositions(lexicalGlobalObject, remappedFrames, framesCount);
// write the remapped lines back to the CallSites
for (size_t i = 0; i < framesCount; i++) {
JSC::JSValue callSiteValue = callSites->getIndex(lexicalGlobalObject, i);
CallSite* callSite = JSC::jsDynamicCast<CallSite*>(callSiteValue);
if (remappedFrames[i].remapped) {
callSite->setColumnNumber(remappedFrames[i].position.column());
callSite->setLineNumber(remappedFrames[i].position.line());
}
}
globalObject->formatStackTrace(vm, lexicalGlobalObject, errorObject, callSites, JSC::JSValue());
RETURN_IF_EXCEPTION(scope, {});
return JSC::JSValue::encode(JSC::jsUndefined());
}
@@ -2689,6 +2740,11 @@ void GlobalObject::finishCreation(VM& vm)
Bun::addNodeModuleConstructorProperties(vm, this);
m_lazyStackCustomGetterSetter.initLater(
[](const Initializer<CustomGetterSetter>& init) {
init.set(CustomGetterSetter::create(init.vm, errorInstanceLazyStackCustomGetter, errorInstanceLazyStackCustomSetter));
});
m_JSDOMFileConstructor.initLater(
[](const Initializer<JSObject>& init) {
JSObject* fileConstructor = Bun::createJSDOMFileConstructor(init.vm, init.owner);
@@ -3634,6 +3690,7 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor)
thisObject->m_JSBufferListClassStructure.visit(visitor);
thisObject->m_JSBufferSubclassStructure.visit(visitor);
thisObject->m_JSCryptoKey.visit(visitor);
thisObject->m_lazyStackCustomGetterSetter.visit(visitor);
thisObject->m_JSDOMFileConstructor.visit(visitor);
thisObject->m_JSFFIFunctionStructure.visit(visitor);
thisObject->m_JSFileSinkClassStructure.visit(visitor);

View File

@@ -191,8 +191,7 @@ public:
void clearDOMGuardedObjects();
static void createCallSitesFromFrames(Zig::GlobalObject* globalObject, JSC::JSGlobalObject* lexicalGlobalObject, JSCStackTrace& stackTrace, JSC::JSArray* callSites);
void formatStackTrace(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSObject* errorObject, JSC::JSArray* callSites, JSValue prepareStack = JSC::jsUndefined());
static void createCallSitesFromFrames(Zig::GlobalObject* globalObject, JSC::JSGlobalObject* lexicalGlobalObject, JSCStackTrace& stackTrace, MarkedArgumentBuffer& callSites);
static void reportUncaughtExceptionAtEventLoop(JSGlobalObject*, JSC::Exception*);
static JSGlobalObject* deriveShadowRealmGlobalObject(JSGlobalObject* globalObject);
@@ -374,6 +373,7 @@ public:
}
bool asyncHooksNeedsCleanup = false;
bool isInsideErrorPrepareStackTraceCallback = false;
/**
* WARNING: You must update visitChildrenImpl() if you add a new field.
@@ -584,6 +584,7 @@ public:
LazyProperty<JSGlobalObject, JSObject> m_navigatorObject;
LazyProperty<JSGlobalObject, JSObject> m_performanceObject;
LazyProperty<JSGlobalObject, JSObject> m_processObject;
LazyProperty<JSGlobalObject, CustomGetterSetter> m_lazyStackCustomGetterSetter;
bool hasOverridenModuleResolveFilenameFunction = false;

View File

@@ -0,0 +1,15 @@
let e = new Error();
const { noInline } = require("bun:jsc");
function sloppyWrapperFn() {
sloppyFn();
}
noInline(sloppyWrapperFn);
function sloppyFn() {
Error.captureStackTrace(e);
module.exports = e.stack;
}
noInline(sloppyFn);
sloppyWrapperFn();

View File

@@ -3358,6 +3358,7 @@ pub const VirtualMachine = struct {
if (frames.len == 0) return;
var top = &frames[0];
var top_frame_is_builtin = false;
if (this.hide_bun_stackframes) {
for (frames) |*frame| {
if (frame.source_url.hasPrefixComptime("bun:") or
@@ -3365,10 +3366,12 @@ pub const VirtualMachine = struct {
frame.source_url.isEmpty() or
frame.source_url.eqlComptime("native"))
{
top_frame_is_builtin = true;
continue;
}
top = frame;
top_frame_is_builtin = false;
break;
}
}
@@ -3417,8 +3420,14 @@ pub const VirtualMachine = struct {
}
}
if (top_frame_is_builtin) {
// Avoid printing "export default 'native'"
break :code ZigString.Slice.empty;
}
var log = logger.Log.init(bun.default_allocator);
defer log.deinit();
var original_source = fetchWithoutOnLoadPlugins(this, this.global, top.source_url, bun.String.empty, &log, .print_source) catch return;
must_reset_parser_arena_later.* = true;
break :code original_source.source_code.toUTF8(bun.default_allocator);

View File

@@ -430,6 +430,9 @@ test("no assertion failures", () => {
}
}
const customError = new CustomError("bar");
customError.stack;
delete customError.originalLine;
delete customError.originalColumn;
assert.strictEqual(util.format(customError), customError.stack.replace(/^Error/, "Custom$&")); //! temp bug workaround
// Doesn't capture stack trace
function BadCustomError(msg) {

View File

@@ -1,6 +1,6 @@
import { nativeFrameForTesting } from "bun:internal-for-testing";
import { afterEach, expect, test } from "bun:test";
import { noInline } from "bun:jsc";
const origPrepareStackTrace = Error.prepareStackTrace;
afterEach(() => {
Error.prepareStackTrace = origPrepareStackTrace;
@@ -376,18 +376,38 @@ test("sanity check", () => {
f1();
});
test("CallFrame.p.getThisgetFunction: works in sloppy mode", () => {
test("CallFrame isEval works as expected", () => {
let prevPrepareStackTrace = Error.prepareStackTrace;
let name, fn;
Error.prepareStackTrace = (e, s) => {
return s;
};
name = "f1";
const stack = eval(`(function ${name}() {
return new Error().stack;
})()`);
Error.prepareStackTrace = prevPrepareStackTrace;
// TODO: 0 and 1 should both return true here.
expect(stack[1].isEval()).toBe(true);
expect(stack[0].getFunctionName()).toBe(name);
});
test("CallFrame isTopLevel returns false for Function constructor", () => {
let prevPrepareStackTrace = Error.prepareStackTrace;
const sloppyFn = new Function("let e=new Error();Error.captureStackTrace(e);return e.stack");
sloppyFn.displayName = "sloppyFnWow";
noInline(sloppyFn);
const that = {};
Error.prepareStackTrace = (e, s) => {
expect(s[0].getThis()).toBe(that);
expect(s[0].getFunction()).toBe(sloppyFn);
expect(s[0].getFunctionName()).toBe(sloppyFn.displayName);
expect(s[0].getFunction()).toBe(sloppyFn);
expect(s[0].isToplevel()).toBe(false);
// TODO: This should be true.
expect(s[0].isEval()).toBe(false);
// Strict-mode functions shouldn't have getThis or getFunction
@@ -480,7 +500,7 @@ test("CallFrame.p.toString", () => {
});
// TODO: line numbers are wrong in a release build
test.todo("err.stack should invoke prepareStackTrace", () => {
test("err.stack should invoke prepareStackTrace", () => {
var lineNumber = -1;
var functionName = "";
var parentLineNumber = -1;
@@ -503,9 +523,8 @@ test.todo("err.stack should invoke prepareStackTrace", () => {
functionWithAName();
expect(functionName).toBe("functionWithAName");
expect(lineNumber).toBe(391);
// TODO: this is wrong
expect(parentLineNumber).toBe(394);
expect(lineNumber).toBe(518);
expect(parentLineNumber).toBe(523);
});
test("Error.prepareStackTrace inside a node:vm works", () => {
@@ -559,3 +578,88 @@ test("Error.prepareStackTrace returns a CallSite object", () => {
expect(error.stack[0]).not.toBeString();
expect(error.stack[0][Symbol.toStringTag]).toBe("CallSite");
});
test("Error.captureStackTrace updates the stack property each call, even if Error.prepareStackTrace is set", () => {
const prevPrepareStackTrace = Error.prepareStackTrace;
var didCallPrepareStackTrace = false;
let error = new Error();
const firstStack = error.stack;
Error.prepareStackTrace = function (err, stack) {
expect(err.stack).not.toBe(firstStack);
didCallPrepareStackTrace = true;
return stack;
};
function outer() {
inner();
}
function inner() {
Error.captureStackTrace(error);
}
outer();
const secondStack = error.stack;
expect(firstStack).not.toBe(secondStack);
expect(firstStack).toBeString();
expect(firstStack).not.toContain("outer");
expect(firstStack).not.toContain("inner");
expect(didCallPrepareStackTrace).toBe(true);
expect(secondStack.find(a => a.getFunctionName() === "outer")).toBeTruthy();
expect(secondStack.find(a => a.getFunctionName() === "inner")).toBeTruthy();
Error.prepareStackTrace = prevPrepareStackTrace;
});
test("Error.captureStackTrace updates the stack property each call", () => {
let error = new Error();
const firstStack = error.stack;
function outer() {
inner();
}
function inner() {
Error.captureStackTrace(error);
}
outer();
const secondStack = error.stack;
expect(firstStack).not.toBe(secondStack);
expect(firstStack.length).toBeLessThan(secondStack.length);
expect(firstStack).not.toContain("outer");
expect(firstStack).not.toContain("inner");
expect(secondStack).toContain("outer");
expect(secondStack).toContain("inner");
});
test("calling .stack later uses the stored StackTrace", function hey() {
let error = new Error();
let stack;
function outer() {
inner();
}
function inner() {
stack = error.stack;
}
outer();
expect(stack).not.toContain("outer");
expect(stack).not.toContain("inner");
expect(stack).toContain("hey");
});
test("calling .stack on a non-materialized Error updates the stack properly", function hey() {
let error = new Error();
let stack;
function outer() {
inner();
}
function inner() {
stack = error.stack;
}
function wrapped() {
Error.captureStackTrace(error);
}
wrapped();
outer();
expect(stack).not.toContain("outer");
expect(stack).not.toContain("inner");
expect(stack).toContain("hey");
expect(stack).toContain("wrapped");
});

View File

@@ -5,20 +5,38 @@ const orig = Error.prepareStackTrace;
Error.prepareStackTrace = (err, stack) => {
return orig(err, stack);
};
var stack2, stack;
const err = new Error();
Error.captureStackTrace(err);
const stack = err.stack;
function twoWrapperLevel() {
const err = new Error();
Error.captureStackTrace(err);
stack = err.stack;
Error.prepareStackTrace = undefined;
const err2 = new Error();
Error.captureStackTrace(err2);
const stack2 = err2.stack;
Error.prepareStackTrace = undefined;
const err2 = new Error();
Error.captureStackTrace(err2);
stack2 = err2.stack;
}
const stackIgnoringLineAndColumn = stack.replaceAll(":10:24", "N");
const stack2IgnoringLineAndColumn = stack2.replaceAll(":15:24", "N");
function oneWrapperLevel() {
// ...
var a = 123;
globalThis.a = a;
// ---
twoWrapperLevel();
}
oneWrapperLevel();
// The native line column numbers might differ a bit here.
const stackIgnoringLineAndColumn = stack.replaceAll(":12:26", ":NN:NN").replaceAll(/native:.*$/gm, "native)");
const stack2IgnoringLineAndColumn = stack2.replaceAll(":17:26", ":NN:NN").replaceAll(/native:.*$/gm, "native)");
if (stackIgnoringLineAndColumn !== stack2IgnoringLineAndColumn) {
console.log("\n-----\n");
console.log(stackIgnoringLineAndColumn);
console.log("\n-----\n");
console.log(stack2IgnoringLineAndColumn);
console.log("\n-----\n");
throw new Error("Stacks are different");
}

15
test/regression/issue/013880-fixture.cjs generated Normal file
View File

@@ -0,0 +1,15 @@
function a() {
try {
new Function("throw new Error(1)")();
} catch (e) {
console.log(Error.prepareStackTrace);
console.log(e.stack);
}
}
Error.prepareStackTrace = function abc() {
console.log("trigger");
a();
};
new Error().stack;

View File

@@ -0,0 +1,5 @@
import { test, expect } from "bun:test";
test("regression", () => {
expect(() => require("./013880-fixture.cjs")).not.toThrow();
});