Skip to content
Open
Show file tree
Hide file tree
Changes from all 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 @@ static String computeErrorInfoWithoutPrepareStackTrace(
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->displayName(vm);
message = domException->displayMessage(vm);
} else {
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 @@ JSC_DEFINE_HOST_FUNCTION(errorConstructorFuncAppendStackTrace, (JSC::JSGlobalObj
}

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 @@ JSC_DEFINE_CUSTOM_GETTER(errorInstanceLazyStackCustomGetter, (JSGlobalObject * g
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
5 changes: 3 additions & 2 deletions src/jsc/bindings/JSDOMExceptionHandling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,9 @@ 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))
errorMessage = error->displayHeader(vm);
else if (auto* error = dynamicDowncast<ErrorInstance>(exception))
errorMessage = error->sanitizedToString(&lexicalGlobalObject);
else
errorMessage = exception.toWTFString(&lexicalGlobalObject);
Expand Down Expand Up @@ -184,7 +186,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->displayName(vm));
else
except.name = Bun::toStringRef(err->sanitizedNameString(global));
if (!scope.clearExceptionExceptTermination()) [[unlikely]] {
return;
}
Expand Down
104 changes: 86 additions & 18 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 @@ -152,19 +155,14 @@ template<> JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSDOMExceptionDOMConstru
RETURN_IF_EXCEPTION(throwScope, {});
}

auto object = DOMException::create(WTF::move(message), WTF::move(name));
if constexpr (IsExceptionOr<decltype(object)>)
RETURN_IF_EXCEPTION(throwScope, {});
static_assert(TypeOrExceptionOrUnderlyingType<decltype(object)>::isRef);
auto jsValue = toJSNewlyCreated<IDLInterface<DOMException>>(*lexicalGlobalObject, *castedThis->globalObject(), throwScope, WTF::move(object));
if constexpr (IsExceptionOr<decltype(object)>)
RETURN_IF_EXCEPTION(throwScope, {});
setSubclassStructureIfNeeded<DOMException>(lexicalGlobalObject, callFrame, asObject(jsValue));
// Not toJSNewlyCreated: own properties must wait until after the subclass structure swap.
auto* wrapper = createWrapper<DOMException>(castedThis->globalObject(), DOMException::create(WTF::move(message), WTF::move(name)));
setSubclassStructureIfNeeded<DOMException>(lexicalGlobalObject, callFrame, wrapper);
RETURN_IF_EXCEPTION(throwScope, {});
if (!cause.isEmpty()) {
jsValue.getObject()->putDirect(vm, vm.propertyNames->cause, cause, JSC::PropertyAttribute::DontEnum | 0);
}
return JSValue::encode(jsValue);
wrapper->putHeaderStackIfNoFrames(vm);
if (!cause.isEmpty())
wrapper->putDirect(vm, vm.propertyNames->cause, cause, JSC::PropertyAttribute::DontEnum | 0);
return JSValue::encode(wrapper);
}
JSC_ANNOTATE_HOST_FUNCTION(JSDOMExceptionDOMConstructorConstruct, JSDOMExceptionDOMConstructor::construct);

Expand Down Expand Up @@ -232,18 +230,85 @@ 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()));
// No own properties here: the constructor may still swap in a subclass structure.
}

void JSDOMException::setStackString(VM& vm, String&& stack)
{
putDirect(vm, vm.propertyNames->stack, jsString(vm, WTF::move(stack)), static_cast<unsigned>(JSC::PropertyAttribute::DontEnum));
setStackFrames(vm, {}); // Nothing will read the captured frames now; stop visitChildren from rooting them.
setStackPropertyAlreadyMaterialized();
Comment thread
robobun marked this conversation as resolved.
}

// 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 header line.
void JSDOMException::putHeaderStackIfNoFrames(VM& vm)
{
auto* trace = stackTrace();
if (trace && !trace->isEmpty())
return;
setStackString(vm, displayHeader(vm));
}

// Same joining rule as formatStackTrace: either side may be empty.
String JSDOMException::displayHeader(VM& vm) const
{
auto name = displayName(vm);
auto message = displayMessage(vm);
if (name.isEmpty())
return message;
if (message.isEmpty())
return name;
return makeString(name, ": "_s, message);
}

String JSDOMException::ownStringOr(VM& vm, PropertyName propertyName, String&& fallback) const
{
JSValue own = getDirect(vm, propertyName);
if (!own || !own.isString())
return WTF::move(fallback);
auto* string = asString(own);
if (string->isRope()) // Resolving would allocate; callers may be inside a finalizer.
return WTF::move(fallback);
return String(string->tryGetValue(false));
}

String JSDOMException::displayName(VM& vm) const
{
return ownStringOr(vm, vm.propertyNames->name, wrapped().name());
}

String JSDOMException::displayMessage(VM& vm) const
{
return ownStringOr(vm, vm.propertyNames->message, wrapped().message());
}

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 +381,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 Expand Up @@ -350,7 +416,9 @@ void JSDOMExceptionOwner::finalize(JSC::Handle<JSC::Unknown> handle, void* conte

JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject*, JSDOMGlobalObject* globalObject, Ref<DOMException>&& impl)
{
return createWrapper<DOMException>(globalObject, WTF::move(impl));
auto* wrapper = createWrapper<DOMException>(globalObject, WTF::move(impl));
wrapper->putHeaderStackIfNoFrames(globalObject->vm());
return wrapper;
}

JSC::JSValue toJS(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, DOMException& impl)
Expand Down
Loading