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
4 changes: 4 additions & 0 deletions src/jsc/JSValue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1071,6 +1071,10 @@ impl JSValue {
self.to_zig_string(&mut out, global)?;
Ok(out)
}
/// `JSValue::toThis` in strict mode (scope objects become `undefined`).
pub fn to_this_strict(self, global: &JSGlobalObject) -> JSValue {
crate::cpp::Bun__JSValue__toThisStrict(self, global)
}
}

// ──────────────────────────────────────────────────────────────────────────
Expand Down
6 changes: 3 additions & 3 deletions src/jsc/bindings/BunPlugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ static JSC::EncodedJSValue jsFunctionAppendOnLoadPluginBody(JSC::JSGlobalObject*
plugin.append(vm, filter->regExp(), func.getObject(), namespaceString);
callback(ctx, globalObject);

return JSValue::encode(callframe->thisValue());
return JSValue::encode(callframe->thisValue().toThis(globalObject, JSC::ECMAMode::strict()));
}

static EncodedJSValue jsFunctionAppendVirtualModulePluginBody(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callframe)
Expand Down Expand Up @@ -167,7 +167,7 @@ static EncodedJSValue jsFunctionAppendVirtualModulePluginBody(JSC::JSGlobalObjec
moduleLoader->removeEntry(idIdent);
}

return JSValue::encode(callframe->thisValue());
return JSValue::encode(callframe->thisValue().toThis(globalObject, JSC::ECMAMode::strict()));
}

static JSC::EncodedJSValue jsFunctionAppendOnResolvePluginBody(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callframe, BunPluginTarget target, BunPlugin::Base& plugin, void* ctx, OnAppendPluginCallback callback)
Expand Down Expand Up @@ -223,7 +223,7 @@ static JSC::EncodedJSValue jsFunctionAppendOnResolvePluginBody(JSC::JSGlobalObje
plugin.append(vm, filter->regExp(), uncheckedDowncast<JSObject>(func), namespaceString);
callback(ctx, globalObject);

return JSValue::encode(callframe->thisValue());
return JSValue::encode(callframe->thisValue().toThis(globalObject, JSC::ECMAMode::strict()));
}

static JSC::EncodedJSValue jsFunctionAppendOnResolvePluginGlobal(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callframe, BunPluginTarget target)
Expand Down
2 changes: 1 addition & 1 deletion src/jsc/bindings/JSMockFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1439,7 +1439,7 @@ BUN_DEFINE_HOST_FUNCTION(JSMock__jsSetSystemTime, (JSC::JSGlobalObject * globalO
// from this value instead of the activation-time clock.
Bun__FakeTimers__setSystemTime(ms);

return JSValue::encode(callframe->thisValue());
return JSValue::encode(callframe->thisValue().toThis(globalObject, ECMAMode::strict()));
}

BUN_DEFINE_HOST_FUNCTION(JSMock__jsRestoreAllMocks, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callframe))
Expand Down
5 changes: 5 additions & 0 deletions src/jsc/bindings/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6852,6 +6852,11 @@ CPP_DECL [[ZIG_EXPORT(nothrow)]] JSC::EncodedJSValue Bun__JSBoundFunction__bound
return JSC::JSValue::encode(boundFunction->boundThis());
}

CPP_DECL [[ZIG_EXPORT(nothrow)]] JSC::EncodedJSValue Bun__JSValue__toThisStrict(JSC::EncodedJSValue value, JSC::JSGlobalObject* globalObject)
{
return JSC::JSValue::encode(JSC::JSValue::decode(value).toThis(globalObject, JSC::ECMAMode::strict()));
}

CPP_DECL [[ZIG_EXPORT(check_slow)]] void Bun__JSValue__setPrototypeDirect(JSC::EncodedJSValue valueEncoded, JSC::EncodedJSValue prototypeEncoded, JSC::JSGlobalObject* globalObject)
{
auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
Expand Down
2 changes: 1 addition & 1 deletion src/jsc/bindings/webcore/JSDOMURL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -759,7 +759,7 @@ JSC_DEFINE_HOST_FUNCTION(jsDOMURLPrototypeFunction_inspectCustom, (JSGlobalObjec
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue thisValue = callFrame->thisValue();
JSValue thisValue = callFrame->thisValue().toThis(lexicalGlobalObject, JSC::ECMAMode::strict());
auto* thisObject = dynamicDowncast<JSDOMURL>(thisValue);
if (!thisObject) [[unlikely]]
return JSValue::encode(thisValue);
Expand Down
2 changes: 1 addition & 1 deletion src/jsc/bindings/webcore/JSURLSearchParams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ JSC_DEFINE_HOST_FUNCTION(jsURLSearchParamsPrototypeFunction_inspectCustom, (JSGl
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue thisValue = callFrame->thisValue();
JSValue thisValue = callFrame->thisValue().toThis(lexicalGlobalObject, JSC::ECMAMode::strict());
Comment thread
coderabbitai[bot] marked this conversation as resolved.
auto* thisObject = dynamicDowncast<JSURLSearchParams>(thisValue);
if (!thisObject) [[unlikely]]
return JSValue::encode(thisValue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ JSC_DEFINE_HOST_FUNCTION(jsByteLengthQueuingStrategyPrototype_inspectCustom, (JS
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue thisValue = callFrame->thisValue();
JSValue thisValue = callFrame->thisValue().toThis(lexicalGlobalObject, JSC::ECMAMode::strict());
auto* thisObject = dynamicDowncast<JSByteLengthQueuingStrategy>(thisValue);
if (!thisObject) [[unlikely]]
return JSValue::encode(thisValue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ JSC_DEFINE_HOST_FUNCTION(jsCountQueuingStrategyPrototype_inspectCustom, (JSGloba
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue thisValue = callFrame->thisValue();
JSValue thisValue = callFrame->thisValue().toThis(lexicalGlobalObject, JSC::ECMAMode::strict());
auto* thisObject = dynamicDowncast<JSCountQueuingStrategy>(thisValue);
if (!thisObject) [[unlikely]]
return JSValue::encode(thisValue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ JSC_DEFINE_HOST_FUNCTION(jsReadableByteStreamControllerPrototype_inspectCustom,
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue thisValue = callFrame->thisValue();
JSValue thisValue = callFrame->thisValue().toThis(lexicalGlobalObject, JSC::ECMAMode::strict());
auto* thisObject = dynamicDowncast<JSReadableByteStreamController>(thisValue);
if (!thisObject) [[unlikely]]
return JSValue::encode(thisValue);
Expand Down
2 changes: 1 addition & 1 deletion src/jsc/bindings/webcore/streams/JSReadableStream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ JSC_DEFINE_HOST_FUNCTION(jsReadableStreamPrototype_inspectCustom, (JSGlobalObjec
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue thisValue = callFrame->thisValue();
JSValue thisValue = callFrame->thisValue().toThis(lexicalGlobalObject, JSC::ECMAMode::strict());
auto* thisObject = dynamicDowncast<JSReadableStream>(thisValue);
if (!thisObject) [[unlikely]]
return JSValue::encode(thisValue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ JSC_DEFINE_HOST_FUNCTION(jsReadableStreamBYOBReaderPrototype_inspectCustom, (JSG
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue thisValue = callFrame->thisValue();
JSValue thisValue = callFrame->thisValue().toThis(lexicalGlobalObject, JSC::ECMAMode::strict());
auto* thisObject = dynamicDowncast<JSReadableStreamBYOBReader>(thisValue);
if (!thisObject) [[unlikely]]
return JSValue::encode(thisValue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ JSC_DEFINE_HOST_FUNCTION(jsReadableStreamBYOBRequestPrototype_inspectCustom, (JS
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue thisValue = callFrame->thisValue();
JSValue thisValue = callFrame->thisValue().toThis(lexicalGlobalObject, JSC::ECMAMode::strict());
auto* thisObject = dynamicDowncast<JSReadableStreamBYOBRequest>(thisValue);
if (!thisObject) [[unlikely]]
return JSValue::encode(thisValue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ JSC_DEFINE_HOST_FUNCTION(jsReadableStreamDefaultControllerPrototype_inspectCusto
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue thisValue = callFrame->thisValue();
JSValue thisValue = callFrame->thisValue().toThis(lexicalGlobalObject, JSC::ECMAMode::strict());
auto* thisObject = dynamicDowncast<JSReadableStreamDefaultController>(thisValue);
if (!thisObject) [[unlikely]]
return JSValue::encode(thisValue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ JSC_DEFINE_HOST_FUNCTION(jsReadableStreamDefaultReaderPrototype_inspectCustom, (
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue thisValue = callFrame->thisValue();
JSValue thisValue = callFrame->thisValue().toThis(lexicalGlobalObject, JSC::ECMAMode::strict());
auto* thisObject = dynamicDowncast<JSReadableStreamDefaultReader>(thisValue);
if (!thisObject) [[unlikely]]
return JSValue::encode(thisValue);
Expand Down
2 changes: 1 addition & 1 deletion src/jsc/bindings/webcore/streams/JSTransformStream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ JSC_DEFINE_HOST_FUNCTION(jsTransformStreamPrototype_inspectCustom, (JSGlobalObje
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue thisValue = callFrame->thisValue();
JSValue thisValue = callFrame->thisValue().toThis(lexicalGlobalObject, JSC::ECMAMode::strict());
auto* thisObject = dynamicDowncast<JSTransformStream>(thisValue);
if (!thisObject) [[unlikely]]
return JSValue::encode(thisValue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ JSC_DEFINE_HOST_FUNCTION(jsTransformStreamDefaultControllerPrototype_inspectCust
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue thisValue = callFrame->thisValue();
JSValue thisValue = callFrame->thisValue().toThis(lexicalGlobalObject, JSC::ECMAMode::strict());
auto* thisObject = dynamicDowncast<JSTransformStreamDefaultController>(thisValue);
if (!thisObject) [[unlikely]]
return JSValue::encode(thisValue);
Expand Down
2 changes: 1 addition & 1 deletion src/jsc/bindings/webcore/streams/JSWritableStream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ JSC_DEFINE_HOST_FUNCTION(jsWritableStreamPrototype_inspectCustom, (JSGlobalObjec
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue thisValue = callFrame->thisValue();
JSValue thisValue = callFrame->thisValue().toThis(lexicalGlobalObject, JSC::ECMAMode::strict());
auto* thisObject = dynamicDowncast<JSWritableStream>(thisValue);
if (!thisObject) [[unlikely]]
return JSValue::encode(thisValue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ JSC_DEFINE_HOST_FUNCTION(jsWritableStreamDefaultControllerPrototype_inspectCusto
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue thisValue = callFrame->thisValue();
JSValue thisValue = callFrame->thisValue().toThis(lexicalGlobalObject, JSC::ECMAMode::strict());
auto* thisObject = dynamicDowncast<JSWritableStreamDefaultController>(thisValue);
if (!thisObject) [[unlikely]]
return JSValue::encode(thisValue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ JSC_DEFINE_HOST_FUNCTION(jsWritableStreamDefaultWriterPrototype_inspectCustom, (
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue thisValue = callFrame->thisValue();
JSValue thisValue = callFrame->thisValue().toThis(lexicalGlobalObject, JSC::ECMAMode::strict());
auto* thisObject = dynamicDowncast<JSWritableStreamDefaultWriter>(thisValue);
if (!thisObject) [[unlikely]]
return JSValue::encode(thisValue);
Expand Down
2 changes: 1 addition & 1 deletion src/jsc/bindings/webcrypto/JSCryptoKey.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ JSC_DEFINE_HOST_FUNCTION(jsCryptoKeyPrototype_inspectCustom, (JSGlobalObject * l
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue thisValue = callFrame->thisValue();
JSValue thisValue = callFrame->thisValue().toThis(lexicalGlobalObject, JSC::ECMAMode::strict());
auto* thisObject = dynamicDowncast<JSCryptoKey>(thisValue);
if (!thisObject) [[unlikely]]
return JSValue::encode(thisValue);
Expand Down
14 changes: 7 additions & 7 deletions src/runtime/test_runner/timers/FakeTimers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ fn use_fake_timers(global: &JSGlobalObject, frame: &CallFrame) -> JsResult<JSVal
// This is used by testing-library/react to detect if jest.advanceTimersByTime should be called.
set_fake_timer_marker(global, true);

Ok(frame.this())
Ok(frame.this().to_this_strict(global))
}

#[bun_jsc::host_fn]
Expand All @@ -385,7 +385,7 @@ fn use_real_timers(global: &JSGlobalObject, frame: &CallFrame) -> JsResult<JSVal
// Remove the setTimeout.clock marker when switching back to real timers.
set_fake_timer_marker(global, false);

Ok(frame.this())
Ok(frame.this().to_this_strict(global))
}

#[bun_jsc::host_fn]
Expand All @@ -394,7 +394,7 @@ fn advance_timers_to_next_timer(global: &JSGlobalObject, frame: &CallFrame) -> J

FakeTimers::execute_next(global)?;

Ok(frame.this())
Ok(frame.this().to_this_strict(global))
}

#[bun_jsc::host_fn]
Expand Down Expand Up @@ -430,7 +430,7 @@ fn advance_timers_by_time(global: &JSGlobalObject, frame: &CallFrame) -> JsResul
CURRENT_TIME.set(global, &target, None);
advanced?;

Ok(frame.this())
Ok(frame.this().to_this_strict(global))
}

#[bun_jsc::host_fn]
Expand All @@ -439,7 +439,7 @@ fn run_only_pending_timers(global: &JSGlobalObject, frame: &CallFrame) -> JsResu

FakeTimers::execute_only_pending_timers(global)?;

Ok(frame.this())
Ok(frame.this().to_this_strict(global))
}

#[bun_jsc::host_fn]
Expand All @@ -448,7 +448,7 @@ fn run_all_timers(global: &JSGlobalObject, frame: &CallFrame) -> JsResult<JSValu

FakeTimers::execute_all_timers(global)?;

Ok(frame.this())
Ok(frame.this().to_this_strict(global))
}

#[bun_jsc::host_fn]
Expand All @@ -469,7 +469,7 @@ fn clear_all_timers(global: &JSGlobalObject, frame: &CallFrame) -> JsResult<JSVa
let cleared = unsafe { (*timer_all()).fake_timers.clear() };
cleared.release(global.bun_vm_ptr());

Ok(frame.this())
Ok(frame.this().to_this_strict(global))
}

#[bun_jsc::host_fn]
Expand Down
25 changes: 25 additions & 0 deletions test/js/bun/plugin/plugins.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,31 @@ it("import(...) without __esModule", async () => {
expect(mod).toBe("world");
});

it("builder methods return the builder only when called as methods", () => {
plugin({
name: "builder receiver",
setup(builder) {
const options = { filter: /.*/, namespace: "builder-receiver" };
const virtualModule = () => ({ exports: {}, loader: "object" }) as const;
expect([
builder.onResolve(options, () => undefined),
builder.onLoad(options, () => undefined),
builder.module("builder-receiver-virtual-module", virtualModule),
]).toEqual([builder, builder, builder]);

const { onResolve, onLoad, module: defineModule } = builder;
// Closed-over bindings make JSC pass the scope object as the raw receiver
// of these calls; it must not come back as the return value.
const bare = () => [
onResolve(options, () => undefined),
onLoad(options, () => undefined),
defineModule("builder-receiver-virtual-module-bare", virtualModule),
];
expect(bare()).toEqual([undefined, undefined, undefined]);
},
});
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.

it("recursion throws stack overflow", () => {
expect(() => {
require("recursion:recursion");
Expand Down
33 changes: 33 additions & 0 deletions test/js/bun/test/test-timers.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { expect, jest, setSystemTime, test } from "bun:test";
import { bunEnv, bunExe } from "harness";
import path from "node:path";

Expand Down Expand Up @@ -77,6 +78,38 @@ test("setSystemTime accepts pre-epoch and epoch times and resets with no argumen
}
});

test("chainable timer functions return their receiver only when called as a method", () => {
// Inside `bare`, every callee is a closed-over binding (setSystemTime is a module
// binding), so JSC hands the native function the scope object as its raw receiver.
// None of that may leak back out as a return value.
const {
useFakeTimers,
advanceTimersByTime,
advanceTimersToNextTimer,
runOnlyPendingTimers,
runAllTimers,
clearAllTimers,
useRealTimers,
} = jest;
const bare = () => [
useFakeTimers(),
advanceTimersByTime(0),
advanceTimersToNextTimer(),
runOnlyPendingTimers(),
runAllTimers(),
clearAllTimers(),
setSystemTime(),
useRealTimers(),
];
try {
expect(jest.useFakeTimers()).toBe(jest);
expect(jest.setSystemTime()).toBe(jest);
expect(bare()).toEqual(new Array(8).fill(undefined));
} finally {
expect(jest.useRealTimers()).toBe(jest);
}
});

test.each(["'x'", "Symbol()", "1n"])("useFakeTimers does not crash when globalThis.setTimeout is %s", async value => {
await using proc = Bun.spawn({
cmd: [
Expand Down
31 changes: 31 additions & 0 deletions test/js/bun/util/inspect.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,37 @@ it("Bun.inspect.custom exists", () => {
expect(Bun.inspect.custom).toBe(util.inspect.custom);
});

it("native inspect.custom functions do not return the scope object of a bare call", () => {
const classes = [
ByteLengthQueuingStrategy,
CountQueuingStrategy,
CryptoKey,
ReadableByteStreamController,
ReadableStream,
ReadableStreamBYOBReader,
ReadableStreamBYOBRequest,
ReadableStreamDefaultController,
ReadableStreamDefaultReader,
TransformStream,
TransformStreamDefaultController,
URL,
URLSearchParams,
WritableStream,
WritableStreamDefaultController,
WritableStreamDefaultWriter,
];
const results = classes.map(klass => {
const inspectCustom = klass.prototype[util.inspect.custom];
// Closing over the binding makes the bare call below pass a scope object as `this`.
const bare = () => inspectCustom(2, {});
return [klass.name, typeof inspectCustom, bare()];
});
expect(results).toEqual(classes.map(klass => [klass.name, "function", undefined]));

const inspectURL = URL.prototype[util.inspect.custom];
expect(inspectURL.call(new URL("http://example.com/"), 2, {})).toContain("http://example.com/");
});

describe("Functions with names", () => {
const closures = [
() => function f() {},
Expand Down
Loading