Skip to content
60 changes: 36 additions & 24 deletions src/jsc/bindings/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6774,7 +6774,12 @@
size_t prefixLen)
{
auto& vm = JSC::getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
// The Rust caller (repl.rs) has no exception scope, so nothing may escape.
auto scope = DECLARE_TOP_EXCEPTION_SCOPE(vm);
auto clearAndEncode = [&](JSC::JSValue v) {
scope.clearException();
return JSC::JSValue::encode(v);
};

JSC::JSValue target = JSC::JSValue::decode(targetValue);
if (!target || target.isUndefined() || target.isNull()) {
Expand All @@ -6783,54 +6788,61 @@

if (!target.isObject()) {
JSObject* boxed = target.toObject(globalObject);
RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::jsUndefined()));
if (scope.exception()) [[unlikely]]
return clearAndEncode(JSC::jsUndefined());
target = boxed;
}

WTF::String prefix = prefixLen > 0
? WTF::String::fromUTF8(std::span { prefixPtr, prefixLen })
: WTF::String();

// getPropertyNames already walks (and dedups) the prototype chain, throwing past maximumPrototypeChainDepth.
JSC::JSObject* object = target.getObject();
JSC::PropertyNameArrayBuilder propertyNames(vm, JSC::PropertyNameMode::Strings, JSC::PrivateSymbolMode::Exclude);
object->getPropertyNames(globalObject, propertyNames, DontEnumPropertiesMode::Include);
RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::jsUndefined()));
if (scope.exception()) [[unlikely]]
return clearAndEncode(JSC::jsUndefined());

JSC::JSArray* completions = JSC::constructEmptyArray(globalObject, nullptr, 0);
RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::jsUndefined()));
if (scope.exception()) [[unlikely]]
return clearAndEncode(JSC::jsUndefined());

unsigned completionIndex = 0;
for (const auto& propertyName : propertyNames) {
WTF::String name = propertyName.string();
if (prefix.isEmpty() || name.startsWith(prefix)) {
completions->putDirectIndex(globalObject, completionIndex++, JSC::jsString(vm, name));
RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::jsUndefined()));
if (scope.exception()) [[unlikely]]
return clearAndEncode(JSC::jsUndefined());
}
}

// Also check the prototype chain
JSC::JSValue proto = object->getPrototype(globalObject);
RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(completions));
return JSC::JSValue::encode(completions);
}

while (proto && proto.isObject()) {
JSC::JSObject* protoObj = proto.getObject();
JSC::PropertyNameArrayBuilder protoNames(vm, JSC::PropertyNameMode::Strings, JSC::PrivateSymbolMode::Exclude);
protoObj->getPropertyNames(globalObject, protoNames, DontEnumPropertiesMode::Include);
RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(completions));
// One `object.name` step of a completion chain: ordinary property semantics (prototype chain, getters run), UTF-8 name; a miss or a throwing getter yields undefined.
extern "C" JSC::EncodedJSValue Bun__REPL__getProperty(
JSC::JSGlobalObject* globalObject,
JSC::EncodedJSValue objectValue,
const unsigned char* namePtr,
size_t nameLen)
{
auto& vm = JSC::getVM(globalObject);
// As in Bun__REPL__getCompletions: the Rust caller has no exception scope.
auto scope = DECLARE_TOP_EXCEPTION_SCOPE(vm);

for (const auto& propertyName : protoNames) {
WTF::String name = propertyName.string();
if (prefix.isEmpty() || name.startsWith(prefix)) {
completions->putDirectIndex(globalObject, completionIndex++, JSC::jsString(vm, name));
RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(completions));
}
}
JSC::JSObject* object = JSC::JSValue::decode(objectValue).getObject();
WTF::String name = WTF::String::fromUTF8(std::span { namePtr, nameLen });

Check warning on line 6836 in src/jsc/bindings/bindings.cpp

View check run for this annotation

Claude / Claude Code Review

Bun__REPL__getProperty doesn't box primitives, so chains through string/number intermediates don't complete

`Bun__REPL__getProperty` uses `.getObject()` (nullptr on primitives) while its sibling `Bun__REPL__getCompletions` boxes with `toObject()`, so a chain whose *intermediate* segment is a primitive — `process.version.length.toF|` — hits `resolve_object_expr`'s `!current.is_object()` guard, returns `UNDEFINED`, and no ghost/Tab completion is offered (Node completes `Number.prototype` methods here). Switching `getObject()` → `toObject(globalObject)` under the existing exception scope, and dropping th
Comment thread
claude[bot] marked this conversation as resolved.
if (!object || name.isNull())
return JSC::JSValue::encode(JSC::jsUndefined());

proto = protoObj->getPrototype(globalObject);
RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(completions));
JSC::JSValue result = object->getIfPropertyExists(globalObject, JSC::Identifier::fromString(vm, name));
if (scope.exception()) [[unlikely]] {
scope.clearException();
return JSC::JSValue::encode(JSC::jsUndefined());
}

return JSC::JSValue::encode(completions);
return JSC::JSValue::encode(result ? result : JSC::jsUndefined());
}

// Format a value for REPL output using util.inspect style
Expand Down
1 change: 1 addition & 0 deletions src/jsc/bindings/headers.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading