diff --git a/JSTests/BUN-TEST-DIFFERENCES.md b/JSTests/BUN-TEST-DIFFERENCES.md index a25802d7fe587..29a9b6caefade 100644 --- a/JSTests/BUN-TEST-DIFFERENCES.md +++ b/JSTests/BUN-TEST-DIFFERENCES.md @@ -235,6 +235,7 @@ Not upstream's: a sync neither brings nor changes them. Most are named `stress/b | `stress/inline-cache-stub-freed-during-call-to-custom-setter.js` | an inline cache stub that has called native code stays until the call returns, also when the callee signed the return address that it saved (`JITStubRoutineSet::mark`). The setter of `$vm`'s `customAccessor` is compiled with `pac-ret` on ARM64 Linux in every build. On a processor without pointer authentication nothing is signed and the test passes whatever the code does | | `stress/bun-return-address-of-function-is-signed.js` | with `ENABLE(RETURN_ADDRESS_SIGNING)` a function signs the return address that it saves in LLInt, Baseline, the DFG and the FTL alike: `$vm.returnAddressOfCallerIsSigned()` signs the address again and compares. Elsewhere, and on a processor without pointer authentication (`$vm.functionsSignReturnAddress()`), the test ends at once | | `stress/bun-return-address-with-wrong-signature-is-not-returned-to.js` | with `ENABLE(RETURN_ADDRESS_SIGNING)` a function does not return once a bit of the signature of its saved return address was flipped, in each of the four tiers: the run must crash. Where nothing is signed the test crashes by itself | +| `stress/derived-constructor-this-initialized-by-another-arrow-function.js` | an arrow function or eval code of a derived constructor has the constructor's `this` once `super()` has returned, also when other code of the constructor called `super()` after it started: each kind of such code, each kind of read of `this` or of a super property, in every tier and from a bytecode cache. In such code a super property that comes before `super()` in the order of evaluation throws, and the `super()` behind it does not run. Upstream's open WebKit/WebKit#76967 makes that check in all code: once a sync brings it, `SuperNode::emitBytecode` needs only upstream's lines | | the fr-CA block of `stress/intl-date-pattern-includes-literal-text.js` (the file is upstream's) | keeps the file's coverage on CLDR 49, where fr's pattern no longer has literal text: an `h` inside literal text is not mistaken for the hour field | | the last block of `stress/regexp-test-sticky-first-char-filter.js` (the file is upstream's; nothing of upstream's text changes) | the first-character filter of a sticky `test()` comes from the first sets of the alternatives (`yarr/YarrFirstFollowSets.*`): behind a leading lookbehind, forward reference or lookahead, and in groups nested more than eight deep, a byte that passes the filter still reaches the matcher | diff --git a/JSTests/stress/derived-constructor-this-initialized-by-another-arrow-function.js b/JSTests/stress/derived-constructor-this-initialized-by-another-arrow-function.js new file mode 100644 index 0000000000000..4432ec4fc6891 --- /dev/null +++ b/JSTests/stress/derived-constructor-this-initialized-by-another-arrow-function.js @@ -0,0 +1,594 @@ +//@ defaultRun; run("bytecode-optimizer", "--useBytecodeOptimizer=1") + +// |this| of a derived constructor is one binding. An arrow function or eval code of the constructor reads the binding +// where it evaluates |this| or a super property. So it has the value once super() has returned, whichever code of the +// constructor called super(), also if that call came after the arrow function or the eval code started. + +const uninitialized = "'super()' must be called in derived constructor before accessing |this| or returning non-object."; +const calledTwice = "'super()' can't be called more than once in a constructor."; + +function shouldBe(actual, expected, name) +{ + if (actual !== expected) + throw new Error(`${name}: expected ${String(expected)} but got ${String(actual)}`); +} + +function shouldThrow(run, message, name) +{ + let error = null; + try { + run(); + } catch (e) { + error = e; + } + if (!(error instanceof ReferenceError) || error.message !== message) + throw new Error(`${name}: expected ReferenceError "${message}" but got ${String(error)}`); +} + +let baseCalls = 0; + +class Base { + constructor() + { + baseCalls++; + this.own = 1; + } + method() { return this; } + get getter() { return this; } + set setter(value) { this.stored = value; } + get tag() { return () => "tagged"; } +} +Base.prototype.count = 1; + +// Eval code that reads |this| before and after initialize(). +const readBeforeAndAfter = ` + const seen = []; + for (let i = 0; i < 3; ++i) { + if (i === 1) + initialize(); + try { + seen.push(typeof this); + } catch (error) { + seen.push(error.message === uninitialized ? "uninitialized" : String(error)); + } + } + seen.join(); +`; + +// The constructor calls test(readers) before any super(). If test is a string, the constructor evaluates it as eval code. +// initialize() is an arrow function that calls super(). A reader calls it, and then reads. +let evalResult; + +class Derived extends Base { + #field = 7; + constructor(test) + { + const initialize = () => { super(); }; + const initializeInEval = () => eval("super()"); + if (typeof test === "string") { + evalResult = eval(test); + return; + } + return test({ + initialize, + read: () => this, + + // Each kind of code that has the |this| of the constructor. + body: () => { initialize(); return this; }, + nested: () => (() => { initialize(); return this; })(), + withInnerArrow: () => { const inner = () => 1; initialize(); return inner() && this; }, + initializedInNestedArrow: () => { const inner = () => { super(); }; return (() => { inner(); return this; })(); }, + initializedInEval: () => { initializeInEval(); return this; }, + initializedInGetter: () => { ({ get x() { return initialize(); } }).x; return this; }, + recursive: depth => { + if (!depth) { + initialize(); + return this; + } + readers.recursive(depth - 1); + return this; + }, + parameter: (value = (initialize(), this)) => value, + evalCode: () => eval("initialize(); this"), + evalCodeBeforeAndAfter: () => eval(readBeforeAndAfter), + asyncBody: async () => { initialize(); return this; }, + asyncParameter: async (value = (initialize(), this)) => value, + asyncBodyAfterAwait: async () => { initialize(); await 0; return this; }, + arrowInAsyncBody: async () => { await 0; return (() => { initialize(); return this; })(); }, + + // Each kind of read. + thisProperty: () => { initialize(); return this.own; }, + thisCall: () => { initialize(); return this.method(); }, + thisStore: () => { initialize(); this.value = 2; return this.value; }, + thisPrivateField: () => { initialize(); return this.#field; }, + typeofThis: () => { initialize(); return typeof this; }, + superGet: () => { initialize(); return super.getter; }, + superGetByVal: key => { initialize(); return super[key]; }, + superCall: () => { initialize(); return super.method(); }, + superCallByVal: key => { initialize(); return super[key](); }, + superCallCall: () => { initialize(); return super.method.call(this); }, + superCallApply: () => { initialize(); return super.method.apply(this, []); }, + superTag: () => { initialize(); return super.tag`x`; }, + superPut: value => { initialize(); const result = (super.setter = value); return result + this.stored; }, + superPutByVal: (key, value) => { initialize(); const result = (super[key] = value); return result + this.stored; }, + superPutByValConstant: key => { initialize(); return super[key] = 21; }, + superAdd: () => { initialize(); return super.count += 1; }, + superAddByVal: key => { initialize(); return super[key] += 1; }, + superOr: key => { initialize(); return super[key] ||= 5; }, + superIncrement: key => { initialize(); super[key]++; return this.count; }, + superDecrement: () => { initialize(); return --super.count; }, + superDestructure: () => { initialize(); [super.a, { b: super["b"] }] = [3, { b: 4 }]; return this.a + this.b; }, + superForOf: key => { initialize(); for (super[key] of [5, 6]); return this.last; }, + superForIn: () => { initialize(); for (super.key in { k: 0 }); return this.key; }, + + // A read that comes before super() in the order of evaluation. + readThenInitialize: (first = this, second = initialize()) => first, + storeThenInitialize: () => { this.early = initialize(); }, + argumentThenInitialize: () => [this, initialize()], + superCallThenInitialize: () => super.method(initialize()), + superGetByValThenInitialize: () => super[(initialize(), "count")], + superPutThenInitialize: () => { super.setter = (initialize(), 1); }, + superPutByValThenInitialize: () => { super[(initialize(), "setter")] = 1; }, + superPutByValValueThenInitialize: key => { super[key] = (initialize(), 1); }, + superAddByValThenInitialize: () => { super[(initialize(), "count")] += 1; }, + superIncrementThenInitialize: () => { super[(initialize(), "count")]++; }, + superDeleteThenInitialize: () => delete super[(initialize(), "count")], + superTagThenInitialize: () => super[(initialize(), "tag")]`x`, + superDestructureThenInitialize: () => { [super[(initialize(), "a")]] = [1]; }, + superForOfThenInitialize: () => { for (super[(initialize(), "last")] of [1]); }, + superForInThenInitialize: () => { for (super[(initialize(), "key")] in { k: 0 }); }, + asyncSuperPutByValThenInitialize: async () => { super[(initialize(), "setter")] = 1; }, + asyncSuperPutByValAfterAwaitThenInitialize: async () => { await 0; super[(initialize(), "setter")] = 1; }, + + readBeforeAndAfter: () => { + const seen = []; + for (let i = 0; i < 3; ++i) { + if (i === 1) + initialize(); + try { + seen.push(typeof this); + } catch (error) { + seen.push(error.message === uninitialized ? "uninitialized" : String(error)); + } + } + return seen.join(); + }, + initializeTwice: () => { initialize(); super(); }, + }); + } +} + +let readers; + +// Calls the reader in a new construction, and checks what it gives against the new object. +function test(name, read, check = (result, instance) => result === instance) +{ + let result; + const instance = new Derived(typeof read === "string" ? read : scope => { + readers = scope; + result = read(scope); + }); + shouldBe(check(typeof read === "string" ? evalResult : result, instance), true, name); + shouldBe(instance instanceof Derived && instance.own === 1, true, `${name}: the new object`); +} + +function testOnce() +{ + for (const name of ["body", "nested", "withInnerArrow", "initializedInNestedArrow", "initializedInEval", "initializedInGetter", "parameter", "evalCode"]) + test(name, scope => scope[name]()); + test("recursive", scope => scope.recursive(3)); + + test("eval code of the constructor", "initialize(); this"); + test("eval code of eval code", "initialize(); eval('this')"); + test("arrow function of eval code", "(() => { initialize(); return this; })()"); + test("eval code of an arrow function of eval code", "(() => eval('initialize(); this'))()"); + test("super property in eval code", "initialize(); super.getter"); + test("eval code, super() in eval code", "initializeInEval(); this"); + test("eval code, assignment to a super property", "initialize(); (super['setter'] = 21)", result => result === 21); + + test("this.property", scope => scope.thisProperty(), result => result === 1); + test("this.method()", scope => scope.thisCall()); + test("this.property = value", scope => scope.thisStore(), result => result === 2); + test("this.#field", scope => scope.thisPrivateField(), result => result === 7); + test("typeof this", scope => scope.typeofThis(), result => result === "object"); + test("super.property", scope => scope.superGet()); + test("super[key]", scope => scope.superGetByVal("getter")); + test("super.method()", scope => scope.superCall()); + test("super[key]()", scope => scope.superCallByVal("method")); + test("super.method.call()", scope => scope.superCallCall()); + test("super.method.apply()", scope => scope.superCallApply()); + test("super.tag`x`", scope => scope.superTag(), result => result === "tagged"); + test("super.property = value", scope => scope.superPut(20), result => result === 40); + test("super[key] = value", scope => scope.superPutByVal("setter", 21), result => result === 42); + test("super[key] = 21", scope => scope.superPutByValConstant("setter"), (result, instance) => result === 21 && instance.stored === 21); + test("super.property += value", scope => scope.superAdd(), (result, instance) => result === 2 && instance.count === 2); + test("super[key] += value", scope => scope.superAddByVal("count"), (result, instance) => result === 2 && instance.count === 2); + test("super[key] ||= value", scope => scope.superOr("absent"), (result, instance) => result === 5 && instance.absent === 5); + test("super[key]++", scope => scope.superIncrement("count"), result => result === 2); + test("--super.property", scope => scope.superDecrement(), (result, instance) => result === 0 && instance.count === 0); + test("destructuring to super properties", scope => scope.superDestructure(), result => result === 7); + test("for-of to a super property", scope => scope.superForOf("last"), result => result === 6); + test("for-in to a super property", scope => scope.superForIn(), result => result === "k"); + + // A read that comes before super() throws, and the super() behind it does not run. + const early = [ + "read", "readThenInitialize", "storeThenInitialize", "argumentThenInitialize", "superCallThenInitialize", "superGetByValThenInitialize", + "superPutThenInitialize", "superPutByValThenInitialize", "superPutByValValueThenInitialize", "superAddByValThenInitialize", "superIncrementThenInitialize", + "superDeleteThenInitialize", "superTagThenInitialize", "superDestructureThenInitialize", "superForOfThenInitialize", "superForInThenInitialize", + ]; + for (const name of early) { + baseCalls = 0; + shouldThrow(() => new Derived(scope => { scope[name](name === "superPutByValValueThenInitialize" ? "setter" : undefined); }), uninitialized, name); + shouldBe(baseCalls, 0, `${name}: calls of the base constructor`); + } + shouldThrow(() => new Derived("this"), uninitialized, "eval code before super()"); + shouldThrow(() => new Derived("(() => this)()"), uninitialized, "arrow function of eval code before super()"); + + for (const code of ["super[(initialize(), 'setter')] = 1", "super.setter = (initialize(), 1)", "delete super[(initialize(), 'count')]"]) { + baseCalls = 0; + shouldThrow(() => new Derived(code), uninitialized, `eval code: ${code}`); + shouldBe(baseCalls, 0, `eval code: ${code}: calls of the base constructor`); + } + + // One activation has |this| in both states, and a second super() does not change it. + test("read, super(), read", scope => scope.readBeforeAndAfter(), result => result === "uninitialized,object,object"); + test("eval code: read, super(), read", readBeforeAndAfter, result => result === "uninitialized,object,object"); + test("eval code of eval code: read, super(), read", "eval(readBeforeAndAfter)", result => result === "uninitialized,object,object"); + test("eval code of an arrow function: read, super(), read", scope => scope.evalCodeBeforeAndAfter(), result => result === "uninitialized,object,object"); + baseCalls = 0; + let first; + shouldThrow(() => new Derived(scope => { + try { + scope.initializeTwice(); + } finally { + first = scope.read(); + } + }), calledTwice, "second super()"); + shouldBe(baseCalls, 2, "second super(): calls of the base constructor"); + shouldBe(first instanceof Derived, true, "second super(): |this| stays the first object"); + + // The constructor returns an object and never calls super(). Its arrow functions still have its |this|. + let escaped; + const replacement = { }; + shouldBe(new Derived(scope => { escaped = scope; return replacement; }), replacement, "the returned object"); + shouldThrow(() => escaped.read(), uninitialized, "escaped arrow function before super()"); + const late = escaped.body(); + shouldBe(late instanceof Derived && late !== replacement, true, "escaped arrow function calls super() and reads"); + shouldBe(escaped.read(), late, "escaped arrow function after super()"); + + // Each construction has its own |this|. + const scopes = []; + const initialized = new Derived(scope => { + scopes.push(scope); + scope.initialize(); + }); + new Derived(scope => { + scopes.push(scope); + return replacement; + }); + shouldBe(scopes[0].read(), initialized, "the construction that called super()"); + shouldThrow(() => scopes[1].read(), uninitialized, "the construction that did not call super()"); +} + +for (let i = 0; i < 100; ++i) + testOnce(); + +// An async arrow function evaluates its parameters, and a body without await, in the call itself. +{ + const names = ["asyncBody", "asyncParameter", "asyncBodyAfterAwait", "arrowInAsyncBody"]; + const settled = []; + for (const name of names) { + for (let i = 0; i < 100; ++i) { + new Derived(scope => { + scope[name]().then(value => settled.push(value), error => settled.push(error)); + return { }; + }); + } + } + drainMicrotasks(); + shouldBe(settled.length, 100 * names.length, "async arrow functions settle"); + for (const value of settled) + shouldBe(value instanceof Derived && value.own === 1, true, `async arrow function gives the new object, got ${String(value)}`); +} + +// In an async arrow function too, a super property that comes before super() rejects, and the super() behind it does not run. +{ + const names = ["asyncSuperPutByValThenInitialize", "asyncSuperPutByValAfterAwaitThenInitialize"]; + const settled = []; + for (const name of names) { + for (let i = 0; i < 100; ++i) { + baseCalls = 0; + new Derived(scope => { + scope[name]().then(() => settled.push("resolved"), error => settled.push(error.message === uninitialized && !baseCalls ? "rejected" : String(error))); + return { }; + }); + drainMicrotasks(); + } + } + shouldBe(settled.length, 100 * names.length, "async arrow functions with a store to a super property settle"); + for (const outcome of settled) + shouldBe(outcome, "rejected", "async arrow function with a store to a super property before super()"); +} + +// Eval code in the constructor of a class without a parent has |this| from the start. +{ + class WithoutParent { + constructor() + { + this.fromEval = eval("this"); + this.fromArrowInEval = eval("(() => this)()"); + } + } + for (let i = 0; i < 100; ++i) { + const instance = new WithoutParent; + shouldBe(instance.fromEval, instance, "eval code in the constructor of a class without a parent"); + shouldBe(instance.fromArrowInEval, instance, "arrow function of eval code in the constructor of a class without a parent"); + } +} + +// The reader keeps the scope that has |this| from its start. The scopes that the reader makes and leaves do not change it. +{ + let result; + class BlockScope extends Base { + constructor() + { + const initialize = () => { super(); }; + const read = () => { + initialize(); + { + let captured = 1; + const inner = () => captured; + inner(); + return this; + } + }; + result = read(); + } + } + class LoopScope extends Base { + constructor() + { + const initialize = () => { super(); }; + const read = () => { + const closures = []; + const seen = []; + for (let i = 0; i < 3; ++i) { + closures.push(() => i); + if (i === 1) + initialize(); + try { + seen.push(typeof this); + } catch (error) { + seen.push(error.message === uninitialized ? "uninitialized" : String(error)); + } + } + return seen.join(); + }; + shouldBe(read(), "uninitialized,object,object", "reads in a loop with a scope for each iteration"); + result = this; + } + } + class CatchScope extends Base { + constructor() + { + const initialize = () => { super(); }; + const read = () => { + initialize(); + try { + throw 1; + } catch (error) { + const inner = () => error; + inner(); + return super.getter; + } + }; + result = read(); + } + } + class ParameterScope extends Base { + constructor() + { + const initialize = () => { super(); }; + const read = captured => { + const inner = () => captured; + initialize(); + return inner() && this; + }; + result = read(1); + } + } + // The scope of the constructor has |this| and no variable. + class NoCapturedVariable extends Base { + constructor() + { + result = ((initialize, enterBlock) => { + initialize(); + if (enterBlock) { + let captured = 1; + const inner = () => captured; + inner(); + } + return this; + })(() => { super(); }, true); + } + } + // Only an eval tells the constructor that its arrow functions read |this| at each use. + class EvalInArrowFunction extends Base { + constructor() + { + const initialize = () => eval("super()"); + const read = () => { initialize(); return this; }; + result = read(); + } + } + class EvalInConstructor extends Base { + constructor() + { + let initialize; + eval("initialize = () => { super(); }"); + const read = () => { initialize(); return this; }; + result = read(); + } + } + // The variable of the eval code is in a scope that is not known before the eval code runs. + class ArrowFunctionOfEvalCodeWithVariable extends Base { + constructor() + { + const initialize = () => { super(); }; + result = eval("var variable = 1; (() => { initialize(); return this; })()"); + } + } + class EvalCodeWithVariable extends Base { + constructor() + { + const initialize = () => { super(); }; + result = eval("var variable = 1; initialize(); this"); + } + } + class EvalCodeWithFunction extends Base { + constructor() + { + const initialize = () => { super(); }; + result = eval("function declared() { return 1; } initialize(); declared() && this"); + } + } + for (const Class of [BlockScope, LoopScope, CatchScope, ParameterScope, NoCapturedVariable, EvalInArrowFunction, EvalInConstructor, ArrowFunctionOfEvalCodeWithVariable, EvalCodeWithVariable, EvalCodeWithFunction]) { + for (let i = 0; i < 100; ++i) { + result = null; + shouldBe(new Class, result, Class.name); + } + } +} + +// A bytecode cache keeps what the constructor told its arrow functions: for an arrow function whose code is generated after +// the decode, and for the eval code of a decoded arrow function. The depth is the count of function levels in the cache. +if (typeof builtinFromBytecodeCache === "function") { + const source = `(function (Base, name) { + "use strict"; + class Cached extends Base { + constructor() + { + const initialize = () => { super(); }; + const readers = { + body: () => { initialize(); return this; }, + nested: () => (() => { initialize(); return this; })(), + evalCode: () => eval("initialize(); this"), + }; + if (readers[name]() !== this) + throw new Error("bytecode cache, " + name + ": not the new object"); + } + } + return new Cached; + })`; + for (const depth of [1, 2]) { + for (const name of ["body", "nested", "evalCode"]) { + const create = builtinFromBytecodeCache(source, true, depth); + for (let i = 0; i < 2; ++i) + shouldBe(create(Base, name).own, 1, `bytecode cache of depth ${depth}, ${name}`); + } + } +} + +// The same in optimized code. One activation of the reader starts before super(), the others after it. +{ + let result; + class HotArrow extends Base { + constructor() + { + const initialize = () => { super(); }; + const read = shouldInitialize => { + if (shouldInitialize) + initialize(); + return this; + }; + noInline(read); + result = read(true); + for (let i = 0; i < 4; ++i) + shouldBe(read(false), result, "hot arrow function, a later activation"); + } + } + class HotInlinedArrow extends Base { + constructor() + { + const initialize = () => { super(); }; + const read = () => { initialize(); return this.own + super.count; }; + shouldBe(read(), 2, "hot inlined arrow function"); + result = this; + } + } + class HotUninitialized extends Base { + constructor(callSuperFirst) + { + const initialize = () => { super(); }; + const read = () => this; + if (callSuperFirst) + initialize(); + else { + shouldThrow(read, uninitialized, "hot arrow function before super()"); + initialize(); + } + result = read(); + } + } + class HotEval extends Base { + constructor() + { + const initialize = () => { super(); }; + result = eval("initialize(); this"); + } + } + class HotEvalInitializedInEval extends Base { + constructor() + { + const initialize = () => eval("super()"); + result = eval("initialize(); this"); + } + } + // One activation gets to optimized code in its loop, after its first reads threw and super() ran. + class HotLoop extends Base { + constructor() + { + const initialize = () => { super(); }; + const read = count => { + let failed = 0; + let sum = 0; + for (let i = 0; i < count; ++i) { + if (i === 3) + initialize(); + try { + sum += this.own; + } catch { + failed++; + } + } + return `${failed},${sum}`; + }; + noInline(read); + const count = 2 * testLoopCount; + shouldBe(read(count), `3,${count - 3}`, "hot loop in an arrow function"); + result = this; + } + } + // The same for eval code with a variable of its own. + class HotLoopInEvalCodeWithVariable extends Base { + constructor() + { + const initialize = () => { super(); }; + const count = 2 * testLoopCount; + shouldBe(eval("var sum = 0; initialize(); for (let i = 0; i < count; ++i) sum += this.own; sum"), count, "hot loop in eval code with a variable"); + result = this; + } + } + for (const Hot of [HotLoop, HotLoopInEvalCodeWithVariable]) { + for (let i = 0; i < 3; ++i) { + result = null; + shouldBe(new Hot, result, Hot.name); + } + } + for (const Hot of [HotArrow, HotInlinedArrow, HotUninitialized, HotEval, HotEvalInitializedInEval]) { + for (let i = 0; i < testLoopCount; ++i) { + result = null; + shouldBe(new Hot(i % 3 === 0), result, Hot.name); + } + } +} diff --git a/Source/JavaScriptCore/aot/runtime/AOTDirectEvalOperations.cpp b/Source/JavaScriptCore/aot/runtime/AOTDirectEvalOperations.cpp index cd26922c2f6f0..c476894ebde8f 100644 --- a/Source/JavaScriptCore/aot/runtime/AOTDirectEvalOperations.cpp +++ b/Source/JavaScriptCore/aot/runtime/AOTDirectEvalOperations.cpp @@ -869,6 +869,7 @@ JSC_DEFINE_JIT_OPERATION(operationAOTDirectEval, EncodedJSValue, (Instance* inst .sourceOrigin = instance->state->modules->sourceOrigin(ModuleID(request.module)), .lexicallyScopedFeatures = request.isStrict ? StrictModeLexicallyScopedFeature : NoLexicallyScopedFeatures, .derivedContextType = request.allowsSuperCall ? DerivedContextType::DerivedConstructorContext : request.allowsSuperProperty ? DerivedContextType::DerivedMethodContext : DerivedContextType::None, + .derivedContextHasInnerSuperCall = !!request.isInDerivedConstructor, .evalContextType = request.isInFieldInitializer ? EvalContextType::InstanceFieldEvalContext : request.isInFunction ? EvalContextType::FunctionEvalContext : EvalContextType::None, .needsClassFieldInitializer = initializesInstances ? NeedsClassFieldInitializer::Yes : NeedsClassFieldInitializer::No, .privateBrandRequirement = PrivateBrandRequirement::None, diff --git a/Source/JavaScriptCore/bytecode/ExecutableInfo.h b/Source/JavaScriptCore/bytecode/ExecutableInfo.h index 63d5df96cb3ad..e652a0f4124b2 100644 --- a/Source/JavaScriptCore/bytecode/ExecutableInfo.h +++ b/Source/JavaScriptCore/bytecode/ExecutableInfo.h @@ -37,7 +37,7 @@ enum class NeedsClassFieldInitializer : bool { No, Yes }; // FIXME: These flags, ParserModes and propagation to XXXCodeBlocks should be reorganized. // https://bugs.webkit.org/show_bug.cgi?id=151547 struct ExecutableInfo { - ExecutableInfo(bool isConstructor, PrivateBrandRequirement privateBrandRequirement, bool isBuiltinFunction, ConstructorKind constructorKind, JSParserScriptMode scriptMode, SuperBinding superBinding, SourceParseMode parseMode, DerivedContextType derivedContextType, NeedsClassFieldInitializer needsClassFieldInitializer, bool isArrowFunctionContext, bool isClassContext, EvalContextType evalContextType, bool isBuiltinDefaultClassConstructor = false) + ExecutableInfo(bool isConstructor, PrivateBrandRequirement privateBrandRequirement, bool isBuiltinFunction, ConstructorKind constructorKind, JSParserScriptMode scriptMode, SuperBinding superBinding, SourceParseMode parseMode, DerivedContextType derivedContextType, NeedsClassFieldInitializer needsClassFieldInitializer, bool isArrowFunctionContext, bool isClassContext, EvalContextType evalContextType, bool isBuiltinDefaultClassConstructor = false, bool derivedContextHasInnerSuperCall = false) : m_isConstructor(isConstructor) , m_privateBrandRequirement(static_cast(privateBrandRequirement)) , m_isBuiltinFunction(isBuiltinFunction) @@ -51,6 +51,7 @@ struct ExecutableInfo { , m_isClassContext(isClassContext) , m_evalContextType(static_cast(evalContextType)) , m_isBuiltinDefaultClassConstructor(isBuiltinDefaultClassConstructor) + , m_derivedContextHasInnerSuperCall(derivedContextHasInnerSuperCall) { ASSERT(m_constructorKind == static_cast(constructorKind)); ASSERT(m_superBinding == static_cast(superBinding)); @@ -70,6 +71,7 @@ struct ExecutableInfo { bool isClassContext() const { return m_isClassContext; } NeedsClassFieldInitializer needsClassFieldInitializer() const { return static_cast(m_needsClassFieldInitializer); } bool isBuiltinDefaultClassConstructor() const { return m_isBuiltinDefaultClassConstructor; } + bool derivedContextHasInnerSuperCall() const { return m_derivedContextHasInnerSuperCall; } private: unsigned m_isConstructor : 1; @@ -85,6 +87,9 @@ struct ExecutableInfo { unsigned m_isClassContext : 1; unsigned m_evalContextType : 2; unsigned m_isBuiltinDefaultClassConstructor : 1; + // For an arrow function or eval code in DerivedConstructorContext: the constructor has a parent class, and an arrow + // function or eval code of the constructor can call super(). So |this| can get its value while this code runs. + unsigned m_derivedContextHasInnerSuperCall : 1; }; } // namespace JSC diff --git a/Source/JavaScriptCore/bytecode/UnlinkedCodeBlock.cpp b/Source/JavaScriptCore/bytecode/UnlinkedCodeBlock.cpp index f26aed4396e03..445ace3608cf6 100644 --- a/Source/JavaScriptCore/bytecode/UnlinkedCodeBlock.cpp +++ b/Source/JavaScriptCore/bytecode/UnlinkedCodeBlock.cpp @@ -62,6 +62,7 @@ UnlinkedCodeBlock::UnlinkedCodeBlock(VM& vm, Structure* structure, CodeType code , m_hasTailCalls(false) , m_constructorKind(static_cast(info.constructorKind())) , m_derivedContextType(static_cast(info.derivedContextType())) + , m_derivedContextHasInnerSuperCall(info.derivedContextHasInnerSuperCall()) , m_evalContextType(static_cast(info.evalContextType())) , m_codeType(static_cast(codeType)) , m_age(0) diff --git a/Source/JavaScriptCore/bytecode/UnlinkedCodeBlock.h b/Source/JavaScriptCore/bytecode/UnlinkedCodeBlock.h index 845ac65aac6bc..e0257203569c8 100644 --- a/Source/JavaScriptCore/bytecode/UnlinkedCodeBlock.h +++ b/Source/JavaScriptCore/bytecode/UnlinkedCodeBlock.h @@ -169,6 +169,7 @@ class UnlinkedCodeBlock : public JSCell { bool hasTailCalls() const { return m_hasTailCalls; } void setHasTailCalls() { m_hasTailCalls = true; } bool isBuiltinDefaultClassConstructor() const { return m_isBuiltinDefaultClassConstructor; } + bool derivedContextHasInnerSuperCall() const { return m_derivedContextHasInnerSuperCall; } // Decoded on first use when the block came from a persistent bytecode cache. // Any thread, including the collector's end phase (ErrorInstance::computeErrorInfo): the first call takes m_lock @@ -445,6 +446,7 @@ class UnlinkedCodeBlock : public JSCell { unsigned m_hasTailCalls : 1; unsigned m_constructorKind : 2; unsigned m_derivedContextType : 2; + unsigned m_derivedContextHasInnerSuperCall : 1; unsigned m_evalContextType : 2; unsigned m_codeType : 2; unsigned m_age : 3; diff --git a/Source/JavaScriptCore/bytecode/UnlinkedCodeBlockGenerator.h b/Source/JavaScriptCore/bytecode/UnlinkedCodeBlockGenerator.h index 55fe14da47114..eafafa8a27a17 100644 --- a/Source/JavaScriptCore/bytecode/UnlinkedCodeBlockGenerator.h +++ b/Source/JavaScriptCore/bytecode/UnlinkedCodeBlockGenerator.h @@ -53,6 +53,7 @@ class UnlinkedCodeBlockGenerator { SourceParseMode parseMode() const { return m_codeBlock->parseMode(); } bool isArrowFunction() { return m_codeBlock->isArrowFunction(); } DerivedContextType derivedContextType() const { return m_codeBlock->derivedContextType(); } + bool derivedContextHasInnerSuperCall() const { return m_codeBlock->derivedContextHasInnerSuperCall(); } EvalContextType evalContextType() const { return m_codeBlock->evalContextType(); } bool isArrowFunctionContext() const { return m_codeBlock->isArrowFunctionContext(); } bool isClassContext() const { return m_codeBlock->isClassContext(); } diff --git a/Source/JavaScriptCore/bytecode/UnlinkedFunctionExecutable.cpp b/Source/JavaScriptCore/bytecode/UnlinkedFunctionExecutable.cpp index a508d87962727..a1382b493a257 100644 --- a/Source/JavaScriptCore/bytecode/UnlinkedFunctionExecutable.cpp +++ b/Source/JavaScriptCore/bytecode/UnlinkedFunctionExecutable.cpp @@ -73,7 +73,7 @@ static UnlinkedFunctionCodeBlock* generateUnlinkedFunctionCodeBlock( bool isClassContext = executable->superBinding() == SuperBinding::Needed || executable->parseMode() == SourceParseMode::ClassFieldInitializerMode; - UnlinkedFunctionCodeBlock* result = UnlinkedFunctionCodeBlock::create(vm, FunctionCode, ExecutableInfo(kind == CodeSpecializationKind::CodeForConstruct, executable->privateBrandRequirement(), functionKind == UnlinkedBuiltinFunction, executable->constructorKind(), scriptMode, executable->superBinding(), parseMode, executable->derivedContextType(), executable->needsClassFieldInitializer(), false, isClassContext, executable->evalContextType(), executable->isBuiltinDefaultClassConstructor()), codeGenerationMode); + UnlinkedFunctionCodeBlock* result = UnlinkedFunctionCodeBlock::create(vm, FunctionCode, ExecutableInfo(kind == CodeSpecializationKind::CodeForConstruct, executable->privateBrandRequirement(), functionKind == UnlinkedBuiltinFunction, executable->constructorKind(), scriptMode, executable->superBinding(), parseMode, executable->derivedContextType(), executable->needsClassFieldInitializer(), false, isClassContext, executable->evalContextType(), executable->isBuiltinDefaultClassConstructor(), executable->derivedContextHasInnerSuperCall()), codeGenerationMode); auto parentScopeTDZVariables = executable->parentScopeTDZVariables(); RefPtr parentDeclaredNames = executable->takeParentDeclaredNames(); @@ -87,7 +87,7 @@ static UnlinkedFunctionCodeBlock* generateUnlinkedFunctionCodeBlock( return result; } -UnlinkedFunctionExecutable::UnlinkedFunctionExecutable(VM& vm, Structure* structure, const SourceCode& parentSource, FunctionMetadataNode* node, UnlinkedFunctionKind kind, ConstructAbility constructAbility, InlineAttribute inlineAttribute, JSParserScriptMode scriptMode, RefPtr parentScopeTDZVariables, Vector&& generatorOrAsyncWrapperFunctionParameterNames, std::optional parentPrivateNameEnvironment, DerivedContextType derivedContextType, EvalContextType evalContextType, NeedsClassFieldInitializer needsClassFieldInitializer, PrivateBrandRequirement privateBrandRequirement, bool isBuiltinDefaultClassConstructor) +UnlinkedFunctionExecutable::UnlinkedFunctionExecutable(VM& vm, Structure* structure, const SourceCode& parentSource, FunctionMetadataNode* node, UnlinkedFunctionKind kind, ConstructAbility constructAbility, InlineAttribute inlineAttribute, JSParserScriptMode scriptMode, RefPtr parentScopeTDZVariables, Vector&& generatorOrAsyncWrapperFunctionParameterNames, std::optional parentPrivateNameEnvironment, DerivedContextType derivedContextType, EvalContextType evalContextType, NeedsClassFieldInitializer needsClassFieldInitializer, PrivateBrandRequirement privateBrandRequirement, bool isBuiltinDefaultClassConstructor, bool derivedContextHasInnerSuperCall) : Base(vm, structure) , m_hasCapturedVariables(false) , m_unlinkedFunctionStart(node->functionStart()) @@ -117,6 +117,7 @@ UnlinkedFunctionExecutable::UnlinkedFunctionExecutable(VM& vm, Structure* struct , m_evalContextType(static_cast(evalContextType)) , m_hasName(!node->ident().isNull()) , m_isClass(false) + , m_derivedContextHasInnerSuperCall(derivedContextHasInnerSuperCall) , m_nameIsDeferred(false) , m_membersAreDeferred(false) , m_scalarsAreDeferred(false) diff --git a/Source/JavaScriptCore/bytecode/UnlinkedFunctionExecutable.h b/Source/JavaScriptCore/bytecode/UnlinkedFunctionExecutable.h index ac5c519c94f2a..bee087b02da07 100644 --- a/Source/JavaScriptCore/bytecode/UnlinkedFunctionExecutable.h +++ b/Source/JavaScriptCore/bytecode/UnlinkedFunctionExecutable.h @@ -75,10 +75,10 @@ class UnlinkedFunctionExecutable final : public JSCell { return &vm.unlinkedFunctionExecutableSpace(); } - static UnlinkedFunctionExecutable* create(VM& vm, const SourceCode& source, FunctionMetadataNode* node, UnlinkedFunctionKind unlinkedFunctionKind, ConstructAbility constructAbility, InlineAttribute inlineAttribute, JSParserScriptMode scriptMode, RefPtr parentScopeTDZVariables, Vector&& generatorOrAsyncWrapperFunctionParameterNames, std::optional parentPrivateNameEnvironment, DerivedContextType derivedContextType, EvalContextType evalContextType, NeedsClassFieldInitializer needsClassFieldInitializer, PrivateBrandRequirement privateBrandRequirement, bool isBuiltinDefaultClassConstructor = false) + static UnlinkedFunctionExecutable* create(VM& vm, const SourceCode& source, FunctionMetadataNode* node, UnlinkedFunctionKind unlinkedFunctionKind, ConstructAbility constructAbility, InlineAttribute inlineAttribute, JSParserScriptMode scriptMode, RefPtr parentScopeTDZVariables, Vector&& generatorOrAsyncWrapperFunctionParameterNames, std::optional parentPrivateNameEnvironment, DerivedContextType derivedContextType, EvalContextType evalContextType, NeedsClassFieldInitializer needsClassFieldInitializer, PrivateBrandRequirement privateBrandRequirement, bool isBuiltinDefaultClassConstructor = false, bool derivedContextHasInnerSuperCall = false) { UnlinkedFunctionExecutable* instance = new (NotNull, allocateCell(vm)) - UnlinkedFunctionExecutable(vm, vm.unlinkedFunctionExecutableStructure.get(), source, node, unlinkedFunctionKind, constructAbility, inlineAttribute, scriptMode, WTF::move(parentScopeTDZVariables), WTF::move(generatorOrAsyncWrapperFunctionParameterNames), WTF::move(parentPrivateNameEnvironment), derivedContextType, evalContextType, needsClassFieldInitializer, privateBrandRequirement, isBuiltinDefaultClassConstructor); + UnlinkedFunctionExecutable(vm, vm.unlinkedFunctionExecutableStructure.get(), source, node, unlinkedFunctionKind, constructAbility, inlineAttribute, scriptMode, WTF::move(parentScopeTDZVariables), WTF::move(generatorOrAsyncWrapperFunctionParameterNames), WTF::move(parentPrivateNameEnvironment), derivedContextType, evalContextType, needsClassFieldInitializer, privateBrandRequirement, isBuiltinDefaultClassConstructor, derivedContextHasInnerSuperCall); instance->finishCreation(vm); return instance; } @@ -279,6 +279,7 @@ class UnlinkedFunctionExecutable final : public JSCell { JSC::DerivedContextType derivedContextType() const {return static_cast(m_derivedContextType); } EvalContextType evalContextType() const { return static_cast(m_evalContextType); } + bool derivedContextHasInnerSuperCall() const { return m_derivedContextHasInnerSuperCall; } InlineAttribute inlineAttribute() const { return static_cast(m_inlineAttribute); } @@ -362,7 +363,7 @@ class UnlinkedFunctionExecutable final : public JSCell { } private: - UnlinkedFunctionExecutable(VM&, Structure*, const SourceCode&, FunctionMetadataNode*, UnlinkedFunctionKind, ConstructAbility, InlineAttribute, JSParserScriptMode, RefPtr, Vector&&, std::optional, JSC::DerivedContextType, EvalContextType, JSC::NeedsClassFieldInitializer, PrivateBrandRequirement, bool isBuiltinDefaultClassConstructor); + UnlinkedFunctionExecutable(VM&, Structure*, const SourceCode&, FunctionMetadataNode*, UnlinkedFunctionKind, ConstructAbility, InlineAttribute, JSParserScriptMode, RefPtr, Vector&&, std::optional, JSC::DerivedContextType, EvalContextType, JSC::NeedsClassFieldInitializer, PrivateBrandRequirement, bool isBuiltinDefaultClassConstructor, bool derivedContextHasInnerSuperCall); UnlinkedFunctionExecutable(Decoder&, const CachedFunctionExecutable&); DECLARE_VISIT_CHILDREN; @@ -410,6 +411,7 @@ class UnlinkedFunctionExecutable final : public JSCell { uint8_t m_evalContextType : 2; uint8_t m_hasName : 1; uint8_t m_isClass : 1; + uint8_t m_derivedContextHasInnerSuperCall : 1; // Own bytes, not bits of the group above: the mutator clears these late, while compiler threads read that group. bool m_nameIsDeferred { false }; // m_ecmaName is still in the cache record; implies m_membersAreDeferred bool m_membersAreDeferred { false }; // TDZ variables + rare data are still in the cache record; the m_deferredMembers* union members are live diff --git a/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp b/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp index 46e1208282689..68d7df3e3e60d 100644 --- a/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp +++ b/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp @@ -834,6 +834,10 @@ IGNORE_GCC_WARNINGS_END if (functionNode->needsNewTargetRegisterForThisScope() || isNewTargetUsedInInnerArrowFunction() || usesEval()) m_newTargetRegister = addVar(); + // Not for the body of an async arrow function, which is resumed without |this|: ensureThis() has a rule for it. + if (codeBlock->derivedContextHasInnerSuperCall() && parseMode != SourceParseMode::AsyncArrowFunctionBodyMode && (functionNode->usesThis() || functionNode->usesSuperProperty())) + m_lexicalEnvironmentOfThisRegister = addVar(); + auto shouldEmitToThis = [&] { if (functionNode->usesThis() || usesEval() || m_scopeNode->doAnyInnerArrowFunctionsUseThis() || m_scopeNode->doAnyInnerArrowFunctionsUseEval()) return true; @@ -960,7 +964,9 @@ IGNORE_GCC_WARNINGS_END // We need load |super| & |this| for arrow function before initializeDefaultParameterValuesAndSetupFunctionScopeStack // if we have default parameter expression. Because |super| & |this| values can be used there if ((SourceParseModeSet(SourceParseMode::ArrowFunctionMode, SourceParseMode::AsyncArrowFunctionMode).contains(parseMode) && !isSimpleParameterList) || parseMode == SourceParseMode::AsyncArrowFunctionBodyMode) { - if (functionNode->usesThis() || functionNode->usesSuperProperty()) + if (m_lexicalEnvironmentOfThisRegister) + emitLoadLexicalEnvironmentOfThis(); + else if (functionNode->usesThis() || functionNode->usesSuperProperty()) emitLoadThisFromArrowFunctionLexicalEnvironment(); if (m_scopeNode->needsNewTargetRegisterForThisScope()) @@ -988,7 +994,9 @@ IGNORE_GCC_WARNINGS_END // after initializeDefaultParameterValuesAndSetupFunctionScopeStack() because that function sets up the // SymbolTable stack and emitLoadThisFromArrowFunctionLexicalEnvironment() consults the SymbolTable stack if (SourceParseModeSet(SourceParseMode::ArrowFunctionMode, SourceParseMode::AsyncArrowFunctionMode).contains(parseMode) && isSimpleParameterList) { - if (functionNode->usesThis() || functionNode->usesSuperProperty()) + if (m_lexicalEnvironmentOfThisRegister) + emitLoadLexicalEnvironmentOfThis(); + else if (functionNode->usesThis() || functionNode->usesSuperProperty()) emitLoadThisFromArrowFunctionLexicalEnvironment(); if (m_scopeNode->needsNewTargetRegisterForThisScope()) @@ -1081,7 +1089,10 @@ BytecodeGenerator::BytecodeGenerator(VM& vm, EvalNode* evalNode, UnlinkedEvalCod if (evalNode->needsNewTargetRegisterForThisScope()) m_newTargetRegister = addVar(); - if (codeBlock->isArrowFunctionContext() && (evalNode->usesThis() || evalNode->usesSuperProperty())) + if (codeBlock->derivedContextHasInnerSuperCall() && (evalNode->usesThis() || evalNode->usesSuperProperty())) { + m_lexicalEnvironmentOfThisRegister = addVar(); + emitLoadLexicalEnvironmentOfThis(); + } else if (codeBlock->isArrowFunctionContext() && (evalNode->usesThis() || evalNode->usesSuperProperty())) emitLoadThisFromArrowFunctionLexicalEnvironment(); if (evalNode->needsNewTargetRegisterForThisScope()) @@ -5409,6 +5420,14 @@ RegisterID* BytecodeGenerator::emitLoadArrowFunctionLexicalEnvironment(const Ide return emitResolveScope(nullptr, variable(identifier, ThisResolutionType::Scoped)); } +void BytecodeGenerator::emitLoadLexicalEnvironmentOfThis() +{ + // The non-local case of emitResolveScope(), with a destination that is not a temporary. An arrow function has no scope of its own with |this|. + Variable thisVariable = variable(propertyNames().builtinNames().thisPrivateName(), ThisResolutionType::Scoped); + ASSERT(thisVariable.offset().kind() == VarKind::Invalid); + OpResolveScope::emit(this, kill(m_lexicalEnvironmentOfThisRegister), scopeRegister(), addConstant(thisVariable.ident()), resolveType(), localScopeDepth()); +} + void BytecodeGenerator::emitLoadThisFromArrowFunctionLexicalEnvironment() { emitGetFromScope(thisRegister(), emitLoadArrowFunctionLexicalEnvironment(propertyNames().builtinNames().thisPrivateName()), variable(propertyNames().builtinNames().thisPrivateName(), ThisResolutionType::Scoped), DoNotThrowIfNotFound); @@ -5439,7 +5458,9 @@ RegisterID* BytecodeGenerator::emitLoadDerivedConstructor() RegisterID* BytecodeGenerator::ensureThis() { if (constructorKind() == ConstructorKind::Extends || isDerivedConstructorContext()) { - if ((needsToUpdateArrowFunctionContext() && isSuperCallUsedInInnerArrowFunction()) || m_codeBlock->parseMode() == SourceParseMode::AsyncArrowFunctionBodyMode) + if (m_lexicalEnvironmentOfThisRegister) + emitGetFromScope(thisRegister(), m_lexicalEnvironmentOfThisRegister, variable(propertyNames().builtinNames().thisPrivateName(), ThisResolutionType::Scoped), DoNotThrowIfNotFound); + else if ((needsToUpdateArrowFunctionContext() && isSuperCallUsedInInnerArrowFunction()) || m_codeBlock->parseMode() == SourceParseMode::AsyncArrowFunctionBodyMode) emitLoadThisFromArrowFunctionLexicalEnvironment(); emitTDZCheck(thisRegister()); diff --git a/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h b/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h index 3de4770d9ac2d..d56745c88fcff 100644 --- a/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h +++ b/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h @@ -766,6 +766,9 @@ namespace JSC { RegisterID* emitLoadArrowFunctionLexicalEnvironment(const Identifier&); RegisterID* ensureThis(); + // In code with derivedContextHasInnerSuperCall, other code of the derived constructor can call super() while this code runs. + // ensureThis() reads |this| from the scope each time there: through the held scope, or as the body of an async arrow function does. + bool readsThisAtEachUse() const { return m_lexicalEnvironmentOfThisRegister || (m_codeBlock->derivedContextHasInnerSuperCall() && parseMode() == SourceParseMode::AsyncArrowFunctionBodyMode); } void emitLoadThisFromArrowFunctionLexicalEnvironment(); RegisterID* emitLoadNewTargetFromArrowFunctionLexicalEnvironment(); @@ -1300,6 +1303,7 @@ namespace JSC { UnlinkedFunctionExecutable* makeFunction(FunctionMetadataNode* metadata) { DerivedContextType newDerivedContextType = DerivedContextType::None; + bool derivedContextHasInnerSuperCall = false; EvalContextType newEvalContextType = EvalContextType::FunctionEvalContext; NeedsClassFieldInitializer needsClassFieldInitializer = metadata->isConstructorAndNeedsClassFieldInitializer() ? NeedsClassFieldInitializer::Yes : NeedsClassFieldInitializer::No; @@ -1307,6 +1311,11 @@ namespace JSC { if (SourceParseModeSet(SourceParseMode::ArrowFunctionMode, SourceParseMode::AsyncArrowFunctionMode, SourceParseMode::AsyncArrowFunctionBodyMode).contains(metadata->parseMode())) { if (constructorKind() == ConstructorKind::Extends || isDerivedConstructorContext()) { newDerivedContextType = DerivedContextType::DerivedConstructorContext; + // The constructor knows each super() and eval in its arrow functions. Their code, and eval code, hand its answer on. + if (constructorKind() == ConstructorKind::Extends) + derivedContextHasInnerSuperCall = isSuperCallUsedInInnerArrowFunction(); + else + derivedContextHasInnerSuperCall = m_codeBlock->derivedContextHasInnerSuperCall(); needsClassFieldInitializer = m_codeBlock->needsClassFieldInitializer(); privateBrandRequirement = m_codeBlock->privateBrandRequirement(); } @@ -1329,7 +1338,7 @@ namespace JSC { if (isGeneratorOrAsyncFunctionWrapperParseMode(m_codeBlock->parseMode()) && isGeneratorOrAsyncFunctionBodyParseMode(parseMode)) generatorOrAsyncWrapperFunctionParameterNames = getParameterNames(); - auto* executable = UnlinkedFunctionExecutable::create(m_vm, m_scopeNode->source(), metadata, isBuiltinFunction() ? UnlinkedBuiltinFunction : UnlinkedNormalFunction, constructAbility, InlineAttribute::None, scriptMode(), WTF::move(optionalVariablesUnderTDZ), WTF::move(generatorOrAsyncWrapperFunctionParameterNames), WTF::move(parentPrivateNameEnvironment), newDerivedContextType, newEvalContextType, needsClassFieldInitializer, privateBrandRequirement); + auto* executable = UnlinkedFunctionExecutable::create(m_vm, m_scopeNode->source(), metadata, isBuiltinFunction() ? UnlinkedBuiltinFunction : UnlinkedNormalFunction, constructAbility, InlineAttribute::None, scriptMode(), WTF::move(optionalVariablesUnderTDZ), WTF::move(generatorOrAsyncWrapperFunctionParameterNames), WTF::move(parentPrivateNameEnvironment), newDerivedContextType, newEvalContextType, needsClassFieldInitializer, privateBrandRequirement, false, derivedContextHasInnerSuperCall); if (shouldRunBytecodeOptimizer()) [[unlikely]] executable->setParentDeclaredNames(currentDeclaredNames()); return executable; @@ -1354,6 +1363,7 @@ namespace JSC { void initializeDefaultParameterValuesAndSetupFunctionScopeStack(FunctionParameters&, bool isSimpleParameterList, FunctionNode*, SymbolTable*, int symbolTableConstantIndex, NOESCAPE const ScopedLambda& captures, bool shouldCreateArgumentsVariableInParameterScope); void initializeArrowFunctionContextScopeIfNeeded(SymbolTable* functionSymbolTable = nullptr, bool canReuseLexicalEnvironment = false); bool LEGACY_NODELETE needsDerivedConstructorInArrowFunctionLexicalEnvironment(); + void emitLoadLexicalEnvironmentOfThis(); enum class TDZNecessityLevel { NotNeeded, @@ -1477,6 +1487,8 @@ namespace JSC { RegisterID* m_isDerivedConstuctor { nullptr }; UncheckedKeyHashMap, WTF::StrongEnumHashTraits> m_linkTimeConstantRegisters; RegisterID* m_arrowFunctionContextLexicalEnvironmentRegister { nullptr }; + // In an arrow function or eval code with derivedContextHasInnerSuperCall that uses |this|: the scope that has |this|. + RegisterID* m_lexicalEnvironmentOfThisRegister { nullptr }; RegisterID* m_promiseRegister { nullptr }; FinallyContext* m_currentFinallyContext { nullptr }; diff --git a/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp b/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp index 5b00e39be6d96..81aef60fa6723 100644 --- a/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp +++ b/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp @@ -243,6 +243,14 @@ static RegisterID* emitGetSuperFunctionForConstruct(BytecodeGenerator& generator RegisterID* SuperNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst) { + // A super property needs |this| before its key and the assigned value are evaluated. The check at the store comes after + // them. In code that reads |this| at each use, that check would accept a |this| that the key or the value gave a value. + // Other code, the body of the constructor too, has only the check at the store. + if (generator.readsThisAtEachUse()) { + generator.emitExpressionInfo(position(), position(), position()); + generator.ensureThis(); + } + RegisterID* result = emitSuperBaseForCallee(generator); return generator.move(generator.finalDestination(dst), result); } diff --git a/Source/JavaScriptCore/debugger/DebuggerCallFrame.cpp b/Source/JavaScriptCore/debugger/DebuggerCallFrame.cpp index 987400e66a416..293ae5baba37c 100644 --- a/Source/JavaScriptCore/debugger/DebuggerCallFrame.cpp +++ b/Source/JavaScriptCore/debugger/DebuggerCallFrame.cpp @@ -30,12 +30,14 @@ #include "DebuggerCallFrame.h" #include "AOTRemovableFeature.h" +#include "BuiltinNames.h" #include "CodeBlock.h" #include "DebuggerEvalEnabler.h" #include "DebuggerScope.h" #include "DirectEvalExecutable.h" #include "Interpreter.h" #include "JSFunction.h" +#include "JSLexicalEnvironment.h" #include "JSWithScope.h" #include "ShadowChickenInlines.h" #include "StackVisitor.h" @@ -150,23 +152,23 @@ DebuggerScope* DebuggerCallFrame::scope(VM& vm) if (!isValid()) return nullptr; - if (!m_scope) { - JSScope* scope; - CodeBlock* codeBlock = m_validMachineFrame->isNativeCalleeFrame() ? nullptr : m_validMachineFrame->codeBlock(); - if (isTailDeleted()) - scope = m_shadowChickenFrame.scope; - else if (codeBlock && codeBlock->scopeRegister().isValid()) - scope = m_validMachineFrame->scope(codeBlock->scopeRegister().offset()); - else if (JSCallee* callee = dynamicDowncast(m_validMachineFrame->jsCallee())) - scope = callee->scope(); - else - scope = m_validMachineFrame->lexicalGlobalObject(vm)->globalLexicalEnvironment(); - - m_scope.set(vm, DebuggerScope::create(vm, scope)); - } + if (!m_scope) + m_scope.set(vm, DebuggerScope::create(vm, jsScope(vm))); return m_scope.get(); } +JSScope* DebuggerCallFrame::jsScope(VM& vm) const +{ + CodeBlock* codeBlock = m_validMachineFrame->isNativeCalleeFrame() ? nullptr : m_validMachineFrame->codeBlock(); + if (isTailDeleted()) + return m_shadowChickenFrame.scope; + if (codeBlock && codeBlock->scopeRegister().isValid()) + return m_validMachineFrame->scope(codeBlock->scopeRegister().offset()); + if (JSCallee* callee = dynamicDowncast(m_validMachineFrame->jsCallee())) + return callee->scope(); + return m_validMachineFrame->lexicalGlobalObject(vm)->globalLexicalEnvironment(); +} + DebuggerCallFrame::Type DebuggerCallFrame::type(VM&) const { ASSERT(isValid()); @@ -198,6 +200,35 @@ JSValue DebuggerCallFrame::thisValue(VM& vm) const codeBlock = m_validMachineFrame->isNativeCalleeFrame() ? nullptr : m_validMachineFrame->codeBlock(); } + // An arrow function or eval code with derivedContextHasInnerSuperCall reads |this| of its derived constructor from a scope at each + // use (BytecodeGenerator::ensureThis), so its frame has a copy only after a read. The frame of the constructor has no copy between + // a super() in such code and the next use of |this| in the constructor. The nearest scope that has |this| has the value. + if (codeBlock) { + UnlinkedCodeBlock* unlinkedCodeBlock = codeBlock->unlinkedCodeBlock(); + bool readsScope = unlinkedCodeBlock->derivedContextHasInnerSuperCall(); + // For the constructor that is a scope of its own activation: the scope of its callee is the first one that is not. + JSScope* end = nullptr; + if (!readsScope && !thisValue && !isTailDeleted() && unlinkedCodeBlock->isConstructor() && unlinkedCodeBlock->constructorKind() == ConstructorKind::Extends) { + if (JSCallee* callee = dynamicDowncast(m_validMachineFrame->jsCallee())) { + readsScope = true; + end = callee->scope(); + } + } + if (readsScope) { + const Identifier& thisName = vm.propertyNames->builtinNames().thisPrivateName(); + for (JSScope* scope = jsScope(vm); scope && scope != end; scope = scope->next()) { + auto* environment = dynamicDowncast(scope); + if (!environment) + continue; + PropertySlot slot(environment, PropertySlot::InternalMethodType::VMInquiry, &vm); + if (symbolTableGet(environment, thisName, slot)) { + thisValue = slot.getValue(codeBlock->globalObject(), thisName); + break; + } + } + } + } + if (!thisValue) return jsUndefined(); @@ -253,7 +284,7 @@ JSValue DebuggerCallFrame::evaluateWithScopeExtension(VM& vm, const String& scri PrivateNameEnvironment privateNameEnvironment; JSScope::collectClosureVariablesUnderTDZ(scope(vm)->jsScope(), variablesUnderTDZ, privateNameEnvironment); - auto* eval = DirectEvalExecutable::create(globalObject, makeSource(script, callFrame->callerSourceOrigin(vm), SourceTaintedOrigin::Untainted), codeBlock->ownerExecutable()->lexicallyScopedFeatures(), codeBlock->unlinkedCodeBlock()->derivedContextType(), codeBlock->unlinkedCodeBlock()->needsClassFieldInitializer(), codeBlock->unlinkedCodeBlock()->privateBrandRequirement(), codeBlock->unlinkedCodeBlock()->isArrowFunction(), codeBlock->ownerExecutable()->isInsideOrdinaryFunction(), evalContextType, &variablesUnderTDZ, &privateNameEnvironment); + auto* eval = DirectEvalExecutable::create(globalObject, makeSource(script, callFrame->callerSourceOrigin(vm), SourceTaintedOrigin::Untainted), codeBlock->ownerExecutable()->lexicallyScopedFeatures(), codeBlock->unlinkedCodeBlock()->derivedContextType(), codeBlock->unlinkedCodeBlock()->derivedContextHasInnerSuperCall(), codeBlock->unlinkedCodeBlock()->needsClassFieldInitializer(), codeBlock->unlinkedCodeBlock()->privateBrandRequirement(), codeBlock->unlinkedCodeBlock()->isArrowFunction(), codeBlock->ownerExecutable()->isInsideOrdinaryFunction(), evalContextType, &variablesUnderTDZ, &privateNameEnvironment); if (catchScope.exception()) [[unlikely]] { exception = catchScope.exception(); catchScope.clearException(); diff --git a/Source/JavaScriptCore/debugger/DebuggerCallFrame.h b/Source/JavaScriptCore/debugger/DebuggerCallFrame.h index d85fd8d39083e..acad6c003acf2 100644 --- a/Source/JavaScriptCore/debugger/DebuggerCallFrame.h +++ b/Source/JavaScriptCore/debugger/DebuggerCallFrame.h @@ -78,6 +78,8 @@ class DebuggerCallFrame : public RefCounted { private: DebuggerCallFrame(VM&, CallFrame*, const ShadowChicken::Frame&); + JSScope* jsScope(VM&) const; + CallFrame* m_validMachineFrame; RefPtr m_caller; TextPosition m_position; diff --git a/Source/JavaScriptCore/interpreter/Interpreter.cpp b/Source/JavaScriptCore/interpreter/Interpreter.cpp index 84880ec8bd583..c744a0e5568c8 100644 --- a/Source/JavaScriptCore/interpreter/Interpreter.cpp +++ b/Source/JavaScriptCore/interpreter/Interpreter.cpp @@ -209,7 +209,7 @@ static ALWAYS_INLINE JSValue evaluateDirectEvalAt(JSGlobalObject* globalObject, JSScope::collectClosureVariablesUnderTDZ(caller.scope, variablesUnderTDZ, privateNameEnvironment); SourceTaintedness sourceTaintedOrigin = computeNewSourceTaintednessFromStack(vm, caller.callFrame); - eval = DirectEvalExecutable::create(globalObject, makeSource(programSource, caller.sourceOrigin, sourceTaintedOrigin), caller.lexicallyScopedFeatures, caller.derivedContextType, caller.needsClassFieldInitializer, caller.privateBrandRequirement, caller.isArrowFunctionContext, caller.isInsideOrdinaryFunction, caller.evalContextType, &variablesUnderTDZ, caller.privateNames ? caller.privateNames : &privateNameEnvironment); + eval = DirectEvalExecutable::create(globalObject, makeSource(programSource, caller.sourceOrigin, sourceTaintedOrigin), caller.lexicallyScopedFeatures, caller.derivedContextType, caller.derivedContextHasInnerSuperCall, caller.needsClassFieldInitializer, caller.privateBrandRequirement, caller.isArrowFunctionContext, caller.isInsideOrdinaryFunction, caller.evalContextType, &variablesUnderTDZ, caller.privateNames ? caller.privateNames : &privateNameEnvironment); EXCEPTION_ASSERT(!!scope.exception() == !eval); if (!eval) [[unlikely]] return { }; @@ -249,10 +249,13 @@ JSValue eval(CallFrame* callFrame, JSValue thisValue, JSScope* callerScopeChain, bool isArrowFunctionContext = callerUnlinkedCodeBlock->isArrowFunction() || callerUnlinkedCodeBlock->isArrowFunctionContext(); DerivedContextType derivedContextType = callerUnlinkedCodeBlock->derivedContextType(); + bool derivedContextHasInnerSuperCall = callerUnlinkedCodeBlock->derivedContextHasInnerSuperCall(); if (!isArrowFunctionContext && callerUnlinkedCodeBlock->isClassContext()) { derivedContextType = callerUnlinkedCodeBlock->isConstructor() ? DerivedContextType::DerivedConstructorContext : DerivedContextType::DerivedMethodContext; + // The eval code of a derived constructor can call super(). + derivedContextHasInnerSuperCall = callerUnlinkedCodeBlock->isConstructor() && callerUnlinkedCodeBlock->constructorKind() == ConstructorKind::Extends; } EvalContextType evalContextType; @@ -268,6 +271,7 @@ JSValue eval(CallFrame* callFrame, JSValue thisValue, JSScope* callerScopeChain, .sourceOrigin = callerBaselineCodeBlock->source().provider()->sourceOrigin(), .lexicallyScopedFeatures = lexicallyScopedFeatures, .derivedContextType = derivedContextType, + .derivedContextHasInnerSuperCall = derivedContextHasInnerSuperCall, .evalContextType = evalContextType, .needsClassFieldInitializer = callerUnlinkedCodeBlock->needsClassFieldInitializer(), .privateBrandRequirement = callerUnlinkedCodeBlock->privateBrandRequirement(), diff --git a/Source/JavaScriptCore/interpreter/Interpreter.h b/Source/JavaScriptCore/interpreter/Interpreter.h index 65293b7982f59..acd7a43ad1dda 100644 --- a/Source/JavaScriptCore/interpreter/Interpreter.h +++ b/Source/JavaScriptCore/interpreter/Interpreter.h @@ -246,6 +246,7 @@ using JSOrWasmInstruction = Variant unsigned scriptMode; unsigned superBinding; unsigned derivedContextType; + bool derivedContextHasInnerSuperCall; unsigned evalContextType; bool inlineAttribute; bool needsClassFieldInitializer; @@ -3997,7 +3998,7 @@ class CachedFunctionExecutable : public CachedObject const CachedPtr* rareData { nullptr }; const uint8_t* tail { nullptr }; const uint8_t* tailEnd { nullptr }; // end of what was parsed: the hot part only for view(HotScalarsOnly) - Scalars scalars; + Scalars scalars { }; }; enum ScalarsToView { AllScalars, HotScalarsOnly }; @@ -4103,6 +4104,7 @@ class CachedCodeBlock : public CachedObject { unsigned isClassContext : 1; unsigned constructorKind : 2; unsigned derivedContextType : 2; + unsigned derivedContextHasInnerSuperCall : 1; unsigned evalContextType : 2; unsigned hasTailCalls : 1; unsigned codeType : 2; @@ -4140,7 +4142,7 @@ class CachedCodeBlock : public CachedObject { }; struct Tail { Layout layout; - Scalars scalars; + Scalars scalars { }; }; static Record* create(Encoder&, const CodeBlockType&); @@ -4463,6 +4465,7 @@ ALWAYS_INLINE UnlinkedCodeBlock::UnlinkedCodeBlock(Decoder& decoder, Structure* m_hasTailCalls = scalars.hasTailCalls; m_constructorKind = scalars.constructorKind; m_derivedContextType = scalars.derivedContextType; + m_derivedContextHasInnerSuperCall = scalars.derivedContextHasInnerSuperCall; m_evalContextType = scalars.evalContextType; m_codeType = scalars.codeType; m_hasCheckpoints = scalars.hasCheckpoints; @@ -4668,6 +4671,7 @@ enum CachedFunctionExecutableFlag : uint32_t { ExecutableNeedsClassFieldInitializerShift = 16, ExecutableIsBuiltinFunctionShift = 17, ExecutableIsBuiltinDefaultClassConstructorShift = 18, + ExecutableDerivedContextHasInnerSuperCallShift = 19, }; static_assert(bitWidthOfImplementationVisibility <= 2); @@ -4686,7 +4690,8 @@ void CachedFunctionExecutable::packScalars(const UnlinkedFunctionExecutable& exe | static_cast(executable.m_inlineAttribute) << ExecutableInlineAttributeShift | static_cast(executable.m_needsClassFieldInitializer) << ExecutableNeedsClassFieldInitializerShift | static_cast(executable.m_isBuiltinFunction) << ExecutableIsBuiltinFunctionShift - | static_cast(executable.m_isBuiltinDefaultClassConstructor) << ExecutableIsBuiltinDefaultClassConstructorShift; + | static_cast(executable.m_isBuiltinDefaultClassConstructor) << ExecutableIsBuiltinDefaultClassConstructorShift + | static_cast(executable.m_derivedContextHasInnerSuperCall) << ExecutableDerivedContextHasInnerSuperCallShift; executable.materializeDeferredScalarsIfNeeded(); // Hot part: what link() (the FunctionExecutable's SourceCode), JSFunction structure selection, the first call // (features, ScriptExecutable::newCodeBlockFor) and compiler threads (parameterCount) need. Source positions cluster @@ -4786,6 +4791,7 @@ auto CachedFunctionExecutable::view(ScalarsToView scalarsToView) const -> View s.needsClassFieldInitializer = bits(ExecutableNeedsClassFieldInitializerShift); s.isBuiltinFunction = bits(ExecutableIsBuiltinFunctionShift); s.isBuiltinDefaultClassConstructor = bits(ExecutableIsBuiltinDefaultClassConstructorShift); + s.derivedContextHasInnerSuperCall = bits(ExecutableDerivedContextHasInnerSuperCallShift); s.lexicallyScopedFeatures = static_cast(reader.u8()); s.hasCapturedVariables = v.header & HasCapturedVariables; s.sourceParseMode = static_cast((v.header >> ParseModeShift) & 0xff); @@ -5045,6 +5051,7 @@ ALWAYS_INLINE UnlinkedFunctionExecutable::UnlinkedFunctionExecutable(Decoder& de m_implementationVisibility = static_cast(scalars.implementationVisibility); m_functionMode = scalars.functionMode; m_derivedContextType = scalars.derivedContextType; + m_derivedContextHasInnerSuperCall = scalars.derivedContextHasInnerSuperCall; m_inlineAttribute = scalars.inlineAttribute; m_evalContextType = scalars.evalContextType; m_hasName = scalars.hasName; @@ -5081,6 +5088,7 @@ enum CachedCodeBlockFlag : uint32_t { CodeBlockCodeTypeShift = 13, // 2 CodeBlockIsBuiltinFunctionShift = 15, CodeBlockIsBuiltinDefaultClassConstructorShift = 16, + CodeBlockDerivedContextHasInnerSuperCallShift = 17, }; template @@ -5098,7 +5106,8 @@ void CachedCodeBlock::packScalars(const UnlinkedCodeBlock& codeBl | static_cast(codeBlock.m_evalContextType) << CodeBlockEvalContextTypeShift | static_cast(codeBlock.m_codeType) << CodeBlockCodeTypeShift | static_cast(codeBlock.m_isBuiltinFunction) << CodeBlockIsBuiltinFunctionShift - | static_cast(codeBlock.m_isBuiltinDefaultClassConstructor) << CodeBlockIsBuiltinDefaultClassConstructorShift; + | static_cast(codeBlock.m_isBuiltinDefaultClassConstructor) << CodeBlockIsBuiltinDefaultClassConstructorShift + | static_cast(codeBlock.m_derivedContextHasInnerSuperCall) << CodeBlockDerivedContextHasInnerSuperCallShift; writer.u32(flags); writer.u8(static_cast(codeBlock.m_parseMode)); writer.u8(codeBlock.m_codeGenerationMode.toRaw()); @@ -5176,6 +5185,7 @@ auto CachedCodeBlock::readTail() const -> Tail s.codeType = bits(CodeBlockCodeTypeShift, 2); s.isBuiltinFunction = bits(CodeBlockIsBuiltinFunctionShift); s.isBuiltinDefaultClassConstructor = bits(CodeBlockIsBuiltinDefaultClassConstructorShift); + s.derivedContextHasInnerSuperCall = bits(CodeBlockDerivedContextHasInnerSuperCallShift); s.parseMode = static_cast(reader.u8()); s.codeGenerationMode = OptionSet::fromRaw(reader.u8()); s.thisRegister = VirtualRegister(reader.i32()); @@ -5328,7 +5338,8 @@ class GenericCacheEntry { // 13: upstream's op_iterator_close_check (with metadata) replaces the fork's, and op_iterator_open / op_iterator_next // keep a String's iteration state in their registers too (321765@main, 322172@main; opcode numbering). // 14: op_profile_control_flow says whether its basic block is in a loop header. - static constexpr uint32_t cachedTypesFormatRevision = 14; + // 15: a function executable's and a code block's flags have derivedContextHasInnerSuperCall. + static constexpr uint32_t cachedTypesFormatRevision = 15; static uint32_t currentCacheVersion() { return computeJSCBytecodeCacheVersion() ^ (cachedTypesFormatRevision * 0x9E3779B9u); } GenericCacheEntry(Encoder& encoder, CachedCodeBlockTag tag) diff --git a/Source/JavaScriptCore/runtime/CodeCache.cpp b/Source/JavaScriptCore/runtime/CodeCache.cpp index eb1806e77fa4c..3be9e672e544f 100644 --- a/Source/JavaScriptCore/runtime/CodeCache.cpp +++ b/Source/JavaScriptCore/runtime/CodeCache.cpp @@ -126,11 +126,13 @@ NEVER_INLINE UnlinkedCodeBlockType* generateUnlinkedCodeBlockImpl(VM& vm, const NeedsClassFieldInitializer needsClassFieldInitializer = NeedsClassFieldInitializer::No; PrivateBrandRequirement privateBrandRequirement = PrivateBrandRequirement::None; + bool derivedContextHasInnerSuperCall = false; if constexpr (std::is_same_v) { needsClassFieldInitializer = executable->needsClassFieldInitializer(); privateBrandRequirement = executable->privateBrandRequirement(); + derivedContextHasInnerSuperCall = executable->derivedContextHasInnerSuperCall(); } - ExecutableInfo executableInfo(false, privateBrandRequirement, false, ConstructorKind::None, scriptMode, SuperBinding::NotNeeded, CacheTypes::parseMode, derivedContextType, needsClassFieldInitializer, isArrowFunctionContext, false, evalContextType); + ExecutableInfo executableInfo(false, privateBrandRequirement, false, ConstructorKind::None, scriptMode, SuperBinding::NotNeeded, CacheTypes::parseMode, derivedContextType, needsClassFieldInitializer, isArrowFunctionContext, false, evalContextType, false, derivedContextHasInnerSuperCall); UnlinkedCodeBlockType* unlinkedCodeBlock = UnlinkedCodeBlockType::create(vm, executableInfo, codeGenerationMode); unlinkedCodeBlock->recordParse(rootNode->features(), rootNode->lexicallyScopedFeatures(), rootNode->hasCapturedVariables()); diff --git a/Source/JavaScriptCore/runtime/DirectEvalExecutable.cpp b/Source/JavaScriptCore/runtime/DirectEvalExecutable.cpp index 20d8c3ff047ed..125eaf65a49e1 100644 --- a/Source/JavaScriptCore/runtime/DirectEvalExecutable.cpp +++ b/Source/JavaScriptCore/runtime/DirectEvalExecutable.cpp @@ -36,7 +36,7 @@ namespace JSC { -NEVER_INLINE DirectEvalExecutable* DirectEvalExecutable::create(JSGlobalObject* globalObject, const SourceCode& source, LexicallyScopedFeatures lexicallyScopedFeatures, DerivedContextType derivedContextType, NeedsClassFieldInitializer needsClassFieldInitializer, PrivateBrandRequirement privateBrandRequirement, bool isArrowFunctionContext, bool isInsideOrdinaryFunction, EvalContextType evalContextType, const TDZEnvironment* variablesUnderTDZ, const PrivateNameEnvironment* privateNameEnvironment) +NEVER_INLINE DirectEvalExecutable* DirectEvalExecutable::create(JSGlobalObject* globalObject, const SourceCode& source, LexicallyScopedFeatures lexicallyScopedFeatures, DerivedContextType derivedContextType, bool derivedContextHasInnerSuperCall, NeedsClassFieldInitializer needsClassFieldInitializer, PrivateBrandRequirement privateBrandRequirement, bool isArrowFunctionContext, bool isInsideOrdinaryFunction, EvalContextType evalContextType, const TDZEnvironment* variablesUnderTDZ, const PrivateNameEnvironment* privateNameEnvironment) { JSC_REMOVABLE_DEFINITION(Interpreter, &DirectEvalExecutable::create); VM& vm = globalObject->vm(); @@ -48,7 +48,7 @@ NEVER_INLINE DirectEvalExecutable* DirectEvalExecutable::create(JSGlobalObject* return nullptr; } - auto* executable = new (NotNull, allocateCell(vm)) DirectEvalExecutable(globalObject, source, lexicallyScopedFeatures, derivedContextType, needsClassFieldInitializer, privateBrandRequirement, isArrowFunctionContext, isInsideOrdinaryFunction, evalContextType); + auto* executable = new (NotNull, allocateCell(vm)) DirectEvalExecutable(globalObject, source, lexicallyScopedFeatures, derivedContextType, derivedContextHasInnerSuperCall, needsClassFieldInitializer, privateBrandRequirement, isArrowFunctionContext, isInsideOrdinaryFunction, evalContextType); executable->finishCreation(vm); ParserError error; @@ -70,9 +70,10 @@ NEVER_INLINE DirectEvalExecutable* DirectEvalExecutable::create(JSGlobalObject* return executable; } -DirectEvalExecutable::DirectEvalExecutable(JSGlobalObject* globalObject, const SourceCode& source, LexicallyScopedFeatures lexicallyScopedFeatures, DerivedContextType derivedContextType, NeedsClassFieldInitializer needsClassFieldInitializer, PrivateBrandRequirement privateBrandRequirement, bool isArrowFunctionContext, bool isInsideOrdinaryFunction, EvalContextType evalContextType) - : EvalExecutable(globalObject, source, lexicallyScopedFeatures, derivedContextType, isArrowFunctionContext, isInsideOrdinaryFunction, evalContextType, needsClassFieldInitializer, privateBrandRequirement) +DirectEvalExecutable::DirectEvalExecutable(JSGlobalObject* globalObject, const SourceCode& source, LexicallyScopedFeatures lexicallyScopedFeatures, DerivedContextType derivedContextType, bool derivedContextHasInnerSuperCall, NeedsClassFieldInitializer needsClassFieldInitializer, PrivateBrandRequirement privateBrandRequirement, bool isArrowFunctionContext, bool isInsideOrdinaryFunction, EvalContextType evalContextType) + : EvalExecutable(globalObject, source, lexicallyScopedFeatures, derivedContextType, derivedContextHasInnerSuperCall, isArrowFunctionContext, isInsideOrdinaryFunction, evalContextType, needsClassFieldInitializer, privateBrandRequirement) { + ASSERT(!derivedContextHasInnerSuperCall || derivedContextType == DerivedContextType::DerivedConstructorContext); ASSERT((needsClassFieldInitializer == NeedsClassFieldInitializer::No && privateBrandRequirement == PrivateBrandRequirement::None) || derivedContextType == DerivedContextType::DerivedConstructorContext); } diff --git a/Source/JavaScriptCore/runtime/DirectEvalExecutable.h b/Source/JavaScriptCore/runtime/DirectEvalExecutable.h index 081325fc8140b..b6a09df141fed 100644 --- a/Source/JavaScriptCore/runtime/DirectEvalExecutable.h +++ b/Source/JavaScriptCore/runtime/DirectEvalExecutable.h @@ -31,9 +31,9 @@ namespace JSC { class DirectEvalExecutable final : public EvalExecutable { public: - static DirectEvalExecutable* create(JSGlobalObject*, const SourceCode&, LexicallyScopedFeatures, DerivedContextType, NeedsClassFieldInitializer, PrivateBrandRequirement, bool isArrowFunctionContext, bool isInsideOrdinaryFunction, EvalContextType, const TDZEnvironment* parentScopeTDZVariables, const PrivateNameEnvironment*); + static DirectEvalExecutable* create(JSGlobalObject*, const SourceCode&, LexicallyScopedFeatures, DerivedContextType, bool derivedContextHasInnerSuperCall, NeedsClassFieldInitializer, PrivateBrandRequirement, bool isArrowFunctionContext, bool isInsideOrdinaryFunction, EvalContextType, const TDZEnvironment* parentScopeTDZVariables, const PrivateNameEnvironment*); private: - DirectEvalExecutable(JSGlobalObject*, const SourceCode&, LexicallyScopedFeatures, DerivedContextType, NeedsClassFieldInitializer, PrivateBrandRequirement, bool isArrowFunctionContext, bool isInsideOrdinaryFunction, EvalContextType); + DirectEvalExecutable(JSGlobalObject*, const SourceCode&, LexicallyScopedFeatures, DerivedContextType, bool derivedContextHasInnerSuperCall, NeedsClassFieldInitializer, PrivateBrandRequirement, bool isArrowFunctionContext, bool isInsideOrdinaryFunction, EvalContextType); }; static_assert(sizeof(DirectEvalExecutable) == sizeof(EvalExecutable)); diff --git a/Source/JavaScriptCore/runtime/EvalExecutable.cpp b/Source/JavaScriptCore/runtime/EvalExecutable.cpp index 844dc498d31eb..446b41fd5cc9c 100644 --- a/Source/JavaScriptCore/runtime/EvalExecutable.cpp +++ b/Source/JavaScriptCore/runtime/EvalExecutable.cpp @@ -33,10 +33,11 @@ namespace JSC { const ClassInfo EvalExecutable::s_info = { "EvalExecutable"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(EvalExecutable) }; -EvalExecutable::EvalExecutable(JSGlobalObject* globalObject, const SourceCode& source, LexicallyScopedFeatures lexicallyScopedFeatures, DerivedContextType derivedContextType, bool isArrowFunctionContext, bool isInsideOrdinaryFunction, EvalContextType evalContextType, NeedsClassFieldInitializer needsClassFieldInitializer, PrivateBrandRequirement privateBrandRequirement) +EvalExecutable::EvalExecutable(JSGlobalObject* globalObject, const SourceCode& source, LexicallyScopedFeatures lexicallyScopedFeatures, DerivedContextType derivedContextType, bool derivedContextHasInnerSuperCall, bool isArrowFunctionContext, bool isInsideOrdinaryFunction, EvalContextType evalContextType, NeedsClassFieldInitializer needsClassFieldInitializer, PrivateBrandRequirement privateBrandRequirement) : Base(globalObject->vm().evalExecutableStructure.get(), globalObject->vm(), source, lexicallyScopedFeatures, derivedContextType, isArrowFunctionContext, isInsideOrdinaryFunction, evalContextType, NoIntrinsic) , m_needsClassFieldInitializer(static_cast(needsClassFieldInitializer)) , m_privateBrandRequirement(static_cast(privateBrandRequirement)) + , m_derivedContextHasInnerSuperCall(derivedContextHasInnerSuperCall) { ASSERT(source.provider()->sourceType() == SourceProviderSourceType::Program); } diff --git a/Source/JavaScriptCore/runtime/EvalExecutable.h b/Source/JavaScriptCore/runtime/EvalExecutable.h index 5d80c52033ccf..82856d2cf97a0 100644 --- a/Source/JavaScriptCore/runtime/EvalExecutable.h +++ b/Source/JavaScriptCore/runtime/EvalExecutable.h @@ -76,6 +76,7 @@ class EvalExecutable : public GlobalExecutable { bool allowDirectEvalCache() const { return unlinkedCodeBlock()->allowDirectEvalCache(); } NeedsClassFieldInitializer needsClassFieldInitializer() const { return static_cast(m_needsClassFieldInitializer); } PrivateBrandRequirement privateBrandRequirement() const { return static_cast(m_privateBrandRequirement); } + bool derivedContextHasInnerSuperCall() const { return m_derivedContextHasInnerSuperCall; } TemplateObjectMap& ensureTemplateObjectMap(VM&); protected: @@ -83,10 +84,11 @@ class EvalExecutable : public GlobalExecutable { friend class ScriptExecutable; using Base::finishCreation; - EvalExecutable(JSGlobalObject*, const SourceCode&, LexicallyScopedFeatures, DerivedContextType, bool isArrowFunctionContext, bool isInsideOrdinaryFunction, EvalContextType, NeedsClassFieldInitializer, PrivateBrandRequirement); + EvalExecutable(JSGlobalObject*, const SourceCode&, LexicallyScopedFeatures, DerivedContextType, bool derivedContextHasInnerSuperCall, bool isArrowFunctionContext, bool isInsideOrdinaryFunction, EvalContextType, NeedsClassFieldInitializer, PrivateBrandRequirement); unsigned m_needsClassFieldInitializer : 1; unsigned m_privateBrandRequirement : 1; + unsigned m_derivedContextHasInnerSuperCall : 1; std::unique_ptr m_templateObjectMap; }; diff --git a/Source/JavaScriptCore/runtime/IndirectEvalExecutable.cpp b/Source/JavaScriptCore/runtime/IndirectEvalExecutable.cpp index e57ec21ad5da8..32ab09027360e 100644 --- a/Source/JavaScriptCore/runtime/IndirectEvalExecutable.cpp +++ b/Source/JavaScriptCore/runtime/IndirectEvalExecutable.cpp @@ -93,7 +93,7 @@ IndirectEvalExecutable* IndirectEvalExecutable::tryCreate(JSGlobalObject* global constexpr bool insideOrdinaryFunction = false; IndirectEvalExecutable::IndirectEvalExecutable(JSGlobalObject* globalObject, const SourceCode& source, LexicallyScopedFeatures lexicallyScopedFeatures, DerivedContextType derivedContextType, bool isArrowFunctionContext, EvalContextType evalContextType) - : EvalExecutable(globalObject, source, lexicallyScopedFeatures, derivedContextType, isArrowFunctionContext, insideOrdinaryFunction, evalContextType, NeedsClassFieldInitializer::No, PrivateBrandRequirement::None) + : EvalExecutable(globalObject, source, lexicallyScopedFeatures, derivedContextType, false, isArrowFunctionContext, insideOrdinaryFunction, evalContextType, NeedsClassFieldInitializer::No, PrivateBrandRequirement::None) { }