aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bun.js/bindings/CallSite.cpp91
-rw-r--r--src/bun.js/bindings/CallSite.h92
-rw-r--r--src/bun.js/bindings/CallSitePrototype.cpp228
-rw-r--r--src/bun.js/bindings/CallSitePrototype.h46
-rw-r--r--src/bun.js/bindings/ErrorStackTrace.cpp375
-rw-r--r--src/bun.js/bindings/ErrorStackTrace.h180
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.cpp226
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.h12
-rw-r--r--src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h1
-rw-r--r--src/bun.js/bindings/webcore/DOMIsoSubspaces.h1
-rw-r--r--src/bun.js/javascript.zig24
11 files changed, 1276 insertions, 0 deletions
diff --git a/src/bun.js/bindings/CallSite.cpp b/src/bun.js/bindings/CallSite.cpp
new file mode 100644
index 000000000..522c6db1c
--- /dev/null
+++ b/src/bun.js/bindings/CallSite.cpp
@@ -0,0 +1,91 @@
+/**
+ * This source code is licensed under the terms found in the LICENSE file in
+ * node-jsc's root directory.
+ */
+
+#include "config.h"
+#include "CallSite.h"
+
+#include "helpers.h"
+
+#include <JavaScriptCore/JSCInlines.h>
+
+using namespace JSC;
+using namespace WebCore;
+
+namespace Zig {
+
+const JSC::ClassInfo CallSite::s_info = { "CallSite"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(CallSite) };
+
+void CallSite::finishCreation(VM& vm, JSC::JSGlobalObject* globalObject, JSCStackFrame& stackFrame, bool encounteredStrictFrame)
+{
+ Base::finishCreation(vm);
+
+ /* 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.".
+ * Thus, if we've already encountered a strict frame, we'll treat our frame as strict too. */
+
+ bool isStrictFrame = encounteredStrictFrame;
+ if (!isStrictFrame) {
+ JSC::CodeBlock* codeBlock = stackFrame.codeBlock();
+ if (codeBlock) {
+ isStrictFrame = codeBlock->ownerExecutable()->isInStrictContext();
+ }
+ }
+
+ JSC::JSObject* calleeObject = JSC::jsCast<JSC::JSObject*>(stackFrame.callee());
+
+ // Initialize "this" and "function" (and set the "IsStrict" flag if needed)
+ JSC::CallFrame* callFrame = stackFrame.callFrame();
+ if (isStrictFrame) {
+ m_thisValue.set(vm, this, JSC::jsUndefined());
+ m_function.set(vm, this, JSC::jsUndefined());
+ m_flags |= static_cast<unsigned int>(Flags::IsStrict);
+ } else {
+ if (callFrame) {
+ // We know that we're not in strict mode
+ m_thisValue.set(vm, this, callFrame->thisValue().toThis(globalObject, JSC::ECMAMode::sloppy()));
+ } else {
+ m_thisValue.set(vm, this, JSC::jsUndefined());
+ }
+
+ m_function.set(vm, this, stackFrame.callee());
+ }
+
+ m_functionName.set(vm, this, stackFrame.functionName());
+ m_sourceURL.set(vm, this, stackFrame.sourceURL());
+
+ const auto* sourcePositions = stackFrame.getSourcePositions();
+ if (sourcePositions) {
+ m_lineNumber = JSC::jsNumber(sourcePositions->line.oneBasedInt());
+ m_columnNumber = JSC::jsNumber(sourcePositions->startColumn.oneBasedInt());
+ }
+
+ if (stackFrame.isEval()) {
+ m_flags |= static_cast<unsigned int>(Flags::IsEval);
+ }
+ if (stackFrame.isConstructor()) {
+ m_flags |= static_cast<unsigned int>(Flags::IsConstructor);
+ }
+ if (!stackFrame.codeBlock()) {
+ m_flags |= static_cast<unsigned int>(Flags::IsNative);
+ }
+}
+
+template<typename Visitor>
+void CallSite::visitChildrenImpl(JSCell* cell, Visitor& visitor)
+{
+ CallSite* thisCallSite = jsCast<CallSite*>(cell);
+ Base::visitChildren(thisCallSite, visitor);
+ visitor.append(thisCallSite->m_thisValue);
+ visitor.append(thisCallSite->m_function);
+ visitor.append(thisCallSite->m_functionName);
+ visitor.append(thisCallSite->m_sourceURL);
+}
+
+DEFINE_VISIT_CHILDREN(CallSite);
+
+} \ No newline at end of file
diff --git a/src/bun.js/bindings/CallSite.h b/src/bun.js/bindings/CallSite.h
new file mode 100644
index 000000000..57b222880
--- /dev/null
+++ b/src/bun.js/bindings/CallSite.h
@@ -0,0 +1,92 @@
+/**
+ * This source code is licensed under the terms found in the LICENSE file in
+ * node-jsc's root directory.
+ */
+
+#pragma once
+
+#include "ErrorStackTrace.h"
+
+#include <JavaScriptCore/JSObject.h>
+
+using namespace JSC;
+using namespace WebCore;
+
+namespace Zig {
+
+class JSCStackFrame;
+
+class CallSite final : public JSC::JSNonFinalObject {
+public:
+ enum class Flags {
+ IsStrict = 1,
+ IsEval = 2,
+ IsConstructor = 4,
+ IsNative = 8,
+ };
+
+private:
+ JSC::WriteBarrier<JSC::Unknown> m_thisValue;
+ JSC::WriteBarrier<JSC::Unknown> m_function;
+ JSC::WriteBarrier<JSC::Unknown> m_functionName;
+ JSC::WriteBarrier<JSC::Unknown> m_sourceURL;
+ JSC::JSValue m_lineNumber;
+ JSC::JSValue m_columnNumber;
+ unsigned int m_flags;
+
+public:
+ using Base = JSC::JSNonFinalObject;
+
+ static CallSite* create(JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSCStackFrame& stackFrame, bool encounteredStrictFrame)
+ {
+ JSC::VM& vm = globalObject->vm();
+ CallSite* callSite = new (NotNull, JSC::allocateCell<CallSite>(vm)) CallSite(vm, structure);
+ callSite->finishCreation(vm, globalObject, stackFrame, encounteredStrictFrame);
+ return callSite;
+ }
+
+ DECLARE_INFO;
+
+ static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
+ {
+ return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
+ }
+
+ template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
+ {
+ if constexpr (mode == JSC::SubspaceAccess::Concurrently)
+ return nullptr;
+
+ return WebCore::subspaceForImpl<CallSite, UseCustomHeapCellType::No>(
+ vm,
+ [](auto& spaces) { return spaces.m_clientSubspaceForCallSite.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForCallSite = WTFMove(space); },
+ [](auto& spaces) { return spaces.m_subspaceForCallSite.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_subspaceForCallSite = WTFMove(space); });
+ }
+
+ JSC::JSValue thisValue() const { return m_thisValue.get(); }
+ JSC::JSValue function() const { return m_function.get(); }
+ JSC::JSValue functionName() const { return m_functionName.get(); }
+ JSC::JSValue sourceURL() const { return m_sourceURL.get(); }
+ JSC::JSValue lineNumber() const { return m_lineNumber; }
+ JSC::JSValue columnNumber() const { return m_columnNumber; }
+ bool isEval() const { return m_flags & static_cast<unsigned int>(Flags::IsEval); }
+ bool isConstructor() const { return m_flags & static_cast<unsigned int>(Flags::IsConstructor); }
+ bool isStrict() const { return m_flags & static_cast<unsigned int>(Flags::IsStrict); }
+ bool isNative() const { return m_flags & static_cast<unsigned int>(Flags::IsNative); }
+
+private:
+ CallSite(JSC::VM& vm, JSC::Structure* structure)
+ : Base(vm, structure)
+ , m_lineNumber(-1)
+ , m_columnNumber(-1)
+ {
+ }
+
+ void finishCreation(VM& vm, JSC::JSGlobalObject* globalObject, JSCStackFrame& stackFrame, bool encounteredStrictFrame);
+
+ DECLARE_VISIT_CHILDREN;
+};
+
+} \ No newline at end of file
diff --git a/src/bun.js/bindings/CallSitePrototype.cpp b/src/bun.js/bindings/CallSitePrototype.cpp
new file mode 100644
index 000000000..faaf571e2
--- /dev/null
+++ b/src/bun.js/bindings/CallSitePrototype.cpp
@@ -0,0 +1,228 @@
+/**
+ * This source code is licensed under the terms found in the LICENSE file in
+ * node-jsc's root directory.
+ */
+
+#include "config.h"
+#include "CallSitePrototype.h"
+#include "CallSite.h"
+#include "helpers.h"
+
+#include <JavaScriptCore/JSGlobalObject.h>
+#include <JavaScriptCore/Error.h>
+#include <JavaScriptCore/CodeBlock.h>
+#include <JavaScriptCore/Operations.h>
+#include <JavaScriptCore/JSCInlines.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);
+
+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);
+ }
+
+ throwTypeError(globalObject, scope, "CallSite operation called on non-CallSite object"_s);
+ return nullptr;
+}
+
+#define ENTER_PROTO_FUNC() \
+ JSC::VM& vm = globalObject->vm(); \
+ auto scope = DECLARE_THROW_SCOPE(vm); \
+ \
+ CallSite* callSite = getCallSite(globalObject, callFrame->thisValue()); \
+ if (!callSite) { \
+ return JSC::JSValue::encode(JSC::jsUndefined()); \
+ }
+
+static const HashTableValue CallSitePrototypeTableValues[]
+ = {
+ { "getThis"_s, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::Function, NoIntrinsic, { HashTableValue::NativeFunctionType, callSiteProtoFuncGetThis, 0 } },
+ { "getTypeName"_s, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::Function, NoIntrinsic, { HashTableValue::NativeFunctionType, callSiteProtoFuncGetTypeName, 0 } },
+ { "getFunction"_s, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::Function, NoIntrinsic, { HashTableValue::NativeFunctionType, callSiteProtoFuncGetFunction, 0 } },
+ { "getFunctionName"_s, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::Function, NoIntrinsic, { HashTableValue::NativeFunctionType, callSiteProtoFuncGetFunctionName, 0 } },
+ { "getMethodName"_s, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::Function, NoIntrinsic, { HashTableValue::NativeFunctionType, callSiteProtoFuncGetMethodName, 0 } },
+ { "getFileName"_s, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::Function, NoIntrinsic, { HashTableValue::NativeFunctionType, callSiteProtoFuncGetFileName, 0 } },
+ { "getLineNumber"_s, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::Function, NoIntrinsic, { HashTableValue::NativeFunctionType, callSiteProtoFuncGetLineNumber, 0 } },
+ { "getColumnNumber"_s, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::Function, NoIntrinsic, { HashTableValue::NativeFunctionType, callSiteProtoFuncGetColumnNumber, 0 } },
+ { "getEvalOrigin"_s, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::Function, NoIntrinsic, { HashTableValue::NativeFunctionType, callSiteProtoFuncGetEvalOrigin, 0 } },
+ { "getScriptNameOrSourceURL"_s, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::Function, NoIntrinsic, { HashTableValue::NativeFunctionType, callSiteProtoFuncGetScriptNameOrSourceURL, 0 } },
+ { "isToplevel"_s, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::Function, NoIntrinsic, { HashTableValue::NativeFunctionType, callSiteProtoFuncIsToplevel, 0 } },
+ { "isEval"_s, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::Function, NoIntrinsic, { HashTableValue::NativeFunctionType, callSiteProtoFuncIsEval, 0 } },
+ { "isNative"_s, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::Function, NoIntrinsic, { HashTableValue::NativeFunctionType, callSiteProtoFuncIsNative, 0 } },
+ { "isConstructor"_s, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::Function, NoIntrinsic, { HashTableValue::NativeFunctionType, callSiteProtoFuncIsConstructor, 0 } },
+ { "isAsync"_s, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::Function, NoIntrinsic, { HashTableValue::NativeFunctionType, callSiteProtoFuncIsAsync, 0 } },
+ { "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 } },
+ };
+
+const JSC::ClassInfo CallSitePrototype::s_info = { "CallSite"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(CallSitePrototype) };
+
+void CallSitePrototype::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
+{
+ Base::finishCreation(vm);
+ ASSERT(inherits(vm, info()));
+
+ reifyStaticProperties(vm, CallSite::info(), CallSitePrototypeTableValues, *this);
+ JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
+}
+
+// TODO: doesn't recognize thisValue as global object
+JSC_DEFINE_HOST_FUNCTION(callSiteProtoFuncGetThis, (JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
+{
+ ENTER_PROTO_FUNC();
+ return JSC::JSValue::encode(callSite->thisValue());
+}
+
+// TODO: doesn't get class name
+JSC_DEFINE_HOST_FUNCTION(callSiteProtoFuncGetTypeName, (JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
+{
+ ENTER_PROTO_FUNC();
+ return JSC::JSValue::encode(JSC::jsTypeStringForValue(globalObject, callSite->thisValue()));
+}
+
+JSC_DEFINE_HOST_FUNCTION(callSiteProtoFuncGetFunction, (JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
+{
+ ENTER_PROTO_FUNC();
+ return JSC::JSValue::encode(callSite->function());
+}
+
+JSC_DEFINE_HOST_FUNCTION(callSiteProtoFuncGetFunctionName, (JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
+{
+ ENTER_PROTO_FUNC();
+ return JSC::JSValue::encode(callSite->functionName());
+}
+
+// TODO
+JSC_DEFINE_HOST_FUNCTION(callSiteProtoFuncGetMethodName, (JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
+{
+ return callSiteProtoFuncGetFunctionName(globalObject, callFrame);
+}
+
+JSC_DEFINE_HOST_FUNCTION(callSiteProtoFuncGetFileName, (JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
+{
+ ENTER_PROTO_FUNC();
+ return JSC::JSValue::encode(callSite->sourceURL());
+}
+
+JSC_DEFINE_HOST_FUNCTION(callSiteProtoFuncGetLineNumber, (JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
+{
+ ENTER_PROTO_FUNC();
+ return JSC::JSValue::encode(callSite->lineNumber());
+}
+
+JSC_DEFINE_HOST_FUNCTION(callSiteProtoFuncGetColumnNumber, (JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
+{
+ ENTER_PROTO_FUNC();
+ return JSC::JSValue::encode(callSite->columnNumber());
+}
+
+// TODO:
+JSC_DEFINE_HOST_FUNCTION(callSiteProtoFuncGetEvalOrigin, (JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
+{
+ return JSC::JSValue::encode(JSC::jsUndefined());
+}
+
+JSC_DEFINE_HOST_FUNCTION(callSiteProtoFuncGetScriptNameOrSourceURL, (JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
+{
+ ENTER_PROTO_FUNC();
+ return JSC::JSValue::encode(callSite->sourceURL());
+}
+
+JSC_DEFINE_HOST_FUNCTION(callSiteProtoFuncIsToplevel, (JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
+{
+ ENTER_PROTO_FUNC();
+
+ 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));
+ }
+
+ JSC::JSObject* thisObject = thisValue.getObject();
+ if (thisObject && thisObject->isGlobalObject()) {
+ return JSC::JSValue::encode(JSC::jsBoolean(true));
+ }
+
+ return JSC::JSValue::encode(JSC::jsBoolean(false));
+}
+
+JSC_DEFINE_HOST_FUNCTION(callSiteProtoFuncIsEval, (JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
+{
+ ENTER_PROTO_FUNC();
+
+ bool isEval = callSite->isEval();
+ return JSC::JSValue::encode(JSC::jsBoolean(isEval));
+}
+
+JSC_DEFINE_HOST_FUNCTION(callSiteProtoFuncIsNative, (JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
+{
+ ENTER_PROTO_FUNC();
+
+ bool isNative = callSite->isNative();
+ return JSC::JSValue::encode(JSC::jsBoolean(isNative));
+}
+
+JSC_DEFINE_HOST_FUNCTION(callSiteProtoFuncIsConstructor, (JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
+{
+ ENTER_PROTO_FUNC();
+
+ bool isConstructor = callSite->isConstructor();
+ return JSC::JSValue::encode(JSC::jsBoolean(isConstructor));
+}
+
+// TODO:
+JSC_DEFINE_HOST_FUNCTION(callSiteProtoFuncIsAsync, (JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
+{
+ ENTER_PROTO_FUNC();
+
+ return JSC::JSValue::encode(JSC::jsBoolean(false));
+}
+
+// TODO:
+JSC_DEFINE_HOST_FUNCTION(callSiteProtoFuncIsPromiseAll, (JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
+{
+ ENTER_PROTO_FUNC();
+
+ return JSC::JSValue::encode(JSC::jsBoolean(false));
+}
+
+// TODO:
+JSC_DEFINE_HOST_FUNCTION(callSiteProtoFuncGetPromiseIndex, (JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
+{
+ ENTER_PROTO_FUNC();
+
+ return JSC::JSValue::encode(JSC::jsNumber(0));
+}
+
+} \ No newline at end of file
diff --git a/src/bun.js/bindings/CallSitePrototype.h b/src/bun.js/bindings/CallSitePrototype.h
new file mode 100644
index 000000000..000bce2de
--- /dev/null
+++ b/src/bun.js/bindings/CallSitePrototype.h
@@ -0,0 +1,46 @@
+/**
+ * This source code is licensed under the terms found in the LICENSE file in
+ * node-jsc's root directory.
+ */
+
+#pragma once
+
+#include <JavaScriptCore/JSObject.h>
+
+using namespace JSC;
+
+namespace Zig {
+
+class CallSitePrototype final : public JSC::JSNonFinalObject {
+public:
+ using Base = JSC::JSNonFinalObject;
+
+ static CallSitePrototype* create(JSC::VM& vm, JSC::Structure* structure, JSC::JSGlobalObject* globalObject)
+ {
+ CallSitePrototype* callSitePrototype = new (NotNull, JSC::allocateCell<CallSitePrototype>(vm)) CallSitePrototype(vm, structure);
+ callSitePrototype->finishCreation(vm, globalObject);
+ return callSitePrototype;
+ }
+
+ DECLARE_INFO;
+ template<typename CellType, JSC::SubspaceAccess>
+ static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
+ {
+ return &vm.plainObjectSpace();
+ }
+
+ static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
+ {
+ return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
+ }
+
+private:
+ CallSitePrototype(JSC::VM& vm, JSC::Structure* structure)
+ : Base(vm, structure)
+ {
+ }
+
+ void finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject);
+};
+
+} \ No newline at end of file
diff --git a/src/bun.js/bindings/ErrorStackTrace.cpp b/src/bun.js/bindings/ErrorStackTrace.cpp
new file mode 100644
index 000000000..dddaa31ea
--- /dev/null
+++ b/src/bun.js/bindings/ErrorStackTrace.cpp
@@ -0,0 +1,375 @@
+/**
+ * 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/CatchScope.h>
+#include <JavaScriptCore/DebuggerPrimitives.h>
+#include <JavaScriptCore/Exception.h>
+#include <JavaScriptCore/JSCInlines.h>
+#include <JavaScriptCore/ErrorInstance.h>
+#include <JavaScriptCore/StackVisitor.h>
+#include <wtf/IterationStatus.h>
+
+using namespace JSC;
+using namespace WebCore;
+
+namespace Zig {
+
+JSCStackTrace JSCStackTrace::fromExisting(JSC::VM& vm, const WTF::Vector<JSC::StackFrame>& existingFrames)
+{
+ WTF::Vector<JSCStackFrame> newFrames;
+
+ size_t frameCount = existingFrames.size();
+ if (0 == frameCount) {
+ return JSCStackTrace();
+ }
+
+ newFrames.reserveInitialCapacity(frameCount);
+ for (size_t i = 0; i < frameCount; i++) {
+ newFrames.constructAndAppend(vm, existingFrames.at(i));
+ }
+
+ return JSCStackTrace(newFrames);
+}
+
+JSCStackTrace JSCStackTrace::captureCurrentJSStackTrace(Zig::GlobalObject* globalObject, JSC::CallFrame* callFrame, size_t frameLimit, JSC::JSValue caller)
+{
+ JSC::VM& vm = globalObject->vm();
+ if (!callFrame) {
+ return JSCStackTrace();
+ }
+
+ WTF::Vector<JSCStackFrame> stackFrames;
+ 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 (!callerFunction->name(vm).isEmpty() || callerFunction->isHostOrBuiltinFunction()) {
+ callerName = callerFunction->name(vm);
+ } else {
+ callerName = callerFunction->jsExecutable()->name().string();
+ }
+ }
+ if (JSC::InternalFunction* callerFunctionInternal = JSC::jsDynamicCast<JSC::InternalFunction*>(caller)) {
+ callerName = callerFunctionInternal->name();
+ }
+
+ JSC::StackVisitor::visit(callFrame, vm, [&](JSC::StackVisitor& visitor) -> WTF::IterationStatus {
+ // skip caller frame and all frames above it
+ if (!callerName.isEmpty()) {
+ if (!belowCaller) {
+ if (visitor->functionName() == callerName) {
+ belowCaller = true;
+ return WTF::IterationStatus::Continue;
+ }
+ skipFrames += 1;
+ }
+ }
+ if (!visitor->isNativeFrame()) {
+ framesCount++;
+ }
+
+ return WTF::IterationStatus::Continue;
+ });
+ framesCount = std::min(frameLimit, framesCount);
+
+ // Create the actual stack frames
+ size_t i = 0;
+ stackFrames.reserveInitialCapacity(framesCount);
+ JSC::StackVisitor::visit(callFrame, vm, [&](JSC::StackVisitor& visitor) -> WTF::IterationStatus {
+ // Skip native frames
+ if (visitor->isNativeFrame()) {
+ return WTF::IterationStatus::Continue;
+ }
+
+ // Skip frames if needed
+ if (skipFrames > 0) {
+ skipFrames--;
+ return WTF::IterationStatus::Continue;
+ }
+
+ stackFrames.constructAndAppend(vm, visitor);
+ i++;
+
+ return (i == framesCount) ? WTF::IterationStatus::Done : WTF::IterationStatus::Continue;
+ });
+
+ return JSCStackTrace(stackFrames);
+}
+
+JSCStackTrace JSCStackTrace::getStackTraceForThrownValue(JSC::VM& vm, JSC::JSValue thrownValue)
+{
+ const WTF::Vector<JSC::StackFrame>* jscStackTrace = nullptr;
+
+ JSC::Exception* currentException = DECLARE_CATCH_SCOPE(vm).exception();
+ if (currentException && currentException->value() == thrownValue) {
+ jscStackTrace = &currentException->stack();
+ } else {
+ JSC::ErrorInstance* error = JSC::jsDynamicCast<JSC::ErrorInstance*>(thrownValue);
+ if (error) {
+ jscStackTrace = error->stackTrace();
+ }
+ }
+
+ if (!jscStackTrace) {
+ return JSCStackTrace();
+ }
+
+ return fromExisting(vm, *jscStackTrace);
+}
+
+JSCStackFrame::JSCStackFrame(JSC::VM& vm, JSC::StackVisitor& visitor)
+ : m_vm(vm)
+ , m_codeBlock(nullptr)
+ , m_bytecodeIndex(JSC::BytecodeIndex())
+ , m_sourceURL(nullptr)
+ , m_functionName(nullptr)
+ , m_isWasmFrame(false)
+ , m_sourcePositionsState(SourcePositionsState::NotCalculated)
+{
+ m_callee = visitor->callee().asCell();
+ m_callFrame = visitor->callFrame();
+
+ // Based on JSC's GetStackTraceFunctor (Interpreter.cpp)
+ if (visitor->isWasmFrame()) {
+ m_wasmFunctionIndexOrName = visitor->wasmFunctionIndexOrName();
+ m_isWasmFrame = true;
+ } else if (!!visitor->codeBlock() && !visitor->codeBlock()->unlinkedCodeBlock()->isBuiltinFunction()) {
+ m_codeBlock = visitor->codeBlock();
+ m_bytecodeIndex = visitor->bytecodeIndex();
+ }
+}
+
+JSCStackFrame::JSCStackFrame(JSC::VM& vm, const JSC::StackFrame& frame)
+ : m_vm(vm)
+ , m_callFrame(nullptr)
+ , m_codeBlock(nullptr)
+ , m_bytecodeIndex(JSC::BytecodeIndex())
+ , m_sourceURL(nullptr)
+ , m_functionName(nullptr)
+ , m_isWasmFrame(false)
+ , 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 {
+ m_codeBlock = frame.codeBlock();
+ if (frame.hasBytecodeIndex()) {
+ m_bytecodeIndex = frame.bytecodeIndex();
+ }
+ }
+}
+
+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 m_sourceURL;
+}
+
+JSC::JSString* JSCStackFrame::functionName()
+{
+ if (!m_functionName) {
+ m_functionName = retrieveFunctionName();
+ }
+
+ return m_functionName;
+}
+
+JSC::JSString* JSCStackFrame::typeName()
+{
+ if (!m_typeName) {
+ m_typeName = retrieveTypeName();
+ }
+
+ return 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;
+}
+
+static auto sourceURLWasmString = MAKE_STATIC_STRING_IMPL("[wasm code]");
+static auto sourceURLNativeString = MAKE_STATIC_STRING_IMPL("[native code]");
+ALWAYS_INLINE JSC::JSString* JSCStackFrame::retrieveSourceURL()
+{
+ if (m_isWasmFrame) {
+ return jsOwnedString(m_vm, sourceURLWasmString);
+ }
+
+ if (!m_codeBlock) {
+ return jsOwnedString(m_vm, sourceURLNativeString);
+ }
+
+ String sourceURL = m_codeBlock->ownerExecutable()->sourceURL();
+ return sourceURL.isNull() ? m_vm.smallStrings.emptyString() : JSC::jsString(m_vm, sourceURL);
+}
+
+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");
+ALWAYS_INLINE JSC::JSString* JSCStackFrame::retrieveFunctionName()
+{
+ if (m_isWasmFrame) {
+ return jsString(m_vm, JSC::Wasm::makeString(m_wasmFunctionIndexOrName));
+ }
+
+ if (m_codeBlock) {
+ switch (m_codeBlock->codeType()) {
+ case JSC::EvalCode:
+ return JSC::jsOwnedString(m_vm, functionNameEvalCodeString);
+ case JSC::ModuleCode:
+ return JSC::jsOwnedString(m_vm, functionNameModuleCodeString);
+ case JSC::FunctionCode:
+ break;
+ case JSC::GlobalCode:
+ return JSC::jsOwnedString(m_vm, functionNameGlobalCodeString);
+ default:
+ ASSERT_NOT_REACHED();
+ }
+ }
+
+ if (!m_callee || !m_callee->isObject()) {
+ return m_vm.smallStrings.emptyString();
+ }
+
+ JSC::JSObject* calleeAsObject = JSC::jsCast<JSC::JSObject*>(m_callee);
+
+ // First, try the "displayName" property
+ JSC::JSValue displayName = calleeAsObject->getDirect(m_vm, m_vm.propertyNames->displayName);
+ if (displayName && isJSString(displayName)) {
+ return JSC::asString(displayName);
+ }
+
+ // Our addition - if there's no "dispalyName" property, try the "name" property
+ JSC::JSValue name = calleeAsObject->getDirect(m_vm, m_vm.propertyNames->name);
+ if (name && isJSString(name)) {
+ return JSC::asString(name);
+ }
+
+ /* For functions (either JSFunction or InternalFunction), fallback to their "native" name property.
+ * Based on JSC::getCalculatedDisplayName, "inlining" the
+ * JSFunction::calculatedDisplayName\InternalFunction::calculatedDisplayName calls */
+ if (JSC::JSFunction* function = JSC::jsDynamicCast<JSC::JSFunction*>(calleeAsObject)) {
+ // Based on JSC::JSFunction::calculatedDisplayName, skipping the "displayName" property check
+ WTF::String actualName = function->name(m_vm);
+ if (!actualName.isEmpty() || function->isHostOrBuiltinFunction()) {
+ return JSC::jsString(m_vm, actualName);
+ }
+
+ return JSC::jsString(m_vm, function->jsExecutable()->name().string());
+ }
+ if (JSC::InternalFunction* function = JSC::jsDynamicCast<JSC::InternalFunction*>(calleeAsObject)) {
+ // Based on JSC::InternalFunction::calculatedDisplayName, skipping the "displayName" property check
+ return JSC::jsString(m_vm, function->name());
+ }
+
+ return m_vm.smallStrings.emptyString();
+}
+
+ALWAYS_INLINE JSC::JSString* JSCStackFrame::retrieveTypeName()
+{
+ JSC::JSObject* calleeObject = JSC::jsCast<JSC::JSObject*>(m_callee);
+ // return JSC::jsTypeStringForValue(m_globalObjectcalleeObject->toThis()
+ return jsString(m_vm, makeString(calleeObject->className()));
+}
+
+// General flow here is based on JSC's appendSourceToError (ErrorInstance.cpp)
+bool JSCStackFrame::calculateSourcePositions()
+{
+ if (!m_codeBlock) {
+ return false;
+ }
+
+ JSC::BytecodeIndex bytecodeIndex = hasBytecodeIndex() ? m_bytecodeIndex : JSC::BytecodeIndex();
+
+ /* Get the "raw" position info.
+ * Note that we're using m_codeBlock->unlinkedCodeBlock()->expressionRangeForBytecodeOffset rather than m_codeBlock->expressionRangeForBytecodeOffset
+ * in order get the "raw" offsets and avoid the CodeBlock's expressionRangeForBytecodeOffset modifications to the line and column numbers,
+ * (we don't need the column number from it, and we'll calculate the line "fixes" ourselves). */
+ int startOffset = 0;
+ int endOffset = 0;
+ int divotPoint = 0;
+ unsigned line = 0;
+ unsigned unusedColumn = 0;
+ m_codeBlock->unlinkedCodeBlock()->expressionRangeForBytecodeIndex(bytecodeIndex, divotPoint, startOffset, endOffset, line, unusedColumn);
+ divotPoint += m_codeBlock->sourceOffset();
+
+ /* On the first line of the source code, it seems that we need to "fix" the column with the starting
+ * offset. We currently use codeBlock->source()->startPosition().m_column.oneBasedInt() as the
+ * offset in the first line rather than codeBlock->firstLineColumnOffset(), which seems simpler
+ * (and what CodeBlock::expressionRangeForBytecodeOffset does). This is because firstLineColumnOffset
+ * values seems different from what we expect (according to v8's tests) and I haven't dove into the
+ * relevant parts in JSC (yet) to figure out why. */
+ unsigned columnOffset = line ? 0 : m_codeBlock->source().startColumn().zeroBasedInt();
+
+ // "Fix" the line number
+ JSC::ScriptExecutable* executable = m_codeBlock->ownerExecutable();
+ line = executable->overrideLineNumber(m_vm).value_or(line + executable->firstLine());
+
+ // Calculate the staring\ending offsets of the entire expression
+ int expressionStart = divotPoint - startOffset;
+ int expressionStop = divotPoint + endOffset;
+
+ // Make sure the range is valid
+ StringView sourceString = m_codeBlock->source().provider()->source();
+ if (!expressionStop || expressionStart > static_cast<int>(sourceString.length())) {
+ return false;
+ }
+
+ // Search for the beginning of the line
+ unsigned int lineStart = expressionStart;
+ while ((lineStart > 0) && ('\n' != sourceString[lineStart - 1])) {
+ lineStart--;
+ }
+ // Search for the end of the line
+ unsigned int lineStop = expressionStop;
+ unsigned int sourceLength = sourceString.length();
+ while ((lineStop < sourceLength) && ('\n' != sourceString[lineStop])) {
+ lineStop++;
+ }
+
+ /* Finally, store the source "positions" info.
+ * Notes:
+ * - The retrieved column seem to point the "end column". To make sure we're current, we'll calculate the
+ * columns ourselves, since we've already found where the line starts. Note that in v8 it should be 0-based
+ * here (in contrast the 1-based column number in v8::StackFrame).
+ * - The static_casts are ugly, but comes from differences between JSC and v8's api, and should be OK
+ * since no source should be longer than "max int" chars.
+ */
+ m_sourcePositions.expressionStart = WTF::OrdinalNumber::fromZeroBasedInt(expressionStart);
+ m_sourcePositions.expressionStop = WTF::OrdinalNumber::fromZeroBasedInt(expressionStop);
+ m_sourcePositions.line = WTF::OrdinalNumber::fromZeroBasedInt(static_cast<int>(line));
+ m_sourcePositions.startColumn = WTF::OrdinalNumber::fromZeroBasedInt((expressionStart - lineStart) + columnOffset);
+ m_sourcePositions.endColumn = WTF::OrdinalNumber::fromZeroBasedInt(m_sourcePositions.startColumn.zeroBasedInt() + (expressionStop - expressionStart));
+ m_sourcePositions.lineStart = WTF::OrdinalNumber::fromZeroBasedInt(static_cast<int>(lineStart));
+ m_sourcePositions.lineStop = WTF::OrdinalNumber::fromZeroBasedInt(static_cast<int>(lineStop));
+
+ return true;
+}
+
+} \ No newline at end of file
diff --git a/src/bun.js/bindings/ErrorStackTrace.h b/src/bun.js/bindings/ErrorStackTrace.h
new file mode 100644
index 000000000..65ee60852
--- /dev/null
+++ b/src/bun.js/bindings/ErrorStackTrace.h
@@ -0,0 +1,180 @@
+/**
+ * This source code is licensed under the terms found in the LICENSE file in
+ * node-jsc's root directory.
+ */
+
+#pragma once
+
+#include <JavaScriptCore/StackVisitor.h>
+#include <JavaScriptCore/CodeBlock.h>
+#include <JavaScriptCore/WasmIndexOrName.h>
+
+#include "ZigGlobalObject.h"
+
+using namespace JSC;
+using namespace WebCore;
+
+namespace Zig {
+
+/* JSCStackFrame is an alternative to JSC::StackFrame, which provides the following advantages\changes:
+ * - Also hold the call frame (ExecState). This is mainly used by CallSite to get "this value".
+ * - More detailed and v8 compatible "source offsets" caculations: JSC::StackFrame only provides the
+ * line number and column numbers. It's column calculation seems to be different than v8's column.
+ * According to v8's unit tests, it seems that their column number points to the beginning of
+ * the expression which raised the exception, while in JSC the column returned by computeLineAndColumn
+ * seem to point to the end of the expression. Thus, we'll do the calculations ourselves.
+ * Here, we'll also provide more information which is needed by jscshim (mainly by Message):
+ * - Full expression range in the source code
+ * - Line number
+ * - Start\end columns (before the "throw", and after the "throw <x>")
+ * - Line start\stop offsets in the source code
+ * Also, to avoid zero\one base confusions, we'll store all offsets as WTF::OrdinalNumber.
+ * - Function name "calculation" also checks the function's "name" property. See retrieveFunctionName's
+ * documentation bellow for more information.
+ * - String properties are exposed (and cached) as JSStrings, instead of WTF::String.
+ * - Helper functions like isEval and isConstructor.
+ *
+ * Note that this is not a heap allocated, garbage collected, JSCell. It must be stack allocated, as it doens't
+ * use any write barriers and rely on the GC to see the stored JSC object pointers on the stack.
+ */
+class JSCStackFrame {
+public:
+ struct SourcePositions {
+ WTF::OrdinalNumber expressionStart;
+ WTF::OrdinalNumber expressionStop;
+ WTF::OrdinalNumber line;
+ WTF::OrdinalNumber startColumn;
+ WTF::OrdinalNumber endColumn;
+ WTF::OrdinalNumber lineStart;
+ WTF::OrdinalNumber lineStop;
+ };
+
+private:
+ JSC::VM& m_vm;
+ JSC::JSCell* m_callee;
+
+ // May be null
+ JSC::CallFrame* m_callFrame;
+
+ // May be null
+ JSC::CodeBlock* m_codeBlock;
+ JSC::BytecodeIndex m_bytecodeIndex;
+
+ // Lazy-initialized
+ JSC::JSString* m_sourceURL;
+ JSC::JSString* m_functionName;
+ JSC::JSString* m_typeName;
+
+ // m_wasmFunctionIndexOrName has meaning only when m_isWasmFrame is set
+ JSC::Wasm::IndexOrName m_wasmFunctionIndexOrName;
+ bool m_isWasmFrame;
+
+ enum class SourcePositionsState {
+ NotCalculated,
+ Failed,
+ Calculated
+ };
+
+ SourcePositions m_sourcePositions;
+ SourcePositionsState m_sourcePositionsState;
+
+public:
+ JSCStackFrame(JSC::VM& vm, JSC::StackVisitor& visitor);
+ JSCStackFrame(JSC::VM& vm, const JSC::StackFrame& frame);
+
+ JSC::JSCell* callee() const { return m_callee; }
+ JSC::CallFrame* callFrame() const { return m_callFrame; }
+ JSC::CodeBlock* codeBlock() const { return m_codeBlock; }
+
+ intptr_t sourceID() const;
+ JSC::JSString* sourceURL();
+ JSC::JSString* functionName();
+ JSC::JSString* typeName();
+
+ bool hasBytecodeIndex() const { return (m_bytecodeIndex.offset() != UINT_MAX) && !m_isWasmFrame; }
+ JSC::BytecodeIndex bytecodeIndex() const
+ {
+ ASSERT(hasBytecodeOffset());
+ return m_bytecodeIndex;
+ }
+
+ // Returns null if can't retreive the source positions
+ 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()); }
+
+private:
+ ALWAYS_INLINE JSC::JSString* retrieveSourceURL();
+
+ /* Regarding real functions (not eval\module\global code), both v8 and JSC seem to follow
+ * the same logic, which is to first try the function's "display name", and if it's not defined,
+ * the function's name. In JSC, StackFrame::functionName uses JSC::getCalculatedDisplayName,
+ * which will internally call the JSFunction\InternalFunction's calculatedDisplayName function.
+ * But, those function don't check the function's "name" property if the "dispaly name" isn't defined.
+ * See JSFunction::name()'s and InternalFunction::name()'s implementation. According to v8's unit tests,
+ * v8 does check the name property in StackFrame::GetFunctionName (see the last part of the
+ * "CaptureStackTrace" test in test-api.cc).
+ * Thus, we'll reimplement the general flow of JSC::getCalculatedDisplayName and it's internal calls,
+ * and just try to use the "name" property when needed, so our lookup will be:
+ * "display name" property -> "name" property -> JSFunction\InternalFunction "name" methods.
+ */
+ ALWAYS_INLINE JSC::JSString* retrieveFunctionName();
+
+ ALWAYS_INLINE JSC::JSString* retrieveTypeName();
+
+ bool calculateSourcePositions();
+};
+
+class JSCStackTrace {
+private:
+ WTF::Vector<JSCStackFrame> m_frames;
+
+public:
+ JSCStackTrace()
+ {
+ }
+
+ 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); }
+
+ 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)
+ * frames, which is what v8 does. Note that we could have just called JSC::Interpreter::getStackTrace
+ * and and filter it later (or let our callers filter it), but that would have been both inefficient, and
+ * problematic with the requested stack size limit (as it should only refer to the non-native frames,
+ * thus we would have needed to pass a large limit to JSC::Interpreter::getStackTrace, and filter out
+ * maxStackSize non-native frames).
+ *
+ * Return value must remain stack allocated. */
+ static JSCStackTrace captureCurrentJSStackTrace(Zig::GlobalObject* globalObject, JSC::CallFrame* callFrame, size_t frameLimit, JSC::JSValue caller);
+
+ /* 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,
+ * TryCatch::Exception returns the thrown value, and we follow the same rule in jscshim.
+ * This is a problem, since JSC::Exception is the one that holds the stack trace.
+ * ErrorInstances might also hold the stack trace (until the error properties are
+ * "materialized" and it is no longer needed). So, to try to get the stack trace for a thrown JSValue,
+ * we'll try two things:
+ * - If the current JSC (vm) exception points to our value, it means our value is probably the current
+ * exception and we could take the stack trace from the vm's current JSC::Exception. The downside
+ * of doing this is that we'll get the last stack trace of the thrown value, meaning that if the value
+ * was thrown, stored in the api and than re-thrown, we'll get the latest stack trace and not the one
+ * that was available when we stored it. For now it'll do.
+ * - If that failed and our thrown value is a JSC::ErrorInstance, we'll try to use it's stack trace,
+ * if it currently has one.
+ *
+ * Return value must remain stack allocated */
+ static JSCStackTrace getStackTraceForThrownValue(JSC::VM& vm, JSC::JSValue thrownValue);
+
+private:
+ JSCStackTrace(WTF::Vector<JSCStackFrame>& frames)
+ : m_frames(WTFMove(frames))
+ {
+ }
+};
+
+} \ No newline at end of file
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);
diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h
index 8613bc912..68fa452b2 100644
--- a/src/bun.js/bindings/ZigGlobalObject.h
+++ b/src/bun.js/bindings/ZigGlobalObject.h
@@ -34,6 +34,10 @@ class EventLoopTask;
#include "DOMIsoSubspaces.h"
#include "BunPlugin.h"
+#include "ErrorStackTrace.h"
+#include "CallSite.h"
+#include "CallSitePrototype.h"
+
extern "C" void Bun__reportError(JSC__JSGlobalObject*, JSC__JSValue);
// defined in ModuleLoader.cpp
extern "C" JSC::EncodedJSValue jsFunctionOnLoadObjectResultResolve(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame);
@@ -58,6 +62,8 @@ extern "C" JSC::EncodedJSValue jsFunctionOnLoadObjectResultReject(JSC::JSGlobalO
namespace Zig {
+class JSCStackTrace;
+
using JSDOMStructureMap = HashMap<const JSC::ClassInfo*, JSC::WriteBarrier<JSC::Structure>>;
using DOMGuardedObjectSet = HashSet<WebCore::DOMGuardedObject*>;
@@ -153,6 +159,9 @@ public:
void clearDOMGuardedObjects();
+ static void createCallSitesFromFrames(JSC::JSGlobalObject* lexicalGlobalObject, JSC::ObjectInitializationScope& objectScope, JSCStackTrace& stackTrace, JSC::JSArray* callSites);
+ JSC::JSValue formatStackTrace(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSObject* errorObject, JSC::JSArray* callSites, ZigStackFrame remappedStackFrames[]);
+
static void reportUncaughtExceptionAtEventLoop(JSGlobalObject*, JSC::Exception*);
static JSGlobalObject* deriveShadowRealmGlobalObject(JSGlobalObject* globalObject);
static void queueMicrotaskToEventLoop(JSC::JSGlobalObject& global, Ref<JSC::Microtask>&& task);
@@ -218,6 +227,8 @@ public:
JSC::JSMap* requireMap() { return m_requireMap.getInitializedOnMainThread(this); }
JSC::JSObject* encodeIntoObjectPrototype() { return m_encodeIntoObjectPrototype.getInitializedOnMainThread(this); }
+ JSC::Structure* callSiteStructure() const { return m_callSiteStructure.getInitializedOnMainThread(this); }
+
JSC::JSObject* performanceObject() { return m_performanceObject.getInitializedOnMainThread(this); }
JSC::JSObject* primordialsObject() { return m_primordialsObject.getInitializedOnMainThread(this); }
@@ -441,6 +452,7 @@ private:
LazyClassStructure m_JSStringDecoderClassStructure;
LazyClassStructure m_NapiClassStructure;
LazyClassStructure m_OnigurumaRegExpClassStructure;
+ LazyClassStructure m_callSiteStructure;
/**
* WARNING: You must update visitChildrenImpl() if you add a new field.
diff --git a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h
index b6641ea4d..cd292d938 100644
--- a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h
+++ b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h
@@ -30,6 +30,7 @@ public:
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForReadableState;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForPendingVirtualModuleResult;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForOnigurumaRegExp;
+ std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForCallSite;
#include "ZigGeneratedClasses+DOMClientIsoSubspaces.h"
/* --- bun --- */
diff --git a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h
index 32c367014..568b4e978 100644
--- a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h
+++ b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h
@@ -30,6 +30,7 @@ public:
std::unique_ptr<IsoSubspace> m_subspaceForReadableState;
std::unique_ptr<IsoSubspace> m_subspaceForPendingVirtualModuleResult;
std::unique_ptr<IsoSubspace> m_subspaceForOnigurumaRegExp;
+ std::unique_ptr<IsoSubspace> m_subspaceForCallSite;
#include "ZigGeneratedClasses+DOMIsoSubspaces.h"
/*-- BUN --*/
diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig
index 1fd6783be..8768b3180 100644
--- a/src/bun.js/javascript.zig
+++ b/src/bun.js/javascript.zig
@@ -1551,6 +1551,25 @@ pub const VirtualMachine = struct {
}
}
+ pub export fn Bun__remapStackFramePositions(globalObject: *JSC.JSGlobalObject, frames: [*]JSC.ZigStackFrame, frames_count: usize) void {
+ globalObject.bunVM().remapStackFramePositions(frames, frames_count);
+ }
+
+ 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;
+ if (this.source_mappings.resolveMapping(
+ frames[i].source_url.slice(),
+ @maximum(frames[i].position.line, 0),
+ @maximum(frames[i].position.column_start, 0),
+ )) |mapping| {
+ frames[i].position.line = mapping.original.lines;
+ frames[i].position.column_start = mapping.original.columns;
+ }
+ }
+ }
+
pub fn remapZigException(
this: *VirtualMachine,
exception: *ZigException,
@@ -1831,6 +1850,11 @@ pub const VirtualMachine = struct {
try writer.print(comptime Output.prettyFmt("<r><red>error<r>\n", allow_ansi_color), .{});
}
}
+
+ comptime {
+ if (!JSC.is_bindgen)
+ _ = Bun__remapStackFramePositions;
+ }
};
const GetterFn = fn (