aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js/bindings/ZigGlobalObject.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/bun.js/bindings/ZigGlobalObject.cpp')
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.cpp226
1 files changed, 226 insertions, 0 deletions
diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp
index 84e63802b..34663c228 100644
--- a/src/bun.js/bindings/ZigGlobalObject.cpp
+++ b/src/bun.js/bindings/ZigGlobalObject.cpp
@@ -68,6 +68,8 @@
// #include "JavaScriptCore/CachedType.h"
#include "JavaScriptCore/JSCallbackObject.h"
#include "JavaScriptCore/JSClassRef.h"
+#include "JavaScriptCore/CallData.h"
+#include "GCDefferalContext.h"
#include "BunClientData.h"
@@ -167,6 +169,8 @@ using JSBuffer = WebCore::JSBuffer;
#include "OnigurumaRegExp.h"
+constexpr size_t DEFAULT_ERROR_STACK_TRACE_LIMIT = 10;
+
#ifdef __APPLE__
#include <sys/sysctl.h>
#else
@@ -2104,11 +2108,224 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionPerformMicrotaskVariadic, (JSGlobalObject * g
return JSValue::encode(jsUndefined());
}
+void GlobalObject::createCallSitesFromFrames(JSC::JSGlobalObject* lexicalGlobalObject, JSC::ObjectInitializationScope& objectScope, JSCStackTrace& stackTrace, JSC::JSArray* 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
+ * strict mode function and all frames below (its caller etc.) are not allow to access
+ * their receiver and function objects. For those frames, getFunction() and getThis()
+ * will return undefined."." */
+ bool encounteredStrictFrame = false;
+ GlobalObject* globalObject = reinterpret_cast<GlobalObject*>(lexicalGlobalObject);
+
+ JSC::Structure* callSiteStructure = globalObject->callSiteStructure();
+ JSC::IndexingType callSitesIndexingType = callSites->indexingType();
+ size_t framesCount = stackTrace.size();
+ for (size_t i = 0; i < framesCount; i++) {
+ /* Note that we're using initializeIndex and not callSites->butterfly()->contiguous().data()
+ * directly, since if we're "having a bad time" (globalObject->isHavingABadTime()),
+ * the array won't be contiguous, but a "slow put" array.
+ * See https://github.com/WebKit/webkit/commit/1c4a32c94c1f6c6aa35cf04a2b40c8fe29754b8e for more info
+ * about what's a "bad time". */
+ CallSite* callSite = CallSite::create(lexicalGlobalObject, callSiteStructure, stackTrace.at(i), encounteredStrictFrame);
+ callSites->initializeIndex(objectScope, i, callSite, callSitesIndexingType);
+
+ if (!encounteredStrictFrame) {
+ encounteredStrictFrame = callSite->isStrict();
+ }
+ }
+}
+
+JSC::JSValue GlobalObject::formatStackTrace(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSObject* errorObject, JSC::JSArray* callSites, ZigStackFrame remappedStackFrames[])
+{
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ JSValue errorValue = this->get(this, JSC::Identifier::fromString(vm, "Error"_s));
+ if (UNLIKELY(scope.exception())) {
+ return JSValue();
+ }
+
+ if (!errorValue || errorValue.isUndefined() || !errorValue.isObject()) {
+ return JSValue(jsEmptyString(vm));
+ }
+
+ auto* errorConstructor = jsDynamicCast<JSC::JSObject*>(errorValue);
+
+ /* If the user has set a callable Error.prepareStackTrace - use it to format the stack trace. */
+ JSC::JSValue prepareStackTrace = errorConstructor->getIfPropertyExists(lexicalGlobalObject, JSC::Identifier::fromString(vm, "prepareStackTrace"_s));
+ if (prepareStackTrace && prepareStackTrace.isCallable()) {
+ JSC::CallData prepareStackTraceCallData = JSC::getCallData(prepareStackTrace);
+
+ if (prepareStackTraceCallData.type != JSC::CallData::Type::None) {
+ JSC::MarkedArgumentBuffer arguments;
+ arguments.append(errorObject);
+ arguments.append(callSites);
+ ASSERT(!arguments.hasOverflowed());
+
+ JSC::JSValue result = profiledCall(
+ lexicalGlobalObject,
+ JSC::ProfilingReason::Other,
+ prepareStackTrace,
+ prepareStackTraceCallData,
+ errorConstructor,
+ arguments);
+ RETURN_IF_EXCEPTION(scope, JSC::jsUndefined());
+ return result;
+ }
+ }
+
+ // default formatting
+ size_t framesCount = callSites->length();
+
+ WTF::StringBuilder sb;
+ if (JSC::JSValue errorMessage = errorObject->getIfPropertyExists(lexicalGlobalObject, vm.propertyNames->message)) {
+ sb.append("Error: "_s);
+ sb.append(errorMessage.getString(lexicalGlobalObject));
+ } else {
+ sb.append("Error"_s);
+ }
+
+ if (framesCount > 0) {
+ sb.append("\n"_s);
+ }
+
+ for (size_t i = 0; i < framesCount; i++) {
+ JSC::JSValue callSiteValue = callSites->getIndex(lexicalGlobalObject, i);
+ CallSite* callSite = JSC::jsDynamicCast<CallSite*>(callSiteValue);
+
+ JSString* typeName = jsTypeStringForValue(lexicalGlobalObject, callSite->thisValue());
+ JSString* function = callSite->functionName().toString(lexicalGlobalObject);
+ JSString* functionName = callSite->functionName().toString(lexicalGlobalObject);
+ JSString* sourceURL = callSite->sourceURL().toString(lexicalGlobalObject);
+ JSString* columnNumber = remappedStackFrames[i].position.column_start >= 0 ? jsNontrivialString(vm, String::number(remappedStackFrames[i].position.column_start)) : jsEmptyString(vm);
+ JSString* lineNumber = remappedStackFrames[i].position.line >= 0 ? jsNontrivialString(vm, String::number(remappedStackFrames[i].position.line)) : jsEmptyString(vm);
+ bool isConstructor = callSite->isConstructor();
+
+ sb.append(" at "_s);
+ if (functionName->length() > 0) {
+ if (isConstructor) {
+ sb.append("new "_s);
+ } else {
+ // TODO: print type or class name if available
+ // sb.append(typeName->getString(lexicalGlobalObject));
+ // sb.append(" "_s);
+ }
+ sb.append(functionName->getString(lexicalGlobalObject));
+ } else {
+ sb.append("<anonymous>"_s);
+ }
+ sb.append(" ("_s);
+ if (callSite->isNative()) {
+ sb.append("native"_s);
+ } else {
+ sb.append(sourceURL->getString(lexicalGlobalObject));
+ sb.append(":"_s);
+ sb.append(lineNumber->getString(lexicalGlobalObject));
+ sb.append(":"_s);
+ sb.append(columnNumber->getString(lexicalGlobalObject));
+ }
+ sb.append(")"_s);
+ if (i != framesCount - 1) {
+ sb.append("\n"_s);
+ }
+ }
+
+ return JSC::JSValue(jsString(vm, sb.toString()));
+}
+
+extern "C" void Bun__remapStackFramePositions(JSC::JSGlobalObject*, ZigStackFrame*, size_t);
+
+JSC_DECLARE_HOST_FUNCTION(errorConstructorFuncCaptureStackTrace);
+JSC_DEFINE_HOST_FUNCTION(errorConstructorFuncCaptureStackTrace, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame))
+{
+ GlobalObject* globalObject = reinterpret_cast<GlobalObject*>(lexicalGlobalObject);
+ JSC::VM& vm = globalObject->vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+
+ JSC::JSValue objectArg = callFrame->argument(0);
+ if (!objectArg.isObject()) {
+ return JSC::JSValue::encode(throwTypeError(lexicalGlobalObject, scope, "invalid_argument"_s));
+ }
+
+ JSC::JSObject* errorObject = objectArg.asCell()->getObject();
+ JSC::JSValue caller = callFrame->argument(1);
+
+ 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;
+ }
+ }
+ }
+ JSCStackTrace stackTrace = JSCStackTrace::captureCurrentJSStackTrace(globalObject, callFrame, stackTraceLimit, caller);
+
+ // Create an (uninitialized) array for our "call sites"
+ JSC::GCDeferralContext deferralContext(vm);
+ JSC::ObjectInitializationScope objectScope(vm);
+ JSC::JSArray* callSites = JSC::JSArray::tryCreateUninitializedRestricted(objectScope,
+ &deferralContext,
+ globalObject->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous),
+ stackTrace.size());
+ RELEASE_ASSERT(callSites);
+
+ // Create the call sites (one per frame)
+ GlobalObject::createCallSitesFromFrames(lexicalGlobalObject, objectScope, stackTrace, callSites);
+
+ /* Foramt 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[framesCount];
+ for (int i = 0; i < framesCount; i++) {
+ remappedFrames[i].source_url = Zig::toZigString(stackTrace.at(i).sourceURL(), lexicalGlobalObject);
+ if (JSCStackFrame::SourcePositions* sourcePositions = stackTrace.at(i).getSourcePositions()) {
+ remappedFrames[i].position.line = sourcePositions->line.zeroBasedInt();
+ remappedFrames[i].position.column_start = sourcePositions->startColumn.zeroBasedInt() + 1;
+ } else {
+ remappedFrames[i].position.line = -1;
+ remappedFrames[i].position.column_start = -1;
+ }
+ }
+
+ // remap line and column start to original source
+ Bun__remapStackFramePositions(lexicalGlobalObject, remappedFrames, framesCount);
+
+ JSC::JSValue formattedStackTrace = globalObject->formatStackTrace(vm, lexicalGlobalObject, errorObject, callSites, remappedFrames);
+ RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode({}));
+
+ if (errorObject->hasProperty(lexicalGlobalObject, vm.propertyNames->stack)) {
+ 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.toString(lexicalGlobalObject), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);
+ }
+
+ RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSValue {}));
+
+ return JSC::JSValue::encode(JSC::jsUndefined());
+}
+
void GlobalObject::finishCreation(VM& vm)
{
Base::finishCreation(vm);
ASSERT(inherits(info()));
+ 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->deleteProperty(this, vm.propertyNames->stackTraceLimit);
+ errorConstructor->putDirect(vm, vm.propertyNames->stackTraceLimit, jsNumber(DEFAULT_ERROR_STACK_TRACE_LIMIT), JSC::PropertyAttribute::DontEnum | 0);
+
// Change prototype from null to object for synthetic modules.
m_moduleNamespaceObjectStructure.initLater(
[](const Initializer<Structure>& init) {
@@ -2318,6 +2535,14 @@ void GlobalObject::finishCreation(VM& vm)
init.setConstructor(constructor);
});
+ m_callSiteStructure.initLater(
+ [](LazyClassStructure::Initializer& init) {
+ auto* prototype = CallSitePrototype::create(init.vm, CallSitePrototype::createStructure(init.vm, init.global, init.global->objectPrototype()), init.global);
+ auto* structure = CallSite::createStructure(init.vm, init.global, prototype);
+ init.setPrototype(prototype);
+ init.setStructure(structure);
+ });
+
m_JSStringDecoderClassStructure.initLater(
[](LazyClassStructure::Initializer& init) {
auto* prototype = JSStringDecoderPrototype::create(
@@ -3037,6 +3262,7 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor)
thisObject->m_processObject.visit(visitor);
thisObject->m_subtleCryptoObject.visit(visitor);
thisObject->m_JSHTTPResponseController.visit(visitor);
+ thisObject->m_callSiteStructure.visit(visitor);
for (auto& barrier : thisObject->m_thenables) {
visitor.append(barrier);