/** * This source code is licensed under the terms found in the LICENSE file in * node-jsc's root directory. */ #include "config.h" #include "ErrorStackTrace.h" #include "JavaScriptCore/CallData.h" #include "JavaScriptCore/CodeType.h" #include "JavaScriptCore/Error.h" #include "JavaScriptCore/ExecutableBase.h" #include "JavaScriptCore/JSType.h" #include "wtf/text/OrdinalNumber.h" #include #include #include #include #include #include #include #include #include #include #include "ErrorStackFrame.h" using namespace JSC; using namespace WebCore; namespace Zig { static ImplementationVisibility getImplementationVisibility(JSC::CodeBlock* codeBlock) { if (auto* executable = codeBlock->ownerExecutable()) { return executable->implementationVisibility(); } return ImplementationVisibility::Public; } bool isImplementationVisibilityPrivate(JSC::StackVisitor& visitor) { ImplementationVisibility implementationVisibility = [&]() -> ImplementationVisibility { if (visitor->callee().isCell()) { if (auto* callee = visitor->callee().asCell()) { if (auto* jsFunction = jsDynamicCast(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; } 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(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& existingFrames) { WTF::Vector newFrames; size_t frameCount = existingFrames.size(); if (0 == frameCount) { return JSCStackTrace(); } newFrames.reserveInitialCapacity(frameCount); for (size_t i = 0; i < frameCount; i++) { if (!isImplementationVisibilityPrivate(existingFrames.at(i))) { newFrames.constructAndAppend(vm, existingFrames.at(i)); } } return JSCStackTrace(newFrames); } void JSCStackTrace::getFramesForCaller(JSC::VM& vm, JSC::CallFrame* callFrame, JSC::JSCell* owner, JSC::JSValue caller, WTF::Vector& stackTrace, size_t stackTraceLimit) { size_t framesCount = 0; bool belowCaller = false; int32_t skipFrames = 0; WTF::String callerName {}; if (JSC::JSFunction* callerFunction = JSC::jsDynamicCast(caller)) { callerName = callerFunction->name(vm); if (callerName.isEmpty() && !callerFunction->isHostFunction() && callerFunction->jsExecutable()) { callerName = callerFunction->jsExecutable()->name().string(); } } else if (JSC::InternalFunction* callerFunctionInternal = JSC::jsDynamicCast(caller)) { callerName = callerFunctionInternal->name(); } size_t totalFrames = 0; 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; } // Skip frames if needed if (skipFrames > 0) { skipFrames--; return WTF::IterationStatus::Continue; } 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::getStackTraceForThrownValue(JSC::VM& vm, JSC::JSValue thrownValue) { const WTF::Vector* jscStackTrace = nullptr; JSC::Exception* currentException = DECLARE_CATCH_SCOPE(vm).exception(); if (currentException && currentException->value() == thrownValue) { jscStackTrace = ¤tException->stack(); } else { JSC::ErrorInstance* error = JSC::jsDynamicCast(thrownValue); if (error) { jscStackTrace = error->stackTrace(); } } if (!jscStackTrace) { return JSCStackTrace(); } return fromExisting(vm, *jscStackTrace); } static bool isVisibleBuiltinFunction(JSC::CodeBlock* codeBlock) { if (!codeBlock->ownerExecutable()) { return false; } const JSC::SourceCode& source = codeBlock->source(); return !Zig::sourceURL(source).isEmpty(); } JSCStackFrame::JSCStackFrame(JSC::VM& vm, JSC::StackVisitor& visitor) : m_vm(vm) , m_codeBlock(nullptr) , m_bytecodeIndex(JSC::BytecodeIndex()) , m_sourceURL() , m_functionName() , m_isWasmFrame(false) , m_isAsync(false) , m_sourcePositionsState(SourcePositionsState::NotCalculated) { m_callee = visitor->callee().asCell(); m_callFrame = visitor->callFrame(); if (auto* codeBlock = visitor->codeBlock()) { auto codeType = codeBlock->codeType(); if (codeType == JSC::FunctionCode || codeType == JSC::EvalCode) { m_isFunctionOrEval = true; } } // Based on JSC's GetStackTraceFunctor (Interpreter.cpp) if (visitor->isNativeCalleeFrame()) { auto* nativeCallee = visitor->callee().asNativeCallee(); switch (nativeCallee->category()) { case NativeCallee::Category::Wasm: { m_wasmFunctionIndexOrName = visitor->wasmFunctionIndexOrName(); m_isWasmFrame = true; break; } case NativeCallee::Category::InlineCache: { break; } } } 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; } } JSCStackFrame::JSCStackFrame(JSC::VM& vm, const JSC::StackFrame& frame) : m_vm(vm) , m_callFrame(nullptr) , m_codeBlock(nullptr) , m_bytecodeIndex(JSC::BytecodeIndex()) , m_sourceURL() , m_functionName() , m_isWasmFrame(false) , m_isAsync(frame.isAsyncFrame()) , m_sourcePositionsState(SourcePositionsState::NotCalculated) { m_callee = frame.callee(); // Based on JSC's GetStackTraceFunctor (Interpreter.cpp) if (frame.isWasmFrame()) { m_wasmFunctionIndexOrName = frame.wasmFunctionIndexOrName(); m_isWasmFrame = true; } else if (auto* codeBlock = frame.codeBlock()) { auto* unlinkedCodeBlock = codeBlock->unlinkedCodeBlock(); if (!unlinkedCodeBlock->isBuiltinFunction() || isVisibleBuiltinFunction(codeBlock)) { m_codeBlock = codeBlock; m_bytecodeIndex = frame.bytecodeIndex(); } auto codeType = codeBlock->codeType(); if (codeType == JSC::FunctionCode || codeType == JSC::EvalCode) { m_isFunctionOrEval = true; } } if (!m_codeBlock && frame.hasLineAndColumnInfo()) { auto lineColumn = frame.computeLineAndColumn(); m_sourcePositions = { OrdinalNumber::fromOneBasedInt(lineColumn.line), OrdinalNumber::fromOneBasedInt(lineColumn.column) }; m_sourcePositionsState = SourcePositionsState::Calculated; auto codeType = frame.codeBlock()->codeType(); if (codeType == JSC::FunctionCode || codeType == JSC::EvalCode) { m_isFunctionOrEval = true; } } } intptr_t JSCStackFrame::sourceID() const { return m_codeBlock ? m_codeBlock->ownerExecutable()->sourceID() : JSC::noSourceID; } JSC::JSString* JSCStackFrame::sourceURL() { if (!m_sourceURL) { m_sourceURL = retrieveSourceURL(); } return jsString(this->m_vm, m_sourceURL); } JSC::JSString* JSCStackFrame::functionName() { if (!m_functionName) { m_functionName = retrieveFunctionName(); } return jsString(this->m_vm, m_functionName); } JSC::JSString* JSCStackFrame::typeName() { if (!m_typeName) { m_typeName = retrieveTypeName(); } return jsString(this->m_vm, m_typeName); } JSCStackFrame::SourcePositions* JSCStackFrame::getSourcePositions() { if (SourcePositionsState::NotCalculated == m_sourcePositionsState) { m_sourcePositionsState = calculateSourcePositions() ? SourcePositionsState::Calculated : SourcePositionsState::Failed; } return (SourcePositionsState::Calculated == m_sourcePositionsState) ? &m_sourcePositions : nullptr; } ALWAYS_INLINE String JSCStackFrame::retrieveSourceURL() { static const auto sourceURLWasmString = MAKE_STATIC_STRING_IMPL("[wasm code]"); if (m_isWasmFrame) { return String(sourceURLWasmString); } auto url = Zig::sourceURL(m_codeBlock); if (!url.isEmpty()) { return url; } if (m_callee && m_callee->isObject()) { if (auto* jsFunction = jsDynamicCast(m_callee)) { WTF::String url = Zig::sourceURL(m_vm, jsFunction); if (!url.isEmpty()) { return url; } } } return String(); } ALWAYS_INLINE String JSCStackFrame::retrieveFunctionName() { if (m_isWasmFrame) { return JSC::Wasm::makeString(m_wasmFunctionIndexOrName); } if (m_callee) { auto* calleeObject = m_callee->getObject(); if (calleeObject) { return Zig::functionName(m_vm, calleeObject->globalObject(), calleeObject); } } if (m_codeBlock) { auto functionName = Zig::functionName(m_vm, m_codeBlock); if (!functionName.isEmpty()) { return functionName; } } return emptyString(); } ALWAYS_INLINE String JSCStackFrame::retrieveTypeName() { JSC::JSObject* calleeObject = JSC::jsCast(m_callee); return calleeObject->className(); } // General flow here is based on JSC's appendSourceToError (ErrorInstance.cpp) bool JSCStackFrame::calculateSourcePositions() { if (!m_codeBlock) { return false; } if (!hasBytecodeIndex()) { return false; } auto location = Bun::getAdjustedPositionForBytecode(m_codeBlock, m_bytecodeIndex); m_sourcePositions.line = location.line(); m_sourcePositions.column = location.column(); return true; } String sourceURL(const JSC::SourceOrigin& origin) { if (origin.isNull()) { return String(); } return origin.string(); } String sourceURL(JSC::SourceProvider* sourceProvider) { if (!sourceProvider) [[unlikely]] { return String(); } String url = sourceProvider->sourceURLDirective(); if (!url.isEmpty()) { return url; } url = sourceProvider->sourceURL(); if (!url.isEmpty()) { return url; } const auto& origin = sourceProvider->sourceOrigin(); return sourceURL(origin); } String sourceURL(const JSC::SourceCode& sourceCode) { return sourceURL(sourceCode.provider()); } String sourceURL(JSC::CodeBlock& codeBlock) { if (!codeBlock.ownerExecutable()) { return String(); } const auto& source = codeBlock.source(); return sourceURL(source); } String sourceURL(JSC::CodeBlock* codeBlock) { if (!codeBlock) [[unlikely]] { return String(); } return Zig::sourceURL(*codeBlock); } String sourceURL(JSC::VM& vm, const JSC::StackFrame& frame) { if (frame.isWasmFrame()) { return "[wasm code]"_s; } if (!frame.hasLineAndColumnInfo()) [[unlikely]] { return "[native code]"_s; } return sourceURL(frame.codeBlock()); } String sourceURL(JSC::StackVisitor& visitor) { switch (visitor->codeType()) { case JSC::StackVisitor::Frame::Eval: case JSC::StackVisitor::Frame::Module: case JSC::StackVisitor::Frame::Function: case JSC::StackVisitor::Frame::Global: { return sourceURL(visitor->codeBlock()); } case JSC::StackVisitor::Frame::Native: return "[native code]"_s; case JSC::StackVisitor::Frame::Wasm: return "[wasm code]"_s; } RELEASE_ASSERT_NOT_REACHED(); } String sourceURL(JSC::VM& vm, JSC::JSFunction* function) { auto* executable = function->executable(); if (!executable || executable->isHostFunction()) { return String(); } auto* jsExecutable = function->jsExecutable(); if (!jsExecutable) { return String(); } return Zig::sourceURL(jsExecutable->source()); } String functionName(JSC::VM& vm, JSC::CodeBlock* codeBlock) { auto codeType = codeBlock->codeType(); auto* executable = codeBlock->ownerExecutable(); if (!executable) { return String(); } if (codeType == JSC::FunctionCode) { auto* jsExecutable = jsCast(executable); if (!jsExecutable) { return String(); } return jsExecutable->ecmaName().string(); } return String(); } String functionName(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSObject* object) { WTF::String functionName; auto jstype = object->type(); if (jstype == JSC::ProxyObjectType) return {}; // First try the "name" property. { WTF::String name; auto catchScope = DECLARE_CATCH_SCOPE(vm); PropertySlot slot(object, PropertySlot::InternalMethodType::VMInquiry, &vm); if (object->getOwnNonIndexPropertySlot(vm, object->structure(), vm.propertyNames->name, slot)) { if (!slot.isAccessor()) { JSValue functionNameValue = slot.getValue(lexicalGlobalObject, vm.propertyNames->name); if (functionNameValue && functionNameValue.isString()) { name = functionNameValue.toWTFString(lexicalGlobalObject); if (!name.isEmpty()) { return name; } } } } if (catchScope.exception()) [[unlikely]] { catchScope.clearException(); } } { // Then try the "displayName" property (what this does internally) auto catchScope = DECLARE_CATCH_SCOPE(vm); functionName = JSC::getCalculatedDisplayName(vm, object); if (catchScope.exception()) [[unlikely]] { catchScope.clearException(); } } { if (functionName.isEmpty()) { if (jstype == JSC::JSFunctionType) { auto* function = jsCast(object); if (function) { functionName = function->nameWithoutGC(vm); if (functionName.isEmpty() && !function->isHostFunction()) { functionName = function->jsExecutable()->ecmaName().string(); } } } else if (jstype == JSC::InternalFunctionType) { auto* function = jsCast(object); if (function) { functionName = function->name(); } } } } return functionName; } String functionName(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, const JSC::StackFrame& frame, bool isInFinalizer, unsigned int* flags) { bool isConstructor = false; if (isInFinalizer) { if (auto* callee = frame.callee()) { if (auto* object = callee->getObject()) { auto jstype = object->type(); Structure* structure = object->structure(); auto setTypeFlagsIfNecessary = [&]() { if (flags) { if (jstype == JSC::JSFunctionType || jstype == JSC::InternalFunctionType) { *flags |= static_cast(FunctionNameFlags::Function); } } }; // First try the "name" property. { unsigned attributes; PropertyOffset offset = structure->getConcurrently(vm.propertyNames->name.impl(), attributes); if (offset != invalidOffset && !(attributes & (PropertyAttribute::Accessor | PropertyAttribute::CustomAccessorOrValue))) { JSValue name = object->getDirect(offset); if (name && name.isString()) { auto str = asString(name)->tryGetValueWithoutGC(); if (!str->isEmpty()) { setTypeFlagsIfNecessary(); return str; } } } } // Then try the "displayName" property. { unsigned attributes; PropertyOffset offset = structure->getConcurrently(vm.propertyNames->displayName.impl(), attributes); if (offset != invalidOffset && !(attributes & (PropertyAttribute::Accessor | PropertyAttribute::CustomAccessorOrValue))) { JSValue name = object->getDirect(offset); if (name && name.isString()) { auto str = asString(name)->tryGetValueWithoutGC(); if (!str->isEmpty()) { setTypeFlagsIfNecessary(); return str; } } } } // Lastly, try type-specific properties. if (jstype == JSC::JSFunctionType) { auto* function = jsCast(object); if (function) { auto str = function->nameWithoutGC(vm); if (str.isEmpty() && !function->isHostFunction()) { setTypeFlagsIfNecessary(); return function->jsExecutable()->ecmaName().string(); } setTypeFlagsIfNecessary(); return str; } } else if (jstype == JSC::InternalFunctionType) { auto* function = jsCast(object); if (function) { auto str = function->name(); setTypeFlagsIfNecessary(); return str; } } } } return emptyString(); } WTF::String functionName; if (frame.hasLineAndColumnInfo()) { auto* codeblock = frame.codeBlock(); if (codeblock->isConstructor()) { isConstructor = true; } // We cannot run this in FinalizeUnconditionally, as we cannot call getters there if (!isInFinalizer) { auto codeType = codeblock->codeType(); switch (codeType) { case JSC::CodeType::FunctionCode: case JSC::CodeType::EvalCode: { if (flags) { if (codeType == JSC::CodeType::EvalCode) { *flags |= static_cast(FunctionNameFlags::Eval); } else if (codeType == JSC::CodeType::FunctionCode) { *flags |= static_cast(FunctionNameFlags::Function); } } if (auto* callee = frame.callee()) { if (auto* object = callee->getObject()) { functionName = Zig::functionName(vm, lexicalGlobalObject, object); if (flags) { if (auto* unlinkedCodeBlock = codeblock->unlinkedCodeBlock()) { if (unlinkedCodeBlock->isBuiltinFunction()) { *flags |= static_cast(FunctionNameFlags::Builtin); } } } } } break; } default: { break; } } if (functionName.isEmpty()) { functionName = Zig::functionName(vm, codeblock); } } } else { if (auto* callee = frame.callee()) { if (auto* object = callee->getObject()) { functionName = Zig::functionName(vm, lexicalGlobalObject, object); } } } if ((flags && (*flags & static_cast(FunctionNameFlags::AddNewKeyword))) && isConstructor && !functionName.isEmpty()) { return makeString("new "_s, functionName); } return functionName; } }