From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- .../tests/environments/bug1671563-strict.js | 25 +++ js/src/jit-test/tests/environments/bug1671563.js | 24 +++ js/src/jit-test/tests/environments/bug1671762.js | 20 ++ js/src/jit-test/tests/environments/bug1710089.js | 36 ++++ .../tests/environments/delete-global-var.js | 13 ++ .../tests/environments/evaluate_envChainObject.js | 32 +++ .../environments/lexical-shadows-global-var.js | 9 + .../environments/replace-global-var-with-getter.js | 13 ++ .../tests/environments/strict-eval-bindings.js | 221 +++++++++++++++++++++ 9 files changed, 393 insertions(+) create mode 100644 js/src/jit-test/tests/environments/bug1671563-strict.js create mode 100644 js/src/jit-test/tests/environments/bug1671563.js create mode 100644 js/src/jit-test/tests/environments/bug1671762.js create mode 100644 js/src/jit-test/tests/environments/bug1710089.js create mode 100644 js/src/jit-test/tests/environments/delete-global-var.js create mode 100644 js/src/jit-test/tests/environments/evaluate_envChainObject.js create mode 100644 js/src/jit-test/tests/environments/lexical-shadows-global-var.js create mode 100644 js/src/jit-test/tests/environments/replace-global-var-with-getter.js create mode 100644 js/src/jit-test/tests/environments/strict-eval-bindings.js (limited to 'js/src/jit-test/tests/environments') diff --git a/js/src/jit-test/tests/environments/bug1671563-strict.js b/js/src/jit-test/tests/environments/bug1671563-strict.js new file mode 100644 index 0000000000..5b9e8112ee --- /dev/null +++ b/js/src/jit-test/tests/environments/bug1671563-strict.js @@ -0,0 +1,25 @@ +// A `var` is `undefined` on entering a function body in strict mode too. + +"use strict"; + +load(libdir + "asserts.js"); + +function f(a = class C{}) { + var x; + return x; +} +assertEq(f(), undefined); + +function* g1(a = class C {}) { + var x; + assertEq(x, undefined); +} +g1().next(); + +function* g2(a = class C {}) { + x; + let x; +} +assertThrowsInstanceOf(() => g2().next(), ReferenceError); + + diff --git a/js/src/jit-test/tests/environments/bug1671563.js b/js/src/jit-test/tests/environments/bug1671563.js new file mode 100644 index 0000000000..29a24fbcd9 --- /dev/null +++ b/js/src/jit-test/tests/environments/bug1671563.js @@ -0,0 +1,24 @@ +// The value of a `var` on entering a function is `undefined`, even if a +// default expression uses the same stack slot for something else. + +load(libdir + "asserts.js"); + +function f(a = class C{}) { + var x; + return x; +} +assertEq(f(), undefined); + +function* g1(a = class C {}) { + var x; + assertEq(x, undefined); +} +g1().next(); + +function* g2(a = class C {}) { + x; + let x; +} +assertThrowsInstanceOf(() => g2().next(), ReferenceError); + + diff --git a/js/src/jit-test/tests/environments/bug1671762.js b/js/src/jit-test/tests/environments/bug1671762.js new file mode 100644 index 0000000000..3d7a452474 --- /dev/null +++ b/js/src/jit-test/tests/environments/bug1671762.js @@ -0,0 +1,20 @@ +(function() { + var v00, v01, v02, v03, v04, v05, v06, v07, v08, v09, v0a, v0b, v0c, v0d, v0e, v0f; + var v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v1a, v1b, v1c, v1d, v1e, v1f; + var v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v2a, v2b, v2c, v2d, v2e, v2f; + var v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v3a, v3b, v3c, v3d, v3e, v3f; + var v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v4a, v4b, v4c, v4d, v4e, v4f; + var v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v5a, v5b, v5c, v5d, v5e, v5f; + var v60, v61, v62, v63, v64, v65, v66, v67, v68, v69, v6a, v6b, v6c, v6d, v6e, v6f; + var v70, v71, v72, v73, v74, v75, v76, v77, v78, v79, v7a, v7b, v7c, v7d, v7e, v7f; + try { a1; } catch {} finally {} + var v80, v81, v82, v83, v84, v85, v86, v87, v88, v89, v8a, v8b, v8c, v8d, v8e, v8f; + var v90, v91, v92, v93, v94, v95, v96, v97, v98, v99, v9a, v9b, v9c, v9d, v9e, v9f; + var va0, va1, va2, va3, va4, va5, va6, va7, va8, va9, vaa, vab, vac, vad, vae, vaf; + var vb0, vb1, vb2, vb3, vb4, vb5, vb6, vb7, vb8, vb9, vba, vbb, vbc, vbd, vbe, vbf; + var vc0, vc1, vc2, vc3, vc4, vc5, vc6, vc7, vc8, vc9, vca, vcb, vcc, vcd, vce, vcf; + var vd0, vd1, vd2, vd3, vd4, vd5, vd6, vd7, vd8, vd9, vda, vdb, vdc, vdd, vde, vdf; + var ve0, ve1, ve2, ve3, ve4, ve5, ve6, ve7, ve8, ve9, vea, veb, vec, ved, vee, vef; + var vf0, vf1, vf2, vf3, vf4, vf5, vf6, vf7, vf8, vf9, vfa, vfb, vfc, vfd, vfe, vff; + var v100; +})(); diff --git a/js/src/jit-test/tests/environments/bug1710089.js b/js/src/jit-test/tests/environments/bug1710089.js new file mode 100644 index 0000000000..df043cb2bf --- /dev/null +++ b/js/src/jit-test/tests/environments/bug1710089.js @@ -0,0 +1,36 @@ +// |jit-test| skip-if: getBuildConfiguration()['wasi'] + +var iters = 250; + +// Generate a deeply nested version of: +// function outer() { +// var top_level_var = 42; +// var x3 = 0; +// function f2() { +// var x2 = x3; +// function f1() { +// var x1 = x2; +// function f0() { +// var x0 = x1; +// return top_level_var + x0; +// } +// return f0(); +// } +// return f1(); +// } +// return f2(); +// } + +var src = "return top_level_var + x0; " + +for (var i = 0; i < iters; i++) { + var def = "var x" + i + " = x" + (i+1) + "; "; + src = "function f" + i + "() { " + def + src + "} return f" + i + "();" +} +src = "var x" + iters + " = 0;" + src; +src = "var top_level_var = 42; " + src; + +var outer = Function(src); +for (var i = 0; i < 2; i++) { + assertEq(outer(), 42); +} diff --git a/js/src/jit-test/tests/environments/delete-global-var.js b/js/src/jit-test/tests/environments/delete-global-var.js new file mode 100644 index 0000000000..03b128affe --- /dev/null +++ b/js/src/jit-test/tests/environments/delete-global-var.js @@ -0,0 +1,13 @@ +let assert = assertEq; + +this.x = "OK"; +this.y = "FAIL"; + +for (let i = 0; i < 50; i++) { + assert(x, "OK"); + if (i === 40) { + this.x = "FAIL"; + delete this.x; + this.x = "OK"; + } +} diff --git a/js/src/jit-test/tests/environments/evaluate_envChainObject.js b/js/src/jit-test/tests/environments/evaluate_envChainObject.js new file mode 100644 index 0000000000..d6edb1f43c --- /dev/null +++ b/js/src/jit-test/tests/environments/evaluate_envChainObject.js @@ -0,0 +1,32 @@ +load(libdir + "asserts.js"); + +// Test with envChainObject in current global. +{ + let global = newGlobal(); + let envChainObject = { a: "test1" }; + assertEq(evaluate("a", { global, envChainObject }), "test1"); +} + +// Test with envChainObject in target global. +{ + let global = newGlobal(); + var envChainObject = global.evaluate('({a: "test2"})'); + assertEq(envChainObject.a, "test2"); + assertEq(evaluate("a", { global, envChainObject }), "test2"); +} + +// Unqualified variables objects are not allowed. + +if (!isProxy(evalcx(""))) { + // if --more-compartments option is not give, evalcx returns sandbox, + // which is unqualified variables object. + assertThrowsInstanceOf(() => { + evaluate("10", { envChainObject: evalcx("") }); + }, Error); +} + +// evalReturningScope returns NonSyntacticVariablesObject, which is unqualified +// variables object. +assertThrowsInstanceOf(() => { + evaluate("10", { envChainObject: evalReturningScope("1") }); +}, Error); diff --git a/js/src/jit-test/tests/environments/lexical-shadows-global-var.js b/js/src/jit-test/tests/environments/lexical-shadows-global-var.js new file mode 100644 index 0000000000..9a8a8792b6 --- /dev/null +++ b/js/src/jit-test/tests/environments/lexical-shadows-global-var.js @@ -0,0 +1,9 @@ +this.x = "OK"; + +for (var i = 0; i < 50; i++) { + assertEq(x, "OK"); + if (i === 40) { + this.x = "FAIL"; + evaluate("let x = 'OK';"); + } +} diff --git a/js/src/jit-test/tests/environments/replace-global-var-with-getter.js b/js/src/jit-test/tests/environments/replace-global-var-with-getter.js new file mode 100644 index 0000000000..f79948933a --- /dev/null +++ b/js/src/jit-test/tests/environments/replace-global-var-with-getter.js @@ -0,0 +1,13 @@ +let assert = assertEq; + +this.x = "OK"; + +for (let i = 0; i < 50; i++) { + assert(x, "OK"); + if (i === 40) { + this.x = "FAIL"; + Object.defineProperty(this, "x", { + get() { return "OK"; } + }); + } +} diff --git a/js/src/jit-test/tests/environments/strict-eval-bindings.js b/js/src/jit-test/tests/environments/strict-eval-bindings.js new file mode 100644 index 0000000000..8e60574d6b --- /dev/null +++ b/js/src/jit-test/tests/environments/strict-eval-bindings.js @@ -0,0 +1,221 @@ +// |jit-test| skip-if: !('disassemble' in this) +// Strict direct eval supports static binding of identifiers. + +"use strict"; + +// Check that a script contains a particular bytecode sequence. +// +// `actual` is the output of the `disassemble()` shell builtin. +// `expected` is a semicolon-separated string of opcodes. +// Can include regular expression syntax, e.g. "GetLocal .* x$" +// to match a GetLocal instruction with ` x` at the end of the line. +// `message` is a string to include in the error message if the test fails. +// +function assertBytecode(actual, expected, message) { + // Grab the opcode name and everything after to the end of the line. This + // intentionally includes the expression stack, as that is what makes the + // `GetLocal .* y$` trick work. The disassemble() output is like this: + // + // 00016: 10 GetLocal 0 # x y + // + let actualOps = + actual.split('\n') + .map(s => /^\d{5}: +\d+ +(.*)$/.exec(s)?.[1]) + .filter(x => x !== undefined); + + // Turn the expectations into regular expressions. + let expectedOps = + expected.split(';') + .map(s => { + s = s.trim(); + // If the op is a single word, like `Dup`, add `\b` to rule out + // similarly named ops like `Dup2`. + if (/^\w+$/.test(s)) { + s += "\\b"; + } + return new RegExp("^" + s); + }); + + // The condition on this for-loop is saying, "continue as long as the range + // [i..i+expectedOps.length] is entirely within in the actualOps array". + // Hence the rare use of `<=` in a for-loop! + for (let i = 0; i + expectedOps.length <= actualOps.length; i++) { + if (expectedOps.every((expectRegExp, j) => expectRegExp.test(actualOps[i + j]))) { + // Found a complete match. + return; + } + } + throw new Error(`Assertion failed: ${message}\nexpected ${uneval(expected)}, got:\n${actual}`); +} + + +// --- Tests + +var bytecode; + +// `var`s in strict eval code are statically bound as locals. +eval(` + var pet = "ostrich"; + bytecode = disassemble(); + pet +`); +assertEq(globalThis.hasOwnProperty('pet'), false); +assertBytecode(bytecode, 'String "ostrich"; SetLocal; Pop', + "`pet` is stored in a stack local"); +assertBytecode(bytecode, "GetLocal; SetRval; RetRval", + "`pet` is loaded from the local at the end of the eval code"); + +// Same for top-level `function`s. +eval(` + function banana() { return "potassium"; } + bytecode = disassemble(); +`); +assertEq(globalThis.hasOwnProperty('banana'), false); +assertBytecode(bytecode, 'Lambda .* banana; SetLocal; Pop', + "`banana` is stored in a stack local"); + +// Same for let/const. +eval(` + let a = "ushiko-san"; + const b = "umao-san"; + bytecode = disassemble(); + [a, b] +`); +assertBytecode(bytecode, 'String "ushiko-san"; InitLexical; Pop', + "`let a` is stored in a stack local"); +assertBytecode(bytecode, 'String "umao-san"; InitLexical; Pop', + "`const b` is stored in a stack local"); +assertBytecode(bytecode, 'GetLocal .* a$; InitElemArray; GetLocal .* b$; InitElemArray', + "lexical variables are loaded from stack locals"); + +// Same for arguments and locals in functions declared in strict eval code. +let g = eval(` + function f(a) { + let x = 'x'; + function g(b) { + let y = "wye"; + return [f, a, x, g, b, y]; + } + return g; + } + f(); +`); +bytecode = disassemble(g); +assertBytecode(bytecode, 'GetAliasedVar "f"', + "closed-over eval-scope `function` is accessed via aliased op"); +assertBytecode(bytecode, 'GetAliasedVar "a"', + "closed-over argument is accessed via aliased op"); +assertBytecode(bytecode, 'GetAliasedVar "x"', + "closed-over local `let` variable is accessed via aliased op"); +assertBytecode(bytecode, 'GetAliasedVar "g"', + "closed-over local `function` is accessed via aliased op"); +assertBytecode(bytecode, 'GetArg .* b$', + "non-closed-over arguments are optimized"); +assertBytecode(bytecode, 'GetLocal .* y$', + "non-closed-over locals are optimized"); + +// Closed-over bindings declared in strict eval code are statically bound. +var fac = eval(` + bytecode = disassemble(); + function fac(x) { return x <= 1 ? 1 : x * fac(x - 1); } + fac +`); +assertBytecode(bytecode, 'SetAliasedVar "fac"', + "strict eval code accesses closed-over top-level function using aliased ops"); +assertBytecode(disassemble(fac), 'GetAliasedVar "fac"', + "function in strict eval accesses itself using aliased ops"); + +// References to `this` in an enclosing method are statically bound. +let obj = { + m(s) { return eval(s); } +}; +let result = obj.m(` + bytecode = disassemble(); + this; +`); +assertEq(result, obj); +assertBytecode(bytecode, 'GetAliasedVar ".this"', + "strict eval in a method can access `this` using aliased ops"); + +// Same for `arguments`. +function fn_with_args() { + return eval(` + bytecode = disassemble(); + arguments[0]; + `); +} +assertEq(fn_with_args(117), 117); +assertBytecode(bytecode, 'GetAliasedVar "arguments"', + "strict eval in a function can access `arguments` using aliased ops"); + +// The frontend can emit GName ops in strict eval. +result = eval(` + bytecode = disassemble(); + fn_with_args; +`); +assertEq(result, fn_with_args); +assertBytecode(bytecode, 'GetGName "fn_with_args"', + "strict eval code can optimize access to globals"); + +// Even within a function. +function test_globals_in_function() { + result = eval(` + bytecode = disassemble(); + fn_with_args; + `); + assertEq(result, fn_with_args); + assertBytecode(bytecode, 'GetGName "fn_with_args"', + "strict eval code in a function can optimize access to globals"); +} +test_globals_in_function(); + +// Nested eval is no obstacle. +{ + let outer = "outer"; + const f = function (code, a, b) { + return eval(code); + }; + let result = f(` + eval("bytecode = disassemble();\\n" + + "outer += a + b;\\n"); + `, 3, 4); + assertEq(outer, "outer7"); + assertBytecode(bytecode, 'GetAliasedVar "outer"', + "access to outer bindings is optimized even through nested strict evals"); + assertBytecode(bytecode, 'GetAliasedVar "a"', + "access to outer bindings is optimized even through nested strict evals"); + assertBytecode(bytecode, 'SetAliasedVar "outer"', + "assignment to outer bindings is optimized even through nested strict evals"); +} + +// Assignment to an outer const is handled correctly. +{ + const doNotSetMe = "i already have a value, thx"; + let f = eval(`() => { doNotSetMe = 34; }`); + assertBytecode(disassemble(f), 'ThrowSetConst "doNotSetMe"', + "assignment to outer const in strict eval code emits ThrowSetConst"); +} + +// OK, there are other scopes but let's just do one more: the +// computed-property-name scope. +{ + let stashed; + (class C { + [( + eval(` + var secret = () => C; + stashed = () => secret; + `), + "method" + )]() { + return "ok"; + } + }); + + bytecode = disassemble(stashed()); + assertBytecode(bytecode, 'GetAliasedVar "C"', + "access to class name uses aliased ops"); + let C = stashed()(); + assertEq(new C().method(), "ok"); +} + -- cgit v1.2.3