Compare commits

...

3 Commits

Author SHA1 Message Date
Claude Bot
64f6d526f3 fix: add exception check in JSC__JSValue__getPrototype
The getPrototype function was missing a proper exception check
using DECLARE_THROW_SCOPE and RETURN_IF_EXCEPTION. This fixes
exception validation when BUN_JSC_validateExceptionChecks is enabled.

Also fixed the exception check pattern in forEachPropertyImpl to check
for exceptions immediately after getPrototype returns, rather than
only in the else branch (when getPrototype returns null/falsy).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-26 22:30:49 +00:00
Dylan Conway
bbff1a6b36 add minimized test 2025-11-22 23:11:21 -08:00
Dylan Conway
0d0f91c9dc exception check 2025-11-22 22:59:34 -08:00
9 changed files with 52 additions and 17 deletions

View File

@@ -2037,7 +2037,7 @@ pub const Formatter = struct {
try value.getClassName(globalThis, &name_str);
if (!name_str.eqlComptime("Object")) {
return name_str;
} else if (value.getPrototype(globalThis).eqlValue(JSValue.null)) {
} else if ((try value.getPrototype(globalThis)) == .null) {
return ZigString.static("[Object: null prototype]").*;
}
return null;
@@ -2318,7 +2318,7 @@ pub const Formatter = struct {
try value.getClassName(this.globalThis, &printable);
this.addForNewLine(printable.len);
const proto = value.getPrototype(this.globalThis);
const proto = try value.getPrototype(this.globalThis);
var printable_proto = ZigString.init(&name_buf);
try proto.getClassName(this.globalThis, &printable_proto);
this.addForNewLine(printable_proto.len);
@@ -2341,7 +2341,7 @@ pub const Formatter = struct {
var printable = try value.getName(this.globalThis);
defer printable.deref();
const proto = value.getPrototype(this.globalThis);
const proto = try value.getPrototype(this.globalThis);
const func_name = try proto.getName(this.globalThis); // "Function" | "AsyncFunction" | "GeneratorFunction" | "AsyncGeneratorFunction"
defer func_name.deref();

View File

@@ -1275,9 +1275,8 @@ pub const JSValue = enum(i64) {
}
extern fn JSC__JSValue__unwrapBoxedPrimitive(*JSGlobalObject, JSValue) JSValue;
extern fn JSC__JSValue__getPrototype(this: JSValue, globalObject: *JSGlobalObject) JSValue;
pub fn getPrototype(this: JSValue, globalObject: *JSGlobalObject) JSValue {
return JSC__JSValue__getPrototype(this, globalObject);
pub fn getPrototype(this: JSValue, globalObject: *JSGlobalObject) JSError!JSValue {
return bun.cpp.JSC__JSValue__getPrototype(this, globalObject);
}
extern fn JSC__JSValue__eqlValue(this: JSValue, other: JSValue) bool;

View File

@@ -3691,10 +3691,14 @@ bool JSC__JSValue__eqlValue(JSC::EncodedJSValue JSValue0, JSC::EncodedJSValue JS
{
return JSC::JSValue::decode(JSValue0) == JSC::JSValue::decode(JSValue1);
};
JSC::EncodedJSValue JSC__JSValue__getPrototype(JSC::EncodedJSValue JSValue0, JSC::JSGlobalObject* arg1)
[[ZIG_EXPORT(zero_is_throw)]] JSC::EncodedJSValue JSC__JSValue__getPrototype(JSC::EncodedJSValue JSValue0, JSC::JSGlobalObject* globalObject)
{
auto& vm = JSC::getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
auto value = JSC::JSValue::decode(JSValue0);
return JSC::JSValue::encode(value.getPrototype(arg1));
JSValue result = value.getPrototype(globalObject);
RETURN_IF_EXCEPTION(scope, {});
return JSC::JSValue::encode(result);
}
bool JSC__JSValue__isException(JSC::EncodedJSValue JSValue0, JSC::VM* arg1)
{
@@ -4964,7 +4968,9 @@ static void JSC__JSValue__forEachPropertyImpl(JSC::EncodedJSValue JSValue0, JSC:
if (structure->outOfLineSize() == 0 && structure->inlineSize() == 0) {
fast = false;
if (JSValue proto = object->getPrototype(globalObject)) {
JSValue proto = object->getPrototype(globalObject);
RETURN_IF_EXCEPTION(scope, void());
if (proto) {
if ((structure = proto.structureOrNull())) {
prototypeObject = proto;
fast = canPerformFastPropertyEnumerationForIterationBun(structure);
@@ -5041,7 +5047,9 @@ restart:
if (anyHits) {
if (prototypeCount++ < 5) {
if (JSValue proto = prototypeObject.getPrototype(globalObject)) {
JSValue proto = prototypeObject.getPrototype(globalObject);
RETURN_IF_EXCEPTION(scope, void());
if (proto) {
if (!(proto == globalObject->objectPrototype() || proto == globalObject->functionPrototype() || (proto.inherits<JSGlobalProxy>() && jsCast<JSGlobalProxy*>(proto)->target() != globalObject))) {
if ((structure = proto.structureOrNull())) {
prototypeObject = proto;
@@ -5050,8 +5058,6 @@ restart:
}
}
}
// Ignore exceptions from Proxy "getPrototype" trap.
CLEAR_IF_EXCEPTION(scope);
}
return;
}
@@ -5164,7 +5170,11 @@ restart:
break;
if (iterating == globalObject)
break;
iterating = iterating->getPrototype(globalObject).getObject();
JSValue proto = iterating->getPrototype(globalObject);
RETURN_IF_EXCEPTION(scope, void());
iterating = proto.getObject();
}
}

View File

@@ -229,7 +229,6 @@ CPP_DECL JSC::EncodedJSValue JSC__JSValue__getErrorsProperty(JSC::EncodedJSValue
CPP_DECL JSC::EncodedJSValue JSC__JSValue__getIfPropertyExistsFromPath(JSC::EncodedJSValue JSValue0, JSC::JSGlobalObject* arg1, JSC::EncodedJSValue JSValue2);
CPP_DECL double JSC__JSValue__getLengthIfPropertyExistsInternal(JSC::EncodedJSValue JSValue0, JSC::JSGlobalObject* arg1);
CPP_DECL void JSC__JSValue__getNameProperty(JSC::EncodedJSValue JSValue0, JSC::JSGlobalObject* arg1, ZigString* arg2);
CPP_DECL JSC::EncodedJSValue JSC__JSValue__getPrototype(JSC::EncodedJSValue JSValue0, JSC::JSGlobalObject* arg1);
CPP_DECL void JSC__JSValue__getSymbolDescription(JSC::EncodedJSValue JSValue0, JSC::JSGlobalObject* arg1, ZigString* arg2);
CPP_DECL double JSC__JSValue__getUnixTimestamp(JSC::EncodedJSValue JSValue0);
CPP_DECL bool JSC__JSValue__hasOwnProperty(JSC::EncodedJSValue JSValue0, JSC::JSGlobalObject* arg1, ZigString arg2);

View File

@@ -1831,7 +1831,9 @@ extern "C" napi_status napi_get_all_property_names(
// Climb up the prototype chain to find inherited properties
JSObject* current_object = object;
while (!current_object->getOwnPropertyDescriptor(globalObject, key.toPropertyKey(globalObject), desc)) {
JSObject* proto = current_object->getPrototype(globalObject).getObject();
JSValue protoValue = current_object->getPrototype(globalObject);
NAPI_RETURN_IF_EXCEPTION(env);
JSObject* proto = protoValue.getObject();
if (!proto) {
break;
}

View File

@@ -140,6 +140,7 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionIsError,
}
JSValue proto = object->getPrototype(globalObject);
RETURN_IF_EXCEPTION(scope, {});
if (proto.isCell() && (proto.inherits<JSC::ErrorInstance>() || proto.asCell()->type() == ErrorInstanceType || proto.inherits<JSC::ErrorPrototype>()))
return JSValue::encode(jsBoolean(true));
}

View File

@@ -453,7 +453,7 @@ pub fn createUnbound(globalThis: *JSGlobalObject, mode: Mode, each: jsc.JSValue,
pub fn bind(value: JSValue, globalThis: *JSGlobalObject, name: bun.String) bun.JSError!JSValue {
const callFn = jsc.JSFunction.create(globalThis, name, callAsFunction, 1, .{});
const bound = try callFn.bind(globalThis, value, &name, 1, &.{});
try bound.setPrototypeDirect(value.getPrototype(globalThis), globalThis);
try bound.setPrototypeDirect(try value.getPrototype(globalThis), globalThis);
return bound;
}

View File

@@ -725,7 +725,7 @@ pub const JestPrettyFormat = struct {
if (name_str.len > 0 and !name_str.eqlComptime("Object")) {
writer.print("{f} ", .{name_str});
} else {
try value.getPrototype(globalThis).getNameProperty(globalThis, &name_str);
try (try value.getPrototype(globalThis)).getNameProperty(globalThis, &name_str);
if (name_str.len > 0 and !name_str.eqlComptime("Object")) {
writer.print("{f} ", .{name_str});
}

View File

@@ -82,6 +82,30 @@ it("getter/setters", () => {
expect(Bun.inspect(obj)).toBe("{\n" + " foo: [Getter/Setter]," + "\n" + "}");
});
it("stack overflow exception checks", () => {
function probe(value) {
let originalPrototype, newPrototype;
let handler = {
set(target, key, value, receiver) {
return Reflect.set(target, key, value, receiver);
},
};
originalPrototype = Object.getPrototypeOf(value);
newPrototype = new Proxy(originalPrototype, handler);
Object.setPrototypeOf(value, newPrototype);
}
class Foo {
get bar() {
Bun.inspect(this);
}
}
const foo = new Foo();
probe(foo);
expect(() => {
foo.bar(Foo, foo);
}).toThrow("Maximum call stack size exceeded");
});
it("Timeout", () => {
const id = setTimeout(() => {}, 0);
expect(Bun.inspect(id)).toBe(`Timeout (#${+id})`);