Repository navigation
[JSC] ShadowRealm: wrap the this value of a wrapped function call instead of dropping it #594
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,249 @@ | ||
| //@ requireOptions("--useShadowRealm=1") | ||
|
|
||
| // https://tc39.es/proposal-shadowrealm/#sec-ordinary-wrapped-function-call | ||
| // A wrapped function passes |this| through GetWrappedValue, like an argument: a primitive crosses as is, a callable | ||
| // is wrapped for the target realm, and any other object throws a TypeError from the caller realm before the target runs. | ||
|
|
||
| function shouldBe(actual, expected, message) { | ||
| if (actual !== expected) | ||
| throw new Error(`${message ? message + ": " : ""}expected ${String(expected)} but got ${String(actual)}`); | ||
| } | ||
|
|
||
| function shouldThrowTypeErrorFromThisRealm(func, message) { | ||
| let error; | ||
| try { | ||
| func(); | ||
| } catch (e) { | ||
| error = e; | ||
| } | ||
| if (!(error instanceof TypeError)) | ||
| throw new Error(`${message}: expected a TypeError but got ${String(error)}`); | ||
| shouldBe($.globalObjectFor(error), globalThis, `${message}: realm of the TypeError`); | ||
| } | ||
|
|
||
| // With the JIT on, only the first call of a plain function target takes remoteFunctionCallForJSFunction. After that the | ||
| // remoteFunctionCallGenerator thunk wraps the arguments and |this| itself, so a few rounds cover both. The last block | ||
| // uses testLoopCount to let the targets tier up as well. | ||
| const rounds = 3; | ||
|
|
||
| const realm = new ShadowRealm(); | ||
| realm.evaluate(`globalThis.calls = 0`); | ||
| const callsInRealm = realm.evaluate(`() => globalThis.calls`); | ||
|
|
||
| const describeThisSource = `(function describeThis() { | ||
| "use strict"; | ||
| globalThis.calls++; | ||
| if (this === undefined || this === null) | ||
| return String(this); | ||
| if (typeof this === "function") | ||
| return "function:" + (Object.getPrototypeOf(this) === Function.prototype) + ":" + this(); | ||
| if (typeof this === "symbol") | ||
| return "symbol:" + this.description; | ||
| return typeof this + ":" + String(this); | ||
| })`; | ||
|
|
||
| // A JSFunction target takes remoteFunctionCallForJSFunction or the remoteFunctionCallGenerator thunk. The others take | ||
| // remoteFunctionCallGeneric. | ||
| const targets = [ | ||
| ["function", realm.evaluate(describeThisSource), null], | ||
| ["proxy", realm.evaluate(`new Proxy(${describeThisSource}, {})`), null], | ||
| ["proxy with an apply trap", realm.evaluate(`new Proxy(${describeThisSource}, { apply(target, thisValue, args) { return Reflect.apply(target, thisValue, args); } })`), null], | ||
| ["bound function", realm.evaluate(`${describeThisSource}.bind("bound")`), "string:bound"], | ||
| ]; | ||
|
|
||
| function outer() { return "outer"; } | ||
|
|
||
| for (let i = 0; i < rounds; ++i) { | ||
| for (const [kind, wrapped, fixedThis] of targets) { | ||
| const expect = (thisValue, expected) => { | ||
| const before = callsInRealm(); | ||
| shouldBe(wrapped.call(thisValue), fixedThis ?? expected, `${kind} target, call()`); | ||
| shouldBe(Reflect.apply(wrapped, thisValue, []), fixedThis ?? expected, `${kind} target, Reflect.apply`); | ||
| shouldBe(wrapped.bind(thisValue)(), fixedThis ?? expected, `${kind} target, bind()`); | ||
| shouldBe(callsInRealm(), before + 3, `${kind} target, calls`); | ||
| }; | ||
|
|
||
| expect(undefined, "undefined"); | ||
| expect(null, "null"); | ||
| expect(5, "number:5"); | ||
| expect(-0, "number:0"); | ||
| expect("s", "string:s"); | ||
| expect(true, "boolean:true"); | ||
| expect(10n, "bigint:10"); | ||
| expect(Symbol("d"), "symbol:d"); | ||
| expect(Symbol.iterator, "symbol:Symbol.iterator"); | ||
| expect(outer, "function:true:outer"); | ||
| expect(() => "arrow", "function:true:arrow"); | ||
|
|
||
| for (const thisValue of [{ }, [], new Proxy({}, {}), new String("boxed"), Object(Symbol("boxed")), globalThis, Object.prototype, new Date(0), /re/, new Error("e")]) { | ||
| const before = callsInRealm(); | ||
| shouldThrowTypeErrorFromThisRealm(() => wrapped.call(thisValue), `${kind} target, call() with a non-callable object`); | ||
| shouldThrowTypeErrorFromThisRealm(() => Reflect.apply(wrapped, thisValue, []), `${kind} target, Reflect.apply with a non-callable object`); | ||
| shouldThrowTypeErrorFromThisRealm(() => wrapped.bind(thisValue)(), `${kind} target, bind() with a non-callable object`); | ||
| shouldThrowTypeErrorFromThisRealm(() => ({ method: wrapped }).method(), `${kind} target, called as a method`); | ||
| shouldBe(callsInRealm(), before, `${kind} target must not run when |this| cannot be wrapped`); | ||
| } | ||
|
|
||
| // A revoked Proxy is not callable any more... but IsCallable only looks at [[Call]], which a revoked callable Proxy keeps. | ||
| { | ||
| const { proxy, revoke } = Proxy.revocable(function () { return "revoked"; }, {}); | ||
| revoke(); | ||
| shouldThrowTypeErrorFromThisRealm(() => wrapped.call(proxy), `${kind} target, revoked callable Proxy as |this|`); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // The wrapped function itself as |this|: JSRemoteFunction::tryCreate unwraps it and wraps its target again, for the | ||
| // shadow realm this time. So the realm sees a callable of its own, and calling that runs describeThis once more with | ||
| // |this| = undefined. | ||
| { | ||
| const wrapped = targets[0][1]; | ||
| for (let i = 0; i < rounds; ++i) { | ||
| const before = callsInRealm(); | ||
| shouldBe(wrapped.call(wrapped), "function:true:undefined"); | ||
| shouldBe(callsInRealm(), before + 2); | ||
| } | ||
| } | ||
|
|
||
| // A sloppy-mode target sees its own global object for undefined and null, and a wrapper object of its own realm for | ||
| // any other primitive. | ||
| { | ||
| const sloppy = realm.evaluate(`(function () { | ||
| if (this === globalThis) | ||
| return "globalThis"; | ||
| if (typeof this === "function") | ||
| return "function:" + this(); | ||
| return typeof this + ":" + (this instanceof Number || this instanceof String || this instanceof Symbol) + ":" + this.toString(); | ||
| })`); | ||
| for (let i = 0; i < rounds; ++i) { | ||
| shouldBe(sloppy(), "globalThis"); | ||
| shouldBe(sloppy.call(undefined), "globalThis"); | ||
| shouldBe(sloppy.call(null), "globalThis"); | ||
| shouldBe(sloppy.call(3), "object:true:3"); | ||
| shouldBe(sloppy.call("s"), "object:true:s"); | ||
| shouldBe(sloppy.call(Symbol("q")), "object:true:Symbol(q)"); | ||
| shouldBe(sloppy.call(outer), "function:outer"); | ||
| shouldThrowTypeErrorFromThisRealm(() => sloppy.call({}), "sloppy target, call() with an object"); | ||
| } | ||
| } | ||
|
|
||
| // An arrow function target ignores |this|, but the wrapper still cannot let an object through. | ||
| { | ||
| const arrow = realm.evaluate(`() => "arrow"`); | ||
| for (let i = 0; i < rounds; ++i) { | ||
| shouldBe(arrow.call(1), "arrow"); | ||
| shouldBe(arrow.call(outer), "arrow"); | ||
| shouldThrowTypeErrorFromThisRealm(() => arrow.call({}), "arrow target, call() with an object"); | ||
| } | ||
| } | ||
|
|
||
| // The other direction: the shadow realm calls a wrapped function of this realm. | ||
| { | ||
| let calls = 0; | ||
| function describeThis() { | ||
| "use strict"; | ||
| calls++; | ||
| if (this === undefined || this === null) | ||
| return String(this); | ||
| if (typeof this === "function") | ||
| return "function:" + (Object.getPrototypeOf(this) === Function.prototype) + ":" + this(); | ||
| return typeof this + ":" + String(this); | ||
| } | ||
| const callWith = realm.evaluate(`(function callWith(f, kind) { | ||
| switch (kind) { | ||
| case "none": return f(); | ||
| case "number": return f.call(42); | ||
| case "string": return Reflect.apply(f, "s", []); | ||
| case "callable": return f.call(() => "inner"); | ||
| case "bound callable": return f.bind(function () { return "inner bound"; })(); | ||
| case "object": | ||
| case "array": | ||
| case "global": | ||
| case "method": { | ||
| try { | ||
| if (kind === "object") | ||
| f.call({}); | ||
| else if (kind === "array") | ||
| f.apply([], []); | ||
| else if (kind === "global") | ||
| f.call(globalThis); | ||
| else | ||
| ({ f }).f(); | ||
| } catch (e) { | ||
| return "threw " + e.constructor.name + " of the shadow realm: " + (e instanceof TypeError); | ||
| } | ||
| return "did not throw"; | ||
| } | ||
| } | ||
| })`); | ||
| for (let i = 0; i < rounds; ++i) { | ||
| shouldBe(callWith(describeThis, "none"), "undefined"); | ||
| shouldBe(callWith(describeThis, "number"), "number:42"); | ||
| shouldBe(callWith(describeThis, "string"), "string:s"); | ||
| shouldBe(callWith(describeThis, "callable"), "function:true:inner"); | ||
| shouldBe(callWith(describeThis, "bound callable"), "function:true:inner bound"); | ||
| const before = calls; | ||
| shouldBe(callWith(describeThis, "object"), "threw TypeError of the shadow realm: true"); | ||
| shouldBe(callWith(describeThis, "array"), "threw TypeError of the shadow realm: true"); | ||
| shouldBe(callWith(describeThis, "global"), "threw TypeError of the shadow realm: true"); | ||
| shouldBe(callWith(describeThis, "method"), "threw TypeError of the shadow realm: true"); | ||
| shouldBe(calls, before, "the target must not run when |this| cannot be wrapped"); | ||
| } | ||
| } | ||
|
|
||
| // Arguments land in the right slots for every argument count, with |this| next to them, on every tier. | ||
| { | ||
| const collect = realm.evaluate(`"use strict"; (function (...args) { return String(this) + "|" + args.join(",") })`); | ||
| const collectViaProxy = realm.evaluate(`"use strict"; new Proxy(function (...args) { return String(this) + "|" + args.join(",") }, {})`); | ||
| const hundred = new Array(100).fill(9); | ||
| const hundredJoined = hundred.join(","); | ||
| function object() { return "O"; } | ||
| for (let i = 0; i < testLoopCount; ++i) { | ||
| for (const f of [collect, collectViaProxy]) { | ||
| shouldBe(f(), "undefined|"); | ||
| shouldBe(f.call("T"), "T|"); | ||
| shouldBe(f.call("T", 1), "T|1"); | ||
| shouldBe(f.call(2, 1, 2), "2|1,2"); | ||
| shouldBe(f.call(true, 1, 2, 3), "true|1,2,3"); | ||
| shouldBe(f.call("T", 1, 2, 3, 4, 5, 6, 7), "T|1,2,3,4,5,6,7"); | ||
| shouldBe(f(1, 2, 3, 4, 5, 6, 7, 8), "undefined|1,2,3,4,5,6,7,8"); | ||
| if (!(i % 64)) { | ||
| shouldBe(f.apply("T", hundred), "T|" + hundredJoined); | ||
| shouldBe(f.call(object, 1, object).replace(/function [^|,]*/g, "F"), "F|1,F"); | ||
| let error = null; | ||
| try { | ||
| f.call({}, 1, 2); | ||
| } catch (e) { | ||
| error = e; | ||
| } | ||
| shouldBe(error instanceof TypeError, true, "object |this| on a warm target"); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Wrapping order is observable through the "length" and "name" reads of WrappedFunctionCreate: each argument in | ||
| // order, then |this|. | ||
| { | ||
| const log = []; | ||
| function observed(tag) { | ||
| const f = function () { return tag; }; | ||
| Object.defineProperty(f, "name", { get() { log.push(tag + ".name"); return tag; } }); | ||
| Object.defineProperty(f, "length", { get() { log.push(tag + ".length"); return 0; } }); | ||
| return f; | ||
| } | ||
| const takesTwo = realm.evaluate(`(function (a, b) { "use strict"; return [this(), a(), b()].join() })`); | ||
| const takesTwoViaProxy = realm.evaluate(`new Proxy(function (a, b) { "use strict"; return [this(), a(), b()].join() }, {})`); | ||
| for (let i = 0; i < rounds; ++i) { | ||
| for (const f of [takesTwo, takesTwoViaProxy]) { | ||
| log.length = 0; | ||
| shouldBe(f.call(observed("this"), observed("a"), observed("b")), "this,a,b"); | ||
| shouldBe(log.join(), "a.length,a.name,b.length,b.name,this.length,this.name"); | ||
|
|
||
| // When an argument cannot be wrapped, |this| is not looked at and the target does not run. | ||
| log.length = 0; | ||
| shouldThrowTypeErrorFromThisRealm(() => f.call(observed("this"), observed("a"), {}), "non-callable last argument"); | ||
| shouldBe(log.join(), "a.length,a.name"); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1504,10 +1504,7 @@ MacroAssemblerCodeRef<JITThunkPtrTag> remoteFunctionCallGenerator(VM& vm) | |
| haveStackSpace.link(&jit); | ||
| jit.move(GPRInfo::regT2, CCallHelpers::stackPointerRegister); | ||
|
|
||
| // Set `this` to undefined | ||
| // NOTE: needs concensus in TC39 (https://github.com/tc39/proposal-shadowrealm/issues/328) | ||
| jit.store32(GPRInfo::regT1, CCallHelpers::calleeFrameLowWordSlot(CallFrameSlot::argumentCountIncludingThis)); | ||
| jit.storeTrustedValue(jsUndefined(), CCallHelpers::calleeArgumentSlot(0)); | ||
|
|
||
| constexpr GPRReg valueGPR = GPRInfo::regT4; | ||
|
|
||
|
|
@@ -1529,12 +1526,28 @@ MacroAssemblerCodeRef<JITThunkPtrTag> remoteFunctionCallGenerator(VM& vm) | |
|
|
||
| CCallHelpers::JumpList exceptionChecks; | ||
|
|
||
| // |this| can be a JSScope: that is what bytecode passes for an identifier call like `f()`, and it means undefined. | ||
| // This is JSValue::toThis() with ECMAMode::strict(), as in remoteFunctionCallForJSFunction. | ||
| { | ||
| jit.loadValue(CCallHelpers::addressFor(virtualRegisterForArgumentIncludingThis(0)), valueGPR); | ||
| CCallHelpers::JumpList thisIsNotScope; | ||
| thisIsNotScope.append(jit.branchIfNotCell(valueGPR, DoNotHaveTagRegisters)); | ||
| thisIsNotScope.append(jit.branchIfNotType(valueGPR, JSTypeRange { JSType(FirstScopeType), JSType(LastScopeType) })); | ||
| jit.storeTrustedValue(jsUndefined(), CCallHelpers::addressFor(virtualRegisterForArgumentIncludingThis(0))); | ||
|
Comment on lines
+1534
to
+1536
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -euo pipefail
# 1) Resolve the scope type range and neighboring JSTypes.
fd -t f 'JSType.h$' Source/JavaScriptCore --exec rg -n 'FirstScopeType|LastScopeType|GlobalObjectType|GlobalProxyType|WithScopeType|GlobalLexicalEnvironmentType|LexicalEnvironmentType|ModuleEnvironmentType|StrictEvalActivationType'
# 2) Every toThis declaration and definition, to compare with the type-range approximation.
rg -nP -C3 '\btoThis\s*\(\s*JSCell\s*\*' Source/JavaScriptCore
rg -nP -C1 'static JSValue toThis\s*\(' Source/JavaScriptCoreRepository: oven-sh/WebKit Length of output: 536 🏁 Script executed: #!/bin/bash
set -euo pipefail
jstype="$(fd -t f 'JSType.h$' Source/JavaScriptCore | head -n 1)"
printf '%s\n' "JSType file: $jstype"
rg -n -C4 'FirstScopeType|LastScopeType|GlobalObjectType|GlobalProxyType|WithScopeType|GlobalLexicalEnvironmentType|LexicalEnvironmentType|ModuleEnvironmentType|StrictEvalActivationType' "$jstype"
printf '%s\n' 'toThis declarations and definitions:'
rg -n -C4 '\btoThis\s*\(' Source/JavaScriptCore --glob '*.{h,cpp,mm}' | head -n 300Repository: oven-sh/WebKit Length of output: 536 🤖 get_repo_knowledge executed:
Length of output: 6805 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' 'Changed thunk context:'
sed -n '1500,1565p' Source/JavaScriptCore/jit/ThunkGenerators.cpp
printf '%s\n' 'Relevant type declarations:'
rg -n -C5 'FirstScopeType|LastScopeType|GlobalObjectType|GlobalProxyType|WithScopeType|GlobalLexicalEnvironmentType|LexicalEnvironmentType|ModuleEnvironmentType|StrictEvalActivationType' Source/JavaScriptCore --glob 'JSType.h' --glob '*.h' --glob '*.cpp' | head -n 240Repository: oven-sh/WebKit Length of output: 536 🏁 Script executed: #!/bin/bash
set -euo pipefail
set +e
jstype="$(fd -t f 'JSType.h$' Source/JavaScriptCore | head -n 1)"
status=$?
set -e
printf 'fd status: %s\n' "$status"
printf 'JSType file: %s\n' "$jstype"
if [ -n "$jstype" ]; then
rg -n -C4 'FirstScopeType|LastScopeType|GlobalObjectType|GlobalProxyType|WithScopeType|GlobalLexicalEnvironmentType|LexicalEnvironmentType|ModuleEnvironmentType|StrictEvalActivationType' "$jstype"
fi
printf '%s\n' 'Relevant toThis declarations and definitions:'
rg -n -C4 '\btoThis\s*\(' Source/JavaScriptCore --glob '*.{h,cpp,mm}' | head -n 300Repository: oven-sh/WebKit Length of output: 536 🌐 Web query:
💡 Result: In WebKit’s JavaScriptCore (JSC), JSType.h defines the JSType enum, which enumerates the various types of heap-allocated cells within the engine [1][2]. These types are critical for runtime type checking, garbage collection, and JIT optimization [3][4]. The terms you mentioned relate to defining ranges and specific classifications of these objects: JSType Ranges JSType.h uses specific constants to define inclusive ranges of JSType values, facilitating efficient type checks [1][4]: FirstScopeType and LastScopeType: These define the range of JSScope-related object types [1][2]. In modern WebKit, FirstScopeType is GlobalObjectType and LastScopeType is WithScopeType [1][2]. This range encompasses various environment records and scope objects (e.g., GlobalLexicalEnvironmentType, LexicalEnvironmentType, ModuleEnvironmentType, and StrictEvalActivationType) [2]. GlobalObjectType and GlobalProxyType These are specific JSType enum values [2]: GlobalObjectType: Represents the global object (e.g., window in browsers) [2]. GlobalProxyType: Represents the global proxy, which acts as a wrapper for the global object to handle cross-origin security and other proxying requirements. WithScopeType This is a JSType value representing the JSWithScope object, which is used to implement the JavaScript with statement [2]. toThis toThis is a key method (often implemented within the JSScope class hierarchy) used in the execution of JavaScript code [5]. It implements the ECMAScript requirement to convert a value to the appropriate this binding [5]. For example, within the DFG (Data Flow Graph) JIT compiler, the engine checks if a specific value or structure is a JSScope [5]. If the type falls within the [FirstScopeType, LastScopeType] range, the engine recognizes that it may override the default toThis behavior [5]. In non-strict mode, if the scope is a global object, toThis often returns the global this, whereas in strict mode, it may return undefined, depending on the specific scope type and context [5]. In summary, these types and ranges allow the engine to quickly identify and specialize operations for different categories of objects, such as environment records versus standard objects, and to correctly handle the semantics of this bindings during JIT-compiled code execution [4][5]. Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' 'Candidate files:'
fd -t f 'JSType.h|ThunkGenerators.cpp|JSGlobalObject.cpp|JSGlobalProxy.cpp|JSScope.cpp|JSWithScope.cpp|JSObject.cpp' Source/JavaScriptCore
printf '%s\n' 'Scope range and toThis symbols:'
rg -n -C6 'FirstScopeType|LastScopeType|GlobalObjectType|GlobalProxyType|WithScopeType|toThis' Source/JavaScriptCore/jit/ThunkGenerators.cpp Source/JavaScriptCore --glob '*JSType.h' --glob '*JSGlobalObject*' --glob '*JSGlobalProxy*' --glob '*JSScope*' --glob '*JSWithScope*' --glob '*JSObject*' | head -n 500Repository: oven-sh/WebKit Length of output: 536 🏁 Script executed: #!/bin/bash
set -euo pipefail
base='https://raw.githubusercontent.com/WebKit/WebKit/main/Source/JavaScriptCore'
for path in runtime/JSType.h runtime/JSObject.cpp runtime/JSGlobalObject.cpp runtime/JSGlobalProxy.cpp runtime/JSWithScope.cpp runtime/JSScope.cpp jit/ThunkGenerators.cpp; do
printf '\n===== %s =====\n' "$path"
curl -fsSL "$base/$path" | rg -n -C5 'FirstScopeType|LastScopeType|GlobalObjectType|GlobalProxyType|WithScopeType|toThis|remoteFunctionCallForJSFunction|storeTrustedValue' | head -n 180 || true
doneRepository: oven-sh/WebKit Length of output: 536 🏁 Script executed: #!/bin/bash
set -euo pipefail
url='https://raw.githubusercontent.com/WebKit/WebKit/main/Source/JavaScriptCore/runtime/JSType.h'
curl -fsSL "$url" | grep -n -E -A4 -B4 'FirstScopeType|LastScopeType|GlobalObjectType|GlobalProxyType|WithScopeType'Repository: oven-sh/WebKit Length of output: 536 🌐 Web query:
💡 Result: In the JavaScriptCore (JSC) engine within WebKit, FirstScopeType and LastScopeType are constants used to define the boundaries of JSScope-derived types in the JSType enum [1]. JSScope acts as the base class for scope objects, and its hierarchy includes types such as JSWithScope, JSLexicalEnvironment, and JSGlobalObject [1]. The JSType enumeration explicitly groups these under the JSScope category, where FirstScopeType marks the beginning of the scope-related types (GlobalObjectType) and LastScopeType marks the end (WithScopeType) [1]. Regarding the toThis method: JSWithScope::toThis and JSGlobalObject::toThis are virtual member functions defined on these respective classes, which inherit from JSScope [1]. In JSC, the toThis method is used to determine the value of this in a given scope [2][3]. - JSGlobalObject::toThis: Typically returns the global object itself (or the global proxy), representing the global scope where this refers to the global object in non-strict mode. - JSWithScope::toThis: Because with statements alter the scope chain by introducing an object into the scope, this method is used to ensure the this context is resolved correctly according to ECMAScript specifications when executing code within a with block. These methods are part of the internal machinery JavaScriptCore uses to handle scope resolution and context binding during bytecode execution [2][3]. Citations:
🌐 Web query:
💡 Result: In the WebKit JavaScriptCore (JSC) engine, "toThis" refers to an internal operation (often implemented as Citations:
🌐 Web query:
💡 Result: JSWithScope is a class within WebKit's JavaScriptCore (JSC) engine that represents a scope created by the JavaScript with statement [1][2]. The method JSWithScope::toThis (and the broader ToThis operation in JSC) has historically been used to resolve the this value within the context of a with statement [3][4]. In JavaScript, when a function is called, the value of this is determined by specific rules; the with statement introduces an object into the scope chain, which complicates this resolution because that object can effectively become the this value in certain scenarios [3][4]. Key technical context: 1. Role in with statements: JSWithScope is used to encapsulate an object added to the scope chain via the with statement [5][6]. 2. Evolution of ToThis: Historically, JSC used a mechanism where JSScope instances (including JSWithScope) could be passed as the this value during function calls [3][7]. Recent architectural changes in WebKit have moved away from this practice due to security vulnerabilities and ECMA-262 specification compliance issues [3][4][7]. 3. Security and Spec Compliance: Using scope objects as the this value was essentially a hack to make the with statement behave as expected [3][4]. Modern WebKit implementations have replaced this with more explicit JSType checks performed specifically within the with statement, leveraging features like TaintedByWithScopeLexicallyScopedFeature [3][4]. This change aims to simplify the ToThis operation and ensure that it is primarily emitted only in sloppy mode [4][7]. For developers working within the WebKit codebase, these changes mean that the responsibility for ensuring a correct this value (e.g., using JSGlobalProxy instead of a raw global object) has been shifted to helper functions like wrapGlobalObject during function calls and evaluations [3][4]. Citations:
🌐 Web query:
💡 Result: In the JavaScriptCore engine (used by WebKit), FirstScopeType and LastScopeType are constants defined in JSType.h used to categorize types of scopes [1]. FirstScopeType is defined as GlobalObjectType, representing the beginning of the range for JSScope types [1]. LastScopeType is defined as WithScopeType, marking the end of that range [1]. These constants define the bounds for scope-related object types within the engine's type system, where JSWithScope is a specific type of scope object that relates to the 'with' statement in JavaScript [1]. Citations: 🌐 Web query:
💡 Result: WebKit Pull Request 35527, titled "[JSC] Don't use JSScope instances as Citations:
🌐 Web query:
💡 Result: WebKit Pull Request 35527, titled "[JSC] Don't use JSScope instances as Citations:
🌐 Web query:
💡 Result: In the WebKit JavaScriptCore engine, JSType.h defines the JSType enumeration, which classifies the various object types within the heap [1]. Within this header file, GlobalProxyType and FirstScopeType are specific constants used to categorize and manage these types [1]. GlobalProxyType is a member of the JSType enum, representing the JavaScript global proxy object [1]. FirstScopeType is a static constexpr uint32_t constant defined in JSType.h, serving as a boundary marker for scope-related types within the engine [1]. Specifically, FirstScopeType is set to the value of GlobalObjectType, establishing the start of a range of JSTypes that represent scopes [1]. This file also defines a corresponding LastScopeType, typically set to WithScopeType, to define the inclusive range of scope types [1]. These constants are essential for the engine's internal type checking, casting, and JIT (Just-In-Time) compilation processes, allowing it to efficiently identify and handle different kinds of scope objects [1][2]. Citations:
🌐 Web query:
💡 Result: In the WebKit JavaScriptCore (JSC) engine, remoteFunctionCallForJSFunction is a C++ host function used to facilitate the invocation of JSRemoteFunction objects [1][2][3]. Specifically, it is utilized as part of the infrastructure to handle function calls that cross execution contexts, such as those occurring within Shadow Realms or remote automation protocols [4][5]. The technical terms you mentioned are associated with internal WebKit development and maintenance: 1. remoteFunctionCallForJSFunction: This is a function defined in JavaScriptCore (typically found in Source/JavaScriptCore/runtime/VM.cpp) that acts as the entry point for executing remote functions [1][2][3]. It is used when a JSRemoteFunction is invoked, ensuring the environment is set up correctly for the call [4]. Development work, such as WebKit Bug 242125, has focused on ensuring this function properly handles memory management (specifically placating MarkedArgumentBuffer overflow checks) to prevent crashes during argument wrapping or execution [6][4]. 2. thisIsNotScope: While "scope" is a common concept in WebKit—frequently appearing in CSS selectors (e.g.,:scope, Citations:
🌐 Web query:
💡 Result: The terms identified in your query are associated with distinct, internal mechanisms within the WebKit JavaScriptCore (JSC) engine, rather than a single unified function or API. remoteFunctionCallForJSFunction: This refers to internal mechanisms related to JSRemoteFunction [1][2]. Specifically, it relates to the infrastructure for invoking remote functions in JavaScript, including handling argument wrapping and the materialization of JIT code for the target function [1][2]. Operations such as operationMaterializeRemoteFunctionTargetCode are used in this context to ensure JIT code is available when calling remote functions, as garbage collection (GC) may jettison previously materialized code [1][2]. FirstScopeType: This is a constant defined in the WebKit source code (specifically in Source/JavaScriptCore/runtime/JSType.h) [3]. It is used for identifying the range of JSTypes that represent scopes in the JavaScript environment [3][4]. It is defined as static constexpr uint32_t FirstScopeType = GlobalObjectType; and marks the start of the JSScope type hierarchy [3]. storeTrustedValue: This is an internal JIT (Just-In-Time) compiler utility function used in WebKit's AssemblyHelpers [5]. It is responsible for storing a trusted value (often involving cell tags or pointers) into memory during JIT code generation [5]. It is one of several helper functions, such as branchIfCell and branchIfNotCell, used to manage object tags and type checking efficiently within generated machine code [5]. These components are part of the complex internal implementation of the JavaScriptCore engine and are typically found in the Source/JavaScriptCore directory of the WebKit repository [3][6][5]. Citations:
🌐 Web query:
💡 Result: In JavaScriptCore (JSC), the identifiers JSWithScope::toThis, JSGlobalObject::toThis, and JSGlobalProxy::toThis refer to mechanisms used to handle the 'this' value in JavaScript function calls [1][2][3]. The concept of 'toThis' is fundamental to implementing the JavaScript specification's 'GetThisValue' and related operations, which map the value provided as 'this' to the appropriate object depending on the execution mode (sloppy vs. strict) [4]. Key Context and Recent Architectural Changes: Historically, JSC utilized various 'toThis' methods across different classes (like JSWithScope and JSGlobalObject) to normalize the 'this' value, particularly to support the 'with' statement and global scope behavior [1][2]. However, recent major architectural changes in JavaScriptCore have significantly simplified this area [1][2][3]: 1. Deprecation of JSScope-based 'this' values: It was identified as a security and specification compliance risk to allow JSScope instances (such as those created by 'with' statements) to act as the 'this' value during function calls [1][2][3]. 2. Refactoring of 'toThis': The 'toThis' functionality has been largely refactored to focus on sloppy mode normalization [1][2][3]. In strict mode, the 'this' value is generally passed as-is, adhering more closely to the ECMAScript specification [4]. 3. Reliance on JSGlobalProxy: To ensure security and correctness—especially regarding cross-origin access—JSC now enforces that function calls in the global context use a JSGlobalProxy rather than the raw global object [5][1][2][3]. The helper wrapGlobalObject is used to ensure this distinction [1][2]. 4. Elimination of Legacy Methods: Many of the specific 'toThis' methods on classes like JSScope and JSGlobalObject have been removed or replaced in favor of cleaner, more unified logic [1][2][3]. In modern JavaScriptCore, 'toThis' logic is typically handled by JSC::JSValue::toThis, which takes an ECMAMode parameter to differentiate between strict and sloppy mode behavior [4]. This method handles the conversion of primitives (like numbers or booleans) to their object equivalents in sloppy mode and ensures the correct global object is used for undefined or null inputs [6][4]. Citations:
Keep the thunk consistent with
🤖 Prompt for AI Agents |
||
| thisIsNotScope.link(&jit); | ||
| } | ||
|
|
||
| // Argument processing loop: | ||
| // For each argument (order should not be observable): | ||
| // For each argument in order, and then for |this| (https://tc39.es/proposal-shadowrealm/#sec-ordinary-wrapped-function-call): | ||
| // if the value is a Primitive, copy it into the new call frame arguments, otherwise | ||
| // perform wrapping logic. If the wrapping logic results in a new JSRemoteFunction, | ||
| // copy it into the new call frame's arguments, otherwise it must have thrown a TypeError. | ||
| CCallHelpers::Jump done = jit.branchSub32(CCallHelpers::Zero, CCallHelpers::TrustedImm32(1), GPRInfo::regT1); | ||
| // The order is observable: wrapping a callable reads its "length" and "name". | ||
| // regT1 is the index of the slot being processed, where slot 0 is |this|. It goes 1, 2, ..., argumentCount, 0. | ||
| jit.sub32(CCallHelpers::TrustedImm32(1), GPRInfo::regT1); | ||
| CCallHelpers::Jump hasNoArguments = jit.branchTest32(CCallHelpers::Zero, GPRInfo::regT1); | ||
| jit.move(CCallHelpers::TrustedImm32(1), GPRInfo::regT1); | ||
| hasNoArguments.link(&jit); | ||
| { | ||
| CCallHelpers::Label loop = jit.label(); | ||
| jit.loadValue(CCallHelpers::addressFor(virtualRegisterForArgumentIncludingThis(0)).indexedBy(GPRInfo::regT1, CCallHelpers::TimesEight), valueGPR); | ||
|
|
@@ -1559,7 +1572,14 @@ MacroAssemblerCodeRef<JITThunkPtrTag> remoteFunctionCallGenerator(VM& vm) | |
|
|
||
| valueIsPrimitive.link(&jit); | ||
| jit.storeValue(valueGPR, CCallHelpers::calleeArgumentSlot(0).indexedBy(GPRInfo::regT1, CCallHelpers::TimesEight)); | ||
| jit.branchSub32(CCallHelpers::NonZero, CCallHelpers::TrustedImm32(1), GPRInfo::regT1).linkTo(loop, &jit); | ||
|
|
||
| // Slot 0, |this|, is the last one. | ||
| CCallHelpers::Jump done = jit.branchTest32(CCallHelpers::Zero, GPRInfo::regT1); | ||
| jit.add32(CCallHelpers::TrustedImm32(1), GPRInfo::regT1); | ||
| jit.load32(CCallHelpers::lowWordFor(CallFrameSlot::argumentCountIncludingThis), GPRInfo::regT2); | ||
| jit.branch32(CCallHelpers::NotEqual, GPRInfo::regT1, GPRInfo::regT2).linkTo(loop, &jit); | ||
| jit.move(CCallHelpers::TrustedImm32(0), GPRInfo::regT1); | ||
| jit.jump().linkTo(loop, &jit); | ||
|
|
||
| done.link(&jit); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: oven-sh/WebKit
Length of output: 536
🏁 Script executed:
Repository: oven-sh/WebKit
Length of output: 536
🤖 get_repo_knowledge executed:
get_repo_knowledge oven-sh/WebKit /tmp/coderabbit-repo-knowledge/oven-sh-webkit-86515bff/learnings /tmp/coderabbit-repo-knowledge/oven-sh-webkit-86515bff/conventionsLength of output: 7434
🌐 Web query:
site:github.com/WebKit/WebKit "globalObjectFor" "useDollarVM"💡 Result:
The terms globalObjectFor and useDollarVM refer to internal implementation details within the JavaScriptCore (JSC) engine, which is the JavaScript engine used by WebKit (the engine behind Safari and other browsers). These are not public-facing JavaScript APIs but rather configuration and runtime settings used for engine development and testing [1][2]. 1. useDollarVM: This is a configuration option in JavaScriptCore, often accessed via the Options system, which, when enabled, exposes a special $vm object to the JavaScript environment [1]. This object provides internal testing and debugging utilities to the JavaScript level [1]. 2. globalObjectFor: This refers to internal mechanisms or functions within the JSC C++ codebase (such as functionDollarGlobalObjectFor) used to retrieve or associate a JavaScript object with its respective global object context [1]. It is frequently used in the context of ensuring that operations performed on objects, such as typed arrays or other host objects, are correctly scoped to the appropriate execution realm [1][2]. In summary, these identifiers are part of the internal machinery of the JavaScriptCore engine, primarily used by WebKit engineers to manage execution realms, expose debugging tools, and ensure proper object scoping within the engine's internal test harness [1][2].
Citations:
🌐 Web query:
WebKit GitHub functionDollarGlobalObjectFor globalObjectFor useDollarVM JSTests💡 Result:
In WebKit's JavaScriptCore (JSC),
$.globalObjectForis a helper function exposed via the$vmobject (often aliased as$or$262) used within the JSC shell (jsc) for testing purposes [1][2][3]. Purpose and Usage: The$vmobject provides internal debugging and testing utilities that are not exposed to standard web content [4][5].$.globalObjectFor(value)is used to retrieve the global object associated with a given JavaScript value or object [1][3]. This is frequently utilized inJSTeststo verify realm-related behaviors, such as ensuring that objects are created in or associated with the correct global execution context [3][6]. Accessing $vm in JSTests: To access$.globalObjectForor other$vmmethods in your tests, thejscshell must be executed with the--useDollarVM=trueflag [5][7]. While this is often the default behavior for test runners likerun-jscin the WebKit repository [8], it can be explicitly enabled for custom test scripts: $ Tools/Scripts/run-jsc --useDollarVM=true path/to/your/test.js Internal Implementation: - The functionfunctionDollarGlobalObjectForis defined in the JSC shell's implementation, typically found inSource/JavaScriptCore/jsc.cpp[1][2]. - It maps theglobalObjectForproperty on the$object to the C++ host functionfunctionDollarGlobalObjectFor[1][2]. - The implementation has evolved over time to ensure safety, including adding checks to handle non-object cells or cases where a global object might not be cleanly associated with the input [3][6]. Other common utilities available on the$vmobject includegc()(garbage collection trigger),createRealm(), andevalScript(), which are essential for advanced debugging and validating JIT compiler behavior or memory management in the JSC engine [1][9].Citations:
Enable the
$debug helper in this test.$.globalObjectFor(error)is provided by the$vmdebug object and requires--useDollarVM. Add--useDollarVM=1torequireOptions; otherwise the test can fail before checking the error realm.🤖 Prompt for AI Agents