diff options
author | 2023-06-28 13:53:09 -0700 | |
---|---|---|
committer | 2023-06-28 13:53:09 -0700 | |
commit | 3d5573921e732c4a63794e5d25f5953d0a40ff0e (patch) | |
tree | fc309e5614f6f727c4bdde030a3805625185e612 /src/bun.js | |
parent | 43752ec3f0fac7ffe790272ba5d0ebbbca02572c (diff) | |
download | bun-3d5573921e732c4a63794e5d25f5953d0a40ff0e.tar.gz bun-3d5573921e732c4a63794e5d25f5953d0a40ff0e.tar.zst bun-3d5573921e732c4a63794e5d25f5953d0a40ff0e.zip |
Error.prototype.stack gets sourcemapped stacktraces and introduce Error.appendStackTrace (#3441)
* Fix potential crash when reading sourcemapped stack traces
* Format & sourcemap Error.prototype.stack
* prevent double sourcemapping
* Introduce Error.appendStackTrace
* Fix source url
* hide private stack traces in non-debug builds
* fixes #3443
* Bump WebKit
* Fix test failure in vm.test
* Support new() & add test
---------
Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
Diffstat (limited to 'src/bun.js')
-rw-r--r-- | src/bun.js/bindings/ZigGlobalObject.cpp | 188 | ||||
-rw-r--r-- | src/bun.js/bindings/bindings.cpp | 28 | ||||
-rw-r--r-- | src/bun.js/javascript.zig | 22 |
3 files changed, 200 insertions, 38 deletions
diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index b27e0aafc..4b4edf097 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -219,7 +219,9 @@ extern "C" void JSCInitialize(const char* envp[], size_t envc, void (*onCrash)(c JSC::Options::useJITCage() = false; JSC::Options::useShadowRealm() = true; JSC::Options::useResizableArrayBuffer() = true; +#ifdef BUN_DEBUG JSC::Options::showPrivateScriptsInStackTraces() = true; +#endif JSC::Options::useSetMethods() = true; /* @@ -312,6 +314,123 @@ extern "C" void JSCInitialize(const char* envp[], size_t envc, void (*onCrash)(c } extern "C" void* Bun__getVM(); +extern "C" JSGlobalObject* Bun__getDefaultGlobal(); + +static String computeErrorInfoWithoutPrepareStackTrace(JSC::VM& vm, Vector<StackFrame>& stackTrace, unsigned& line, unsigned& column, String& sourceURL, JSObject* errorInstance) +{ + if (!errorInstance) { + return String(); + } + + Zig::GlobalObject* globalObject = jsDynamicCast<Zig::GlobalObject*>(errorInstance->globalObject()); + if (!globalObject) { + // Happens in node:vm + globalObject = jsDynamicCast<Zig::GlobalObject*>(Bun__getDefaultGlobal()); + } + + WTF::String name = "Error"_s; + WTF::String message; + + if (errorInstance) { + // Note that we are not allowed to allocate memory in here. It's called inside a finalizer. + if (auto* instance = jsDynamicCast<ErrorInstance*>(errorInstance)) { + name = instance->sanitizedNameString(globalObject); + message = instance->sanitizedMessageString(globalObject); + } + } + + WTF::StringBuilder sb; + + if (!name.isEmpty()) { + sb.append(name); + sb.append(": "_s); + } + + if (!message.isEmpty()) { + sb.append(message); + } + + if (stackTrace.isEmpty()) { + return sb.toString(); + } + + if ((!message.isEmpty() || !name.isEmpty())) { + sb.append("\n"_s); + } + + size_t framesCount = stackTrace.size(); + ZigStackFrame remappedFrames[framesCount]; + bool hasSet = false; + for (size_t i = 0; i < framesCount; i++) { + StackFrame& frame = stackTrace.at(i); + + sb.append(" at "_s); + + WTF::String functionName = frame.functionName(vm); + + if (auto codeblock = frame.codeBlock()) { + if (codeblock->isConstructor()) { + sb.append("new "_s); + } + + // TODO: async + } + + if (functionName.isEmpty()) { + sb.append("<anonymous>"_s); + } else { + sb.append(functionName); + } + + sb.append(" ("_s); + + if (frame.hasLineAndColumnInfo()) { + unsigned int thisLine = 0; + unsigned int thisColumn = 0; + frame.computeLineAndColumn(thisLine, thisColumn); + remappedFrames[i].position.line = thisLine; + remappedFrames[i].position.column_start = thisColumn; + remappedFrames[i].source_url = Zig::toZigString(frame.sourceURL(vm)); + + // This ensures the lifetime of the sourceURL is accounted for correctly + Bun__remapStackFramePositions(globalObject, remappedFrames + i, 1); + + if (!hasSet) { + hasSet = true; + line = thisLine; + column = thisColumn; + sourceURL = frame.sourceURL(vm); + + if (errorInstance) { + if (remappedFrames[i].remapped) { + errorInstance->putDirect(vm, Identifier::fromString(vm, "originalLine"_s), jsNumber(thisLine), 0); + errorInstance->putDirect(vm, Identifier::fromString(vm, "originalColumn"_s), jsNumber(thisColumn), 0); + } + } + } + + sb.append(frame.sourceURL(vm)); + sb.append(":"_s); + sb.append(remappedFrames[i].position.line); + sb.append(":"_s); + sb.append(remappedFrames[i].position.column_start); + } else { + sb.append("native"_s); + } + sb.append(")"_s); + + if (i != framesCount - 1) { + sb.append("\n"_s); + } + } + + return sb.toString(); +} + +static String computeErrorInfo(JSC::VM& vm, Vector<StackFrame>& stackTrace, unsigned& line, unsigned& column, String& sourceURL, JSObject* errorInstance) +{ + return computeErrorInfoWithoutPrepareStackTrace(vm, stackTrace, line, column, sourceURL, errorInstance); +} extern "C" JSC__JSGlobalObject* Zig__GlobalObject__create(JSClassRef* globalObjectClass, int count, void* console_client) @@ -329,6 +448,9 @@ extern "C" JSC__JSGlobalObject* Zig__GlobalObject__create(JSClassRef* globalObje Zig::GlobalObject* globalObject = Zig::GlobalObject::create(vm, Zig::GlobalObject::createStructure(vm, JSC::JSGlobalObject::create(vm, JSC::JSGlobalObject::createStructure(vm, JSC::jsNull())), JSC::jsNull())); globalObject->setConsole(globalObject); globalObject->isThreadLocalDefaultGlobalObject = true; + globalObject->setStackTraceLimit(DEFAULT_ERROR_STACK_TRACE_LIMIT); // Node.js defaults to 10 + vm.setOnComputeErrorInfo(computeErrorInfo); + if (count > 0) { globalObject->installAPIGlobals(globalObjectClass, count, vm); } @@ -2585,7 +2707,32 @@ JSC::JSValue GlobalObject::formatStackTrace(JSC::VM& vm, JSC::JSGlobalObject* le extern "C" EncodedJSValue JSPasswordObject__create(JSC::JSGlobalObject*, bool); -JSC_DECLARE_HOST_FUNCTION(errorConstructorFuncCaptureStackTrace); +JSC_DEFINE_HOST_FUNCTION(errorConstructorFuncAppendStackTrace, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + GlobalObject* globalObject = reinterpret_cast<GlobalObject*>(lexicalGlobalObject); + JSC::VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSC::ErrorInstance* source = jsDynamicCast<JSC::ErrorInstance*>(callFrame->argument(0)); + JSC::ErrorInstance* destination = jsDynamicCast<JSC::ErrorInstance*>(callFrame->argument(1)); + + if (!source || !destination) { + throwTypeError(lexicalGlobalObject, scope, "First & second argument must be an Error object"_s); + return JSC::JSValue::encode(jsUndefined()); + } + + if (!destination->stackTrace()) { + destination->captureStackTrace(vm, globalObject, 1); + } + + if (source->stackTrace()) { + destination->stackTrace()->appendVector(*source->stackTrace()); + source->stackTrace()->clear(); + } + + return JSC::JSValue::encode(jsUndefined()); +} + JSC_DEFINE_HOST_FUNCTION(errorConstructorFuncCaptureStackTrace, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) { GlobalObject* globalObject = reinterpret_cast<GlobalObject*>(lexicalGlobalObject); @@ -2600,18 +2747,15 @@ JSC_DEFINE_HOST_FUNCTION(errorConstructorFuncCaptureStackTrace, (JSC::JSGlobalOb JSC::JSObject* errorObject = objectArg.asCell()->getObject(); JSC::JSValue caller = callFrame->argument(1); + // We cannot use our ErrorInstance::captureStackTrace() fast path here unfortunately. + // We need to return these CallSite array objects which means we need to create them JSValue errorValue = lexicalGlobalObject->get(lexicalGlobalObject, vm.propertyNames->Error); auto* errorConstructor = jsDynamicCast<JSC::JSObject*>(errorValue); - - size_t stackTraceLimit = DEFAULT_ERROR_STACK_TRACE_LIMIT; - if (JSC::JSValue stackTraceLimitProp = errorConstructor->getIfPropertyExists(lexicalGlobalObject, vm.propertyNames->stackTraceLimit)) { - if (stackTraceLimitProp.isNumber()) { - stackTraceLimit = std::min(std::max(static_cast<size_t>(stackTraceLimitProp.toIntegerOrInfinity(lexicalGlobalObject)), 0ul), 2048ul); - if (stackTraceLimit == 0) { - stackTraceLimit = 2048; - } - } + size_t stackTraceLimit = globalObject->stackTraceLimit().value(); + if (stackTraceLimit == 0) { + stackTraceLimit = DEFAULT_ERROR_STACK_TRACE_LIMIT; } + JSCStackTrace stackTrace = JSCStackTrace::captureCurrentJSStackTrace(globalObject, callFrame, stackTraceLimit, caller); // Create an (uninitialized) array for our "call sites" @@ -2672,9 +2816,20 @@ JSC_DEFINE_HOST_FUNCTION(errorConstructorFuncCaptureStackTrace, (JSC::JSGlobalOb errorObject->deleteProperty(lexicalGlobalObject, vm.propertyNames->stack); } if (formattedStackTrace.isUndefinedOrNull()) { - errorObject->putDirect(vm, vm.propertyNames->stack, jsUndefined(), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum); - } else { - errorObject->putDirect(vm, vm.propertyNames->stack, formattedStackTrace, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum); + formattedStackTrace = JSC::jsUndefined(); + } + + errorObject->putDirect(vm, vm.propertyNames->stack, formattedStackTrace, 0); + + if (!(caller && caller.isObject())) { + if (auto* instance = jsDynamicCast<JSC::ErrorInstance*>(errorObject)) { + // we make a separate copy of the StackTrace unfortunately so that we + // can later console.log it without losing the info + // + // This is not good. We should remove this in the future as it strictly makes this function + // already slower than necessary. + instance->captureStackTrace(vm, globalObject, 1, false); + } } RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSValue {})); @@ -3118,11 +3273,8 @@ void GlobalObject::finishCreation(VM& vm) RELEASE_ASSERT(classInfo()); JSC::JSObject* errorConstructor = this->errorConstructor(); - errorConstructor->putDirectNativeFunctionWithoutTransition(vm, this, JSC::Identifier::fromString(vm, "captureStackTrace"_s), 2, errorConstructorFuncCaptureStackTrace, ImplementationVisibility::Public, JSC::NoIntrinsic, PropertyAttribute::DontEnum | 0); - - // JSC default is 100 - errorConstructor->putDirect(vm, vm.propertyNames->stackTraceLimit, jsNumber(DEFAULT_ERROR_STACK_TRACE_LIMIT), JSC::PropertyAttribute::DontEnum | 0); - + errorConstructor->putDirectNativeFunction(vm, this, JSC::Identifier::fromString(vm, "captureStackTrace"_s), 2, errorConstructorFuncCaptureStackTrace, ImplementationVisibility::Public, JSC::NoIntrinsic, PropertyAttribute::DontEnum | 0); + errorConstructor->putDirectNativeFunction(vm, this, JSC::Identifier::fromString(vm, "appendStackTrace"_s), 2, errorConstructorFuncAppendStackTrace, ImplementationVisibility::Private, JSC::NoIntrinsic, PropertyAttribute::DontEnum | 0); JSC::JSValue console = this->get(this, JSC::Identifier::fromString(vm, "console"_s)); JSC::JSObject* consoleObject = console.getObject(); consoleObject->putDirectBuiltinFunction(vm, this, vm.propertyNames->asyncIteratorSymbol, consoleObjectAsyncIteratorCodeGenerator(vm), PropertyAttribute::Builtin | PropertyAttribute::DontDelete); diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index 3f3d82dc4..68aef29dd 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -3528,12 +3528,13 @@ static void fromErrorInstance(ZigException* except, JSC::JSGlobalObject* global, JSC::JSValue val) { JSC::JSObject* obj = JSC::jsDynamicCast<JSC::JSObject*>(val); + JSC::VM& vm = global->vm(); bool getFromSourceURL = false; if (stackTrace != nullptr && stackTrace->size() > 0) { - populateStackTrace(global->vm(), *stackTrace, &except->stack); + populateStackTrace(vm, *stackTrace, &except->stack); } else if (err->stackTrace() != nullptr && err->stackTrace()->size() > 0) { - populateStackTrace(global->vm(), *err->stackTrace(), &except->stack); + populateStackTrace(vm, *err->stackTrace(), &except->stack); } else { getFromSourceURL = true; } @@ -3546,7 +3547,7 @@ static void fromErrorInstance(ZigException* except, JSC::JSGlobalObject* global, } if (except->code == SYNTAX_ERROR_CODE) { except->message = Zig::toZigString(err->sanitizedMessageString(global)); - } else if (JSC::JSValue message = obj->getIfPropertyExists(global, global->vm().propertyNames->message)) { + } else if (JSC::JSValue message = obj->getIfPropertyExists(global, vm.propertyNames->message)) { except->message = Zig::toZigString(message, global); @@ -3556,7 +3557,7 @@ static void fromErrorInstance(ZigException* except, JSC::JSGlobalObject* global, except->name = Zig::toZigString(err->sanitizedNameString(global)); except->runtime_type = err->runtimeTypeForCause(); - auto clientData = WebCore::clientData(global->vm()); + auto clientData = WebCore::clientData(vm); if (except->code != SYNTAX_ERROR_CODE) { if (JSC::JSValue syscall = obj->getIfPropertyExists(global, clientData->builtinNames().syscallPublicName())) { @@ -3571,7 +3572,7 @@ static void fromErrorInstance(ZigException* except, JSC::JSGlobalObject* global, except->path = Zig::toZigString(path, global); } - if (JSC::JSValue fd = obj->getIfPropertyExists(global, Identifier::fromString(global->vm(), "fd"_s))) { + if (JSC::JSValue fd = obj->getIfPropertyExists(global, Identifier::fromString(vm, "fd"_s))) { if (fd.isAnyInt()) { except->fd = fd.toInt32(global); } @@ -3583,17 +3584,17 @@ static void fromErrorInstance(ZigException* except, JSC::JSGlobalObject* global, } if (getFromSourceURL) { - if (JSC::JSValue sourceURL = obj->getIfPropertyExists(global, global->vm().propertyNames->sourceURL)) { + if (JSC::JSValue sourceURL = obj->getIfPropertyExists(global, vm.propertyNames->sourceURL)) { except->stack.frames_ptr[0].source_url = Zig::toZigString(sourceURL, global); - if (JSC::JSValue column = obj->getIfPropertyExists(global, global->vm().propertyNames->column)) { + if (JSC::JSValue column = obj->getIfPropertyExists(global, vm.propertyNames->column)) { except->stack.frames_ptr[0].position.column_start = column.toInt32(global); } - if (JSC::JSValue line = obj->getIfPropertyExists(global, global->vm().propertyNames->line)) { + if (JSC::JSValue line = obj->getIfPropertyExists(global, vm.propertyNames->line)) { except->stack.frames_ptr[0].position.line = line.toInt32(global); - if (JSC::JSValue lineText = obj->getIfPropertyExists(global, JSC::Identifier::fromString(global->vm(), "lineText"_s))) { + if (JSC::JSValue lineText = obj->getIfPropertyExists(global, JSC::Identifier::fromString(vm, "lineText"_s))) { if (JSC::JSString* jsStr = lineText.toStringOrNull(global)) { auto str = jsStr->value(global); except->stack.source_lines_ptr[0] = Zig::toZigString(str); @@ -3603,7 +3604,9 @@ static void fromErrorInstance(ZigException* except, JSC::JSGlobalObject* global, } } } + except->stack.frames_len = 1; + except->stack.frames_ptr[0].remapped = obj->hasProperty(global, JSC::Identifier::fromString(vm, "originalLine"_s)); } } @@ -3654,7 +3657,12 @@ void exceptionFromString(ZigException* except, JSC::JSValue value, JSC::JSGlobal if (JSC::JSValue line = obj->getIfPropertyExists(global, global->vm().propertyNames->line)) { if (line) { - except->stack.frames_ptr[0].position.line = line.toInt32(global); + // TODO: don't sourcemap it twice + if (auto originalLine = obj->getIfPropertyExists(global, JSC::Identifier::fromString(global->vm(), "originalLine"_s))) { + except->stack.frames_ptr[0].position.line = originalLine.toInt32(global); + } else { + except->stack.frames_ptr[0].position.line = line.toInt32(global); + } except->stack.frames_len = 1; } } diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig index cb1a50f1d..5c58eff60 100644 --- a/src/bun.js/javascript.zig +++ b/src/bun.js/javascript.zig @@ -1981,19 +1981,21 @@ pub const VirtualMachine = struct { } pub fn remapStackFramePositions(this: *VirtualMachine, frames: [*]JSC.ZigStackFrame, frames_count: usize) void { - var i: usize = 0; - while (i < frames_count) : (i += 1) { - if (frames[i].position.isInvalid()) continue; + for (frames[0..frames_count]) |*frame| { + if (frame.position.isInvalid() or frame.remapped) continue; + var sourceURL = frame.source_url.toSlice(bun.default_allocator); + defer sourceURL.deinit(); + if (this.source_mappings.resolveMapping( - frames[i].source_url.slice(), - @max(frames[i].position.line, 0), - @max(frames[i].position.column_start, 0), + sourceURL.slice(), + @max(frame.position.line, 0), + @max(frame.position.column_start, 0), )) |mapping| { - frames[i].position.line = mapping.original.lines; - frames[i].position.column_start = mapping.original.columns; - frames[i].remapped = true; + frame.position.line = mapping.original.lines; + frame.position.column_start = mapping.original.columns; + frame.remapped = true; } else { - frames[i].remapped = true; + frame.remapped = true; } } } |