Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/jsc/bindings/BunClientData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "napi_handle_scope.h"
#include "NativePromiseContext.h"
#include "StrongRootBlock.h"
#include "JSDOMException.h"

namespace WebCore {
using namespace JSC;
Expand All @@ -39,6 +40,7 @@ JSHeapData::JSHeapData(Heap& heap)
, m_heapCellTypeForBakeGlobalObject(JSC::IsoHeapCellType::Args<Bake::GlobalObject>())
, m_heapCellTypeForNapiHandleScopeImpl(JSC::IsoHeapCellType::Args<Bun::NapiHandleScopeImpl>())
, m_heapCellTypeForNativePromiseContext(JSC::IsoHeapCellType::Args<Bun::NativePromiseContext>())
, m_heapCellTypeForJSDOMException(JSC::IsoHeapCellType::Args<WebCore::JSDOMException>())
, m_domConstructorSpace ISO_SUBSPACE_INIT(heap, heap.cellHeapCellType, JSDOMConstructorBase)
, m_domNamespaceObjectSpace ISO_SUBSPACE_INIT(heap, heap.cellHeapCellType, JSDOMObject)
, m_subspaces(makeUnique<ExtendedDOMIsoSubspaces>())
Expand Down
1 change: 1 addition & 0 deletions src/jsc/bindings/BunClientData.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ class JSHeapData {
JSC::IsoHeapCellType m_heapCellTypeForNapiHandleScopeImpl;
JSC::IsoHeapCellType m_heapCellTypeForBakeGlobalObject;
JSC::IsoHeapCellType m_heapCellTypeForNativePromiseContext;
JSC::IsoHeapCellType m_heapCellTypeForJSDOMException;
// JSC::IsoHeapCellType m_heapCellTypeForGeneratedClass;

private:
Expand Down
25 changes: 18 additions & 7 deletions src/jsc/bindings/FormatStackTraceForJS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "BunClientData.h"
#include "CallSite.h"
#include "ErrorStackTrace.h"
#include "JSDOMException.h"
#include "headers-handwritten.h"

#include <wtf/Scope.h>
Expand Down Expand Up @@ -414,10 +415,15 @@
if (!lexicalGlobalObject) {
lexicalGlobalObject = errorInstance->globalObject();
}
name = instance->sanitizedNameString(lexicalGlobalObject);
RETURN_IF_EXCEPTION(scope, {});
message = instance->sanitizedMessageString(lexicalGlobalObject);
RETURN_IF_EXCEPTION(scope, {});
if (auto* domException = dynamicDowncast<WebCore::JSDOMException>(instance)) {
name = domException->wrapped().name();
message = domException->wrapped().message();
} else {

Check warning on line 421 in src/jsc/bindings/FormatStackTraceForJS.cpp

View check run for this annotation

Claude / Claude Code Review

DOMException .stack header ignores own name/message overrides

The `.stack` header for a DOMException reads `wrapped().name()`/`wrapped().message()` — the C++ impl's construction-time strings — so an own data property set via `Object.defineProperty(e, 'name', {value:'CustomError'})` before the first `.stack` read is ignored, whereas the else-branch's `sanitizedNameString` (and Node) would honor it. The branch is needed (`sanitizedNameString` skips the CustomAccessor prototype slot and falls back to `"Error"`), but it should check for an own value slot first
Comment thread
robobun marked this conversation as resolved.
name = instance->sanitizedNameString(lexicalGlobalObject);
RETURN_IF_EXCEPTION(scope, {});
message = instance->sanitizedMessageString(lexicalGlobalObject);
RETURN_IF_EXCEPTION(scope, {});
}
}
}

Expand Down Expand Up @@ -673,8 +679,12 @@
}

if (source->stackTrace()) {
destination->stackTrace()->appendVector(*source->stackTrace());
source->stackTrace()->clear();
// setStackFrames takes the cellLock that JSDOMException::visitChildren reads under.
WTF::Vector<JSC::StackFrame> combined;
combined.appendVector(*destination->stackTrace());
combined.appendVector(*source->stackTrace());
destination->setStackFrames(vm, WTF::move(combined));
source->setStackFrames(vm, {});
}

return JSC::JSValue::encode(jsUndefined());
Expand Down Expand Up @@ -724,7 +734,8 @@
WTF::Vector<JSC::StackFrame> emptyTrace;
result = computeErrorInfoToJSValue(vm, emptyTrace, line, column, sourceURL, errorObject, nullptr);
} else {
auto ownedStackTrace = makeUnique<WTF::Vector<JSC::StackFrame>>(WTF::move(*stackTrace));
// Copy: a move here races JSDOMException::visitChildren reading under the cellLock.
auto ownedStackTrace = makeUnique<WTF::Vector<JSC::StackFrame>>(*stackTrace);
JSC::MarkedArgumentBuffer protectedFrameCells;
protectedFrameCells.ensureCapacity(ownedStackTrace->size() * 2);
for (auto& frame : *ownedStackTrace) {
Expand Down
6 changes: 4 additions & 2 deletions src/jsc/bindings/JSDOMExceptionHandling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,10 @@ String retrieveErrorMessage(JSGlobalObject& lexicalGlobalObject, VM& vm, JSValue
// FIXME: <http://webkit.org/b/115087> Web Inspector: WebCore::reportException should not evaluate JavaScript handling exceptions
// If this is a custom exception object, call toString on it to try and get a nice string representation for the exception.
String errorMessage;
if (auto* error = dynamicDowncast<ErrorInstance>(exception))
if (auto* error = dynamicDowncast<JSDOMException>(exception)) {
auto& impl = error->wrapped();
errorMessage = impl.message().isEmpty() ? impl.name() : makeString(impl.name(), ": "_s, impl.message());
} else if (auto* error = dynamicDowncast<ErrorInstance>(exception))
errorMessage = error->sanitizedToString(&lexicalGlobalObject);
else
errorMessage = exception.toWTFString(&lexicalGlobalObject);
Expand Down Expand Up @@ -184,7 +187,6 @@ JSValue createDOMException(JSGlobalObject* lexicalGlobalObject, ExceptionCode ec
JSValue errorObject = toJS(lexicalGlobalObject, globalObject, DOMException::create(ec, message));

ASSERT(errorObject);
addErrorInfo(lexicalGlobalObject, asObject(errorObject), true);
return errorObject;
}
}
Expand Down
12 changes: 6 additions & 6 deletions src/jsc/bindings/JSDOMWrapperCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,15 @@ template<typename WrapperClass> JSC::JSObject* getDOMPrototype(JSC::VM&, JSDOMGl
JSC::WeakHandleOwner* wrapperOwner(DOMWrapperWorld&, JSC::ArrayBuffer*);
void* wrapperKey(JSC::ArrayBuffer*);

std::optional<JSDOMObject*> getInlineCachedWrapper(DOMWrapperWorld&, void*);
std::optional<JSC::JSObject*> getInlineCachedWrapper(DOMWrapperWorld&, void*);
std::optional<JSDOMObject*> getInlineCachedWrapper(DOMWrapperWorld&, ScriptWrappable*);
std::optional<JSC::JSArrayBuffer*> getInlineCachedWrapper(DOMWrapperWorld&, JSC::ArrayBuffer*);

bool setInlineCachedWrapper(DOMWrapperWorld&, void*, JSDOMObject*, JSC::WeakHandleOwner*);
bool setInlineCachedWrapper(DOMWrapperWorld&, void*, JSC::JSObject*, JSC::WeakHandleOwner*);
bool setInlineCachedWrapper(DOMWrapperWorld&, ScriptWrappable*, JSDOMObject* wrapper, JSC::WeakHandleOwner* wrapperOwner);
bool setInlineCachedWrapper(DOMWrapperWorld&, JSC::ArrayBuffer*, JSC::JSArrayBuffer* wrapper, JSC::WeakHandleOwner* wrapperOwner);

bool clearInlineCachedWrapper(DOMWrapperWorld&, void*, JSDOMObject*);
bool clearInlineCachedWrapper(DOMWrapperWorld&, void*, JSC::JSObject*);
bool clearInlineCachedWrapper(DOMWrapperWorld&, ScriptWrappable*, JSDOMObject* wrapper);
bool clearInlineCachedWrapper(DOMWrapperWorld&, JSC::ArrayBuffer*, JSC::JSArrayBuffer* wrapper);

Expand Down Expand Up @@ -98,9 +98,9 @@ inline void* wrapperKey(JSC::ArrayBuffer* domObject)
return domObject;
}

inline std::optional<JSDOMObject*> getInlineCachedWrapper(DOMWrapperWorld&, void*) { return std::nullopt; }
inline bool setInlineCachedWrapper(DOMWrapperWorld&, void*, JSDOMObject*, JSC::WeakHandleOwner*) { return false; }
inline bool clearInlineCachedWrapper(DOMWrapperWorld&, void*, JSDOMObject*) { return false; }
inline std::optional<JSC::JSObject*> getInlineCachedWrapper(DOMWrapperWorld&, void*) { return std::nullopt; }
inline bool setInlineCachedWrapper(DOMWrapperWorld&, void*, JSC::JSObject*, JSC::WeakHandleOwner*) { return false; }
inline bool clearInlineCachedWrapper(DOMWrapperWorld&, void*, JSC::JSObject*) { return false; }

inline std::optional<JSDOMObject*> getInlineCachedWrapper(DOMWrapperWorld& world, ScriptWrappable* domObject)
{
Expand Down
28 changes: 15 additions & 13 deletions src/jsc/bindings/ZigException.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "ZigGlobalObject.h"
#include "helpers.h"
#include "JavaScriptCore/JSObjectInlines.h"
#include "JSDOMException.h"

#include "wtf/Assertions.h"
#include "wtf/text/OrdinalNumber.h"
Expand Down Expand Up @@ -456,19 +457,17 @@ static void populateStackTrace(JSC::VM& vm, const WTF::Vector<JSC::StackFrame>&

static JSC::JSValue getNonObservable(JSC::VM& vm, JSC::JSGlobalObject* global, JSC::JSObject* obj, const JSC::PropertyName& propertyName)
{
auto scope = DECLARE_THROW_SCOPE(vm);
PropertySlot slot = PropertySlot(obj, PropertySlot::InternalMethodType::VMInquiry, &vm);
if (obj->getNonIndexPropertySlot(global, propertyName, slot)) {
if (slot.isAccessor()) {
return {};
}

JSValue value = slot.getValue(global, propertyName);
if (!value || value.isUndefinedOrNull()) {
return {};
}
return value;
}
return {};
bool found = obj->getNonIndexPropertySlot(global, propertyName, slot);
RETURN_IF_EXCEPTION(scope, {});
// isValue() also rejects custom getters (DOMException.prototype.code), which isAccessor() does not.
if (!found || !slot.isValue())
return {};
JSValue value = slot.getValue(global, propertyName);
if (!value || value.isUndefinedOrNull())
return {};
return value;
}

static void fromErrorInstance(ZigException& except, JSC::JSGlobalObject* global,
Expand Down Expand Up @@ -512,7 +511,10 @@ static void fromErrorInstance(ZigException& except, JSC::JSGlobalObject* global,
return;
}

except.name = Bun::toStringRef(err->sanitizedNameString(global));
if (auto* domException = dynamicDowncast<WebCore::JSDOMException>(err))
except.name = Bun::toStringRef(domException->wrapped().name());
else
except.name = Bun::toStringRef(err->sanitizedNameString(global));
if (!scope.clearExceptionExceptTermination()) [[unlikely]] {
return;
}
Expand Down
42 changes: 37 additions & 5 deletions src/jsc/bindings/webcore/JSDOMException.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,15 @@
#include <JavaScriptCore/FunctionPrototype.h>
#include <JavaScriptCore/HeapAnalyzer.h>

#include <JavaScriptCore/JSCInlines.h>
#include <JavaScriptCore/JSDestructibleObjectHeapCellType.h>
#include <JavaScriptCore/SlotVisitorMacros.h>
#include <JavaScriptCore/StackFrame.h>
#include <JavaScriptCore/SubspaceInlines.h>
#include <wtf/GetPtr.h>
#include <wtf/PointerPreparations.h>
#include <wtf/URL.h>
#include <wtf/text/MakeString.h>

namespace WebCore {
using namespace JSC;
Expand Down Expand Up @@ -232,18 +235,46 @@ void JSDOMExceptionPrototype::finishCreation(VM& vm)
const ClassInfo JSDOMException::s_info = { "DOMException"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSDOMException) };

JSDOMException::JSDOMException(Structure* structure, JSDOMGlobalObject& globalObject, Ref<DOMException>&& impl)
: JSDOMWrapper<DOMException>(structure, globalObject, WTF::move(impl))
: Base(globalObject.vm(), structure, JSC::ErrorType::Error)
, m_wrapped(WTF::move(impl))
{
}

void JSDOMException::finishCreation(VM& vm)
{
Base::finishCreation(vm);
// Null message/cause: those stay prototype accessors over the wrapped impl.
Base::finishCreation(vm, String(), JSValue(), nullptr, JSC::TypeNothing, true);
ASSERT(inherits(info()));

// static_assert(!std::is_base_of<ActiveDOMObject, DOMException>::value, "Interface is not marked as [ActiveDOMObject] even though implementation class subclasses ActiveDOMObject.");
// ErrorInstance leaves .stack unset on an empty trace; other engines still give the name: message header.
auto* trace = stackTrace();
if (!trace || trace->isEmpty()) {
auto& impl = wrapped();
auto name = impl.name();
auto message = impl.message();
auto header = message.isEmpty() ? name : makeString(name, ": "_s, message);
putDirect(vm, vm.propertyNames->stack, jsString(vm, WTF::move(header)), static_cast<unsigned>(JSC::PropertyAttribute::DontEnum));
setStackPropertyAlreadyMaterialized();
}
Comment thread
robobun marked this conversation as resolved.
Outdated
}

template<typename Visitor>
void JSDOMException::visitChildrenImpl(JSCell* cell, Visitor& visitor)
{
auto* thisObject = uncheckedDowncast<JSDOMException>(cell);
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
Base::visitChildren(thisObject, visitor);

// Heap sweeps dead frames only for vm.errorInstanceSpace(); this subspace must keep its own alive.
Locker locker { thisObject->cellLock() };
if (auto* stackTrace = thisObject->stackTrace()) {
for (auto& frame : *stackTrace)
frame.visitAggregate(visitor);
}
Comment thread
claude[bot] marked this conversation as resolved.
}

DEFINE_VISIT_CHILDREN(JSDOMException);

JSObject* JSDOMException::createPrototype(VM& vm, JSDOMGlobalObject& globalObject)
{
return JSDOMExceptionPrototype::create(vm, &globalObject, JSDOMExceptionPrototype::createStructure(vm, &globalObject, globalObject.errorPrototype()));
Expand Down Expand Up @@ -316,12 +347,13 @@ JSC_DEFINE_CUSTOM_GETTER(jsDOMException_message, (JSGlobalObject * lexicalGlobal

JSC::GCClient::IsoSubspace* JSDOMException::subspaceForImpl(JSC::VM& vm)
{
return WebCore::subspaceForImpl<JSDOMException, UseCustomHeapCellType::No>(
return WebCore::subspaceForImpl<JSDOMException, UseCustomHeapCellType::Yes>(
vm,
[](auto& spaces) { return spaces.m_clientSubspaceForDOMException.get(); },
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForDOMException = std::forward<decltype(space)>(space); },
[](auto& spaces) { return spaces.m_subspaceForDOMException.get(); },
[](auto& spaces, auto&& space) { spaces.m_subspaceForDOMException = std::forward<decltype(space)>(space); });
[](auto& spaces, auto&& space) { spaces.m_subspaceForDOMException = std::forward<decltype(space)>(space); },
[](auto& server) -> JSC::HeapCellType& { return server.m_heapCellTypeForJSDOMException; });
}

void JSDOMException::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer)
Expand Down
24 changes: 21 additions & 3 deletions src/jsc/bindings/webcore/JSDOMException.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,21 @@

#include "DOMException.h"
#include "JSDOMWrapper.h"
#include <JavaScriptCore/ErrorInstance.h>
#include <JavaScriptCore/ErrorPrototype.h>
#include <wtf/NeverDestroyed.h>

namespace WebCore {

class JSDOMException : public JSDOMWrapper<DOMException> {
// An ErrorInstance so DOMException has [[ErrorData]] and a stack, as WebIDL requires.
class JSDOMException : public JSC::ErrorInstance {
public:
using Base = JSDOMWrapper<DOMException>;
using Base = JSC::ErrorInstance;
using DOMWrapped = DOMException;

static constexpr unsigned StructureFlags = Base::StructureFlags;
static constexpr JSC::DestructionMode needsDestruction = JSC::NeedsDestruction;

static JSDOMException* create(JSC::Structure* structure, JSDOMGlobalObject* globalObject, Ref<DOMException>&& impl)
{
JSDOMException* ptr = new (NotNull, JSC::allocateCell<JSDOMException>(globalObject->vm())) JSDOMException(structure, *globalObject, WTF::move(impl));
Expand All @@ -45,10 +52,11 @@ class JSDOMException : public JSDOMWrapper<DOMException> {
static void destroy(JSC::JSCell*);

DECLARE_INFO;
DECLARE_VISIT_CHILDREN;

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(), JSC::NonArray);
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ErrorInstanceType, StructureFlags), info(), JSC::NonArray);
}

static JSC::JSValue getConstructor(JSC::VM&, const JSC::JSGlobalObject*);
Expand All @@ -61,10 +69,20 @@ class JSDOMException : public JSDOMWrapper<DOMException> {
static JSC::GCClient::IsoSubspace* subspaceForImpl(JSC::VM& vm);
static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&);

DOMException& wrapped() const { return m_wrapped; }
Ref<DOMException> protectedWrapped() const { return m_wrapped; }
static constexpr ptrdiff_t offsetOfWrapped() { return OBJECT_OFFSETOF(JSDOMException, m_wrapped); }
constexpr static bool hasCustomPtrTraits() { return false; }

JSDOMGlobalObject* globalObject() const { return uncheckedDowncast<JSDOMGlobalObject>(JSC::JSNonFinalObject::globalObject()); }

protected:
JSDOMException(JSC::Structure*, JSDOMGlobalObject&, Ref<DOMException>&&);

void finishCreation(JSC::VM&);

private:
Ref<DOMException> m_wrapped;
};

class JSDOMExceptionOwner final : public JSC::WeakHandleOwner {
Expand Down
8 changes: 4 additions & 4 deletions src/jsc/bindings/webcore/SerializedScriptValue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1183,6 +1183,10 @@ class CloneSerializer : public CloneBase {
write(String::fromLatin1(JSC::Yarr::flagsString(regExp->regExp()->flags()).data()));
return true;
}
if (obj->inherits<JSDOMException>()) {
dumpDOMException(obj, code);
return true;
}
Comment thread
robobun marked this conversation as resolved.
Outdated
if (auto* errorInstance = dynamicDowncast<ErrorInstance>(obj)) {
if (!startObjectInternal(errorInstance)) // handle duplicates
return true;
Expand Down Expand Up @@ -1391,10 +1395,6 @@ class CloneSerializer : public CloneBase {
return true;
}
#endif
if (obj->inherits<JSDOMException>()) {
dumpDOMException(obj, code);
return true;
}

// write bun types
auto _cloneable = StructuredCloneableSerialize::fromJS(value);
Expand Down
Loading