Skip to content
64 changes: 41 additions & 23 deletions src/jsc/bindings/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6774,7 +6774,12 @@ extern "C" JSC::EncodedJSValue Bun__REPL__getCompletions(
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,67 @@ extern "C" JSC::EncodedJSValue Bun__REPL__getCompletions(

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 `base.name` step of a completion chain: ordinary property semantics (primitives boxed, 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 baseValue,
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::JSValue base = JSC::JSValue::decode(baseValue);
WTF::String name = WTF::String::fromUTF8(std::span { namePtr, nameLen });
Comment thread
claude[bot] marked this conversation as resolved.
if (!base || base.isUndefinedOrNull() || name.isNull())
return JSC::JSValue::encode(JSC::jsUndefined());

proto = protoObj->getPrototype(globalObject);
RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(completions));
JSC::JSObject* object = base.toObject(globalObject);
if (scope.exception()) [[unlikely]] {
scope.clearException();
return JSC::JSValue::encode(JSC::jsUndefined());
}

return 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(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