diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /js/src/jit-test/tests/gc | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/jit-test/tests/gc')
393 files changed, 8453 insertions, 0 deletions
diff --git a/js/src/jit-test/tests/gc/bug-1004457.js b/js/src/jit-test/tests/gc/bug-1004457.js new file mode 100644 index 0000000000..80e1150f34 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1004457.js @@ -0,0 +1,3 @@ +var argObj = (function () { return arguments })(); +gczeal(4); +delete argObj.callee; diff --git a/js/src/jit-test/tests/gc/bug-1016016.js b/js/src/jit-test/tests/gc/bug-1016016.js new file mode 100644 index 0000000000..7fb6e64d12 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1016016.js @@ -0,0 +1,16 @@ +// |jit-test| error:ReferenceError +toPrinted(this.reason); +function toPrinted(value) { + return value = String(value); +} +var lfcode = new Array(); +lfcode.push = loadFile; +lfcode.push("enableTrackAllocations();"); +lfcode.push("\ +gczeal(9, 2);\ +newGlobal();\ +''.addDebuggee(g1);\ +"); +function loadFile(lfVarx) { + evaluate(lfVarx, { noScriptRval : true, isRunOnce : true }); +} diff --git a/js/src/jit-test/tests/gc/bug-1017141.js b/js/src/jit-test/tests/gc/bug-1017141.js new file mode 100644 index 0000000000..9c4533924c --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1017141.js @@ -0,0 +1,25 @@ +var min = gcparam('minEmptyChunkCount'); +var max = gcparam('maxEmptyChunkCount'); + +gcparam('minEmptyChunkCount', 10); +gcparam('maxEmptyChunkCount', 20); +assertEq(gcparam('minEmptyChunkCount'), 10); +assertEq(gcparam('maxEmptyChunkCount'), 20); +gc(); + +/* We maintain the invariant that maxEmptyChunkCount >= minEmptyChunkCount. */ +gcparam('minEmptyChunkCount', 30); +assertEq(gcparam('minEmptyChunkCount'), 30); +assertEq(gcparam('maxEmptyChunkCount'), 30); +gc(); + +gcparam('maxEmptyChunkCount', 5); +assertEq(gcparam('minEmptyChunkCount'), 5); +assertEq(gcparam('maxEmptyChunkCount'), 5); +gc(); + +gcparam('minEmptyChunkCount', min); +gcparam('maxEmptyChunkCount', max); +assertEq(gcparam('minEmptyChunkCount'), min); +assertEq(gcparam('maxEmptyChunkCount'), max); +gc(); diff --git a/js/src/jit-test/tests/gc/bug-1028863.js b/js/src/jit-test/tests/gc/bug-1028863.js new file mode 100644 index 0000000000..699c02fb75 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1028863.js @@ -0,0 +1,3 @@ +function writeTestCaseResult( expect, actual, string ) {} +schedulegc(10); +if (saveStack() == 3) done = true; diff --git a/js/src/jit-test/tests/gc/bug-1032206.js b/js/src/jit-test/tests/gc/bug-1032206.js new file mode 100644 index 0000000000..c58cc53a8d --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1032206.js @@ -0,0 +1,3 @@ +gczeal(4); +var symbols = [Symbol(), Symbol("comet"), Symbol.for("moon"), Symbol.iterator, 0]; +for (var a of symbols) {} diff --git a/js/src/jit-test/tests/gc/bug-1035371.js b/js/src/jit-test/tests/gc/bug-1035371.js new file mode 100644 index 0000000000..b42a74656d --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1035371.js @@ -0,0 +1,4 @@ +x = function() {}; +y = new WeakMap; +selectforgc({});; +y.set(x, Symbol()); diff --git a/js/src/jit-test/tests/gc/bug-1039516.js b/js/src/jit-test/tests/gc/bug-1039516.js new file mode 100644 index 0000000000..15f2a994d9 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1039516.js @@ -0,0 +1,6 @@ +gczeal(9); +Symbol.for("a"); +gcslice(1); +var a = Symbol.for("a"); +gcslice(); +print(Symbol.keyFor(a)); diff --git a/js/src/jit-test/tests/gc/bug-1053676.js b/js/src/jit-test/tests/gc/bug-1053676.js new file mode 100644 index 0000000000..bebada5767 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1053676.js @@ -0,0 +1,11 @@ +// |jit-test| --ion-eager + +var x +(function() { + x +}()); +verifyprebarriers(); +x = x * 0 +x = Symbol(); +gc(); +evalcx("x=1", this); diff --git a/js/src/jit-test/tests/gc/bug-1055219.js b/js/src/jit-test/tests/gc/bug-1055219.js new file mode 100644 index 0000000000..421aa55ae3 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1055219.js @@ -0,0 +1,5 @@ +gczeal(13); +function A() {}; +A.prototype = []; +function B() {}; +B.prototype = new A(); diff --git a/js/src/jit-test/tests/gc/bug-1070638.js b/js/src/jit-test/tests/gc/bug-1070638.js new file mode 100644 index 0000000000..23793765cf --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1070638.js @@ -0,0 +1,20 @@ +function m() { + for (var j = 0; j < 99; ++j) { + for (var k = 0; k < 99; ++k) { + try { + undefined()() + } catch (e) {} + } + } +} +m() +m() +m() +m() +for (var j = 0; j < 99; ++j) { + for (var k = 0; k < 99; ++k) { + try { + gcslice(1)() + } catch (e) {} + } +} diff --git a/js/src/jit-test/tests/gc/bug-1075546.js b/js/src/jit-test/tests/gc/bug-1075546.js new file mode 100644 index 0000000000..b9fdb510b6 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1075546.js @@ -0,0 +1,30 @@ +for (var i = 0; i < 200; ++i) { + Object.getOwnPropertyNames(undefined + ""); +} +function p(s) { + for (var i = 0; i < s.length; i++) { + s.charCodeAt(i); + } +} +function m(f) { + var a = []; + for (var j = 0; j < 700; ++j) { + try { + f() + } catch (e) { + a.push(e.toString()); + } + } + p(JSON.stringify(a)); +} +f = Function("\ + function f() {\ + functionf\n{}\ + }\ + m(f);\ +"); +f(); +f(); +for (var x = 0; x < 99; x++) { + newGlobal() +} diff --git a/js/src/jit-test/tests/gc/bug-1104162.js b/js/src/jit-test/tests/gc/bug-1104162.js new file mode 100644 index 0000000000..33af69cda0 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1104162.js @@ -0,0 +1,7 @@ +gczeal(11); +g = newGlobal({newCompartment: true}) +g.eval("undefined;function f(){}") +Debugger(g).onDebuggerStatement = function(x) { + x.eval("f").return.script.setBreakpoint(0, {}) +} +g.eval("debugger") diff --git a/js/src/jit-test/tests/gc/bug-1108007.js b/js/src/jit-test/tests/gc/bug-1108007.js new file mode 100644 index 0000000000..35daaefc91 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1108007.js @@ -0,0 +1,23 @@ +// |jit-test| --no-threads; --no-ion; --no-baseline; skip-if: !('gczeal' in this) + +gczeal(2); +(function() { + evaluate(cacheEntry((function() { + return "(new String(\"\"))" + })()), Object.create({ global: newGlobal({ cloneSingletons: true }) }, { + saveIncrementalBytecode: { + value: true + } + })) +})(); +[[0], [0], [0], [0], [0], [0], [0], [0], [0], [0], + [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], + [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], + [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], + [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], + [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], + [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], + [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], + [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], + [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], + [0], [0], [0], [0]]; diff --git a/js/src/jit-test/tests/gc/bug-1108836.js b/js/src/jit-test/tests/gc/bug-1108836.js new file mode 100644 index 0000000000..e8d32b3e3c --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1108836.js @@ -0,0 +1,9 @@ +const root = newGlobal(); +var g = newGlobal(); +for (var indexI = 0; indexI <= 65535; indexI++) { + eval("/*var " + String.fromCharCode(indexI) + "xx = 1*/"); +} +for (var i = 0; i < 100; ++i) { + gc(); + gcslice(1000000); +} diff --git a/js/src/jit-test/tests/gc/bug-1109913.js b/js/src/jit-test/tests/gc/bug-1109913.js new file mode 100644 index 0000000000..c84db18c4b --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1109913.js @@ -0,0 +1,7 @@ +// |jit-test| allow-oom; allow-unhandlable-oom + +gcparam("maxBytes", gcparam("gcBytes")); +eval(` + gczeal(2, 1); + newGlobal(); +`); diff --git a/js/src/jit-test/tests/gc/bug-1109922.js b/js/src/jit-test/tests/gc/bug-1109922.js new file mode 100644 index 0000000000..8bd5c52da7 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1109922.js @@ -0,0 +1,6 @@ +if (this.hasOwnProperty("Intl")) { + gczeal(14); + b = {}; + b.__proto__ = evalcx("lazy"); + (function m(b) {})(b.Intl.Collator(0)) +} diff --git a/js/src/jit-test/tests/gc/bug-1123648.js b/js/src/jit-test/tests/gc/bug-1123648.js new file mode 100644 index 0000000000..54226949a0 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1123648.js @@ -0,0 +1,4 @@ +// |jit-test| skip-if: !('ctypes' in this) + +gczeal(14, 1); +ctypes.FunctionType(ctypes.default_abi, ctypes.void_t, []).ptr; diff --git a/js/src/jit-test/tests/gc/bug-1124563.js b/js/src/jit-test/tests/gc/bug-1124563.js new file mode 100644 index 0000000000..73599025b8 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1124563.js @@ -0,0 +1,4 @@ +try { + gc(0, 'shrinking')({x: 0}) +} catch (e) {} +eval("({x: 0, x: 0})[{x: 0}]") diff --git a/js/src/jit-test/tests/gc/bug-1124653.js b/js/src/jit-test/tests/gc/bug-1124653.js new file mode 100644 index 0000000000..8dca8dc330 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1124653.js @@ -0,0 +1,5 @@ +var o = {}; +gczeal(14); +for (var i = 0; i < 200; i++) { + with (o) { } +} diff --git a/js/src/jit-test/tests/gc/bug-1136597.js b/js/src/jit-test/tests/gc/bug-1136597.js new file mode 100644 index 0000000000..075933acf8 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1136597.js @@ -0,0 +1,25 @@ +// |jit-test| error:ReferenceError +var evalInFrame = (function (global) { + var dbgGlobal = newGlobal({newCompartment: true}); + var dbg = new dbgGlobal.Debugger(); + return function evalInFrame(upCount, code) { + dbg.addDebuggee(global); + }; +})(this); +var gTestcases = new Array(); +var gTc = gTestcases.length; +function TestCase() { + return gTestcases[gTc++] = this; +} +function checkCollation(extensionCoValue, usageValue) { + var collator = new Intl.Collator(["de-DE"]); + collator.resolvedOptions().collation; +} +checkCollation(undefined, "sort"); +checkCollation(); +for ( addpow = 0; addpow < 33; addpow++ ) { + new TestCase(); +} +evalInFrame(0, "i(true)", true); +gc(3, 'shrinking') +eval("gc(); h = g1"); diff --git a/js/src/jit-test/tests/gc/bug-1137341.js b/js/src/jit-test/tests/gc/bug-1137341.js new file mode 100644 index 0000000000..baac2d7bb0 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1137341.js @@ -0,0 +1,11 @@ +// |jit-test| skip-if: helperThreadCount() === 0 + +gczeal(0); +gc(); + +schedulezone(this); +startgc(0, "shrinking"); +var g = newGlobal(); +g.offThreadCompileToStencil('debugger;', {}); +var stencil = g.finishOffThreadStencil(); +g.evalStencil(stencil); diff --git a/js/src/jit-test/tests/gc/bug-1143706.js b/js/src/jit-test/tests/gc/bug-1143706.js new file mode 100644 index 0000000000..2ae7930cb4 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1143706.js @@ -0,0 +1,2 @@ +gczeal(14, 1); +var g = newGlobal(); diff --git a/js/src/jit-test/tests/gc/bug-1144738.js b/js/src/jit-test/tests/gc/bug-1144738.js new file mode 100644 index 0000000000..ef121faaf8 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1144738.js @@ -0,0 +1,32 @@ +// |jit-test| error: ReferenceError; --fuzzing-safe; --thread-count=1; --ion-eager +const ALL_TESTS = [ + "CONTEXT_OBJECT_PROPERTY_DOT_REFERENCE_IS_FUNCTION", + ]; +function r(keyword, tests) { + function Reserved(keyword, tests) { + this.keyword = keyword; + if (tests) + this.tests = tests; + else + this.tests = ALL_TESTS; + } + return new Reserved(keyword, tests); +} +for (var i = 2; i >= 0; i--) { + gc(); + gczeal(14, 17); + [ + r("break"), + r("case"), + r("catch"), + r("continue"), + ]; + [ + r("true"), + r("null"), + r("each"), + r("let") + ]; +} +Failure; + diff --git a/js/src/jit-test/tests/gc/bug-1146696.js b/js/src/jit-test/tests/gc/bug-1146696.js new file mode 100644 index 0000000000..02df084673 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1146696.js @@ -0,0 +1,21 @@ +// |jit-test| --no-ggc; allow-oom; allow-unhandlable-oom +gc(); +dbg1 = new Debugger(); +root2 = newGlobal({newCompartment: true}); +dbg1.memory.onGarbageCollection = function(){} +dbg1.addDebuggee(root2); +for (var j = 0; j < 9999; ++j) { + try { + a + } catch (e) {} +} +gcparam("maxBytes", gcparam("gcBytes") + 8000); +function g(i) { + if (i == 0) + return; + var x = ""; + function f() {} + eval(''); + g(i - 1); +} +g(100); diff --git a/js/src/jit-test/tests/gc/bug-1148383.js b/js/src/jit-test/tests/gc/bug-1148383.js new file mode 100644 index 0000000000..486e6e0ba5 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1148383.js @@ -0,0 +1,17 @@ +// This testcase tests setting object metadata for objects created from JIT +// code. +opts = getJitCompilerOptions(); +if (!opts['ion.enable'] || !opts['baseline.enable']) + quit(); + +function TestCase() {} +function reportCompare () { + var output = ""; + var testcase = new TestCase(); + testcase.reason = output; +} +reportCompare(); +gczeal(4, 1000); +enableShellAllocationMetadataBuilder(); +for (var i = 0; i < 10000; ++i) + reportCompare(); diff --git a/js/src/jit-test/tests/gc/bug-1155455.js b/js/src/jit-test/tests/gc/bug-1155455.js new file mode 100644 index 0000000000..c9b8040884 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1155455.js @@ -0,0 +1,16 @@ +// |jit-test| error: TypeError; skip-if: !('gczeal' in this) +var g = newGlobal(); +gczeal(10, 2) +var dbg = Debugger(g); +dbg.onDebuggerStatement = function (frame1) { + function hit(frame2) { + return hit[0] = "mutated"; + } + var s = frame1.script; + var offs = s.getLineOffsets(g.line0 + 2); + for (var i = 0; i < offs.length; i++) + s.setBreakpoint(offs[i], {hit: hit}); + return; +}; +var lfGlobal = newGlobal(); +g.eval("var line0 = Error().lineNumber;\n debugger;\nx = 1;\n"); diff --git a/js/src/jit-test/tests/gc/bug-1157577.js b/js/src/jit-test/tests/gc/bug-1157577.js new file mode 100644 index 0000000000..2ec7556ea3 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1157577.js @@ -0,0 +1,4 @@ +x = evalcx(''); +gcslice(10); +for (var p in x) {} + diff --git a/js/src/jit-test/tests/gc/bug-1161303.js b/js/src/jit-test/tests/gc/bug-1161303.js new file mode 100644 index 0000000000..872f137632 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1161303.js @@ -0,0 +1,7 @@ +function f(x) { + for (var i = 0; i < 100000; i++ ) { + [(x, 2)]; + try { g(); } catch (t) {} + } +} +f(2); diff --git a/js/src/jit-test/tests/gc/bug-1161968.js b/js/src/jit-test/tests/gc/bug-1161968.js new file mode 100644 index 0000000000..5f83aa510c --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1161968.js @@ -0,0 +1,12 @@ +// |jit-test| skip-if: !('gczeal' in this) + +// This test case is a simplified version of debug/Source-invisible.js. + +gczeal(2,21); + +var gi = newGlobal(); + +var gv = newGlobal(); +gi.evaluate('function f() {}', {global: gv}); + +var dbg = new Debugger; diff --git a/js/src/jit-test/tests/gc/bug-1165966.js b/js/src/jit-test/tests/gc/bug-1165966.js new file mode 100644 index 0000000000..79c58274cd --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1165966.js @@ -0,0 +1,6 @@ +// |jit-test| --no-ion; skip-if: !('oomTest' in this) + +var g = newGlobal(); +oomTest(function() { + Debugger(g); +}); diff --git a/js/src/jit-test/tests/gc/bug-1171909.js b/js/src/jit-test/tests/gc/bug-1171909.js new file mode 100644 index 0000000000..755c6ff89d --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1171909.js @@ -0,0 +1,3 @@ +// |jit-test| skip-if: !('oomTest' in this) + +oomTest((function(x) { assertEq(x + y + ex, 25); })); diff --git a/js/src/jit-test/tests/gc/bug-1175755.js b/js/src/jit-test/tests/gc/bug-1175755.js new file mode 100644 index 0000000000..1b6960fb8c --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1175755.js @@ -0,0 +1,6 @@ +// |jit-test| allow-oom; allow-unhandlable-oom; skip-if: !('oomAfterAllocations' in this) + +setGCCallback({ + action: "majorGC", +}); +oomAfterAllocations(50); diff --git a/js/src/jit-test/tests/gc/bug-1177778.js b/js/src/jit-test/tests/gc/bug-1177778.js new file mode 100644 index 0000000000..155763f3a2 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1177778.js @@ -0,0 +1,15 @@ +// |jit-test| skip-if: !('setGCCallback' in this) + +setGCCallback({ + action: "majorGC", + phases: "both" +}); +var g = newGlobal({newCompartment: true}); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +g.eval("function h() { debugger; }"); +dbg.onDebuggerStatement = function(hframe) { + var env = hframe.older.environment; +}; +g.eval("h();"); + diff --git a/js/src/jit-test/tests/gc/bug-1191576.js b/js/src/jit-test/tests/gc/bug-1191576.js new file mode 100644 index 0000000000..6346905256 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1191576.js @@ -0,0 +1,14 @@ +// |jit-test| allow-oom; skip-if: !('gczeal' in this && 'oomAfterAllocations' in this) + +var lfcode = new Array(); +gczeal(14); +loadFile(` +for (let e of Object.values(newGlobal())) { + if (oomAfterAllocations(100)) + continue; +} +`); +function loadFile(lfVarx) { + for (lfLocal in this) {} + evaluate(lfVarx); +} diff --git a/js/src/jit-test/tests/gc/bug-1206677.js b/js/src/jit-test/tests/gc/bug-1206677.js new file mode 100644 index 0000000000..a0d2ff3a1f --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1206677.js @@ -0,0 +1,12 @@ +// |jit-test| skip-if: !('oomTest' in this) || helperThreadCount() === 0 + +var lfGlobal = newGlobal(); +for (lfLocal in this) { + if (!(lfLocal in lfGlobal)) { + lfGlobal[lfLocal] = this[lfLocal]; + } +} +const script = 'oomTest(() => getBacktrace({args: true, locals: "123795", thisprops: true}));'; +lfGlobal.offThreadCompileToStencil(script); +var stencil = lfGlobal.finishOffThreadStencil(); +lfGlobal.evalStencil(stencil); diff --git a/js/src/jit-test/tests/gc/bug-1208994.js b/js/src/jit-test/tests/gc/bug-1208994.js new file mode 100644 index 0000000000..12c24f62af --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1208994.js @@ -0,0 +1,3 @@ +// |jit-test| skip-if: !('oomTest' in this) + +oomTest(() => getBacktrace({args: oomTest[load+1], locals: true, thisprops: true})); diff --git a/js/src/jit-test/tests/gc/bug-1209001.js b/js/src/jit-test/tests/gc/bug-1209001.js new file mode 100644 index 0000000000..a737224d0d --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1209001.js @@ -0,0 +1,3 @@ +// |jit-test| skip-if: !('oomTest' in this) + +oomTest(() => parseModule('import v from "mod";')); diff --git a/js/src/jit-test/tests/gc/bug-1210607.js b/js/src/jit-test/tests/gc/bug-1210607.js new file mode 100644 index 0000000000..15312c810a --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1210607.js @@ -0,0 +1,6 @@ +// |jit-test| allow-oom; skip-if: !('oomAfterAllocations' in this) + +var g = newGlobal({newCompartment: true}); +x = Debugger(g); +selectforgc(g); +oomAfterAllocations(1); diff --git a/js/src/jit-test/tests/gc/bug-1214006.js b/js/src/jit-test/tests/gc/bug-1214006.js new file mode 100644 index 0000000000..ed2c6468dc --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1214006.js @@ -0,0 +1,7 @@ +// |jit-test| allow-oom; skip-if: !('oomTest' in this) + +function f() { + eval("(function() y)()"); +} +oomTest(f); +fullcompartmentchecks(true); diff --git a/js/src/jit-test/tests/gc/bug-1214781.js b/js/src/jit-test/tests/gc/bug-1214781.js new file mode 100644 index 0000000000..d18845812c --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1214781.js @@ -0,0 +1,7 @@ +// |jit-test| allow-oom; skip-if: !('oomTest' in this) + +try { + gcparam("maxBytes", gcparam("gcBytes")); + newGlobal(""); +} catch (e) {}; +oomTest(function() {}) diff --git a/js/src/jit-test/tests/gc/bug-1214846.js b/js/src/jit-test/tests/gc/bug-1214846.js new file mode 100644 index 0000000000..23b5b9fe94 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1214846.js @@ -0,0 +1,7 @@ +// |jit-test| skip-if: !('oomTest' in this) || helperThreadCount() === 0 + +enableGeckoProfiling(); +var s = newGlobal(); +s.offThreadCompileToStencil('oomTest(() => {});'); +var stencil = s.finishOffThreadStencil(); +s.evalStencil(stencil); diff --git a/js/src/jit-test/tests/gc/bug-1215363-1.js b/js/src/jit-test/tests/gc/bug-1215363-1.js new file mode 100644 index 0000000000..3ed21e1f9a --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1215363-1.js @@ -0,0 +1,3 @@ +// |jit-test| skip-if: !('oomTest' in this) + +oomTest(() => parseModule(10)); diff --git a/js/src/jit-test/tests/gc/bug-1215363-2.js b/js/src/jit-test/tests/gc/bug-1215363-2.js new file mode 100644 index 0000000000..4b51a5a96d --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1215363-2.js @@ -0,0 +1,6 @@ +// |jit-test| skip-if: !('oomTest' in this) + +var lfcode = new Array(); +oomTest((function(x) { + assertEq(...Object); +})); diff --git a/js/src/jit-test/tests/gc/bug-1215363-3.js b/js/src/jit-test/tests/gc/bug-1215363-3.js new file mode 100644 index 0000000000..33495af2e1 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1215363-3.js @@ -0,0 +1,4 @@ +// |jit-test| skip-if: !('oomTest' in this) + +var lfcode = new Array(); +oomTest(() => getBacktrace({})); diff --git a/js/src/jit-test/tests/gc/bug-1215678.js b/js/src/jit-test/tests/gc/bug-1215678.js new file mode 100644 index 0000000000..8a9723776b --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1215678.js @@ -0,0 +1,10 @@ +// |jit-test| error: ReferenceError +if (!('oomTest' in this)) + a; + +enableShellAllocationMetadataBuilder() +oomTest(() => { + newGlobal() +}) +gczeal(9, 1); +a; diff --git a/js/src/jit-test/tests/gc/bug-1216607.js b/js/src/jit-test/tests/gc/bug-1216607.js new file mode 100644 index 0000000000..1afac7faab --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1216607.js @@ -0,0 +1,18 @@ +// |jit-test| skip-if: !('oomTest' in this) + +enableGeckoProfilingWithSlowAssertions(); +try { +(function() { + while (n--) { + } +})(); +} catch(exc1) {} +function arrayProtoOutOfRange() { + function f(obj) {} + function test() { + for (var i = 0; i < 1000; i++) + var r = f(i % 2 ? a : b); + } + test(); +} +oomTest(arrayProtoOutOfRange); diff --git a/js/src/jit-test/tests/gc/bug-1218900-2.js b/js/src/jit-test/tests/gc/bug-1218900-2.js new file mode 100644 index 0000000000..6e99475266 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1218900-2.js @@ -0,0 +1,3 @@ +// |jit-test| error: Error +startTimingMutator(); +startTimingMutator(); diff --git a/js/src/jit-test/tests/gc/bug-1218900.js b/js/src/jit-test/tests/gc/bug-1218900.js new file mode 100644 index 0000000000..a3bc82bd2b --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1218900.js @@ -0,0 +1,8 @@ +// |jit-test| --fuzzing-safe +readline = function() {}; +Function.prototype.toString = function() { + for (var i = 0; i < 2; i++) { + this() + } +}; +getBacktrace({thisprops: true}); diff --git a/js/src/jit-test/tests/gc/bug-1221359.js b/js/src/jit-test/tests/gc/bug-1221359.js new file mode 100644 index 0000000000..dcbafeb446 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1221359.js @@ -0,0 +1,6 @@ +// |jit-test| skip-if: !('oomTest' in this) + +oomTest(() => getBacktrace({ + locals: true, + thisprops: true +})); diff --git a/js/src/jit-test/tests/gc/bug-1221747.js b/js/src/jit-test/tests/gc/bug-1221747.js new file mode 100644 index 0000000000..5ff33dd64e --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1221747.js @@ -0,0 +1,6 @@ +// |jit-test| --dump-bytecode; skip-if: !('oomTest' in this) + +function f() { + eval("(function() {})()"); +} +oomTest(f); diff --git a/js/src/jit-test/tests/gc/bug-1223021.js b/js/src/jit-test/tests/gc/bug-1223021.js new file mode 100644 index 0000000000..bbc40aa1fb --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1223021.js @@ -0,0 +1,11 @@ +// |jit-test| skip-if: !('oomTest' in this) + +function f() { + return this === null; +}; + +function g() { + if (!f.apply(9)) {} +} + +oomTest(g); diff --git a/js/src/jit-test/tests/gc/bug-1224710.js b/js/src/jit-test/tests/gc/bug-1224710.js new file mode 100644 index 0000000000..9cb9aa7cd3 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1224710.js @@ -0,0 +1,13 @@ +// |jit-test| skip-if: !('oomTest' in this) + +oomTest(function() { + eval("\ + function g() {\ + \"use asm\";\ + function f(d) {\ + d = +d;\ + print(.0 + d);\ + }\ + }\ + ") +}) diff --git a/js/src/jit-test/tests/gc/bug-1226896.js b/js/src/jit-test/tests/gc/bug-1226896.js new file mode 100644 index 0000000000..0b8c513cc8 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1226896.js @@ -0,0 +1,6 @@ +// |jit-test| --ion-pruning=on; skip-if: !('oomTest' in this) + +oomTest(() => { + var g = newGlobal({sameZoneAs: this}); + g.eval("(function() {})()"); +}); diff --git a/js/src/jit-test/tests/gc/bug-1231386.js b/js/src/jit-test/tests/gc/bug-1231386.js new file mode 100644 index 0000000000..c2dc55b734 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1231386.js @@ -0,0 +1,18 @@ +// |jit-test| slow; skip-if: !('oomTest' in this) + +function f1() {} +function f2() {} +r = [function() {}, function() {}, [], function() {}, f1, function() {}, f2]; +l = [0]; +function f3() { + return { + a: 0 + }; +} +var x = f3(); +var h = newGlobal({newCompartment: true}); +var dbg = new Debugger; +dbg.addDebuggee(h); +oomTest(() => getBacktrace({ + thisprops: gc() +})); diff --git a/js/src/jit-test/tests/gc/bug-1232386.js b/js/src/jit-test/tests/gc/bug-1232386.js new file mode 100644 index 0000000000..82a33a7ec4 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1232386.js @@ -0,0 +1,10 @@ +// |jit-test| allow-oom; skip-if: !('oomTest' in this) + +var dbg = new Debugger; +dbg.onNewGlobalObject = function(global) { + global.seen = true; +}; + +oomTest(function() { + newGlobal({sameZoneAs: this}) +}); diff --git a/js/src/jit-test/tests/gc/bug-1234410.js b/js/src/jit-test/tests/gc/bug-1234410.js new file mode 100644 index 0000000000..fe400f8013 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1234410.js @@ -0,0 +1,9 @@ +// |jit-test| skip-if: !('oomTest' in this) + +enableGeckoProfiling(); +oomTest(() => { + try { + for (var quit of oomTest.gcparam("//").ArrayBuffer(1)) {} + } catch (e) {} +}); + diff --git a/js/src/jit-test/tests/gc/bug-1236473.js b/js/src/jit-test/tests/gc/bug-1236473.js new file mode 100644 index 0000000000..0051e789a6 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1236473.js @@ -0,0 +1,7 @@ +// |jit-test| skip-if: !('oomTest' in this) + +oomTest(() => { + offThreadCompileToStencil(`try {} catch (NaN) {}`); + var stencil = finishOffThreadStencil(); + evalStencil(stencil); +}); diff --git a/js/src/jit-test/tests/gc/bug-1237153.js b/js/src/jit-test/tests/gc/bug-1237153.js new file mode 100644 index 0000000000..bdb0b011f2 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1237153.js @@ -0,0 +1,2 @@ +// |jit-test| error: Error +gcparam("sliceTimeBudgetMS", -1); diff --git a/js/src/jit-test/tests/gc/bug-1238548.js b/js/src/jit-test/tests/gc/bug-1238548.js new file mode 100644 index 0000000000..4f2e1e51d9 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1238548.js @@ -0,0 +1,2 @@ +// |jit-test| error: Error +gcparam("highFrequencySmallHeapGrowth", 1); diff --git a/js/src/jit-test/tests/gc/bug-1238555.js b/js/src/jit-test/tests/gc/bug-1238555.js new file mode 100644 index 0000000000..4b9963292e --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1238555.js @@ -0,0 +1,11 @@ +// |jit-test| skip-if: !('oomTest' in this) + +oomTest( + function x() { + try { + eval('let ') + } catch (ex) { + (function() {})() + } + } +); diff --git a/js/src/jit-test/tests/gc/bug-1238575-2.js b/js/src/jit-test/tests/gc/bug-1238575-2.js new file mode 100644 index 0000000000..9fe011efa1 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1238575-2.js @@ -0,0 +1,3 @@ +// |jit-test| skip-if: !('oomTest' in this) + +oomTest(() => evalInWorker("1")); diff --git a/js/src/jit-test/tests/gc/bug-1238575.js b/js/src/jit-test/tests/gc/bug-1238575.js new file mode 100644 index 0000000000..8e6a629d9f --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1238575.js @@ -0,0 +1,5 @@ +// |jit-test| allow-oom; allow-unhandlable-oom; skip-if: !('oomAfterAllocations' in this) + +oomAfterAllocations(5) +gcslice(11); +evalInWorker("1"); diff --git a/js/src/jit-test/tests/gc/bug-1238582.js b/js/src/jit-test/tests/gc/bug-1238582.js new file mode 100644 index 0000000000..b5dad7a64d --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1238582.js @@ -0,0 +1,3 @@ +// |jit-test| skip-if: !('oomTest' in this) + +oomTest(() => { let a = [2147483651]; [a[0], a[undefined]].sort(); }); diff --git a/js/src/jit-test/tests/gc/bug-1240503.js b/js/src/jit-test/tests/gc/bug-1240503.js new file mode 100644 index 0000000000..167752962b --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1240503.js @@ -0,0 +1,7 @@ +// |jit-test| skip-if: !('oomTest' in this) + +function arrayProtoOutOfRange() { + for (let [] = () => r, get;;) + var r = f(i % 2 ? a : b); +} +oomTest(arrayProtoOutOfRange); diff --git a/js/src/jit-test/tests/gc/bug-1240527.js b/js/src/jit-test/tests/gc/bug-1240527.js new file mode 100644 index 0000000000..ca4e0e3eb6 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1240527.js @@ -0,0 +1,8 @@ +// |jit-test| skip-if: helperThreadCount() === 0 || !('oomTest' in this) + +offThreadCompileToStencil(` + oomTest(() => "".search(/d/)); + fullcompartmentchecks(3); +`); +var stencil = finishOffThreadStencil(); +evalStencil(stencil); diff --git a/js/src/jit-test/tests/gc/bug-1241731.js b/js/src/jit-test/tests/gc/bug-1241731.js new file mode 100644 index 0000000000..015c7f3e67 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1241731.js @@ -0,0 +1,3 @@ +// |jit-test| skip-if: !('oomTest' in this) + +oomTest(() => serialize(0, [{}])); diff --git a/js/src/jit-test/tests/gc/bug-1242812.js b/js/src/jit-test/tests/gc/bug-1242812.js new file mode 100644 index 0000000000..df4ae09998 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1242812.js @@ -0,0 +1,4 @@ +// |jit-test| skip-if: !('oomTest' in this) + +var lfcode = new Array(); +oomTest(() => { let a = [2147483651]; [-1, 0, 1, 31, 32].sort(); }); diff --git a/js/src/jit-test/tests/gc/bug-1245520.js b/js/src/jit-test/tests/gc/bug-1245520.js new file mode 100644 index 0000000000..1f59c3dbab --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1245520.js @@ -0,0 +1,4 @@ +// |jit-test| skip-if: !('oomTest' in this) + +var t = {}; +oomTest(() => serialize(t)); diff --git a/js/src/jit-test/tests/gc/bug-1246593.js b/js/src/jit-test/tests/gc/bug-1246593.js new file mode 100644 index 0000000000..4d90bbb24b --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1246593.js @@ -0,0 +1,4 @@ +length = 10000; +array = Array(); +for (i = length;i > -100000; i--) + array[i] = {}; diff --git a/js/src/jit-test/tests/gc/bug-1252329.js b/js/src/jit-test/tests/gc/bug-1252329.js new file mode 100644 index 0000000000..8f5b1ce282 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1252329.js @@ -0,0 +1,32 @@ +// |jit-test| allow-oom; skip-if: helperThreadCount() == 0 || !('oomAfterAllocations' in this) + +var lfcode = new Array(); +lfcode.push("5"); +lfcode.push(` +gczeal(8, 2); +try { + [new String, y] +} catch (e) {} +oomAfterAllocations(50); +try { + (5).replace(r, () => {}); +} catch (x) {} +`); +while (true) { + var file = lfcode.shift(); if (file == undefined) { break; } + loadFile(file) +} +function loadFile(lfVarx) { + if (lfVarx.substr(-3) != ".js" && lfVarx.length != 1) { + switch (lfRunTypeId) { + case 5: + var lfGlobal = newGlobal(); + for (lfLocal in this) {} + lfGlobal.offThreadCompileToStencil(lfVarx); + var stencil = lfGlobal.finishOffThreadStencil(); + lfGlobal.evalStencil(stencil); + } + } else if (!isNaN(lfVarx)) { + lfRunTypeId = parseInt(lfVarx); + } +} diff --git a/js/src/jit-test/tests/gc/bug-1253124.js b/js/src/jit-test/tests/gc/bug-1253124.js new file mode 100644 index 0000000000..6949605b00 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1253124.js @@ -0,0 +1,10 @@ +// |jit-test| skip-if: !('oomTest' in this) + +for (let i = 0; i < 10; i++) + toPrimitive = Date.prototype[Symbol.toPrimitive]; +assertThrowsInstanceOf(() => 0); +obj = {}; +oomTest(() => assertThrowsInstanceOf(() => toPrimitive.call(obj, "boolean"))); +function assertThrowsInstanceOf(f) { + f(); +} diff --git a/js/src/jit-test/tests/gc/bug-1254108.js b/js/src/jit-test/tests/gc/bug-1254108.js new file mode 100644 index 0000000000..bf18e798ae --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1254108.js @@ -0,0 +1,5 @@ +|jit-test| error: Error +function testChangeParam(key) { + gcparam(key, 0x22222222); +} +testChangeParam("lowFrequencyHeapGrowth"); diff --git a/js/src/jit-test/tests/gc/bug-1258407.js b/js/src/jit-test/tests/gc/bug-1258407.js new file mode 100644 index 0000000000..9338c981f2 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1258407.js @@ -0,0 +1,10 @@ +|jit-test| error: Error +gcparam("lowFrequencyHeapGrowth", 0.1); +gcparam("lowFrequencyHeapGrowth", 2); +let ok = false; +try { + gcparam("lowFrequencyHeapGrowth", 0x22222222); +} catch (e) { + ok = true; +} +assertEq(ok, true); diff --git a/js/src/jit-test/tests/gc/bug-1259306.js b/js/src/jit-test/tests/gc/bug-1259306.js new file mode 100644 index 0000000000..fba5f71b6a --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1259306.js @@ -0,0 +1,19 @@ +// |jit-test| skip-if: !('oomTest' in this) + +let runCount = 0; +oomTest(() => { + if (runCount < 5) { + let lfGlobal = newGlobal(); + var lfVarx = ` + gczeal(8, 1); + try { + (5).replace(r, () => {}); + } catch (x) {} + gczeal(0); + `; + lfGlobal.offThreadCompileToStencil(lfVarx); + var stencil = lfGlobal.finishOffThreadStencil(); + lfGlobal.evalStencil(stencil); + runCount++; + } +}); diff --git a/js/src/jit-test/tests/gc/bug-1259490.js b/js/src/jit-test/tests/gc/bug-1259490.js new file mode 100644 index 0000000000..d349bf27d4 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1259490.js @@ -0,0 +1,13 @@ +try { eval("3 ** 4") } catch (e) { + if (!(e instanceof SyntaxError)) + throw e; + quit(); +} +eval(` + +gczeal(8); +for (var k = 0; k < 99; ++k) { + String(-(0 ** (Object | 0 * Object))) +} + +`) diff --git a/js/src/jit-test/tests/gc/bug-1261329.js b/js/src/jit-test/tests/gc/bug-1261329.js new file mode 100644 index 0000000000..afa1db2c3d --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1261329.js @@ -0,0 +1,9 @@ +// |jit-test| skip-if: !('oomTest' in this) + +print = function() {} +function k() { return dissrc(print); } +function j() { return k(); } +function h() { return j(); } +function f() { return h(); } +f(); +oomTest(() => f()) diff --git a/js/src/jit-test/tests/gc/bug-1263862.js b/js/src/jit-test/tests/gc/bug-1263862.js new file mode 100644 index 0000000000..955805047a --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1263862.js @@ -0,0 +1,8 @@ +// |jit-test| skip-if: !('oomTest' in this) + +function loadFile(lfVarx) { + oomTest(() => eval(lfVarx)); +} +for (var i = 0; i < 10; ++i) { + loadFile(`"use strict"; const s = () => s;`); +} diff --git a/js/src/jit-test/tests/gc/bug-1263871.js b/js/src/jit-test/tests/gc/bug-1263871.js new file mode 100644 index 0000000000..6680affedf --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1263871.js @@ -0,0 +1,8 @@ +// |jit-test| skip-if: !('oomTest' in this) + +lfLogBuffer = `this[''] = function() {}`; +loadFile(lfLogBuffer); +loadFile(lfLogBuffer); +function loadFile(lfVarx) { + return oomTest(function() { return parseModule(lfVarx); }); +} diff --git a/js/src/jit-test/tests/gc/bug-1263884.js b/js/src/jit-test/tests/gc/bug-1263884.js new file mode 100644 index 0000000000..949945e0a4 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1263884.js @@ -0,0 +1,9 @@ +// |jit-test| skip-if: !('oomTest' in this) + +oomTest(function() { + eval(` + var argObj = function () { return arguments }() + for (var p in argObj); + delete argObj.callee; + `); +}); diff --git a/js/src/jit-test/tests/gc/bug-1271110.js b/js/src/jit-test/tests/gc/bug-1271110.js new file mode 100644 index 0000000000..12d1617c57 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1271110.js @@ -0,0 +1,38 @@ +// |jit-test| skip-if: !('oomTest' in this) + +gczeal(0); + +var x1 = []; +var x2 = []; +var x3 = []; +var x4 = []; +(function() { + var gns = Object.getOwnPropertyNames(this); + for (var i = 0; i < 49; ++i) { + var gn = gns[i]; + var g = this[gn]; + if (typeof g == "function") { + var hns = Object.getOwnPropertyNames(gn); + for (var j = 0; j < hns.length; ++j) { + x1.push(""); + x1.push(""); + x2.push(""); + x2.push(""); + x3.push(""); + x3.push(""); + x4.push(""); + x4.push(""); + } + } + } +})(); +try { + __proto__ = function(){}; +} catch (e) { + "" + e; +} +startgc(9222); +Function("\ + (function() {})();\ + oomTest(Debugger.Script);\ +")(); diff --git a/js/src/jit-test/tests/gc/bug-1276631.js b/js/src/jit-test/tests/gc/bug-1276631.js new file mode 100644 index 0000000000..f80e7f0210 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1276631.js @@ -0,0 +1,17 @@ +gczeal(15,5); +try { + foobar(); +} catch (e) {} +function newFunc(x) { + new Function(x)(); +}; +loadFile(` + try { gczeal(10, 2)() } catch (e) {} +`); +function loadFile(lfVarx) { + function newFunc(x) { + new Function(x)(); + }; + newFunc(lfVarx); + if (helperThreadCount() && getJitCompilerOptions()["offthread-compilation.enable"]) {} +} diff --git a/js/src/jit-test/tests/gc/bug-1278832.js b/js/src/jit-test/tests/gc/bug-1278832.js new file mode 100644 index 0000000000..aa9e269b3d --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1278832.js @@ -0,0 +1,12 @@ +function assertThrowsInstanceOf() {} +gczeal(15) +try { + gczeal(10, 2) +} catch (Atomics) {} +for (define of[__defineSetter__]) { + let nonCallable = [{}] + for (let value of nonCallable) assertThrowsInstanceOf(TypeError) + key = { + [Symbol]() {} + } +} diff --git a/js/src/jit-test/tests/gc/bug-1280588.js b/js/src/jit-test/tests/gc/bug-1280588.js new file mode 100644 index 0000000000..a6b2c4f075 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1280588.js @@ -0,0 +1,5 @@ +// |jit-test| skip-if: !('oomTest' in this) + +var x = []; +oomTest(() => setGCCallback({ action: "minorGC" })); +oomTest(() => setGCCallback({ action: "majorGC" })); diff --git a/js/src/jit-test/tests/gc/bug-1280889.js b/js/src/jit-test/tests/gc/bug-1280889.js new file mode 100644 index 0000000000..3af1ee3e08 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1280889.js @@ -0,0 +1,8 @@ +// |jit-test| skip-if: helperThreadCount() === 0 + +evalInWorker(` + function f() { + fullcompartmentchecks(f); + } + try { f(); } catch(e) {} +`); diff --git a/js/src/jit-test/tests/gc/bug-1282986.js b/js/src/jit-test/tests/gc/bug-1282986.js new file mode 100644 index 0000000000..934cea5b61 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1282986.js @@ -0,0 +1,16 @@ +// |jit-test| skip-if: !('oomTest' in this) + +var lfLogBuffer = ` +evalInWorker(\` + try { oomAfterAllocations(2); } catch(e) {} + \`); +`; +loadFile(""); +loadFile(lfLogBuffer); +function loadFile(lfVarx) { + oomTest(function() { + let m = parseModule(lfVarx); + moduleLink(m); + moduleEvaluate(m); + }); +} diff --git a/js/src/jit-test/tests/gc/bug-1286244.js b/js/src/jit-test/tests/gc/bug-1286244.js new file mode 100644 index 0000000000..1b69fdae11 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1286244.js @@ -0,0 +1,8 @@ +// |jit-test| skip-if: !getBuildConfiguration()['has-gczeal'] || helperThreadCount() === 0 + +// This will fail with --no-threads. +verifyprebarriers(); +var lfGlobal = newGlobal(); +lfGlobal.offThreadCompileToStencil(` + version(185); +`); diff --git a/js/src/jit-test/tests/gc/bug-1287399.js b/js/src/jit-test/tests/gc/bug-1287399.js new file mode 100644 index 0000000000..c7e6b8f44d --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1287399.js @@ -0,0 +1,11 @@ +// |jit-test| skip-if: typeof gczeal !== 'function' || helperThreadCount() === 0 + +var lfGlobal = newGlobal(); +gczeal(4); +for (lfLocal in this) {} +lfGlobal.offThreadCompileToStencil(` + var desc = { + value: 'bar', + value: false, + }; +`); diff --git a/js/src/jit-test/tests/gc/bug-1287869.js b/js/src/jit-test/tests/gc/bug-1287869.js new file mode 100644 index 0000000000..dc04345ccf --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1287869.js @@ -0,0 +1,7 @@ +// |jit-test| skip-if: !('gczeal' in this) + +gczeal(16); +let a = []; +for (let i = 0; i < 1000; i++) + a.push({x: i}); +gc(); diff --git a/js/src/jit-test/tests/gc/bug-1292564.js b/js/src/jit-test/tests/gc/bug-1292564.js new file mode 100644 index 0000000000..f292e1682c --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1292564.js @@ -0,0 +1,9 @@ +// |jit-test| allow-oom; skip-if: !('oomTest' in this) + +oomTest(() => { + let global = newGlobal({sameZoneAs: this}); + Debugger(global).onDebuggerStatement = function (frame) { + frame.eval("f") + } + global.eval("debugger") +}, false); diff --git a/js/src/jit-test/tests/gc/bug-1293127.js b/js/src/jit-test/tests/gc/bug-1293127.js new file mode 100644 index 0000000000..195798bf76 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1293127.js @@ -0,0 +1,11 @@ +// Test that we can create 700 cross compartment wrappers to nursery objects +// without triggering a minor GC. +gczeal(0); +let g = newGlobal({newCompartment: true}); +evalcx("function f(x) { return {x: x}; }", g); +gc(); +let initial = gcparam("gcNumber"); +for (let i = 0; i < 700; i++) + g.f(i); +let final = gcparam("gcNumber"); +assertEq(final, initial); diff --git a/js/src/jit-test/tests/gc/bug-1294241.js b/js/src/jit-test/tests/gc/bug-1294241.js new file mode 100644 index 0000000000..62c1acf62e --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1294241.js @@ -0,0 +1,13 @@ +if (helperThreadCount() == 0) + quit(); + +ignoreUnhandledRejections(); + +gczeal(9); +function rejectionTracker(state) {} +setPromiseRejectionTrackerCallback(rejectionTracker) + lfGlobal = newGlobal(); +offThreadCompileToStencil(`new Promise(()=>rej)`); +var stencil = lfGlobal.finishOffThreadStencil(); +lfGlobal.evalStencil(stencil); +for (lfLocal in this); diff --git a/js/src/jit-test/tests/gc/bug-1298356.js b/js/src/jit-test/tests/gc/bug-1298356.js new file mode 100644 index 0000000000..4c8a213125 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1298356.js @@ -0,0 +1,6 @@ +// |jit-test| skip-if: !('oomTest' in this) + +/x/; +oomTest(function(){ + offThreadCompileToStencil(''); +}) diff --git a/js/src/jit-test/tests/gc/bug-1301377.js b/js/src/jit-test/tests/gc/bug-1301377.js new file mode 100644 index 0000000000..2843e264da --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1301377.js @@ -0,0 +1,12 @@ +var lfLogBuffer = ` + gczeal(14); + enableGeckoProfiling(); + gczeal(15,3); + var s = ""; + for (let i = 0; i != 30; i+=2) {} + readGeckoProfilingStack(s, "c0d1c0d1c0d1c0d1c0d1c0d1c0d1c0"); +`; +loadFile(lfLogBuffer); +function loadFile(lfVarx) { + evaluate(lfVarx); +} diff --git a/js/src/jit-test/tests/gc/bug-1301496.js b/js/src/jit-test/tests/gc/bug-1301496.js new file mode 100644 index 0000000000..e3aa0544f6 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1301496.js @@ -0,0 +1,7 @@ +// |jit-test| skip-if: helperThreadCount() === 0 +gczeal(0); +startgc(1, 'shrinking'); +offThreadCompileToStencil(""); +// Adapted from randomly chosen test: js/src/jit-test/tests/parser/bug-1263355-13.js +gczeal(9); +newGlobal(); diff --git a/js/src/jit-test/tests/gc/bug-1303015.js b/js/src/jit-test/tests/gc/bug-1303015.js new file mode 100644 index 0000000000..20b34d65d7 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1303015.js @@ -0,0 +1,12 @@ +// |jit-test| skip-if: !('oomTest' in this) + +var x = ``.split(); +oomTest(function() { + var lfGlobal = newGlobal({sameZoneAs: this}); + for (lfLocal in this) { + if (!(lfLocal in lfGlobal)) { + lfGlobal[lfLocal] = this[lfLocal]; + } + } +}); + diff --git a/js/src/jit-test/tests/gc/bug-1305220.js b/js/src/jit-test/tests/gc/bug-1305220.js new file mode 100644 index 0000000000..b6dad199a9 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1305220.js @@ -0,0 +1,22 @@ +// |jit-test| allow-oom; skip-if: !('oomAfterAllocations' in this) + +s = newGlobal(); +evalcx("\ + gczeal(10, 2);\ + k = {\ + [Symbol]() {}\ + };\ +", s); +gczeal(0); +evalcx("\ + var g = newGlobal();\ + b = new Debugger;\ + g.h = function() {\ + g.oomAfterAllocations(1);\ + };\ + g.eval(\"\" + function f() { return g(); });\ + g.eval(\"\" + function g() { return h(); });\ + g.eval(\"(\" + function() {\ + f();\ + } + \")()\");\ +", s); diff --git a/js/src/jit-test/tests/gc/bug-1308048.js b/js/src/jit-test/tests/gc/bug-1308048.js new file mode 100644 index 0000000000..7de26784b4 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1308048.js @@ -0,0 +1,10 @@ +// |jit-test| skip-if: helperThreadCount() === 0 + +m = 'x'; +for (var i = 0; i < 10; i++) + m += m; +offThreadCompileToStencil("", ({elementAttributeName: m})); +var n = newGlobal(); +gczeal(2,1); +var stencil = n.finishOffThreadStencil(); +n.evalStencil(stencil); diff --git a/js/src/jit-test/tests/gc/bug-1310589.js b/js/src/jit-test/tests/gc/bug-1310589.js new file mode 100644 index 0000000000..2907c3c440 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1310589.js @@ -0,0 +1,298 @@ +// |jit-test| skip-if: !('oomTest' in this) + +a = o = s = r = [] +o2 = s2 = r2 = g2 = f2 = m2 = Map +e2 = Set +v2 = b2 = new ArrayBuffer +t2 = new Uint8ClampedArray +minorgc() +x = /x/ +for (var i = 0; i < 4; ++i) { + function f1() {} +} +Object.defineProperty(a, 12, {}).push(1); +toString = (function() { return a.reverse(); }) +oomTest(Date.prototype.toJSON) +function f1000(){} +function f1001(){} +function f1002(){} +function f1003(){} +function f1004(){} +function f1005(){} +function f1006(){} +function f1007(){} +function f1008(){} +function f1009(){} +function f1010(){} +function f1011(){} +function f1012(){} +function f1013(){} +function f1014(){} +function f1015(){} +function f1016(){} +function f1017(){} +function f1018(){} +function f1019(){} +function f1020(){} +function f1021(){} +function f1022(){} +function f1023(){} +function f1024(){} +function f1025(){} +function f1026(){} +function f1027(){} +function f1028(){} +function f1029(){} +function f1030(){} +function f1031(){} +function f1032(){} +function f1033(){} +function f1034(){} +function f1035(){} +function f1036(){} +function f1037(){} +function f1038(){} +function f1039(){} +function f1040(){} +function f1041(){} +function f1042(){} +function f1043(){} +function f1044(){} +function f1045(){} +function f1046(){} +function f1047(){} +function f1048(){} +function f1049(){} +function f1050(){} +function f1051(){} +function f1052(){} +function f1053(){} +function f1054(){} +function f1055(){} +function f1056(){} +function f1057(){} +function f1058(){} +function f1059(){} +function f1060(){} +function f1061(){} +function f1062(){} +function f1063(){} +function f1064(){} +function f1065(){} +function f1066(){} +function f1067(){} +function f1068(){} +function f1069(){} +function f1070(){} +function f1071(){} +function f1072(){} +function f1073(){} +function f1074(){} +function f1075(){} +function f1076(){} +function f1077(){} +function f1078(){} +function f1079(){} +function f1080(){} +function f1081(){} +function f1082(){} +function f1083(){} +function f1084(){} +function f1085(){} +function f1086(){} +function f1087(){} +function f1088(){} +function f1089(){} +function f1090(){} +function f1091(){} +function f1092(){} +function f1093(){} +function f1094(){} +function f1095(){} +function f1096(){} +function f1097(){} +function f1098(){} +function f1099(){} +function f1100(){} +function f1101(){} +function f1102(){} +function f1103(){} +function f1104(){} +function f1105(){} +function f1106(){} +function f1107(){} +function f1108(){} +function f1109(){} +function f1110(){} +function f1111(){} +function f1112(){} +function f1113(){} +function f1114(){} +function f1115(){} +function f1116(){} +function f1117(){} +function f1118(){} +function f1119(){} +function f1120(){} +function f1121(){} +function f1122(){} +function f1123(){} +function f1124(){} +function f1125(){} +function f1126(){} +function f1127(){} +function f1128(){} +function f1129(){} +function f1130(){} +function f1131(){} +function f1132(){} +function f1133(){} +function f1134(){} +function f1135(){} +function f1136(){} +function f1137(){} +function f1138(){} +function f1139(){} +function f1140(){} +function f1141(){} +function f1142(){} +function f1143(){} +function f1144(){} +function f1145(){} +function f1146(){} +function f1147(){} +function f1148(){} +function f1149(){} +function f1150(){} +function f1151(){} +function f1152(){} +function f1153(){} +function f1154(){} +function f1155(){} +function f1156(){} +function f1157(){} +function f1158(){} +function f1159(){} +function f1160(){} +function f1161(){} +function f1162(){} +function f1163(){} +function f1164(){} +function f1165(){} +function f1166(){} +function f1167(){} +function f1168(){} +function f1169(){} +function f1170(){} +function f1171(){} +function f1172(){} +function f1173(){} +function f1174(){} +function f1175(){} +function f1176(){} +function f1177(){} +function f1178(){} +function f1179(){} +function f1180(){} +function f1181(){} +function f1182(){} +function f1183(){} +function f1184(){} +function f1185(){} +function f1186(){} +function f1187(){} +function f1188(){} +function f1189(){} +function f1190(){} +function f1191(){} +function f1192(){} +function f1193(){} +function f1194(){} +function f1195(){} +function f1196(){} +function f1197(){} +function f1198(){} +function f1199(){} +function f1200(){} +function f1201(){} +function f1202(){} +function f1203(){} +function f1204(){} +function f1205(){} +function f1206(){} +function f1207(){} +function f1208(){} +function f1209(){} +function f1210(){} +function f1211(){} +function f1212(){} +function f1213(){} +function f1214(){} +function f1215(){} +function f1216(){} +function f1217(){} +function f1218(){} +function f1219(){} +function f1220(){} +function f1221(){} +function f1222(){} +function f1223(){} +function f1224(){} +function f1225(){} +function f1226(){} +function f1227(){} +function f1228(){} +function f1229(){} +function f1230(){} +function f1231(){} +function f1232(){} +function f1233(){} +function f1234(){} +function f1235(){} +function f1236(){} +function f1237(){} +function f1238(){} +function f1239(){} +function f1240(){} +function f1241(){} +function f1242(){} +function f1243(){} +function f1244(){} +function f1245(){} +function f1246(){} +function f1247(){} +function f1248(){} +function f1249(){} +function f1250(){} +function f1251(){} +function f1252(){} +function f1253(){} +function f1254(){} +function f1255(){} +function f1256(){} +function f1257(){} +function f1258(){} +function f1259(){} +function f1260(){} +function f1261(){} +function f1262(){} +function f1263(){} +function f1264(){} +function f1265(){} +function f1266(){} +function f1267(){} +function f1268(){} +function f1269(){} +function f1270(){} +function f1271(){} +function f1272(){} +function f1273(){} +function f1274(){} +function f1275(){} +function f1276(){} +function f1277(){} +function f1278(){} +function f1279(){} +function f1280(){} +function f1281(){} +function f1282(){} diff --git a/js/src/jit-test/tests/gc/bug-1311060.js b/js/src/jit-test/tests/gc/bug-1311060.js new file mode 100644 index 0000000000..6237a4575d --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1311060.js @@ -0,0 +1,2 @@ +// |jit-test| skip-if: helperThreadCount() === 0 +evalInWorker(`schedulezone("s1");`); diff --git a/js/src/jit-test/tests/gc/bug-1313347.js b/js/src/jit-test/tests/gc/bug-1313347.js new file mode 100644 index 0000000000..c5f684f7a6 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1313347.js @@ -0,0 +1,5 @@ +let tenured = {}; +gc(); +for (let i = 0; i < 100000; i++) { + tenured[i/2] = {}; +} diff --git a/js/src/jit-test/tests/gc/bug-1315946.js b/js/src/jit-test/tests/gc/bug-1315946.js new file mode 100644 index 0000000000..a0668d00b5 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1315946.js @@ -0,0 +1,13 @@ +// |jit-test| skip-if: !('oomTest' in this) + +// Don't run a full oomTest because it takes ages - a few iterations are +// sufficient to trigger the bug. +let i = 0; + +oomTest(Function(` + if (i < 10) { + i++; + gczeal(15,1); + foo; + } +`)); diff --git a/js/src/jit-test/tests/gc/bug-1321597.js b/js/src/jit-test/tests/gc/bug-1321597.js new file mode 100644 index 0000000000..d5d96ddab6 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1321597.js @@ -0,0 +1,6 @@ +gczeal(9,3); +function test(s, okLine) { }; +var dbg = new Debugger; +dbg.onNewGlobalObject = function(global) {}; +x = evalcx(test()); +shortestPaths(["\$4"], { start: this, maxNumPaths: 5 }); diff --git a/js/src/jit-test/tests/gc/bug-1322420.js b/js/src/jit-test/tests/gc/bug-1322420.js new file mode 100644 index 0000000000..750eefa3c1 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1322420.js @@ -0,0 +1,8 @@ +options('strict_mode'); +var g1 = newGlobal({newCompartment: true}); +var g2 = newGlobal({newCompartment: true}); +var dbg = new Debugger(); +dbg.addDebuggee(g1); +g1.eval('function f() {}'); +gczeal(9, 1); +dbg.findScripts({}); diff --git a/js/src/jit-test/tests/gc/bug-1322648.js b/js/src/jit-test/tests/gc/bug-1322648.js new file mode 100644 index 0000000000..bb6279f525 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1322648.js @@ -0,0 +1,9 @@ +// |jit-test| skip-if: helperThreadCount() === 0 +gczeal(0); +print = function(s) {} +startgc(1); +offThreadCompileToStencil(""); +gczeal(10, 3); +for (var count = 0; count < 20; count++) { + print(count); +} diff --git a/js/src/jit-test/tests/gc/bug-1323868.js b/js/src/jit-test/tests/gc/bug-1323868.js new file mode 100644 index 0000000000..1d37a19ba0 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1323868.js @@ -0,0 +1,6 @@ +// |jit-test| allow-oom; skip-if: helperThreadCount() === 0 + +gczeal(0); +startgc(8301); +offThreadCompileToStencil("(({a,b,c}))"); +gcparam("maxBytes", gcparam("gcBytes")); diff --git a/js/src/jit-test/tests/gc/bug-1324512.js b/js/src/jit-test/tests/gc/bug-1324512.js new file mode 100644 index 0000000000..9366b2c277 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1324512.js @@ -0,0 +1,12 @@ +// |jit-test| skip-if: helperThreadCount() === 0 + +evalInWorker(` + if (!('gczeal' in this)) + quit(); + try { + gczeal(2,1); + throw new Error(); + } catch (e) { + assertEq("" + e, "Error"); + } +`); diff --git a/js/src/jit-test/tests/gc/bug-1325551.js b/js/src/jit-test/tests/gc/bug-1325551.js new file mode 100644 index 0000000000..700f61daf8 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1325551.js @@ -0,0 +1,10 @@ +// |jit-test| skip-if: !('oomTest' in this) + +let g = newGlobal({newCompartment: true}); +let dbg = new Debugger; +let gw = dbg.addDebuggee(g); +g.eval("function f(){}"); +oomTest(() => { + gw.makeDebuggeeValue(g.f).script.source.sourceMapURL = 'a'; +}); + diff --git a/js/src/jit-test/tests/gc/bug-1328251.js b/js/src/jit-test/tests/gc/bug-1328251.js new file mode 100644 index 0000000000..5e492af8fe --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1328251.js @@ -0,0 +1,11 @@ +// |jit-test| skip-if: helperThreadCount() === 0 + +evalInWorker(` + if (!('gczeal' in this)) + quit(); + gczeal(2); + for (let i = 0; i < 30; i++) { + var a = [1, 2, 3]; + a.indexOf(1); + relazifyFunctions(); } +`); diff --git a/js/src/jit-test/tests/gc/bug-1332773.js b/js/src/jit-test/tests/gc/bug-1332773.js new file mode 100644 index 0000000000..77445bc2eb --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1332773.js @@ -0,0 +1,9 @@ +// |jit-test| skip-if: helperThreadCount() === 0 + +evalInWorker(` +var gTestcases = new Array(); +typeof document != "object" || !document.location.href.match(/jsreftest.html/); +gczeal(4, 10); +f = ([a = class target extends b {}, b] = [void 0]) => {}; +f() +`) diff --git a/js/src/jit-test/tests/gc/bug-1337414.js b/js/src/jit-test/tests/gc/bug-1337414.js new file mode 100644 index 0000000000..9e647a810c --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1337414.js @@ -0,0 +1,46 @@ +var lfLogBuffer = ` +gczeal(15,10); +try { + a = [] + gczeal(2, 2)() +} catch (e) {} +a.every(function() {}) +//corefuzz-dcd-endofdata +//corefuzz-dcd-selectmode 5 +`; +lfLogBuffer = lfLogBuffer.split('\n'); +lfPreamble = ` +`; +var lfCodeBuffer = ""; +var lfRunTypeLimit = 7; +var lfOffThreadGlobal = newGlobal(); +try {} catch (lfVare5) {} +var lfAccumulatedCode = lfPreamble; +while (true) { + var line = lfLogBuffer.shift(); + if (line == null) { + break; + } else if (line == "//corefuzz-dcd-endofdata") { + loadFile(lfCodeBuffer); + } else if (line.indexOf("//corefuzz-dcd-selectmode ") === 0) { + loadFile(line); + } else { + lfCodeBuffer += line + "\n"; + } +} +if (lfCodeBuffer) loadFile(lfCodeBuffer); +function loadFile(lfVarx) { + try { + if (lfVarx.indexOf("//corefuzz-dcd-selectmode ") === 0) { + lfRunTypeId = parseInt(lfVarx.split(" ")[1]) % lfRunTypeLimit; + } else { + switch (lfRunTypeId) { + case 5: + evalInWorker(lfAccumulatedCode); + evaluate(lfVarx); + } + } + } catch (lfVare) { + lfAccumulatedCode += "try { evaluate(`\n" + lfVarx + "\n`); } catch(exc) {}\n"; + } +} diff --git a/js/src/jit-test/tests/gc/bug-1338383.js b/js/src/jit-test/tests/gc/bug-1338383.js new file mode 100644 index 0000000000..930566492d --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1338383.js @@ -0,0 +1,17 @@ +// |jit-test| error: InternalError + +if (helperThreadCount() === 0) + throw InternalError(); + +var lfOffThreadGlobal = newGlobal(); +enableShellAllocationMetadataBuilder() +lfOffThreadGlobal.offThreadCompileToStencil(` + if ("gczeal" in this) + gczeal(8, 1) + function recurse(x) { + recurse(x + 1); + }; + recurse(0); +`); +var stencil = lfOffThreadGlobal.finishOffThreadStencil(); +lfOffThreadGlobal.evalStencil(stencil); diff --git a/js/src/jit-test/tests/gc/bug-1340010.js b/js/src/jit-test/tests/gc/bug-1340010.js new file mode 100644 index 0000000000..30c3cb08b9 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1340010.js @@ -0,0 +1,22 @@ +// |jit-test| skip-if: helperThreadCount() === 0 || !('deterministicgc' in this) +gczeal(0); + +gc(); +function weighted(wa) { + var a = []; + for (var i = 0; i < 30; ++i) { + for (var j = 0; j < 20; ++j) { + a.push(0); + } + } + return a; +} +var statementMakers = weighted(); +if (typeof oomTest == "function") { + statementMakers = statementMakers.concat([function (d, b) { + return "oomTest(" + makeFunction(d - 1, b) + ")"; + }, ]); +} +deterministicgc(true); +startgc(9469, "shrinking"); +offThreadCompileToStencil(""); diff --git a/js/src/jit-test/tests/gc/bug-1342261.js b/js/src/jit-test/tests/gc/bug-1342261.js new file mode 100644 index 0000000000..bcf4c586ba --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1342261.js @@ -0,0 +1,7 @@ +// With --baseline-eager, create a cross-compartment wrapper IC referring to a +// target in an otherwise-doomed compartment. + +fullcompartmentchecks(true); +newGlobal({ + sameZoneAs: [] +}).z; diff --git a/js/src/jit-test/tests/gc/bug-1354480.js b/js/src/jit-test/tests/gc/bug-1354480.js new file mode 100644 index 0000000000..c80ec01f9f --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1354480.js @@ -0,0 +1,10 @@ +// |jit-test| error: TypeError +test = "function f(a) { return a } f`a$b`"; +evalWithCache(test, {}); +dbg = new Debugger(); +gczeal(9, 1); +dbg.findScripts('January 0 0 is invalid'); +function evalWithCache(code, ctx) { + ctx.global = newGlobal(); + evaluate(code, ctx) +} diff --git a/js/src/jit-test/tests/gc/bug-1357022.js b/js/src/jit-test/tests/gc/bug-1357022.js new file mode 100644 index 0000000000..f7665f2d4b --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1357022.js @@ -0,0 +1,6 @@ +const root3 = newGlobal({newCompartment: true}); +function test(constructor) { + if (!nukeCCW(root3.load)) {} +} +test(Map); +test(Set); diff --git a/js/src/jit-test/tests/gc/bug-1359252.js b/js/src/jit-test/tests/gc/bug-1359252.js new file mode 100644 index 0000000000..9359ec1a75 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1359252.js @@ -0,0 +1,7 @@ +gczeal(4); +setJitCompilerOption("ion.warmup.trigger", 1); +function h() { + for ([a, b] in { z: 9 }) {} +} +for (var j = 0; j < 10; j++) + h(); diff --git a/js/src/jit-test/tests/gc/bug-1370069.js b/js/src/jit-test/tests/gc/bug-1370069.js new file mode 100644 index 0000000000..7117a22175 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1370069.js @@ -0,0 +1,6 @@ +// |jit-test| error:ReferenceError +try { + eval("}"); +} catch (exc) {} +gczeal(17, 1); +6.900653737167637, (yield); diff --git a/js/src/jit-test/tests/gc/bug-1371908.js b/js/src/jit-test/tests/gc/bug-1371908.js new file mode 100644 index 0000000000..e669f6b63a --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1371908.js @@ -0,0 +1,7 @@ +// |jit-test| skip-if: helperThreadCount() === 0 + +gczeal(0); +offThreadCompileToStencil(""); +startgc(0); +var stencil = finishOffThreadStencil(); +evalStencil(stencil); diff --git a/js/src/jit-test/tests/gc/bug-1374797.js b/js/src/jit-test/tests/gc/bug-1374797.js new file mode 100644 index 0000000000..f22b06e278 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1374797.js @@ -0,0 +1,27 @@ +// |jit-test| skip-if: helperThreadCount() === 0 + +// Exercise triggering GC of atoms zone while off-thread parsing is happening. + +gczeal(0); + +// Reduce some GC parameters so that we can trigger a GC more easily. +gcparam('lowFrequencyHeapGrowth', 120); +gcparam('highFrequencyLargeHeapGrowth', 120); +gcparam('highFrequencySmallHeapGrowth', 120); +gcparam('allocationThreshold', 1); +gc(); + +// Start an off-thread parse. +offThreadCompileToStencil("print('Finished')"); + +// Allocate lots of atoms, parsing occasionally. +for (let i = 0; i < 10; i++) { + print(i); + for (let j = 0; j < 10000; j++) + Symbol.for(i + 10 * j); + eval(`${i}`); +} + +// Finish the off-thread parse. +var stencil = finishOffThreadStencil(); +evalStencil(stencil); diff --git a/js/src/jit-test/tests/gc/bug-1382431.js b/js/src/jit-test/tests/gc/bug-1382431.js new file mode 100644 index 0000000000..86fb418121 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1382431.js @@ -0,0 +1,5 @@ +// |jit-test| skip-if: helperThreadCount() === 0 + +var fe = "vv"; +for (i = 0; i < 24; i++) fe += fe; +offThreadCompileToStencil(fe, {}); diff --git a/js/src/jit-test/tests/gc/bug-1384047.js b/js/src/jit-test/tests/gc/bug-1384047.js new file mode 100644 index 0000000000..4ec6a5272d --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1384047.js @@ -0,0 +1,4 @@ +// |jit-test| skip-if: !('oomTest' in this) + +newGlobal(); +evalcx("oomTest(newGlobal);", newGlobal()); diff --git a/js/src/jit-test/tests/gc/bug-1388701.js b/js/src/jit-test/tests/gc/bug-1388701.js new file mode 100644 index 0000000000..8f0a6292c8 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1388701.js @@ -0,0 +1,18 @@ +load(libdir + "asserts.js"); + +// This is out of range +assertErrorMessage( + () => gcparam('maxNurseryBytes', 0), + Error, + "Parameter value out of range"); + +assertErrorMessage( + () => gcparam('maxNurseryBytes', 5), + Error, + "Parameter value out of range"); + +// this is okay +gcparam('maxNurseryBytes', 1024*1024); +assertEq(gcparam('maxNurseryBytes'), 1024*1024); +gc(); + diff --git a/js/src/jit-test/tests/gc/bug-1390087.js b/js/src/jit-test/tests/gc/bug-1390087.js new file mode 100644 index 0000000000..4d77467ca0 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1390087.js @@ -0,0 +1,10 @@ +// |jit-test| skip-if: helperThreadCount() === 0 + +setGCCallback({ + action: "majorGC" +}); +gcparam("allocationThreshold", 1); +offThreadCompileToStencil(""); +for (let i = 0; i < 40000; i++) + Symbol.for(i); +eval(0); diff --git a/js/src/jit-test/tests/gc/bug-1399889.js b/js/src/jit-test/tests/gc/bug-1399889.js new file mode 100644 index 0000000000..356c682273 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1399889.js @@ -0,0 +1,4 @@ +gczeal(7, 1); +enableShellAllocationMetadataBuilder(); +var testMap = new Map(); +for (var [k, v] of testMap) {} diff --git a/js/src/jit-test/tests/gc/bug-1401141.js b/js/src/jit-test/tests/gc/bug-1401141.js new file mode 100644 index 0000000000..6ed1bea611 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1401141.js @@ -0,0 +1,10 @@ +// |jit-test| skip-if: !('gczeal' in this) || helperThreadCount() === 0 + +gczeal(15,1); +setGCCallback({ + action: "majorGC", +}); +gcslice(3); +var lfGlobal = newGlobal(); +lfGlobal.offThreadCompileToStencil(""); + diff --git a/js/src/jit-test/tests/gc/bug-1411302.js b/js/src/jit-test/tests/gc/bug-1411302.js new file mode 100644 index 0000000000..20c051edd9 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1411302.js @@ -0,0 +1,17 @@ +// |jit-test| skip-if: !('oomTest' in this) + +let lfPreamble = ` + value:{ +`; +try { + evaluate(""); + evalInWorker(""); +} catch (exc) {} +try { + evalInWorker(""); +} catch (exc) {} +try { + oomTest(function() { + eval("function testDeepBail1() {"); + }); +} catch (exc) {} diff --git a/js/src/jit-test/tests/gc/bug-1413914.js b/js/src/jit-test/tests/gc/bug-1413914.js new file mode 100644 index 0000000000..20f90d90b4 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1413914.js @@ -0,0 +1,4 @@ +let a = grayRoot(); +a[0] = {}; +gczeal(18); +gc(); diff --git a/js/src/jit-test/tests/gc/bug-1430752.js b/js/src/jit-test/tests/gc/bug-1430752.js new file mode 100644 index 0000000000..cb4986140f --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1430752.js @@ -0,0 +1,2 @@ +gczeal(18); +gcslice(3); diff --git a/js/src/jit-test/tests/gc/bug-1435295.js b/js/src/jit-test/tests/gc/bug-1435295.js new file mode 100644 index 0000000000..01214a6214 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1435295.js @@ -0,0 +1,9 @@ +// |jit-test| skip-if: helperThreadCount() === 0 || !('oomTest' in this) + +oomTest(new Function(`function execOffThread(source) { + offThreadCompileModuleToStencil(source); + var stencil = finishOffThreadStencil(); + return instantiateModuleStencil(stencil); +} +b = execOffThread("[1, 2, 3]") +`)); diff --git a/js/src/jit-test/tests/gc/bug-1435321.js b/js/src/jit-test/tests/gc/bug-1435321.js new file mode 100644 index 0000000000..f8c0172d24 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1435321.js @@ -0,0 +1,30 @@ +// Check that corresponding parameters are updated to ensure that invariants are +// preserved when updating various GC parameters. + +gcparam('highFrequencyLargeHeapGrowth', 200); +gcparam('highFrequencySmallHeapGrowth', 400); +assertEq(gcparam('highFrequencyLargeHeapGrowth'), 200); +assertEq(gcparam('highFrequencySmallHeapGrowth'), 400); + +gcparam('highFrequencySmallHeapGrowth', 150); +assertEq(gcparam('highFrequencyLargeHeapGrowth'), 150); +assertEq(gcparam('highFrequencySmallHeapGrowth'), 150); + +gcparam('highFrequencyLargeHeapGrowth', 300); +assertEq(gcparam('highFrequencyLargeHeapGrowth'), 300); +assertEq(gcparam('highFrequencySmallHeapGrowth'), 300); + +// The following parameters are stored in bytes but specified/retrieved in MiB. + +gcparam('smallHeapSizeMax', 200); +gcparam('largeHeapSizeMin', 500); +assertEq(gcparam('smallHeapSizeMax'), 200); +assertEq(gcparam('largeHeapSizeMin'), 500); + +gcparam('largeHeapSizeMin', 100); +assertEq(gcparam('smallHeapSizeMax'), 99); +assertEq(gcparam('largeHeapSizeMin'), 100); + +gcparam('smallHeapSizeMax', 300); +assertEq(gcparam('smallHeapSizeMax'), 300); +assertEq(gcparam('largeHeapSizeMin'), 300); diff --git a/js/src/jit-test/tests/gc/bug-1439284.js b/js/src/jit-test/tests/gc/bug-1439284.js new file mode 100644 index 0000000000..4e6bcd11b0 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1439284.js @@ -0,0 +1,16 @@ +// |jit-test| skip-if: helperThreadCount() === 0 + +gcparam('allocationThreshold', 1); +setGCCallback({ + action: "majorGC", +}); +offThreadCompileToStencil(('Boolean.prototype.toString.call(new String())')); +for (let i = 0; i < 10; i++) { + for (let j = 0; j < 10000; j++) Symbol.for(i + 10 * j); +} +try { + var stencil = finishOffThreadStencil(); + evalStencil(stencil); +} catch (e) { + assertEq(e.constructor, TypeError); +} diff --git a/js/src/jit-test/tests/gc/bug-1449887.js b/js/src/jit-test/tests/gc/bug-1449887.js new file mode 100644 index 0000000000..ef7fa45c7f --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1449887.js @@ -0,0 +1,3 @@ +// |jit-test| skip-if: !('oomTest' in this) + +oomTest(function() { x, 0, { z: function() {} } }); diff --git a/js/src/jit-test/tests/gc/bug-1456508.js b/js/src/jit-test/tests/gc/bug-1456508.js new file mode 100644 index 0000000000..3a6c5be85a --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1456508.js @@ -0,0 +1,4 @@ +// |jit-test| error: ReferenceError +gczeal(11, 5); +gczeal(22, 9); +grayRoot().map = wm; diff --git a/js/src/jit-test/tests/gc/bug-1456536.js b/js/src/jit-test/tests/gc/bug-1456536.js new file mode 100644 index 0000000000..adfe3f0c9b --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1456536.js @@ -0,0 +1,3 @@ +// |jit-test| skip-if: !('oomTest' in this) + +oomTest(new Function(`let a = grayRoot();`)); diff --git a/js/src/jit-test/tests/gc/bug-1459568.js b/js/src/jit-test/tests/gc/bug-1459568.js new file mode 100644 index 0000000000..57affab64f --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1459568.js @@ -0,0 +1,7 @@ +gczeal(0); +setMarkStackLimit(1); +gczeal(18, 1); +grayRoot()[0] = "foo"; +grayRoot()[1] = {}; +grayRoot().x = 0; +gc(); diff --git a/js/src/jit-test/tests/gc/bug-1459860.js b/js/src/jit-test/tests/gc/bug-1459860.js new file mode 100644 index 0000000000..d474995d4e --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1459860.js @@ -0,0 +1,8 @@ +// |jit-test| allow-overrecursed; skip-if: helperThreadCount() === 0 +function eval(source) { + offThreadCompileModuleToStencil(source); + let get = (eval("function w(){}") ++); +}; +gczeal(21, 10); +gczeal(11, 8); +eval(""); diff --git a/js/src/jit-test/tests/gc/bug-1461319.js b/js/src/jit-test/tests/gc/bug-1461319.js new file mode 100644 index 0000000000..46f9ad50cd --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1461319.js @@ -0,0 +1,7 @@ +// |jit-test| error: InternalError +gczeal(14); +var g = newGlobal({newCompartment: true}); +g.eval('function f(a) { evaluate("f(" + " - 1);", {newContext: true}); }'); +var dbg = new Debugger(g); +dbg.onEnterFrame = function(frame) {}; +g.f(); diff --git a/js/src/jit-test/tests/gc/bug-1461448.js b/js/src/jit-test/tests/gc/bug-1461448.js new file mode 100644 index 0000000000..b9f4d8dd0c --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1461448.js @@ -0,0 +1,40 @@ +// |jit-test| skip-if: helperThreadCount() === 0 + +gczeal(0); + +let lfPreamble = ` + var lfOffThreadGlobal = newGlobal({newCompartment: true}); + for (lfLocal in this) + try {} catch(lfVare5) {} +`; +evaluate(lfPreamble); +evaluate(` + var g = newGlobal({newCompartment: true}); + var dbg = new Debugger; + var gw = dbg.addDebuggee(g); + for (lfLocal in this) + if (!(lfLocal in lfOffThreadGlobal)) + try { + lfOffThreadGlobal[lfLocal] = this[lfLocal]; + } catch(lfVare5) {} + var g = newGlobal({newCompartment: true}); + var gw = dbg.addDebuggee(g); +`); +lfOffThreadGlobal.offThreadCompileToStencil(` + setMarkStackLimit(1); + grayRoot()[0] = "foo"; +`); +var stencil = lfOffThreadGlobal.finishOffThreadStencil(); +lfOffThreadGlobal.evalStencil(stencil); +eval(` + var lfOffThreadGlobal = newGlobal({newCompartment: true}); + try { evaluate(\` + gczeal(18, 1); + grayRoot()[0] = "foo"; + let inst = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary( + \\\`(module + (memory (export "memory") 1 1) + )\\\` + ))); +\`); } catch(exc) {} +`); diff --git a/js/src/jit-test/tests/gc/bug-1462337.js b/js/src/jit-test/tests/gc/bug-1462337.js new file mode 100644 index 0000000000..84a5392a1f --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1462337.js @@ -0,0 +1,9 @@ +// |jit-test| skip-if: !('oomTest' in this) + +oomTest(function() { + grayRoot().x = Object.create((obj[name]++)); +}); +oomTest(function() { + gczeal(9); + gcslice(new.target); +}); diff --git a/js/src/jit-test/tests/gc/bug-1464872.js b/js/src/jit-test/tests/gc/bug-1464872.js new file mode 100644 index 0000000000..adf8c4f8c1 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1464872.js @@ -0,0 +1,16 @@ +gczeal(0); + +var g = newGlobal({newCompartment: true}); +var dbg = Debugger(g); +dbg.onEnterFrame = function(frame) {}; + +var g2 = newGlobal({newCompartment: true}); +g2[g] = g; +g2.evaluate("grayRoot()") +g2 = undefined; + +g = undefined; +dbg = undefined; + +gc(); +startgc(100000); diff --git a/js/src/jit-test/tests/gc/bug-1468792.js b/js/src/jit-test/tests/gc/bug-1468792.js new file mode 100644 index 0000000000..9049aca721 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1468792.js @@ -0,0 +1,18 @@ +// |jit-test| error: ReferenceError + +loadFile(` + gczeal(2,9); + evaluate(\` + reportCompare(expect, actual, summary); + \`); +`); +function loadFile(lfVarx) { + try { + evaluate(lfVarx); + } catch (lfVare) {} +} +eval("(function(){({6953421313:0})})")(); +function f() { + x[6953421313] = "a"; +} +f(); diff --git a/js/src/jit-test/tests/gc/bug-1472734.js b/js/src/jit-test/tests/gc/bug-1472734.js new file mode 100644 index 0000000000..f88f3af1c6 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1472734.js @@ -0,0 +1,13 @@ +// |jit-test| skip-if: !('oomTest' in this) || helperThreadCount() === 0 + +try { + oomTest(function() { + eval(` + function eval(source) { + offThreadCompileModuleToStencil(source); + minorgc(); + } + eval(""); + `); + }); +} catch (exc) {} diff --git a/js/src/jit-test/tests/gc/bug-1478943.js b/js/src/jit-test/tests/gc/bug-1478943.js new file mode 100644 index 0000000000..c736f11ab4 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1478943.js @@ -0,0 +1,3 @@ +gczeal(0); +setMarkStackLimit(1); +startgc(1); diff --git a/js/src/jit-test/tests/gc/bug-1490042.js b/js/src/jit-test/tests/gc/bug-1490042.js new file mode 100644 index 0000000000..b043f25486 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1490042.js @@ -0,0 +1,31 @@ +// |jit-test| --no-ion; --no-baseline; --no-blinterp; skip-if: !('gcstate' in this && 'oomAfterAllocations' in this) + +gczeal(0); + +// Create a bunch of ObjectGroups with TypeNewScript attached. +const count = 1000; +let c = []; +let a = []; +for (let i = 0; i < count; i++) { + c[i] = function() { this.a = 1; this.b = 0; this.c = 2; }; + a[i] = new c[i]; +} + +// Start an incremental GC and run until we're about to sweep objects. +assertEq(gcstate(), "NotActive"); +gczeal(21); +startgc(1); + +// Run incremental slices with simulated OOM set up to provoke OOM when sweeping +// types. +assertEq(gcstate(), "Sweep"); +gczeal(10); +unsetgczeal(20); +while (gcstate() == "Sweep") { + oomAfterAllocations(2); + gcslice(1); + resetOOMFailure(); +} + +// Ensure our type information stays alive. +let x = c.length + a.length; diff --git a/js/src/jit-test/tests/gc/bug-1491326.js b/js/src/jit-test/tests/gc/bug-1491326.js new file mode 100644 index 0000000000..b750177d87 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1491326.js @@ -0,0 +1,9 @@ +// |jit-test| --fuzzing-safe + +if (!('oomTest' in this)) + quit(); + +var g = newGlobal({newCompartment: true}); +g.parent = this; +g.eval("new Debugger(parent).onExceptionUnwind = function() {}"); +oomTest(() => l, (true)); diff --git a/js/src/jit-test/tests/gc/bug-1498177.js b/js/src/jit-test/tests/gc/bug-1498177.js new file mode 100644 index 0000000000..d423d47dcb --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1498177.js @@ -0,0 +1,17 @@ + +let a, b; +for (i=0; i < 300000; i++) { + let c = { a: a, b: b }; + a = b; + b = c; +} + +gc(); + +// GCRuntime::setLargeHeapSizeMinBytes will change the low value to be one +// byte lower than the high value (if necessary). But this blew up later +// when the values were mistakingly cast to float then compared, rather than +// kept as size_t. +gcparam('largeHeapSizeMin', 99); + + diff --git a/js/src/jit-test/tests/gc/bug-1505622.js b/js/src/jit-test/tests/gc/bug-1505622.js new file mode 100644 index 0000000000..3f6f83c076 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1505622.js @@ -0,0 +1,54 @@ +// Test that we don't repeatedly trigger last-ditch GCs. + +// Turn of any zeal which will disrupt GC number checks. +gczeal(0); + +function allocUntilFail() { + gc(null, 'shrinking'); + + const initialSize = gcparam("gcBytes"); + const initialMaxSize = gcparam("maxBytes"); + const initGCNumber = gcparam("majorGCNumber"); + + // Set a small heap limit. + gcparam("maxBytes", initialSize + 16 * 1024); + + let error; + try { + let a = []; + while (true) { + a.push(Symbol()); // Symbols are tenured. + } + } catch(err) { + error = err; + } + + const finalGCNumber = gcparam("majorGCNumber"); + + // Resetore heap limit. + gcparam("maxBytes", initialMaxSize); + + gc(); + assertEq(error, "out of memory"); + return finalGCNumber - initGCNumber; +} + +// Set the time limit for skipping last ditch GCs to 5 seconds. +gcparam("minLastDitchGCPeriod", 5); +assertEq(gcparam("minLastDitchGCPeriod"), 5); + +// Allocate up to the heap limit. This triggers a last ditch GC. +let gcCount = allocUntilFail(); +assertEq(gcCount, 1) + +// Allocate up to the limit again. The second time we fail without +// triggering a GC. +gcCount = allocUntilFail(); +assertEq(gcCount, 0) + +// Wait for time limit to expire. +sleep(6); + +// Check we trigger a GC again. +gcCount = allocUntilFail(); +assertEq(gcCount, 1) diff --git a/js/src/jit-test/tests/gc/bug-1513991.js b/js/src/jit-test/tests/gc/bug-1513991.js new file mode 100644 index 0000000000..c694b04b39 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1513991.js @@ -0,0 +1,9 @@ +// |jit-test| skip-if: helperThreadCount() === 0 +evalInWorker(` +var sym4 = Symbol.match; +function test(makeNonArray) {} +function basicSweeping() {} +var wm1 = new WeakMap(); +wm1.set(basicSweeping, sym4); +startgc(100000, 'shrinking'); +`); diff --git a/js/src/jit-test/tests/gc/bug-1514927.js b/js/src/jit-test/tests/gc/bug-1514927.js new file mode 100644 index 0000000000..8f2f1ef52b --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1514927.js @@ -0,0 +1,6 @@ +gczeal(0); +var x = newGlobal(); +x.evaluate("grayRoot()"); +x = 0; +setMarkStackLimit(4); +gc(); diff --git a/js/src/jit-test/tests/gc/bug-1515993.js b/js/src/jit-test/tests/gc/bug-1515993.js new file mode 100644 index 0000000000..8b87000918 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1515993.js @@ -0,0 +1,15 @@ +ignoreUnhandledRejections(); + +function checkGetOffsetsCoverage() { + var g = newGlobal({newCompartment: true}); + var dbg = Debugger(g); + var topLevel; + dbg.onNewScript = function(s) { + topLevel = s; + }; + g.eval(`import(() => 1)`); + topLevel.getChildScripts(); +} +checkGetOffsetsCoverage(); +gczeal(14, 10); +Object.defineProperty(this, "fuzzutils", {}); diff --git a/js/src/jit-test/tests/gc/bug-1517158.js b/js/src/jit-test/tests/gc/bug-1517158.js new file mode 100644 index 0000000000..6ffd8ea40a --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1517158.js @@ -0,0 +1,21 @@ +gczeal(0); + +(function() { + var g = newGlobal({newCompartment: true}); + g.debuggeeGlobal = this; + g.eval("(" + function() { + dbg = new Debugger(debuggeeGlobal); + dbg.onExceptionUnwind = function(frame, exc) { + var s = '!'; + for (var f = frame; f; f = f.older) + if (f.type === "call") + s += f.callee.name; + }; + } + ")();"); + try { + h(); + } catch (e) {} + g.dbg.enabled = false; +})(); +// jsfunfuzz-generated +startgc(114496726); diff --git a/js/src/jit-test/tests/gc/bug-1520778.js b/js/src/jit-test/tests/gc/bug-1520778.js new file mode 100644 index 0000000000..df72248983 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1520778.js @@ -0,0 +1,18 @@ +// |jit-test| error: ReferenceError +gczeal(0); +setMarkStackLimit(1); +var g = newGlobal({ + newCompartment: true +}); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +dbg.onDebuggerStatement = function(frame) { + frame.environment.parent.getVariable('y') +}; +g.eval(` + let y = 1; + g = function () { debugger; }; + g(); +`); +gczeal(9, 10); +f4(); diff --git a/js/src/jit-test/tests/gc/bug-1530643.js b/js/src/jit-test/tests/gc/bug-1530643.js new file mode 100644 index 0000000000..6e9d068cc7 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1530643.js @@ -0,0 +1,4 @@ +// |jit-test| skip-if: !('oomAtAllocation' in this); error: Error + +// OOM testing of worker threads is disallowed because it's not thread safe. +oomAtAllocation(11, 11); diff --git a/js/src/jit-test/tests/gc/bug-1531018.js b/js/src/jit-test/tests/gc/bug-1531018.js new file mode 100644 index 0000000000..efca854197 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1531018.js @@ -0,0 +1,3 @@ +gczeal(14,1); +const one = BigInt(1); +new Map([[one, 42]]).has(one); diff --git a/js/src/jit-test/tests/gc/bug-1531626.js b/js/src/jit-test/tests/gc/bug-1531626.js new file mode 100644 index 0000000000..bf003e96cc --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1531626.js @@ -0,0 +1,61 @@ +// Test that setting the nursery size works as expected. +// +// It's an error to set the minimum size greater than the maximum +// size. Parameter values are rounded to the nearest legal nursery +// size. + +load(libdir + "asserts.js"); + +const chunkSizeKB = gcparam('chunkBytes') / 1024; +const pageSizeKB = gcparam('systemPageSizeKB'); + +const testSizesKB = [128, 129, 255, 256, 516, 1023, 1024, 3*1024, 4*1024+1, 16*1024]; + +// Valid maximum sizes must be >= 1MB. +const testMaxSizesKB = testSizesKB.filter(x => x >= 1024); + +for (let max of testMaxSizesKB) { + // Don't test minimums greater than the maximum. + for (let min of testSizesKB.filter(x => x <= max)) { + setMinMax(min, max); + } +} + +// The above loops raised the nursery size. Now reduce it to ensure that +// forcibly-reducing it works correctly. +setMinMax(256, 1024); + +// Try invalid configurations. +const badSizesKB = [ 0, 1, 129 * 1024]; +function assertParamOutOfRange(f) { + assertErrorMessage(f, Object, "Parameter value out of range"); +} +for (let size of badSizesKB) { + assertParamOutOfRange(() => gcparam('minNurseryBytes', size * 1024)); + assertParamOutOfRange(() => gcparam('maxNurseryBytes', size * 1024)); +} + +function setMinMax(min, max) { + gcparam('minNurseryBytes', min * 1024); + gcparam('maxNurseryBytes', max * 1024); + assertEq(gcparam('minNurseryBytes'), nearestLegalSize(min) * 1024); + assertEq(gcparam('maxNurseryBytes'), nearestLegalSize(max) * 1024); + allocateSomeThings(); + gc(); +} + +function allocateSomeThings() { + for (let i = 0; i < 1000; i++) { + let obj = { an: 'object', with: 'fields' }; + } +} + +function nearestLegalSize(sizeKB) { + let step = sizeKB >= chunkSizeKB ? chunkSizeKB : pageSizeKB; + return round(sizeKB, step); +} + +function round(x, y) { + x += y / 2; + return x - (x % y); +} diff --git a/js/src/jit-test/tests/gc/bug-1532376.js b/js/src/jit-test/tests/gc/bug-1532376.js new file mode 100644 index 0000000000..c523ba06b8 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1532376.js @@ -0,0 +1,10 @@ +"use strict"; +function __f_276() { + this.getNameReallyHard = () => eval("eval('(() => this.name)()')") +} +for (var __v_1377 = 0; __v_1377 < 10000; __v_1377++) { + var __v_1378 = new __f_276(); + try { + __v_1376[__getRandomProperty()]; + } catch (e) {} +__v_1378.getNameReallyHard()} diff --git a/js/src/jit-test/tests/gc/bug-1540670.js b/js/src/jit-test/tests/gc/bug-1540670.js new file mode 100644 index 0000000000..4b271a4fee --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1540670.js @@ -0,0 +1,17 @@ +// Adapted from a fuzz test, the origianl test was: +// +// gcparam('minNurseryBytes', 0); +// gczeal(4); +// +// And would trick the GC into a state where the nursery had been setup but +// thought that it was disabled. + +load(libdir + "asserts.js"); + +assertErrorMessage( + () => gcparam('minNurseryBytes', 0), + Error, + "Parameter value out of range"); + +gczeal(4); + diff --git a/js/src/jit-test/tests/gc/bug-1542279.js b/js/src/jit-test/tests/gc/bug-1542279.js new file mode 100644 index 0000000000..0efdc32ce2 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1542279.js @@ -0,0 +1,13 @@ + +load(libdir + "asserts.js"); + +assertErrorMessage( + () => gcparam('maxNurseryBytes', 2 ** 32 - 1), + Error, + "Parameter value out of range"); +gc() + +gcparam('minNurseryBytes', 32*1024); +gcparam('maxNurseryBytes', 64*1024); +gc() + diff --git a/js/src/jit-test/tests/gc/bug-1542982.js b/js/src/jit-test/tests/gc/bug-1542982.js new file mode 100644 index 0000000000..feeb8522c2 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1542982.js @@ -0,0 +1,20 @@ + +load(libdir + "asserts.js"); + +assertErrorMessage( + () => gcparam('minNurseryBytes', 0), + Error, + "Parameter value out of range"); + +assertErrorMessage( + () => gcparam('maxNurseryBytes', 256*1024*1024), + Error, + "Parameter value out of range"); + +// This is both bigger than the maximum and out of range. but there's no way +// to test out of range without testing bigger than the maximum. +assertErrorMessage( + () => gcparam('minNurseryBytes', 256*1024*1024), + Error, + "Parameter value out of range"); + diff --git a/js/src/jit-test/tests/gc/bug-1543014.js b/js/src/jit-test/tests/gc/bug-1543014.js new file mode 100644 index 0000000000..9f91ad3526 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1543014.js @@ -0,0 +1,11 @@ +// |jit-test| skip-if: helperThreadCount() === 0 +gczeal(0); +evalInWorker(` + var sym4 = Symbol.match; + function basicSweeping() {}; + var wm1 = new WeakMap(); + wm1.set(basicSweeping, sym4); + startgc(100000, 'shrinking'); +`); +gczeal(2); +var d1 = newGlobal({}); diff --git a/js/src/jit-test/tests/gc/bug-1543589.js b/js/src/jit-test/tests/gc/bug-1543589.js new file mode 100644 index 0000000000..38d5eb9249 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1543589.js @@ -0,0 +1,16 @@ +// |jit-test| skip-if: getBuildConfiguration()["arm64-simulator"] === true +// This test times out in ARM64 simulator builds. + +gczeal(0); +gcparam('maxNurseryBytes', 16 * 1024 * 1024); +gcparam('minNurseryBytes', 16 * 1024 * 1024); + +let a = []; +for (var i = 0; i < 20000; i++) { + a.push(import("nonexistent.js")); + Symbol(); +} + +for (let p of a) { + p.catch(() => {}); +} diff --git a/js/src/jit-test/tests/gc/bug-1556155.js b/js/src/jit-test/tests/gc/bug-1556155.js new file mode 100644 index 0000000000..3c0dd11251 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1556155.js @@ -0,0 +1,7 @@ +// |jit-test| skip-if: !('oomTest' in this) +a = []; +minorgc(); +Object.defineProperty(a, 12, {}).push(1); +toString = (function() { return a.reverse(); }); +oomTest(Date.prototype.toJSON); +oomTest(Date.prototype.toJSON); diff --git a/js/src/jit-test/tests/gc/bug-1557928.js b/js/src/jit-test/tests/gc/bug-1557928.js new file mode 100644 index 0000000000..7746d353fb --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1557928.js @@ -0,0 +1,19 @@ +// |jit-test| skip-if: helperThreadCount() === 0 + +var g = newGlobal({ newCompartment: true }); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +lfOffThreadGlobal = g; +lfOffThreadGlobal.offThreadCompileToStencil(` + grayRoot()[0] = "foo"; + `); +var stencil = lfOffThreadGlobal.finishOffThreadStencil(); +lfOffThreadGlobal.evalStencil(stencil); +var g = newGlobal({newCompartment: true}); +var gw = dbg.addDebuggee(g); +lfOffThreadGlobal = null; +gc(); +schedulezone(this); +schedulezone('atoms'); +gc('zone'); + diff --git a/js/src/jit-test/tests/gc/bug-1565272.js b/js/src/jit-test/tests/gc/bug-1565272.js new file mode 100644 index 0000000000..96dc0c63b9 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1565272.js @@ -0,0 +1,20 @@ +// |jit-test| --fuzzing-safe; --ion-offthread-compile=off; --ion-warmup-threshold=10; skip-if: (getBuildConfiguration()['android'] && getBuildConfiguration()['debug'] && getBuildConfiguration()['arm64']) + +// Test that Nursery::disable() waits for poisoning to finish before +// discarding and re-poisoning its chunks. + +for(var i = 0; i < 100; i++) { + try { + evalInWorker(` + function testOneSize(current_size) { + var eval_string = 'obj = {'; + for (var current = 0; current <= current_size; ++current) + eval_string += 'k' + current + ':' + current + ',' + } + testOneSize(1023); + testOneSize(1024); + gczeal(4); + `); + } catch (exc) {} +} + diff --git a/js/src/jit-test/tests/gc/bug-1568119.js b/js/src/jit-test/tests/gc/bug-1568119.js new file mode 100644 index 0000000000..1aed85c325 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1568119.js @@ -0,0 +1,21 @@ +// |jit-test| skip-if: !('oomTest' in this) + +function allocateSomeStuff() { + return {a: "a fish", b: [1, 2, 3]}; +} + +oomTest(() => { + // Run a minor GC with a small nursery. + gcparam('minNurseryBytes', 256 * 1024); + gcparam('maxNurseryBytes', 256 * 1024); + allocateSomeStuff(); + minorgc(); + + // Run a minor GC with a larger nursery to get it to attempt to grow and + // fail the allocation there. + gcparam('maxNurseryBytes', 1024 * 1024); + gcparam('minNurseryBytes', 1024 * 1024); + allocateSomeStuff(); + minorgc(); +}); + diff --git a/js/src/jit-test/tests/gc/bug-1568740.js b/js/src/jit-test/tests/gc/bug-1568740.js new file mode 100644 index 0000000000..6cc003cb94 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1568740.js @@ -0,0 +1,20 @@ +gczeal(0); + +function setAndTest(param, value) { + gcparam(param, value); + assertEq(gcparam(param), value); +} + + +// Set a large nursery size. +setAndTest("maxNurseryBytes", 1024*1024); +setAndTest("minNurseryBytes", 1024*1024); +minorgc(); +assertEq(gcparam("nurseryBytes"), 1024*1024); + +// Force it to shrink by more then one half. +setAndTest("minNurseryBytes", 64*1024); +setAndTest("maxNurseryBytes", 64*1024); +minorgc(); +assertEq(gcparam("nurseryBytes"), 64*1024); + diff --git a/js/src/jit-test/tests/gc/bug-1569840.js b/js/src/jit-test/tests/gc/bug-1569840.js new file mode 100644 index 0000000000..70d28add73 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1569840.js @@ -0,0 +1,9 @@ + +gczeal(0); + +gcparam("maxNurseryBytes", 1024*1024); +gcparam("minNurseryBytes", 1024*1024); +var obj = { foo: 'bar', baz: [1, 2, 3]}; +minorgc(); +assertEq(gcparam("nurseryBytes"), 1024*1024); + diff --git a/js/src/jit-test/tests/gc/bug-1571439.js b/js/src/jit-test/tests/gc/bug-1571439.js new file mode 100644 index 0000000000..ebaa8e80d2 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1571439.js @@ -0,0 +1,24 @@ +// |jit-test| --ion-offthread-compile=off; --blinterp-warmup-threshold=1; error:TypeError + +gcparam("maxBytes", 1024*1024); +function complex(aReal, aImag) { + this.r35 = aReal; + gczeal(4, 10); + this.square = function() { + return new complex(this.r35 * this.r35 - this.i14 * this.i14, 2 * this.r35 * this.i14); + } +} +function mandelbrotValueOO(aC, aIterMax) { + let Z90 = new complex(0.0, 0.0); + Z90 = Z90.square().add(aC); +} +const width = 60; +const height = 60; +const max_iters = 50; +for (let img_x = 0; img_x < width; img_x++) { + for (let img_y = 0; img_y < height; img_y++) { + let C57 = new complex(-2 + (img_x / width) * 3, -1.5 + (img_y / height) * 3); + var res = mandelbrotValueOO(C57, max_iters); + } +} + diff --git a/js/src/jit-test/tests/gc/bug-1573458.js b/js/src/jit-test/tests/gc/bug-1573458.js new file mode 100644 index 0000000000..f33b57eac5 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1573458.js @@ -0,0 +1,4 @@ +gczeal(0); +setGCCallback({action: "enterNullRealm"}); +gczeal(2, 1); +Symbol(); diff --git a/js/src/jit-test/tests/gc/bug-1574877.js b/js/src/jit-test/tests/gc/bug-1574877.js new file mode 100644 index 0000000000..e53746bcb0 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1574877.js @@ -0,0 +1,17 @@ +// |jit-test| skip-if: !('oomTest' in this) + +function parseModule(source) { + offThreadCompileModuleToStencil(source); + var stencil = finishOffThreadStencil(); + return instantiateModuleStencil(stencil); +} +function loadFile(lfVarx) { + oomTest(function() { + parseModule(lfVarx); + }); +} +loadFile(` + expect = new class prototype extends Object { + a43 = function () {} + } +`); diff --git a/js/src/jit-test/tests/gc/bug-1578462.js b/js/src/jit-test/tests/gc/bug-1578462.js new file mode 100644 index 0000000000..da3644511a --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1578462.js @@ -0,0 +1,59 @@ +// |jit-test| skip-if: !hasFunction["gczeal"] + +// Check that incoming CCW edges are marked gray in a zone GC if the +// source is gray. + +// Set up a new compartment with a gray object wrapper to this +// compartment. +function createOtherCompartment() { + let t = {}; + addMarkObservers([t]); + let g = newGlobal({newCompartment: true}); + g.t = t; + g.eval(`grayRoot().push(t);`); + g.t = null; + t = null; + return g; +} + +function startGCMarking() { + startgc(1); + while (gcstate() === "Prepare") { + gcslice(1); + } +} + +gczeal(0); + +let g = createOtherCompartment(); + +// The target should be gray in a full GC... +gc(); +assertEq(getMarks()[0], "gray"); + +// and subsequently gray in a zone GC of only this compartment. +gc(this); +assertEq(getMarks()[0], "gray"); + +// If a barrier marks the gray wrapper black after the start of the +// GC, the target ends up black. +schedulezone(this); +startGCMarking() +assertEq(getMarks()[0], "unmarked"); +g.eval(`grayRoot()`); // Barrier marks gray roots black. +assertEq(getMarks()[0], "black"); +finishgc(); + +// A full collection resets the wrapper to gray. +gc(); +assertEq(getMarks()[0], "gray"); + +// If a barrier marks the gray wrapper black after the target has +// already been marked gray, the target ends up black. +gczeal(25); // Yield during gray marking. +schedulezone(this); +startGCMarking(); +assertEq(getMarks()[0], "gray"); +g.eval(`grayRoot()`); // Barrier marks gray roots black. +assertEq(getMarks()[0], "black"); +finishgc(); diff --git a/js/src/jit-test/tests/gc/bug-1579025.js b/js/src/jit-test/tests/gc/bug-1579025.js new file mode 100644 index 0000000000..c11e7fdf1e --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1579025.js @@ -0,0 +1,58 @@ +// Test that a zone GC collects the selected zones. + +function waitForState(state) { + while (gcstate() !== state && gcstate() !== "NotActive") { + gcslice(100); + } +} + +gczeal(0); +gc(); + +let z1 = this; +let z2 = newGlobal({newCompartment: true}); + +// Initially nothing is being collected. + +assertEq(gcstate(), "NotActive"); +assertEq(gcstate(z1), "NoGC"); +assertEq(gcstate(z2), "NoGC"); + +// No zones selected => full GC. + +startgc(1); + +// It's non-deterministic whether we see the prepare state or not. +waitForState("Mark"); + +assertEq(gcstate(), "Mark"); +assertEq(gcstate(z1), "MarkBlackOnly"); +assertEq(gcstate(z2), "MarkBlackOnly"); +finishgc(); + +// Use of schedulezone() => zone GC. + +schedulezone(z1); +startgc(1); +waitForState("Mark"); +assertEq(gcstate(), "Mark"); +assertEq(gcstate(z1), "MarkBlackOnly"); +assertEq(gcstate(z2), "NoGC"); +finishgc(); + +schedulezone(z2); +startgc(1); +waitForState("Mark"); +assertEq(gcstate(), "Mark"); +assertEq(gcstate(z1), "NoGC"); +assertEq(gcstate(z2), "MarkBlackOnly"); +finishgc(); + +schedulezone(z1); +schedulezone(z2); +startgc(1); +waitForState("Mark"); +assertEq(gcstate(), "Mark"); +assertEq(gcstate(z1), "MarkBlackOnly"); +assertEq(gcstate(z2), "MarkBlackOnly"); +finishgc(); diff --git a/js/src/jit-test/tests/gc/bug-1585159.js b/js/src/jit-test/tests/gc/bug-1585159.js new file mode 100644 index 0000000000..077dee8832 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1585159.js @@ -0,0 +1,11 @@ + +// Set a nursery size larger than the current nursery size. +gcparam("minNurseryBytes", 1024 * 1024); +gcparam("maxBytes", gcparam("gcBytes") + 4096); + +// Allocate something in the nursery. +let obj = {'foo': 38, 'bar': "A string"}; + +// Trigger a last-ditch GC. +print(gc(0, "last-ditch")); + diff --git a/js/src/jit-test/tests/gc/bug-1590176.js b/js/src/jit-test/tests/gc/bug-1590176.js new file mode 100644 index 0000000000..db446d56ae --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1590176.js @@ -0,0 +1,13 @@ +// Create function clones in a separate Zone so that the GC can relazify them. +let g = newGlobal({newCompartment: true}); +g.evaluate(` + function factory() { + return function() { }; + } +`); + +// Delazify, Relazify, Delazify +g.factory()(); +finishgc(); +startgc(0, 'shrinking'); +g.factory()(); diff --git a/js/src/jit-test/tests/gc/bug-1590904.js b/js/src/jit-test/tests/gc/bug-1590904.js new file mode 100644 index 0000000000..ab3e7b432f --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1590904.js @@ -0,0 +1,6 @@ +// |jit-test| skip-if: !getBuildConfiguration()['has-gczeal'] +gczeal(0); +gczeal(20); +startgc(1); +gczeal(10); +while (gcstate() == "Sweep") {} diff --git a/js/src/jit-test/tests/gc/bug-1592487.js b/js/src/jit-test/tests/gc/bug-1592487.js new file mode 100644 index 0000000000..6f3343836f --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1592487.js @@ -0,0 +1,28 @@ +try {} catch (e) {} +try {} catch (e) {} +try { + (function() { + (function() {}); + }) +} catch (e) {} +try { + t; +} catch (e) {} +try {} catch (e) {} +try { + try { + setMarkStackLimit(1); + } catch (e) {} +} catch (e) {} +try { + (function() { + ""(function() {}); + }) +} catch (e) {} +try { + for (t of Number) { + (function() { + (function() {}); + }) + } +} catch (e) {} diff --git a/js/src/jit-test/tests/gc/bug-1593975.js b/js/src/jit-test/tests/gc/bug-1593975.js new file mode 100644 index 0000000000..69ebcafb94 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1593975.js @@ -0,0 +1,21 @@ +// |jit-test| error: ReferenceError +function runtest(func) { + func(); +} +const g1 = newGlobal({ + newCompartment: true +}); +function transplantMarking() { + const vals = {}; + vals.map = new WeakMap(); + enqueueMark(vals.map); + enqueueMark("yield"); + enqueueMark("enter-weak-marking-mode"); +} +if (this.enqueueMark) { + enqueueMark("enter-weak-marking-mode"); + runtest(transplantMarking); + egc = 60; + gcslice(egc * 100); +} +x(); diff --git a/js/src/jit-test/tests/gc/bug-1597970.js b/js/src/jit-test/tests/gc/bug-1597970.js new file mode 100644 index 0000000000..5b384a399f --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1597970.js @@ -0,0 +1,5 @@ +enableShellAllocationMetadataBuilder(); +evaluate(` + gczeal(9,3); + new FinalizationRegistry(function() {}); +`); diff --git a/js/src/jit-test/tests/gc/bug-1600238.js b/js/src/jit-test/tests/gc/bug-1600238.js new file mode 100644 index 0000000000..6b6aeb85ac --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1600238.js @@ -0,0 +1,21 @@ +gczeal(0); +newGlobal(); +nukeAllCCWs(); +function f() { + global = newGlobal({ + newCompartment: true + }); + try { + return global.eval("new FinalizationRegistry(function(){})"); + } catch (e) { + if (e instanceof TypeError && e.message.includes('dead')) { + // Creating a new CCW to the global fails with + // --more-compartments option. + quit(); + } + throw e; + } +} +r = f(); +r.register({}, {}, {}); +startgc(); diff --git a/js/src/jit-test/tests/gc/bug-1602741.js b/js/src/jit-test/tests/gc/bug-1602741.js new file mode 100644 index 0000000000..5f1cc7eca8 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1602741.js @@ -0,0 +1,24 @@ +// Test that drainJobQueue() drains all jobs, including those queued +// by FinalizationRegistry callbacks. + +let finalizeRan = false; +let promiseRan = false; + +let fr = new FinalizationRegistry(() => { + finalizeRan = true; + Promise.resolve().then(() => { + promiseRan = true; + }); +}); + +fr.register({}, {}); + +gc(); + +assertEq(finalizeRan, false); +assertEq(promiseRan, false); + +drainJobQueue(); + +assertEq(finalizeRan, true); +assertEq(promiseRan, true); diff --git a/js/src/jit-test/tests/gc/bug-1603330.js b/js/src/jit-test/tests/gc/bug-1603330.js new file mode 100644 index 0000000000..888ae40a07 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1603330.js @@ -0,0 +1,16 @@ +// Allocate the object in the function to prevent marked as a singleton so the +// object won't be kept alive by IC stub. +function allocObj() { return {}; } + +let wr; +{ + let obj = allocObj(); + wr = new WeakRef(obj); +} + +assertEq(wr.deref() !== undefined, true); + +clearKeptObjects(); +gc(); + +assertEq(wr.deref(), undefined); diff --git a/js/src/jit-test/tests/gc/bug-1603917.js b/js/src/jit-test/tests/gc/bug-1603917.js new file mode 100644 index 0000000000..c1b58f9a28 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1603917.js @@ -0,0 +1,2 @@ +// |jit-test| skip-if: helperThreadCount() === 0 +evalInWorker("new WeakRef({});"); diff --git a/js/src/jit-test/tests/gc/bug-1605348.js b/js/src/jit-test/tests/gc/bug-1605348.js new file mode 100644 index 0000000000..f55b13af32 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1605348.js @@ -0,0 +1,5 @@ +fullcompartmentchecks(true); +var g37 = newGlobal({ + newCompartment: true +}); +new g37.WeakRef({}); diff --git a/js/src/jit-test/tests/gc/bug-1605633.js b/js/src/jit-test/tests/gc/bug-1605633.js new file mode 100644 index 0000000000..87f5668886 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1605633.js @@ -0,0 +1,12 @@ +newGlobal(); +nukeAllCCWs(); +var g28 = newGlobal({ + newCompartment: true +}); +try { + let wr6 = new g28.WeakRef(newGlobal()); + new WeakRef(wr6); +} catch (e) { + assertEq(e.message == "can't access dead object", true); +} + diff --git a/js/src/jit-test/tests/gc/bug-1607495.js b/js/src/jit-test/tests/gc/bug-1607495.js new file mode 100644 index 0000000000..a759f27dff --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1607495.js @@ -0,0 +1,6 @@ +gczeal(14, 2); +var g32 = newGlobal(); +let wr6 = new g32.WeakRef(newGlobal({ + newCompartment: true +})); +let wr7 = new WeakRef(wr6); diff --git a/js/src/jit-test/tests/gc/bug-1607665.js b/js/src/jit-test/tests/gc/bug-1607665.js new file mode 100644 index 0000000000..a54a4e0455 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1607665.js @@ -0,0 +1,9 @@ +var ta = new BigInt64Array(12); + +var s = ""; +for (var i = 0; i < ta.length; ++i) { + var x = ta[i]; + s += x; +} + +assertEq(s, "000000000000"); diff --git a/js/src/jit-test/tests/gc/bug-1607687.js b/js/src/jit-test/tests/gc/bug-1607687.js new file mode 100644 index 0000000000..dd7bde2ad1 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1607687.js @@ -0,0 +1,4 @@ +var limit = 1 << 16; +var m = new Map; +for (var i = 1n; i < limit; i++) + m.set(i, i); diff --git a/js/src/jit-test/tests/gc/bug-1608355.js b/js/src/jit-test/tests/gc/bug-1608355.js new file mode 100644 index 0000000000..359292aabc --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1608355.js @@ -0,0 +1,7 @@ +try { + enableShellAllocationMetadataBuilder(); + gczeal(11); + gczeal(22); + grayRoot() = 0; +} catch (e) {} +newGlobal(); diff --git a/js/src/jit-test/tests/gc/bug-1610621.js b/js/src/jit-test/tests/gc/bug-1610621.js new file mode 100644 index 0000000000..8db61cb264 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1610621.js @@ -0,0 +1 @@ +assertEq(clearKeptObjects(), undefined); diff --git a/js/src/jit-test/tests/gc/bug-1620195.js b/js/src/jit-test/tests/gc/bug-1620195.js new file mode 100644 index 0000000000..7ee5540e26 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1620195.js @@ -0,0 +1,15 @@ +var g99 = newGlobal({}); +nukeAllCCWs(); +let group = new FinalizationRegistry(x90 => 0); +let other = newGlobal({ + newCompartment: true +}); + +try { + group.register(evalcx('({})', other), 1); +} catch (e) { + // With the args --more-compartments evalcx will throw. + assertEq(e.message == "can't access dead object" || + e.message == "Invalid scope argument to evalcx", + true); +} diff --git a/js/src/jit-test/tests/gc/bug-1620196.js b/js/src/jit-test/tests/gc/bug-1620196.js new file mode 100644 index 0000000000..34fda22c80 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1620196.js @@ -0,0 +1,7 @@ +gczeal(4); +let heldValues = []; +registry = new FinalizationRegistry(value => { + heldValues.push(value); +}); +registry.register({}, 42); +gc(); diff --git a/js/src/jit-test/tests/gc/bug-1620209.js b/js/src/jit-test/tests/gc/bug-1620209.js new file mode 100644 index 0000000000..a7d7f61374 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1620209.js @@ -0,0 +1,5 @@ +// |jit-test| skip-if: helperThreadCount() === 0 +verifyprebarriers(); +offThreadCompileToStencil(''); +var dbg = new Debugger(); +var objects = dbg.findObjects(); diff --git a/js/src/jit-test/tests/gc/bug-1620213.js b/js/src/jit-test/tests/gc/bug-1620213.js new file mode 100644 index 0000000000..7eeb62a652 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1620213.js @@ -0,0 +1,3 @@ +for (var i = 0; i < 32768; i++) { + new ArrayBuffer(1024*1024); +} diff --git a/js/src/jit-test/tests/gc/bug-1620221.js b/js/src/jit-test/tests/gc/bug-1620221.js new file mode 100644 index 0000000000..d363dab3dd --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1620221.js @@ -0,0 +1,23 @@ +function testStepping(script, expected) { + let g = newGlobal({newCompartment: true}); + let f = g.eval(script); + let log = []; + function maybePause(frame) { + let line = frame.script.getOffsetLocation(frame.offset).lineNumber; + log.push(line); + } + let dbg = new Debugger(g); + dbg.onEnterFrame = frame => { + maybePause(frame); + }; + f(); +} +var g7 = newGlobal({newCompartment: true}); +g7.parent = this; +g7.eval(` + Debugger(parent).onEnterFrame = function(frame) { + let v = frame.environment.getVariable('var0'); + }; +`); +testStepping("(function() {})"); +gc(); diff --git a/js/src/jit-test/tests/gc/bug-1628440.js b/js/src/jit-test/tests/gc/bug-1628440.js new file mode 100644 index 0000000000..e128fcb7ac --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1628440.js @@ -0,0 +1,9 @@ +// |jit-test| error: ReferenceError +gczeal(10, 2); +let cleanup = function(iter) {} +let key = {"k": "this is my key"}; +let fg = new FinalizationRegistry(cleanup); +let object = {}; +fg.register(object, {}, key); +let success = fg.unregister(key); +throw new ReferenceError(); diff --git a/js/src/jit-test/tests/gc/bug-1643913.js b/js/src/jit-test/tests/gc/bug-1643913.js new file mode 100644 index 0000000000..1f83a9a0a3 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1643913.js @@ -0,0 +1,36 @@ +gczeal(0); + +for (let p of [false, true]) { + f(p); + + // Run an incremental GC to completion. + startgc(1); + while (gcstate() !== 'NotActive') { + gcslice(10000, { dontStart: true }); + } +} + +function ccwToObject() { + return evalcx('({})', newGlobal({newCompartment: true})); +} + +function ccwToRegistry() { + return evalcx('new FinalizationRegistry(value => {})', + newGlobal({newCompartment: true})); +} + +function f(p) { + let registry = ccwToRegistry(); + let target = ccwToObject(); + registry.register(target, undefined); + + // Add a CCW from registry to target zone or vice versa to control + // the order the zones are swept in. + if (p) { + registry.ptr = target; + } else { + target.ptr = registry; + } + + gc(); +} diff --git a/js/src/jit-test/tests/gc/bug-1644985-2.js b/js/src/jit-test/tests/gc/bug-1644985-2.js new file mode 100644 index 0000000000..fbb00b59de --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1644985-2.js @@ -0,0 +1,4 @@ +let fr = new FinalizationRegistry(x => 1); +fr.register(evalcx('({})', newGlobal({newCompartment: true}))); +nukeAllCCWs(); +gc(); diff --git a/js/src/jit-test/tests/gc/bug-1644985.js b/js/src/jit-test/tests/gc/bug-1644985.js new file mode 100644 index 0000000000..aac0e99277 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1644985.js @@ -0,0 +1,4 @@ +let wr = new WeakRef(evalcx('({})', newGlobal({newCompartment: true}))); +nukeAllCCWs(); +clearKeptObjects(); +gc(); diff --git a/js/src/jit-test/tests/gc/bug-1647747-debugger-weakmark.js b/js/src/jit-test/tests/gc/bug-1647747-debugger-weakmark.js new file mode 100644 index 0000000000..4eb4944c27 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1647747-debugger-weakmark.js @@ -0,0 +1,17 @@ +var g27 = newGlobal({newCompartment: true}); +g27.debuggeeGlobal = this; +g27.eval(` + dbg = new Debugger(debuggeeGlobal); + dbg.onExceptionUnwind = function (frame, exc) {}; +`); +s45 = newGlobal({newCompartment: true}); +try { + evalcx(` + function h(h) {} + h.valueOf=g; + `, s45); +} catch (x) {} +try { + evalcx("throw h", s45) +} catch (x) {} +gcslice(100000); diff --git a/js/src/jit-test/tests/gc/bug-1648901.js b/js/src/jit-test/tests/gc/bug-1648901.js new file mode 100644 index 0000000000..13e4895068 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1648901.js @@ -0,0 +1,7 @@ +// |jit-test| skip-if: !('gczeal' in this) + +gczeal(15); +enableShellAllocationMetadataBuilder(); +var registry = new FinalizationRegistry(x => 0); +gczeal(9, 3); +registry.register({}, 1, {}); diff --git a/js/src/jit-test/tests/gc/bug-1651345.js b/js/src/jit-test/tests/gc/bug-1651345.js new file mode 100644 index 0000000000..54187c5d55 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1651345.js @@ -0,0 +1,7 @@ +function main() { + const v3 = {get:Object}; + const v5 = Object.defineProperty(Object,0,v3); + addMarkObservers(Object) + gc(); +} +main(); diff --git a/js/src/jit-test/tests/gc/bug-1652425.js b/js/src/jit-test/tests/gc/bug-1652425.js new file mode 100644 index 0000000000..fec591948c --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1652425.js @@ -0,0 +1,11 @@ +// |jit-test| error: Error + +function varying(mapColor, keyColor) { + enqueueMark(`set-color-${keyColor}`); + enqueueMark("yield"); + startgc(100000); +} +for (const mapColor of ['gray', 'black']) { + for (const keyColor of ['gray', 'black', 'unmarked']) + varying(mapColor, keyColor); +} diff --git a/js/src/jit-test/tests/gc/bug-1652492.js b/js/src/jit-test/tests/gc/bug-1652492.js new file mode 100644 index 0000000000..a64f541a12 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1652492.js @@ -0,0 +1,17 @@ +function f1() { + arr = f2; + var p = arr[(gczeal(9))|0]; +} +f2 = f1; +f2(); +try { + function allocObj() { return {}; } + { + let obj = allocObj(); + wr = new WeakRef(obj); + } + clearKeptObjects(); +(new obj); +} catch(exc) {} +let obj = allocObj(); +wr = new WeakRef(obj); diff --git a/js/src/jit-test/tests/gc/bug-1654186.js b/js/src/jit-test/tests/gc/bug-1654186.js new file mode 100644 index 0000000000..562938d8f8 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1654186.js @@ -0,0 +1,8 @@ +// |jit-test| allow-oom; skip-if: !('oomAfterAllocations' in this) + +gczeal(14, 5); +var g = newGlobal(); +g.eval("(" + function() { + oomAfterAllocations(100); +} + ")()"); +f.x(""); diff --git a/js/src/jit-test/tests/gc/bug-1655917.js b/js/src/jit-test/tests/gc/bug-1655917.js new file mode 100644 index 0000000000..5805958ed7 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1655917.js @@ -0,0 +1,10 @@ +var g = newGlobal({ newCompartment: true }); +g.eval(` + var obj = {}; + var ref = new WeakRef(obj); + Promise.resolve().then(() => { + assertEq(ref.deref(), obj); + }); +`); +nukeCCW(g.ref); +drainJobQueue(); diff --git a/js/src/jit-test/tests/gc/bug-1657554.js b/js/src/jit-test/tests/gc/bug-1657554.js new file mode 100644 index 0000000000..f751d442b9 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1657554.js @@ -0,0 +1,2 @@ +// |jit-test| skip-if: !('oomTest' in this) +oomTest(() => eval("new WeakRef({});")); diff --git a/js/src/jit-test/tests/gc/bug-1660293.js b/js/src/jit-test/tests/gc/bug-1660293.js new file mode 100644 index 0000000000..a2c953c11f --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1660293.js @@ -0,0 +1,12 @@ +// |jit-test| skip-if: !('oomAfterAllocations' in this) + +try { +function varying(mapColor, keyColor) { + enqueueMark(`set-color-${keyColor}`); + enqueueMark("yield"); + startgc(100000); +} +for (const mapColor of ['gray', 'black']) + varying(mapColor, 0x7fff); +} catch (exc) {} +oomAfterAllocations(100); diff --git a/js/src/jit-test/tests/gc/bug-1667336.js b/js/src/jit-test/tests/gc/bug-1667336.js new file mode 100644 index 0000000000..11b1fd456d --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1667336.js @@ -0,0 +1,17 @@ +// |jit-test| --ion-offthread-compile=off; skip-if: helperThreadCount() === 0 + +var g = newGlobal(); +gczeal(9, 1); +gczeal(11, 2); +g.offThreadCompileToStencil(""); +setJitCompilerOption("offthread-compilation.enable", 1); + +for (let i = 0 ; i < 3000; i++) { + loadFile(); +} + +function loadFile() { + try { + x; + } catch(exc) {} +} diff --git a/js/src/jit-test/tests/gc/bug-1671125.js b/js/src/jit-test/tests/gc/bug-1671125.js new file mode 100644 index 0000000000..74944ea350 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1671125.js @@ -0,0 +1,5 @@ +// |jit-test| skip-if: helperThreadCount() === 0 +verifyprebarriers() +evalInWorker(` + Object.defineProperty(this, "x", {}); +`); diff --git a/js/src/jit-test/tests/gc/bug-1688749.js b/js/src/jit-test/tests/gc/bug-1688749.js new file mode 100644 index 0000000000..1135646d64 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1688749.js @@ -0,0 +1,160 @@ +function f(x) { + Math.round == Math.round(); +} +function g(f, y) { + for (var k = 0; k < 9999; ++k) { + f() + } +} +g(f, [ + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + /x/, + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + createIsHTMLDDA(), + new Boolean(false), + new Boolean(false), + new Boolean(false), + new Boolean(false), + new Boolean(false), + new Boolean(false), + new Boolean(false), + new Boolean(false), + new Boolean(false), + new Boolean(false), + new Boolean(false), + new Boolean(false), + new Boolean(false), + new Boolean(false), + new Boolean(false), + new Boolean(false), + new Boolean(false), + new Boolean(false), + new Boolean(false), + new Boolean(false), + new Boolean(false), + new Boolean(false), + new Boolean(false), + new Boolean(false), +]); +for (var k = 0; k < 99999; ++k) { + f() +} diff --git a/js/src/jit-test/tests/gc/bug-1689039.js b/js/src/jit-test/tests/gc/bug-1689039.js new file mode 100644 index 0000000000..4bfe9bd140 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1689039.js @@ -0,0 +1,232 @@ +// |jit-test| skip-if: !('oomAfterAllocations' in this) + +gczeal(7); +for (let i = 0; i < 9999; ++i) { + undefined + "y"; +} +function f(x) { + x.replace(/ /g, " "); + x.replace(/ /g, " "); +} +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +f(" "); +oomAfterAllocations(1); diff --git a/js/src/jit-test/tests/gc/bug-1689794.js b/js/src/jit-test/tests/gc/bug-1689794.js new file mode 100644 index 0000000000..159016edb8 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1689794.js @@ -0,0 +1,22 @@ +// |jit-test| error: Error; --cpu-count=2; --fast-warmup + +function main() { + new main; +} +try { main(); } catch(exc) {} +evaluate(` + function main() { + function v0(v1,v2) { + try { + let v3 = v0(); + } catch(v28) { + eval(\` + v13(v11,RegExp); + \`); + } + } + new Promise(v0); + } + main(); + gczeal(7,1); +`); diff --git a/js/src/jit-test/tests/gc/bug-1691901.js b/js/src/jit-test/tests/gc/bug-1691901.js new file mode 100644 index 0000000000..c9bb02e7aa --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1691901.js @@ -0,0 +1,4 @@ +gczeal(0); +enableShellAllocationMetadataBuilder(); +setMarkStackLimit(1); +Function('gc()'.replace(/x/))(); diff --git a/js/src/jit-test/tests/gc/bug-1692221.js b/js/src/jit-test/tests/gc/bug-1692221.js new file mode 100644 index 0000000000..6300788ad9 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1692221.js @@ -0,0 +1,39 @@ +// |jit-test| allow-oom; skip-if: !('oomAtAllocation' in this) + +// Test TenuredChunk::decommitFreeArenasWithoutUnlocking updates chunk +// metadata correctly. The data is checked by assertions so this test is about +// exercising the code in question. + +function allocateGarbage() { + gc(); + for (let j = 0; j < 100000; j++) { + Symbol(); + } +} + +function collectUntilDecommit() { + startgc(1); + while (gcstate() != "NotActive" && gcstate() != "Decommit") { + gcslice(1000); + } +} + +function triggerSyncDecommit() { + reportLargeAllocationFailure(1); +} + +gczeal(0); + +// Normally we skip decommit if GCs are happening frequently. Disable that for +// this test +gcparam("highFrequencyTimeLimit", 0); + +allocateGarbage(); +collectUntilDecommit(); +triggerSyncDecommit(); + +allocateGarbage(); +collectUntilDecommit(); +oomAtAllocation(10); +triggerSyncDecommit(); +resetOOMFailure(); diff --git a/js/src/jit-test/tests/gc/bug-1695861.js b/js/src/jit-test/tests/gc/bug-1695861.js new file mode 100644 index 0000000000..6bb2a6a877 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1695861.js @@ -0,0 +1,6 @@ +// |jit-test| skip-if: helperThreadCount() === 0 + +evalInWorker(` + verifyprebarriers(); + Number + 1 >> 2; +`) diff --git a/js/src/jit-test/tests/gc/bug-1696880.js b/js/src/jit-test/tests/gc/bug-1696880.js new file mode 100644 index 0000000000..cb4890503d --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1696880.js @@ -0,0 +1,12 @@ +// |jit-test| skip-if: helperThreadCount() === 0 + +gczeal(0); +gczeal(4); +function a(b) { + c = cacheEntry(b); + evaluate(c, { + saveIncrementalBytecode: true + }); + return c; +} +offThreadDecodeStencil(a("")); diff --git a/js/src/jit-test/tests/gc/bug-1696886.js b/js/src/jit-test/tests/gc/bug-1696886.js new file mode 100644 index 0000000000..76b97ab90a --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1696886.js @@ -0,0 +1,9 @@ +gczeal(0); +try { evaluate(` + for (let v3 = 0; v3 < 256; v3++) + x1 = this.__defineSetter__("x", function(z69) { }) + const v9 = {}; + const v10 = v9[gczeal(4)]; +`); } catch(exc) {} +for (let v3 = 0; v3 < 256; v3++) + x1 = this.__defineSetter__("x", function(z69) {}) diff --git a/js/src/jit-test/tests/gc/bug-1698543.js b/js/src/jit-test/tests/gc/bug-1698543.js new file mode 100644 index 0000000000..865607d12b --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1698543.js @@ -0,0 +1,34 @@ +// |jit-test| allow-overrecursed; skip-if: !getJitCompilerOptions()['blinterp.enable'] + +foo = ""; + +doit(` + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + function u() { broken( + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + // XXXXXXXXXXXXXXXXXXXXXXXXXXX +`); + +gczeal(4); + +doit(""); + +unescape(foo); + +function doit(x) { + try { + evaluate(x); + } catch (e) { + if (e instanceof SyntaxError) + doit(x); + } + try { + x = x.replace(/!/g, ""); + } catch (e) {} + foo += x + " "; +} diff --git a/js/src/jit-test/tests/gc/bug-1699364.js b/js/src/jit-test/tests/gc/bug-1699364.js new file mode 100644 index 0000000000..20542b7123 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1699364.js @@ -0,0 +1,6 @@ +// |jit-test| allow-overrecursed; skip-if: !getJitCompilerOptions()['blinterp.enable'] + +gczeal(4); +for (let i = 0; i < 1000; i++) { + "".padStart(10000).startsWith(); +} diff --git a/js/src/jit-test/tests/gc/bug-1714530.js b/js/src/jit-test/tests/gc/bug-1714530.js new file mode 100644 index 0000000000..ed8adcaf6e --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1714530.js @@ -0,0 +1,9 @@ +// |jit-test| skip-if: typeof Intl === 'undefined' + +gczeal(0); // Prevent timeouts with certain gczeal values. + +for (i = 0; i < 10000; i++, bailAfter(10)) { + if (hasOwnProperty) { + Intl.DateTimeFormat(0, {}).format(); + } +} diff --git a/js/src/jit-test/tests/gc/bug-1723840.js b/js/src/jit-test/tests/gc/bug-1723840.js new file mode 100644 index 0000000000..95bd7fd2df --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1723840.js @@ -0,0 +1,12 @@ +gczeal(0); +gcstress = 1; +Object.defineProperty(this, "", { + value: eval +}); +gczeal(11); +gczeal(6, 5); +const a = { b: 7 }; +const c = [a, ""]; +const d = [c]; +new WeakMap(d); + diff --git a/js/src/jit-test/tests/gc/bug-1723841.js b/js/src/jit-test/tests/gc/bug-1723841.js new file mode 100644 index 0000000000..97ceccd70b --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1723841.js @@ -0,0 +1,7 @@ +gczeal(0); +m0 = new WeakMap; +o = {}; +s = ''; +m0.set(o,s); +verifyprebarriers(); +startgc(''); diff --git a/js/src/jit-test/tests/gc/bug-1736310.js b/js/src/jit-test/tests/gc/bug-1736310.js new file mode 100644 index 0000000000..da0d83188d --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1736310.js @@ -0,0 +1,16 @@ +gczeal(9, 10); +function a() { + var b = new Int32Array(buffer); + function c(d) { + b[5] = d; + } + return c; +} +b = new Int32Array(6); +var buffer = b.buffer; +a()({ + valueOf() { + detachArrayBuffer(buffer); + } +}) + diff --git a/js/src/jit-test/tests/gc/bug-1739972.js b/js/src/jit-test/tests/gc/bug-1739972.js new file mode 100644 index 0000000000..7094635c8d --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1739972.js @@ -0,0 +1,5 @@ +enableShellAllocationMetadataBuilder(); +a = newGlobal(); +a.evaluate("function x() {}"); +for (i = 0; i < 20; ++i) + new a.x; diff --git a/js/src/jit-test/tests/gc/bug-1744979.js b/js/src/jit-test/tests/gc/bug-1744979.js new file mode 100644 index 0000000000..ada98946f7 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1744979.js @@ -0,0 +1,3 @@ +gczeal(14, 3); +Array(4294967).sort(); +reportLargeAllocationFailure(1); diff --git a/js/src/jit-test/tests/gc/bug-1749298.js b/js/src/jit-test/tests/gc/bug-1749298.js new file mode 100644 index 0000000000..7fa184501f --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1749298.js @@ -0,0 +1,17 @@ +// Test that a finalization registry target that is reachable from its global +// does not keep the global alive indefinitely. + +let global = newGlobal({newCompartment: true}); +global.eval(` + this.target = {}; // Target is reachable from global. + this.registry = new FinalizationRegistry(() => assertEq(0, 1)); + registry.register(target, 'held'); + this.finalizeObserver = makeFinalizeObserver(); +`); + +assertEq(finalizeCount(), 0); + +global = undefined; +gc(); + +assertEq(finalizeCount(), 1); diff --git a/js/src/jit-test/tests/gc/bug-1755257.js b/js/src/jit-test/tests/gc/bug-1755257.js new file mode 100644 index 0000000000..052d2cc427 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1755257.js @@ -0,0 +1 @@ +new FinalizationRegistry(a => 1).register(newGlobal({newCompartment: true})) diff --git a/js/src/jit-test/tests/gc/bug-1755874.js b/js/src/jit-test/tests/gc/bug-1755874.js new file mode 100644 index 0000000000..b1a1cdfe31 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1755874.js @@ -0,0 +1,4 @@ +fullcompartmentchecks(true); +let a = new FinalizationRegistry(b => {}); +let c = newGlobal({newCompartment: true}); +a.register(c); diff --git a/js/src/jit-test/tests/gc/bug-1756590.js b/js/src/jit-test/tests/gc/bug-1756590.js new file mode 100644 index 0000000000..5a5cadc1d9 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1756590.js @@ -0,0 +1,19 @@ +// |jit-test| error: Error + +function asyncGC(...targets) { + var finalizationRegistry = new FinalizationRegistry(() => {}); + for (let target of targets) { + finalizationRegistry.register(target, 'target'); + } + return Promise.resolve('tick').then(() => asyncGCDeref()).then(() => { + finalizationRegistry.cleanupSome(name => { names.push(name); }); + }); +} +const root = newGlobal({newCompartment: true}); +const dbg = new Debugger(); +dbg.each = asyncGC; +const wrappedRoot = dbg.each (root) +gczeal(14,10); +evaluate(` + var StructType = class {}; +`); diff --git a/js/src/jit-test/tests/gc/bug-1757573.js b/js/src/jit-test/tests/gc/bug-1757573.js new file mode 100644 index 0000000000..9a83cd0755 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1757573.js @@ -0,0 +1,7 @@ +new FinalizationRegistry(a => {}); +b = newGlobal({newCompartment: true}); +b.eval(` + c = {}; + d = new WeakRef(c); +`); +nukeCCW(b.d); diff --git a/js/src/jit-test/tests/gc/bug-1762771.js b/js/src/jit-test/tests/gc/bug-1762771.js new file mode 100644 index 0000000000..6a81910ec2 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1762771.js @@ -0,0 +1,9 @@ +if (this.enqueueMark) { + gczeal(0); + enqueueMark('set-color-gray'); + enqueueMark('set-color-black'); + enqueueMark(newGlobal()); + enqueueMark('set-color-gray'); + newGlobal(); + startgc(); +} diff --git a/js/src/jit-test/tests/gc/bug-1766648-markQueue.js b/js/src/jit-test/tests/gc/bug-1766648-markQueue.js new file mode 100644 index 0000000000..bd5cb693e2 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1766648-markQueue.js @@ -0,0 +1,12 @@ +if (this.enqueueMark) { + enqueueMark('set-color-gray'); + enqueueMark('set-color-black'); + enqueueMark(newGlobal()); + enqueueMark('set-color-gray'); + enqueueMark('set-color-black'); + enqueueMark(newGlobal()); + enqueueMark('set-color-gray'); + enqueueMark('set-color-black'); + enqueueMark(newGlobal()); + gc(); +} diff --git a/js/src/jit-test/tests/gc/bug-1766656.js b/js/src/jit-test/tests/gc/bug-1766656.js new file mode 100644 index 0000000000..7096f6b274 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1766656.js @@ -0,0 +1,17 @@ +// |jit-test| error: Error + +const thisGlobal = this; +const otherGlobalSameCompartment = newGlobal({sameCompartmentAs: thisGlobal}); +const globals = [thisGlobal, otherGlobalSameCompartment, undefined]; +function testProperties(global, count) { + let {object: source, transplant} = transplantableObject(); + for (let i9 = 0; i9 < count; i9++) { + source[(0) + i9] = i9; + } + transplant(global); +} +for (let global of globals) { + for (let count of [0, 10, 30]) { + testProperties(global, count); + } +} diff --git a/js/src/jit-test/tests/gc/bug-1768813.js b/js/src/jit-test/tests/gc/bug-1768813.js new file mode 100644 index 0000000000..531322cc31 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1768813.js @@ -0,0 +1,19 @@ +gczeal(0); + +function f() { + let global = newGlobal({newCompartment: true}); + global.a = Debugger(newGlobal({newCompartment: true})); + global.evaluate("grayRoot()"); +} + +function g(i) { + const str = " ".padStart(10000, " "); + str.startsWith("1"); + if (i > 0) { + g(i - 1); + } +} + +f(); +gczeal(11,3); +g(5000); diff --git a/js/src/jit-test/tests/gc/bug-1770266.js b/js/src/jit-test/tests/gc/bug-1770266.js new file mode 100644 index 0000000000..b417e09d2f --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1770266.js @@ -0,0 +1,2 @@ +new FinalizationRegistry(() => 0).register(newGlobal({newCompartment: true})); +recomputeWrappers(); diff --git a/js/src/jit-test/tests/gc/bug-1779833.js b/js/src/jit-test/tests/gc/bug-1779833.js new file mode 100644 index 0000000000..7b2f132046 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1779833.js @@ -0,0 +1,20 @@ +// This failed when run under ASAN. + +let size = 6; + +let map = new Map(); +assertEq(isNurseryAllocated(map), true); +for (let i = 0; i < size; i++) { + map.set(i, {}); +} + +for (let i = 0; i < size - 1; i++) { + map.delete(i); +} + +for (let i = 0; i < size - 1; i++) { + map.set(i, {}); +} + +map = undefined; +minorgc(); diff --git a/js/src/jit-test/tests/gc/bug-1787351.js b/js/src/jit-test/tests/gc/bug-1787351.js new file mode 100644 index 0000000000..28542b8bf1 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1787351.js @@ -0,0 +1 @@ +// |jit-test| --gc-zeal=15 diff --git a/js/src/jit-test/tests/gc/bug-1791363.js b/js/src/jit-test/tests/gc/bug-1791363.js new file mode 100644 index 0000000000..52d25db895 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1791363.js @@ -0,0 +1,13 @@ +// |jit-test| allow-unhandlable-oom; skip-if: (getBuildConfiguration()['android'] && getBuildConfiguration()['debug']) + +gczeal(0); +if (!this.enqueueMark) { + quit(); +} + +enqueueMark('set-color-gray'); +enqueueMark(newGlobal()); +enqueueMark('set-color-black'); +enqueueMark(newGlobal()); +setMarkStackLimit(1); +gc(); diff --git a/js/src/jit-test/tests/gc/bug-1791975.js b/js/src/jit-test/tests/gc/bug-1791975.js new file mode 100644 index 0000000000..a194a92dd0 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1791975.js @@ -0,0 +1,13 @@ +// |jit-test| skip-if: !('oomAtAllocation' in this) + +gczeal(10, 10); +try { + throw 0; +} catch { + for (let i = 1; i < 20 ; i++) { + oomAtAllocation(i); + try { + newGlobal(); + } catch {} + } +} diff --git a/js/src/jit-test/tests/gc/bug-1792338.js b/js/src/jit-test/tests/gc/bug-1792338.js new file mode 100644 index 0000000000..724f41b0fd --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1792338.js @@ -0,0 +1,14 @@ +// |jit-test| allow-unhandlable-oom; skip-if: (getBuildConfiguration()['android'] && getBuildConfiguration()['debug']) + +gczeal(0); +if (!this.enqueueMark) { + quit(); +} + +enqueueMark('set-color-gray'); +enqueueMark(newGlobal({newCompartment: true})); +enqueueMark('set-color-black'); +enqueueMark({}); +setMarkStackLimit(1); +gc(); +gc(); diff --git a/js/src/jit-test/tests/gc/bug-1796901.js b/js/src/jit-test/tests/gc/bug-1796901.js new file mode 100644 index 0000000000..081b5b8e6e --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1796901.js @@ -0,0 +1,4 @@ +// |jit-test| --no-threads +gcslice(0); +evalcx("lazy"); +abortgc(); diff --git a/js/src/jit-test/tests/gc/bug-1799678.js b/js/src/jit-test/tests/gc/bug-1799678.js new file mode 100644 index 0000000000..a1423355ad --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1799678.js @@ -0,0 +1,21 @@ +gczeal(0); +setMarkStackLimit(1); +loadFile(` + function wasmEvalText(str, imports) { + let binary = wasmTextToBinary(str); + m = new WebAssembly.Module(binary); + return new WebAssembly.Instance(m, imports); + } + let WasmFuncrefValues = [ + wasmEvalText(\`(module (func (export "")))\`).exports[''], + ]; + g1 = newGlobal({newCompartment: true}); + gczeal(10,10); +`); +for (let i = 0; i < 1000; ++i) + loadFile("}"); +function loadFile(lfVarx) { + try { + evaluate(lfVarx); + } catch (lfVare) {} +} diff --git a/js/src/jit-test/tests/gc/bug-1802308.js b/js/src/jit-test/tests/gc/bug-1802308.js new file mode 100644 index 0000000000..91e482dc4b --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1802308.js @@ -0,0 +1,8 @@ +function test() { + for (var i = 0; i < 10; i++) + /0|[1-9][0-9]*/.test(""); +}; +test(); +test(); +gczeal(4); +enableShellAllocationMetadataBuilder(); diff --git a/js/src/jit-test/tests/gc/bug-1802478.js b/js/src/jit-test/tests/gc/bug-1802478.js new file mode 100644 index 0000000000..05559c3f6f --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1802478.js @@ -0,0 +1,10 @@ +// |jit-test| skip-if: !('oomAfterAllocations' in this) + +enableTrackAllocations(); +for (a of "x") { + gczeal(2, 1); +} +oomAfterAllocations(1); +try { + newString(); +} catch (x) {} diff --git a/js/src/jit-test/tests/gc/bug-1803233.js b/js/src/jit-test/tests/gc/bug-1803233.js new file mode 100644 index 0000000000..14e37f0065 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1803233.js @@ -0,0 +1,5 @@ +// |jit-test| allow-oom; allow-unhandlable-oom + +gczeal(10, 1); +gcparam("maxBytes", gcparam("gcBytes")); +newGlobal(); diff --git a/js/src/jit-test/tests/gc/bug-1804629-2.js b/js/src/jit-test/tests/gc/bug-1804629-2.js new file mode 100644 index 0000000000..fc7c75573b --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1804629-2.js @@ -0,0 +1,5 @@ +gczeal(0); +enableShellAllocationMetadataBuilder(); +setMarkStackLimit(1); +grayRoot()[1] = {}; +gc(); diff --git a/js/src/jit-test/tests/gc/bug-1804629.js b/js/src/jit-test/tests/gc/bug-1804629.js new file mode 100644 index 0000000000..6ed62eec8c --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1804629.js @@ -0,0 +1,6 @@ +// |jit-test| skip-if: !('gczeal' in this); error: ReferenceError + +gczeal(0); +setMarkStackLimit(1); +gczeal(4); +a; diff --git a/js/src/jit-test/tests/gc/bug-1804637.js b/js/src/jit-test/tests/gc/bug-1804637.js new file mode 100644 index 0000000000..1f136b1965 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1804637.js @@ -0,0 +1,8 @@ +// |jit-test| error: ReferenceError + +gczeal(0); +enqueueMark('set-color-gray'); +enqueueMark({}); +gczeal(9); +gczeal(11, 2); +a; diff --git a/js/src/jit-test/tests/gc/bug-1806976.js b/js/src/jit-test/tests/gc/bug-1806976.js new file mode 100644 index 0000000000..9b3dab46cd --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1806976.js @@ -0,0 +1,5 @@ +gczeal(4); +evaluate(` + for (x = 1; x < 2; ++x) + gcparam("maxHelperThreads", 1) +`); diff --git a/js/src/jit-test/tests/gc/bug-1817598.js b/js/src/jit-test/tests/gc/bug-1817598.js new file mode 100644 index 0000000000..b13c3a74cc --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1817598.js @@ -0,0 +1,3 @@ +const v2 = this.blackRoot(); +v2[1000n] = 1000n; +gc(); diff --git a/js/src/jit-test/tests/gc/bug-1820543.js b/js/src/jit-test/tests/gc/bug-1820543.js new file mode 100644 index 0000000000..0ef5f9915e --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1820543.js @@ -0,0 +1,16 @@ +gczeal(0); + +const v1 = ("DEB1").startsWith("DEB1"); +function f2(a3, a4, a5, a6) { + return ({"constructor":this,"b":a3,"__proto__":this}).newGlobal(f2); +} +f2.newCompartment = v1; +with (f2()) { + function f11(a12, a13) { + return "DEB1"; + } + const v15 = new FinalizationRegistry(f11); + v15.register(f2); +} +this.reportLargeAllocationFailure(); +gc() diff --git a/js/src/jit-test/tests/gc/bug-1822995.js b/js/src/jit-test/tests/gc/bug-1822995.js new file mode 100644 index 0000000000..5822b3a5ac --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1822995.js @@ -0,0 +1,19 @@ +function f0(a1, a2, a3) { + try { + a2(a1, a2); + } catch(e5) { + const v7 = new Set(); + const v8 = v7.add(); + function f9(a10, a11) { + a11.sameZoneAs = v8; + return this; + } + f9(v8, f9).newGlobal(f9).blackRoot(a1); + } + return a2; +} +const v17 = this.wrapWithProto(f0, this); +const v19 = Proxy.revocable(f0, v17); +v19.proxy(v17, v17); +v19.proxy(v19, v17); +gc(); diff --git a/js/src/jit-test/tests/gc/bug-1825671.js b/js/src/jit-test/tests/gc/bug-1825671.js new file mode 100644 index 0000000000..b8ab1b1cbe --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1825671.js @@ -0,0 +1,12 @@ +let newTarget = Object.defineProperty(function () {}.bind(), "prototype", { + get() { + throw 0; + } +}); + +try { + Reflect.construct(FinalizationRegistry, [ 1 ], newTarget); +} catch (n) { + assertEq(n === 0, false); + assertEq(n instanceof TypeError, true); +} diff --git a/js/src/jit-test/tests/gc/bug-1825936.js b/js/src/jit-test/tests/gc/bug-1825936.js new file mode 100644 index 0000000000..6d87cc4667 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1825936.js @@ -0,0 +1,24 @@ +let src = ` +function f0() { + return f0; +} +const v10 = f0.bind(); +v10.sameZoneAs = f0; +const v37 = this.newGlobal(v10); + +try { + v37.moduleEvaluate(); +} catch(e48) { + this.grayRoot(); + const v59 = new FinalizationRegistry(FinalizationRegistry); + v59.register(e48, v59, e48); + v59.register(f0, v59, e48); +} +this.nukeAllCCWs(); +`; + +gczeal(0); +let global = newGlobal(); +global.eval(src); +global = undefined; +gc(); diff --git a/js/src/jit-test/tests/gc/bug-1828396.js b/js/src/jit-test/tests/gc/bug-1828396.js new file mode 100644 index 0000000000..465377f027 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1828396.js @@ -0,0 +1,16 @@ +a = ` + fullcompartmentchecks(true); + oomTest(Debugger) +`.split('\n') +c = ""; +while (true) { + d = a.shift() + if (d == null) break; + c += d + e(c); + function e(f) { + try { + evaluate(f); + } catch {} + } +} diff --git a/js/src/jit-test/tests/gc/bug-1830921.js b/js/src/jit-test/tests/gc/bug-1830921.js new file mode 100644 index 0000000000..fa4e8aee0f --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1830921.js @@ -0,0 +1,15 @@ +gczeal(4); +x = [0, 0, 0, 0, 0]; +for (let i = 0; i < 5; ++i) { + for (let j = 0; j < x[i]; ++j) {} +} +gczeal(0); +let y = []; +for (let k = 0; k < 9999; ++k) { + try { + throw z; + } catch (e) { + y.push("" + e); + } +} +print(y); diff --git a/js/src/jit-test/tests/gc/bug-1852729.js b/js/src/jit-test/tests/gc/bug-1852729.js new file mode 100644 index 0000000000..460fe6b774 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1852729.js @@ -0,0 +1,5 @@ +const g = newGlobal({newCompartment: true}); +const domObj = this.transplantableObject().object; +const bar = new g.WeakRef(domObj); +bar.deref(); +this.nukeAllCCWs(); diff --git a/js/src/jit-test/tests/gc/bug-787703.js b/js/src/jit-test/tests/gc/bug-787703.js new file mode 100644 index 0000000000..bbc01bae73 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-787703.js @@ -0,0 +1,7 @@ +// |jit-test| slow; + +eval(" function x() {}" + Array(241).join(" ")); +for (var i = 0; i < 100; i++) { + gczeal(4, 2); + String(x); +} diff --git a/js/src/jit-test/tests/gc/bug-820186.js b/js/src/jit-test/tests/gc/bug-820186.js new file mode 100644 index 0000000000..cced86e208 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-820186.js @@ -0,0 +1,297 @@ +// |jit-test| slow;
+
+function randomRecursion() {
+ var y = ""
+ if (rnd(2)) {
+ var x = 2;
+ "{" + x + "}";
+ randomRecursion();
+ randomRecursion();
+ return [""];
+ }
+ return [""];
+}
+
+function thisFunctionIsNeverCalled() {
+}
+
+function testOne() {
+ ox = newGlobal();
+ var code = randomRecursion()[rnd(3)];
+}
+
+initRnd();
+gczeal(10, 3);
+
+for (var count = 0; count < 20; count++) {
+ print(count);
+ testOne()
+}
+
+// ==========================================================================================
+
+// this program is a JavaScript version of Mersenne Twister, with concealment and encapsulation in class,
+// an almost straight conversion from the original program, mt19937ar.c,
+// translated by y. okada on July 17, 2006.
+// Changes by Jesse Ruderman: added "var" keyword in a few spots; added export_mta etc; pasted into fuzz.js.
+// in this program, procedure descriptions and comments of original source code were not removed.
+// lines commented with //c// were originally descriptions of c procedure. and a few following lines are appropriate JavaScript descriptions.
+// lines commented with /* and */ are original comments.
+// lines commented with // are additional comments in this JavaScript version.
+// before using this version, create at least one instance of MersenneTwister19937 class, and initialize the each state, given below in c comments, of all the instances.
+/*
+ A C-program for MT19937, with initialization improved 2002/1/26.
+ Coded by Takuji Nishimura and Makoto Matsumoto.
+
+ Before using, initialize the state by using init_genrand(seed)
+ or init_by_array(init_key, key_length).
+
+ Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ 3. The names of its contributors may not be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+ Any feedback is very welcome.
+ http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html
+ email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space)
+*/
+
+function MersenneTwister19937()
+{
+ /* Period parameters */
+ //c//#define N 624
+ //c//#define M 397
+ //c//#define MATRIX_A 0x9908b0dfUL /* constant vector a */
+ //c//#define UPPER_MASK 0x80000000UL /* most significant w-r bits */
+ //c//#define LOWER_MASK 0x7fffffffUL /* least significant r bits */
+ var N = 624;
+ var M = 397;
+ var MATRIX_A = 0x9908b0df; /* constant vector a */
+ var UPPER_MASK = 0x80000000; /* most significant w-r bits */
+ var LOWER_MASK = 0x7fffffff; /* least significant r bits */
+ //c//static unsigned long mt[N]; /* the array for the state vector */
+ //c//static int mti=N+1; /* mti==N+1 means mt[N] is not initialized */
+ var mt = new Array(N); /* the array for the state vector */
+ var mti = N+1; /* mti==N+1 means mt[N] is not initialized */
+
+ function unsigned32 (n1) // returns a 32-bits unsiged integer from an operand to which applied a bit operator.
+ {
+ return n1 < 0 ? (n1 ^ UPPER_MASK) + UPPER_MASK : n1;
+ }
+
+ function subtraction32 (n1, n2) // emulates lowerflow of a c 32-bits unsiged integer variable, instead of the operator -. these both arguments must be non-negative integers expressible using unsigned 32 bits.
+ {
+ return n1 < n2 ? unsigned32((0x100000000 - (n2 - n1)) & 0xffffffff) : n1 - n2;
+ }
+
+ function addition32 (n1, n2) // emulates overflow of a c 32-bits unsiged integer variable, instead of the operator +. these both arguments must be non-negative integers expressible using unsigned 32 bits.
+ {
+ return unsigned32((n1 + n2) & 0xffffffff)
+ }
+
+ function multiplication32 (n1, n2) // emulates overflow of a c 32-bits unsiged integer variable, instead of the operator *. these both arguments must be non-negative integers expressible using unsigned 32 bits.
+ {
+ var sum = 0;
+ for (var i = 0; i < 32; ++i){
+ if ((n1 >>> i) & 0x1){
+ sum = addition32(sum, unsigned32(n2 << i));
+ }
+ }
+ return sum;
+ }
+
+ /* initializes mt[N] with a seed */
+ //c//void init_genrand(unsigned long s)
+ this.init_genrand = function (s)
+ {
+ //c//mt[0]= s & 0xffffffff;
+ mt[0]= unsigned32(s & 0xffffffff);
+ for (mti=1; mti<N; mti++) {
+ mt[mti] =
+ //c//(1812433253 * (mt[mti-1] ^ (mt[mti-1] >> 30)) + mti);
+ addition32(multiplication32(1812433253, unsigned32(mt[mti-1] ^ (mt[mti-1] >>> 30))), mti);
+ /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
+ /* In the previous versions, MSBs of the seed affect */
+ /* only MSBs of the array mt[]. */
+ /* 2002/01/09 modified by Makoto Matsumoto */
+ //c//mt[mti] &= 0xffffffff;
+ mt[mti] = unsigned32(mt[mti] & 0xffffffff);
+ /* for >32 bit machines */
+ }
+ }
+
+ /* initialize by an array with array-length */
+ /* init_key is the array for initializing keys */
+ /* key_length is its length */
+ /* slight change for C++, 2004/2/26 */
+ //c//void init_by_array(unsigned long init_key[], int key_length)
+ this.init_by_array = function (init_key, key_length)
+ {
+ //c//int i, j, k;
+ var i, j, k;
+ //c//init_genrand(19650218);
+ this.init_genrand(19650218);
+ i=1; j=0;
+ k = (N>key_length ? N : key_length);
+ for (; k; k--) {
+ //c//mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) * 1664525))
+ //c// + init_key[j] + j; /* non linear */
+ mt[i] = addition32(addition32(unsigned32(mt[i] ^ multiplication32(unsigned32(mt[i-1] ^ (mt[i-1] >>> 30)), 1664525)), init_key[j]), j);
+ mt[i] =
+ //c//mt[i] &= 0xffffffff; /* for WORDSIZE > 32 machines */
+ unsigned32(mt[i] & 0xffffffff);
+ i++; j++;
+ if (i>=N) { mt[0] = mt[N-1]; i=1; }
+ if (j>=key_length) j=0;
+ }
+ for (k=N-1; k; k--) {
+ //c//mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) * 1566083941))
+ //c//- i; /* non linear */
+ mt[i] = subtraction32(unsigned32((dbg=mt[i]) ^ multiplication32(unsigned32(mt[i-1] ^ (mt[i-1] >>> 30)), 1566083941)), i);
+ //c//mt[i] &= 0xffffffff; /* for WORDSIZE > 32 machines */
+ mt[i] = unsigned32(mt[i] & 0xffffffff);
+ i++;
+ if (i>=N) { mt[0] = mt[N-1]; i=1; }
+ }
+ mt[0] = 0x80000000; /* MSB is 1; assuring non-zero initial array */
+ }
+
+ this.export_state = function() { return [mt, mti]; };
+ this.import_state = function(s) { mt = s[0]; mti = s[1]; };
+ this.export_mta = function() { return mt; };
+ this.import_mta = function(_mta) { mt = _mta };
+ this.export_mti = function() { return mti; };
+ this.import_mti = function(_mti) { mti = _mti; }
+
+ /* generates a random number on [0,0xffffffff]-interval */
+ //c//unsigned long genrand_int32(void)
+ this.genrand_int32 = function ()
+ {
+ //c//unsigned long y;
+ //c//static unsigned long mag01[2]={0x0UL, MATRIX_A};
+ var y;
+ var mag01 = new Array(0x0, MATRIX_A);
+ /* mag01[x] = x * MATRIX_A for x=0,1 */
+
+ if (mti >= N) { /* generate N words at one time */
+ //c//int kk;
+ var kk;
+
+ if (mti == N+1) /* if init_genrand() has not been called, */
+ //c//init_genrand(5489); /* a default initial seed is used */
+ this.init_genrand(5489); /* a default initial seed is used */
+
+ for (kk=0;kk<N-M;kk++) {
+ //c//y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK);
+ //c//mt[kk] = mt[kk+M] ^ (y >> 1) ^ mag01[y & 0x1];
+ y = unsigned32((mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK));
+ mt[kk] = unsigned32(mt[kk+M] ^ (y >>> 1) ^ mag01[y & 0x1]);
+ }
+ for (;kk<N-1;kk++) {
+ //c//y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK);
+ //c//mt[kk] = mt[kk+(M-N)] ^ (y >> 1) ^ mag01[y & 0x1];
+ y = unsigned32((mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK));
+ mt[kk] = unsigned32(mt[kk+(M-N)] ^ (y >>> 1) ^ mag01[y & 0x1]);
+ }
+ //c//y = (mt[N-1]&UPPER_MASK)|(mt[0]&LOWER_MASK);
+ //c//mt[N-1] = mt[M-1] ^ (y >> 1) ^ mag01[y & 0x1];
+ y = unsigned32((mt[N-1]&UPPER_MASK)|(mt[0]&LOWER_MASK));
+ mt[N-1] = unsigned32(mt[M-1] ^ (y >>> 1) ^ mag01[y & 0x1]);
+ mti = 0;
+ }
+
+ y = mt[mti++];
+
+ /* Tempering */
+ //c//y ^= (y >> 11);
+ //c//y ^= (y << 7) & 0x9d2c5680;
+ //c//y ^= (y << 15) & 0xefc60000;
+ //c//y ^= (y >> 18);
+ y = unsigned32(y ^ (y >>> 11));
+ y = unsigned32(y ^ ((y << 7) & 0x9d2c5680));
+ y = unsigned32(y ^ ((y << 15) & 0xefc60000));
+ y = unsigned32(y ^ (y >>> 18));
+
+ return y;
+ }
+
+ /* generates a random number on [0,0x7fffffff]-interval */
+ //c//long genrand_int31(void)
+ this.genrand_int31 = function ()
+ {
+ //c//return (genrand_int32()>>1);
+ return (this.genrand_int32()>>>1);
+ }
+
+ /* generates a random number on [0,1]-real-interval */
+ //c//double genrand_real1(void)
+ this.genrand_real1 = function ()
+ {
+ //c//return genrand_int32()*(1.0/4294967295.0);
+ return this.genrand_int32()*(1.0/4294967295.0);
+ /* divided by 2^32-1 */
+ }
+
+ /* generates a random number on [0,1)-real-interval */
+ //c//double genrand_real2(void)
+ this.genrand_real2 = function ()
+ {
+ //c//return genrand_int32()*(1.0/4294967296.0);
+ return this.genrand_int32()*(1.0/4294967296.0);
+ /* divided by 2^32 */
+ }
+
+ /* generates a random number on (0,1)-real-interval */
+ //c//double genrand_real3(void)
+ this.genrand_real3 = function ()
+ {
+ //c//return ((genrand_int32()) + 0.5)*(1.0/4294967296.0);
+ return ((this.genrand_int32()) + 0.5)*(1.0/4294967296.0);
+ /* divided by 2^32 */
+ }
+
+ /* generates a random number on [0,1) with 53-bit resolution*/
+ //c//double genrand_res53(void)
+ this.genrand_res53 = function ()
+ {
+ //c//unsigned long a=genrand_int32()>>5, b=genrand_int32()>>6;
+ var a=this.genrand_int32()>>>5, b=this.genrand_int32()>>>6;
+ return(a*67108864.0+b)*(1.0/9007199254740992.0);
+ }
+ /* These real versions are due to Isaku Wada, 2002/01/09 added */
+}
+
+function initRnd() {
+ var fuzzMT = new MersenneTwister19937;
+ var fuzzSeed = 53;
+ fuzzMT.init_genrand(fuzzSeed);
+ rnd = function (n) { var v = Math.floor(fuzzMT.genrand_real2() * n); return v; };
+ rnd.rndReal = function() { return fuzzMT.genrand_real2(); };
+ rnd.fuzzMT = fuzzMT;
+}
diff --git a/js/src/jit-test/tests/gc/bug-821551.js b/js/src/jit-test/tests/gc/bug-821551.js new file mode 100644 index 0000000000..8d13799bfe --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-821551.js @@ -0,0 +1,16 @@ +function g() { + z = newGlobal(''); + return function(code) { + evalcx(code, z) + } +} +f = g(); +f("\ + options('strict_mode');\ + for (var x = 0; x < 1; ++x) {\ + a = x;\ + }\ + options('strict_mode');\ +"); +f("a in eval"); + diff --git a/js/src/jit-test/tests/gc/bug-824321.js b/js/src/jit-test/tests/gc/bug-824321.js new file mode 100644 index 0000000000..424e2db0ce --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-824321.js @@ -0,0 +1,3 @@ +x = "\udada\udada"; +gc(); + diff --git a/js/src/jit-test/tests/gc/bug-825326.js b/js/src/jit-test/tests/gc/bug-825326.js new file mode 100644 index 0000000000..1579f1d291 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-825326.js @@ -0,0 +1,18 @@ +// |jit-test| + +try { + a = [] + r = /x/ + gczeal(10, 2)() +} catch (e) {} +try { + (function() { + r(function() { + eval() + }) + })() +} catch (e) {} +try { + s +} catch (e) {} +a.every(function() {}) diff --git a/js/src/jit-test/tests/gc/bug-832103.js b/js/src/jit-test/tests/gc/bug-832103.js new file mode 100644 index 0000000000..9ada62e40f --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-832103.js @@ -0,0 +1,27 @@ +//|jit-test| error:TypeError +RegExp("").exec() +Object.defineProperty(this, "x", { + get: function() { + return new Array + } +}) +Object.defineProperty(this, "y", { + get: function() { + return [function() {}, 0, 0, 0, 0, 0, 0] + } +}) +r = RegExp(""); +String(undefined) +with({ + b: gczeal(9, 2) +}); +r = /()/; +y.sort(function(j) { + if (j) { + a = + new + Array + } else { + x.v() + } +}) diff --git a/js/src/jit-test/tests/gc/bug-880816.js b/js/src/jit-test/tests/gc/bug-880816.js new file mode 100644 index 0000000000..7d7e9e622d --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-880816.js @@ -0,0 +1,30 @@ +var lfcode = new Array(); +lfcode.push("const baz = 'bar';"); +lfcode.push("2"); +lfcode.push("{ function foo() {} }"); +lfcode.push("evaluate('\ +var INVALIDATE_MODES = INVALIDATE_MODE_STRINGS.map(s => ({mode: s}));\ +function range(n, m) {}\ +function seq_scan(array, f) {}\ +function assertStructuralEq(e1, e2) {}\ +for (var i = 0, l = a.length; i < l; i++) {}\ +');"); +lfcode.push("for (var x of new Set(Object.getOwnPropertyNames(this))) {}"); +var lfRunTypeId = -1; +while (true) { + var file = lfcode.shift(); if (file == undefined) { break; } + loadFile(file) +} +function loadFile(lfVarx) { + try { + if (lfVarx.substr(-3) == ".js") {} + if (!isNaN(lfVarx)) { + lfRunTypeId = parseInt(lfVarx); + } else { + switch (lfRunTypeId) { + case 2: new Function(lfVarx)(); break; + default: evaluate(lfVarx); break; + } + } + } catch (lfVare) {} +} diff --git a/js/src/jit-test/tests/gc/bug-880886.js b/js/src/jit-test/tests/gc/bug-880886.js new file mode 100644 index 0000000000..2f83a2c637 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-880886.js @@ -0,0 +1,10 @@ +// |jit-test| error: too much recursion + +function testUniqueness(asmJSModule) { + var f = asmJSModule(); +} +function lambda() { + var x = function inner() { "use asm"; function g() {} return g }; + return lambda(); +} +testUniqueness(lambda); diff --git a/js/src/jit-test/tests/gc/bug-886551-1.js b/js/src/jit-test/tests/gc/bug-886551-1.js new file mode 100644 index 0000000000..b3f2bcd45f --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-886551-1.js @@ -0,0 +1,8 @@ +if (this.hasOwnProperty('Intl')) { + gc(); + gcslice(1); + var thisValues = [ "x" ]; + thisValues.forEach(function (value) { + var format = Intl.DateTimeFormat.call(value); + }); +} diff --git a/js/src/jit-test/tests/gc/bug-886551-2.js b/js/src/jit-test/tests/gc/bug-886551-2.js new file mode 100644 index 0000000000..9381c867d5 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-886551-2.js @@ -0,0 +1,7 @@ +gc(); +gcslice(1); +function isClone(a, b) { + var rmemory = new WeakMap(); + rmemory.set(a,b); +} +isClone([]); diff --git a/js/src/jit-test/tests/gc/bug-886560.js b/js/src/jit-test/tests/gc/bug-886560.js new file mode 100644 index 0000000000..c506b05d04 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-886560.js @@ -0,0 +1,11 @@ +// |jit-test| error: x is not defined + +// enableShellAllocationMetadataBuilder ignores its argument, because we don't +// permit metadata callbacks to run JS any more, so this test may be +// unnecessary. We'll preserve its structure just in case. +enableShellAllocationMetadataBuilder(function(obj) { + var res = {}; + return res; + }); +gczeal(4); +x(); diff --git a/js/src/jit-test/tests/gc/bug-886630.js b/js/src/jit-test/tests/gc/bug-886630.js new file mode 100644 index 0000000000..d794cfac17 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-886630.js @@ -0,0 +1,109 @@ +function errorToString(e) { + try {} catch (e2) {} +} +Object.getOwnPropertyNames(this); +if (false) { + for (let x of constructors) + print(x); +} +var tryRunning = tryRunningDirectly; +function unlikelyToHang(code) { + var codeL = code.replace(/\s/g, " "); + return true && code.indexOf("infloop") == -1 && !(codeL.match(/const.*for/)) // can be an infinite loop: function() { const x = 1; for(x in ({a1:1})) dumpln(3); } + && !(codeL.match(/for.*const/)) // can be an infinite loop: for (x in ...); const x; + && !(codeL.match(/for.*in.*uneval/)) // can be slow to loop through the huge string uneval(this), for example + && !(codeL.match(/for.*for.*for/)) // nested for loops (including for..in, etc) can take a while + && !(codeL.match(/for.*for.*gc/)) +} +function whatToTestSpidermonkeyTrunk(code) { + var codeL = code.replace(/\s/g, " "); + return { + allowParse: true, + allowExec: unlikelyToHang(code), + allowIter: true, + expectConsistentOutput: true && code.indexOf("Date") == -1 // time marches on + && code.indexOf("random") == -1 && code.indexOf("dumpObject") == -1 // shows heap addresses + && code.indexOf("oomAfterAllocations") == -1 && code.indexOf("ParallelArray") == -1, + expectConsistentOutputAcrossIter: true && code.indexOf("options") == -1 // options() is per-cx, and the js shell doesn't create a new cx for each sandbox/compartment + , + expectConsistentOutputAcrossJITs: true && code.indexOf("'strict") == -1 // bug 743425 + && code.indexOf("preventExtensions") == -1 // bug 887521 + && !(codeL.match(/\/.*[\u0000\u0080-\uffff]/)) // doesn't stay valid utf-8 after going through python (?) + }; +} +function tryRunningDirectly(f, code, wtt) { + try { + eval(code); + } catch (e) {} + try { + var rv = f(); + tryIteration(rv); + } catch (runError) { + var err = errorToString(runError); + } + tryEnsureSanity(); +} +var realEval = eval; +var realMath = Math; +var realFunction = Function; +var realGC = gc; +function tryEnsureSanity() { + try { + delete this.Math; + delete this.Function; + delete this.gc; + this.Math = realMath; + this.eval = realEval; + this.Function = realFunction; + this.gc = realGC; + } catch (e) {} +} +function tryIteration(rv) { + try { + var iterCount = 0; + for /* each */ + ( /* let */ iterValue in rv) + print("Iterating succeeded, iterCount == " + iterCount); + } catch (iterError) {} +} +function failsToCompileInTry(code) { + try { + new Function(" try { " + code + " } catch(e) { }"); + } catch (e) {} +} +function tryItOut(code) { + if (count % 1000 == 0) { + gc(); + } + var wtt = whatToTestSpidermonkeyTrunk(code); + code = code.replace(/\/\*DUPTRY\d+\*\//, function(k) { + var n = parseInt(k.substr(8), 10); + print(n); + return strTimes("try{}catch(e){}", n); + }) + try { + f = new Function(code); + } catch (compileError) {} + if (code.indexOf("\n") == -1 && code.indexOf("\r") == -1 && code.indexOf("\f") == -1 && code.indexOf("\0") == -1 && code.indexOf("\u2028") == -1 && code.indexOf("\u2029") == -1 && code.indexOf("<--") == -1 && code.indexOf("-->") == -1 && code.indexOf("//") == -1) { + var nCode = code; + if (nCode.indexOf("return") != -1 || nCode.indexOf("yield") != -1 || nCode.indexOf("const") != -1 || failsToCompileInTry(nCode)) nCode = "(function(){" + nCode + "})()" + } + tryRunning(f, code, false); +} +var count = 0; +tryItOut(""); +count = 2 +tryItOut(""); +tryItOut(""); +tryItOut("o") +tryItOut("") +tryItOut("") +tryItOut("\ + with((/ /-7))\ + {\ + for(let mjcpxc=0;mjcpxc<9;++mjcpxc)\ + {\ + e=mjcpxc;\ + yield/x/\ + }}") + diff --git a/js/src/jit-test/tests/gc/bug-889682-1.js b/js/src/jit-test/tests/gc/bug-889682-1.js new file mode 100644 index 0000000000..75b3f4089b --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-889682-1.js @@ -0,0 +1,13 @@ +// |jit-test| error:TypeError +gc(); +var recursiveFunctions = [{ + text: "(function(){if(a){}g()})" +}]; +(function testAllRecursiveFunctions() { + for (var i = 0; i < recursiveFunctions.length; ++i) { + var a = recursiveFunctions[i]; + eval(a.text.replace(/@/g, "")) + } +})(); +gcslice(2869); +Function("v={c:[{x:[[]],N:{x:[{}[d]]}}]}=minorgc(true)")() diff --git a/js/src/jit-test/tests/gc/bug-889682-2.js b/js/src/jit-test/tests/gc/bug-889682-2.js new file mode 100644 index 0000000000..efc47337a2 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-889682-2.js @@ -0,0 +1,14 @@ +// |jit-test| error:TypeError +(function(){}) +gc(); +var recursiveFunctions = [{ + text: "(function(){if(a){}g()})" +}]; +(function testAllRecursiveFunctions() { + for (var i = 0; i < recursiveFunctions.length; ++i) { + var a = recursiveFunctions[i]; + eval(a.text.replace(/@/g, "")) + } +})(); +gcslice(2869); +Function("v={c:[{x:[[]],N:{x:[{}[d]]}}]}=minorgc(true)")() diff --git a/js/src/jit-test/tests/gc/bug-889682-3.js b/js/src/jit-test/tests/gc/bug-889682-3.js new file mode 100644 index 0000000000..601c8300a4 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-889682-3.js @@ -0,0 +1,14 @@ +// |jit-test| error:TypeError +function f(){} +gc(); +var recursiveFunctions = [{ + text: "(function(){if(a){}g()})" +}]; +(function testAllRecursiveFunctions() { + for (var i = 0; i < recursiveFunctions.length; ++i) { + var a = recursiveFunctions[i]; + eval(a.text.replace(/@/g, "")) + } +})(); +gcslice(2869); +Function("v={c:[{x:[[]],N:{x:[{}[d]]}}]}=minorgc(true)")() diff --git a/js/src/jit-test/tests/gc/bug-891773.js b/js/src/jit-test/tests/gc/bug-891773.js new file mode 100644 index 0000000000..c5cb237782 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-891773.js @@ -0,0 +1,14 @@ +x = newGlobal() +Int32Array = x.Int32Array +x.p = new ArrayBuffer() +schedulegc(29); +(function(stdlib, n, heap) { + "use asm" + var Int32ArrayView = new stdlib.Int32Array(heap) + function f() { + Int32ArrayView[1] + } + return f +})(this, { + f: new Function +}, new ArrayBuffer()) diff --git a/js/src/jit-test/tests/gc/bug-906236.js b/js/src/jit-test/tests/gc/bug-906236.js new file mode 100644 index 0000000000..7566bda428 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-906236.js @@ -0,0 +1,9 @@ +// |jit-test| error: too much recursion +(function() { + (function f(x) { + return x * f(x - 1); + with({}) + var r = "" + })() +})() + diff --git a/js/src/jit-test/tests/gc/bug-906241.js b/js/src/jit-test/tests/gc/bug-906241.js new file mode 100644 index 0000000000..ccbc73e594 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-906241.js @@ -0,0 +1,9 @@ +// |jit-test| error: InternalError: too much recursion +for (let y in []); +(function f(x) { + new Float64Array(new ArrayBuffer()); + { + f(x) + function t() {} + } +})(); diff --git a/js/src/jit-test/tests/gc/bug-912813.js b/js/src/jit-test/tests/gc/bug-912813.js new file mode 100644 index 0000000000..1babaac28e --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-912813.js @@ -0,0 +1,7 @@ +// |jit-test| slow +gczeal(9, 1) +for (var a = 0; a < 1; a++) { + newGlobal({ + sameZoneAs: {} + }) +} diff --git a/js/src/jit-test/tests/gc/bug-913224.js b/js/src/jit-test/tests/gc/bug-913224.js new file mode 100644 index 0000000000..815164d764 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-913224.js @@ -0,0 +1 @@ +dumpHeap(); diff --git a/js/src/jit-test/tests/gc/bug-913715.js b/js/src/jit-test/tests/gc/bug-913715.js new file mode 100644 index 0000000000..3a05cf5881 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-913715.js @@ -0,0 +1,31 @@ +try { + (function() { + Object.defineProperty(this, "x", { + get: function() { + Object.defineProperty(this, "y", { + configurable: true, + get: function() { + return Proxy(this.y) + } + }); + x; + } + }) + })() + x +} catch (e) {} +try { + x +} catch (e) {} +try { + x +} catch (e) {} +try { + y +} catch (e) {} +try { + y +} catch (e) {} +try { + y +} catch (e) {} diff --git a/js/src/jit-test/tests/gc/bug-919536.js b/js/src/jit-test/tests/gc/bug-919536.js new file mode 100644 index 0000000000..0c07b4b598 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-919536.js @@ -0,0 +1,17 @@ +if ("gczeal" in this) { + gczeal(2, 1000); + + var a = new Array(10 * 1000); + + var i = a.length; + while (i-- != 0) { + switch (i % 3) { + case 0: + a[i] = { }; + break; + } + } + + gc(); +} + diff --git a/js/src/jit-test/tests/gc/bug-924690.js b/js/src/jit-test/tests/gc/bug-924690.js new file mode 100644 index 0000000000..e7fdc981ba --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-924690.js @@ -0,0 +1,25 @@ +x = [] +try { + (function() { + schedulegc(1); + ((function() { + return { + y: function() { + u() = [] + } + } + })()) + })() + watch.call(x, "valueOf", function() {}) + gc() +} catch (e) { print(e); } +try { + (function() { + x.valueOf = + (function() { + y(); + }) + })() + x + 2 + print('foo') +} catch (e) { print(e); } diff --git a/js/src/jit-test/tests/gc/bug-935022.js b/js/src/jit-test/tests/gc/bug-935022.js new file mode 100644 index 0000000000..2ebe148911 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-935022.js @@ -0,0 +1,4 @@ +function callback(obj) {} +enableShellAllocationMetadataBuilder(); +gczeal(7); +var statusitems = []; diff --git a/js/src/jit-test/tests/gc/bug-939499.js b/js/src/jit-test/tests/gc/bug-939499.js new file mode 100644 index 0000000000..a54d78cab9 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-939499.js @@ -0,0 +1,4 @@ +gczeal(0); +gc(); +verifyprebarriers(); +setMarkStackLimit(5); diff --git a/js/src/jit-test/tests/gc/bug-945275.js b/js/src/jit-test/tests/gc/bug-945275.js new file mode 100644 index 0000000000..26cdb23d3c --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-945275.js @@ -0,0 +1,11 @@ +function TestCase(n) { + this.name = undefined; + this.description = undefined; +} +gczeal(7,1); +eval("\ +function reportCompare() { return new TestCase; };\ +reportCompare();\ +Object.defineProperty(Object.prototype, 'name', {});\ +reportCompare();\ +"); diff --git a/js/src/jit-test/tests/gc/bug-945280.js b/js/src/jit-test/tests/gc/bug-945280.js new file mode 100644 index 0000000000..3546c864d9 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-945280.js @@ -0,0 +1,4 @@ +gczeal(7,1); +enableShellAllocationMetadataBuilder(); +gczeal(false); +var statusitems = []; diff --git a/js/src/jit-test/tests/gc/bug-945285.js b/js/src/jit-test/tests/gc/bug-945285.js new file mode 100644 index 0000000000..7544d86c04 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-945285.js @@ -0,0 +1,3 @@ +gczeal(11); +function callback(obj) {} +enableShellAllocationMetadataBuilder(); diff --git a/js/src/jit-test/tests/gc/bug-950927.js b/js/src/jit-test/tests/gc/bug-950927.js new file mode 100644 index 0000000000..23096c483b --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-950927.js @@ -0,0 +1,31 @@ +var lfcode = new Array(); +lfcode.push("\ +var optionNames = options().split(',');\ + for (var i = 0; i < optionNames.length; i++) {}\ +"); +lfcode.push("gczeal(7,5);"); +lfcode.push("4"); +lfcode.push("\ +var S = new Array();\ +var x = 1;\ +for ( var i = 8; i >= 0; i-- ) {\ + S[0] += ' ';\ + S[0] += ',';\ +}\ +eval(S);\ +"); +var lfRunTypeId = -1; +while (true) { + var file = lfcode.shift(); if (file == undefined) { break; } + loadFile(file) +} +function loadFile(lfVarx) { + if (lfVarx.substr(-3) != ".js" && lfVarx.length != 1) { + switch (lfRunTypeId) { + case 4: eval("(function() { " + lfVarx + " })();"); break; + default: evaluate(lfVarx, { noScriptRval : true }); break; + } + } else if (!isNaN(lfVarx)) { + lfRunTypeId = parseInt(lfVarx); + } +} diff --git a/js/src/jit-test/tests/gc/bug-952819.js b/js/src/jit-test/tests/gc/bug-952819.js new file mode 100644 index 0000000000..3b118a2dc3 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-952819.js @@ -0,0 +1,3 @@ +verifypostbarriers() +verifyprebarriers() +verifypostbarriers() diff --git a/js/src/jit-test/tests/gc/bug-956324.js b/js/src/jit-test/tests/gc/bug-956324.js new file mode 100644 index 0000000000..711fff9516 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-956324.js @@ -0,0 +1,28 @@ +var g = newGlobal({newCompartment: true}); +g.eval("function f() {\n" + + " debugger;\n" + + "}\n") + +var dbg = new Debugger(g); +var handler = {}; +dbg.onDebuggerStatement = function (frame) { + frame.script.setBreakpoint(0, {}); +}; + +// create breakpoint +g.f() + +// drop our references to things +handler = undefined; +dbg.onDebuggerStatement = undefined; + +dbg.removeAllDebuggees(); + +gc(); + +//create garbage to trigger a minor GC +var x; +for (var i = 0; i < 100; ++i) + x = {}; + +gc(); diff --git a/js/src/jit-test/tests/gc/bug-957110.js b/js/src/jit-test/tests/gc/bug-957110.js new file mode 100644 index 0000000000..372e15915f --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-957110.js @@ -0,0 +1,7 @@ +// |jit-test| allow-unhandlable-oom +gczeal(7,1); +try { +gcparam("maxBytes", gcparam("gcBytes") + 4*1024); +newGlobal("same-compartment"); +} catch(exc1) {} +gczeal(1); diff --git a/js/src/jit-test/tests/gc/bug-957114.js b/js/src/jit-test/tests/gc/bug-957114.js new file mode 100644 index 0000000000..5c61c8210b --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-957114.js @@ -0,0 +1,13 @@ +gczeal(7,1); +function TestCase(n) { + this.name = ''; + this.description = ''; + this.expect = ''; + this.actual = ''; + this.reason = ''; + this.passed = ''; +} +function test() { return new TestCase; } +test(); +Object.defineProperty(Object.prototype, "name", {}); +test(); diff --git a/js/src/jit-test/tests/gc/bug-961741.js b/js/src/jit-test/tests/gc/bug-961741.js new file mode 100644 index 0000000000..c4c7823636 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-961741.js @@ -0,0 +1,5 @@ +function r() { + for (var x in undefined) {} +} +enableShellAllocationMetadataBuilder(); +r(); diff --git a/js/src/jit-test/tests/gc/bug-961877.js b/js/src/jit-test/tests/gc/bug-961877.js new file mode 100644 index 0000000000..37b146feaf --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-961877.js @@ -0,0 +1,14 @@ +g = Function("", "for (var i = 0; i < 0; ++i) { eval('this.arg'+0 +'=arg'+0); }"); +Math.abs(undefined); +gczeal(2,300); +evaluate("\ +var toFloat32 = (function() {\ + var f32 = new Float32Array(1);\ + function f(x) { return f32[0] = x; }\ + return f;\ +})();\ +for (var i = 0; i < 64; ++i) {\ + var p = Math.pow(2, i) + 1;\ + g(toFloat32(p));\ + toFloat32(-p);\ +}"); diff --git a/js/src/jit-test/tests/gc/bug-969012.js b/js/src/jit-test/tests/gc/bug-969012.js new file mode 100644 index 0000000000..ed03f66a73 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-969012.js @@ -0,0 +1,60 @@ +function testClosureCreationAndInvocation() { + var a = 'foobar'; + function makeaddv(vvvv) { + var z = -4 * vvvv; + var y = -3 * vvvv; + var x = -2 * vvvv; + var w = -1 * vvvv; + var v = 0 * vvvv; + var u = 1 * vvvv; + var t = 2 * vvvv; + var s = 3 * vvvv; + var r = 4 * vvvv; + var q = 5 * vvvv; + var p = 6 * vvvv; + var o = 7 * vvvv; + var n = 8 * vvvv; + var m = 9 * vvvv; + var l = 10 * vvvv; + var k = 11 * vvvv; + var j = 12 * vvvv; + var i = 13 * vvvv; + var h = 14 * vvvv; + var g = 15 * vvvv; + var f = 16 * vvvv; + var e = 17 * vvvv; + var d = 18 * vvvv; + var c = 19 * vvvv; + var b = 20 * vvvv; + var a = 21 * vvvv; + return function (x) { + switch (x) { + case 0: return a; case 1: return b; + case 2: return c; case 3: return d; + case 4: return e; case 5: return f; + case 6: return g; case 7: return h; + case 8: return i; case 9: return j; + case 10: return k; case 11: return l; + case 12: return m; case 13: return n; + case 14: return o; case 15: return p; + case 16: return q; case 17: return r; + case 18: return s; case 19: return t; + case 20: return u; case 21: return v; + case 22: return w; case 23: return x; + case 24: return y; case 25: return z; + } + }; + } + var a = Array(); + for (var i = 0; i < 26; ++i) { + a.push(makeaddv(Math.random())); + } + return a; +} + +var a = testClosureCreationAndInvocation(); +for (var i = 0; i < 26; ++i) { + print(a[i](i)); +} + + diff --git a/js/src/jit-test/tests/gc/bug-978353.js b/js/src/jit-test/tests/gc/bug-978353.js new file mode 100644 index 0000000000..44635209fe --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-978353.js @@ -0,0 +1,6 @@ +var arr = new Float64Array(2); +function test(m) { + arr[1] = m; +} +for(var i=0; i<20000; ++i, Array('x')) + test(0); diff --git a/js/src/jit-test/tests/gc/bug-978802.js b/js/src/jit-test/tests/gc/bug-978802.js new file mode 100644 index 0000000000..1e13b76e0e --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-978802.js @@ -0,0 +1,17 @@ +// |jit-test| skip-if: !('oomTest' in this) + +oomTest(() => { + try { + var max = 400; + function f(b) { + if (b) { + f(b - 1); + } else { + g = {}; + } + g.apply(null, arguments); + } + f(max - 1); + } catch(exc0) {} + f(); +}); diff --git a/js/src/jit-test/tests/gc/bug-981289.js b/js/src/jit-test/tests/gc/bug-981289.js new file mode 100644 index 0000000000..edba3fc974 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-981289.js @@ -0,0 +1,8 @@ +gcPreserveCode(); +function test() { + for (var i=0; i<20; i++) { + arguments.x = {}; + gc(); + } +} +test(); diff --git a/js/src/jit-test/tests/gc/bug-981295.js b/js/src/jit-test/tests/gc/bug-981295.js new file mode 100644 index 0000000000..0f570bf73d --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-981295.js @@ -0,0 +1,9 @@ +var NotEarlyErrorString = "NotEarlyError"; +var NotEarlyError = new Error(NotEarlyErrorString); +var juneDate = new Date(2000, 5, 20, 0, 0, 0, 0); +for (var i = 0; i < function(x) { return myObj(Date.prototype.toString.apply(x)); }; void i) { + eval(a.text.replace(/@/g, "")) +} +gcslice(2601); +function testcase() {} +new Uint16Array(testcase); diff --git a/js/src/jit-test/tests/gc/bug-985732.js b/js/src/jit-test/tests/gc/bug-985732.js new file mode 100644 index 0000000000..eb560f1878 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-985732.js @@ -0,0 +1,84 @@ +// |jit-test| error: expected is not defined +function testx() { +function compareArray(aExpected, aActual) {} + for (var i = 0; i < expected.length; i++) {} +var supportsArrayIndexGettersOnArrays = undefined; +function fnSupportsArrayIndexGettersOnArrays() {} +var supportsArrayIndexGettersOnObjects = undefined; +function fnSupportsArrayIndexGettersOnObjects() {} +function ConvertToFileUrl(pathStr) { +} +function fnExists() {} +var __globalObject = Function("return this;")(); +function fnGlobalObject() {} +function fnSupportsStrict() { + eval('with ({}) {}'); +} +function dataPropertyAttributesAreCorrect(obj, configurable) {} +function accessorPropertyAttributesAreCorrect(obj, configurable) {} +var NotEarlyErrorString = "NotEarlyError"; +var EarlyErrorRePat = "^((?!" + NotEarlyErrorString + ").)*$"; +var NotEarlyError = new Error(NotEarlyErrorString); +function Test262Error(message) {}; +function testFailed(message) {} +function testPrint(message) {} +function $PRINT(message) {} +function $INCLUDE(message) { } +function $ERROR(message) {} +function $FAIL(message) {} +function getPrecision(num) {} +var prec; +function isEqual(num1, num2) {} +function ToInteger(p) {} +var HoursPerDay = 24; +var MinutesPerHour = 60; +var SecondsPerMinute = 60; +var msPerDay = 86400000; +var msPerSecond = 1000; +var msPerMinute = 60000; +var msPerHour = 3600000; +var date_1899_end = -2208988800001; +var date_1900_start = -2208988800000; +var date_1969_end = -1; +var date_1970_start = 0; +var date_1999_end = 946684799999; +var date_2000_start = 946684800000; +var date_2099_end = 4102444799999; +var date_2100_start = 4102444800000; +var $LocalTZ, + $DST_start_month, + $DST_start_sunday, + $DST_start_hour, + $DST_start_minutes, + $DST_end_month, + $DST_end_sunday, + $DST_end_hour, + $DST_end_minutes; +function Day(t) {} +function TimeWithinDay(t) {} +function DaysInYear(y){} +function DayFromYear(y) {} +function TimeFromYear(y){} +function YearFromTime(t) {} +function InLeapYear(t){} +function DayWithinYear(t) {} +function MonthFromTime(t){} +function DateFromTime(t) {} +function WeekDay(t) {} +var LocalTZA = $LocalTZ*msPerHour; +function DaysInMonth(m, leap) {} +function GetSundayInMonth(t, m, count){} +function DaylightSavingTA(t) {} +function LocalTime(t){} +function UTC(t) {} +function HourFromTime(t){} +function MinFromTime(t){} +function SecFromTime(t){} +function msFromTime(t){} +function MakeTime(hour, min, sec, ms){} +function MakeDay(year, month, date) {} +function MakeDate( day, time ) {} +function TimeClip(time) {} +function ConstructDate(year, month, date, hours, minutes, seconds, ms){} +function runTestCase(testcase) {} +} testx(); diff --git a/js/src/jit-test/tests/gc/bug-993768.js b/js/src/jit-test/tests/gc/bug-993768.js new file mode 100644 index 0000000000..2fc05fb5b8 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-993768.js @@ -0,0 +1,13 @@ +var SECTION = ""; +gcPreserveCode() +gczeal(9, 1000); +function test() { + var f32 = new Float32Array(10); + f32[0] = 5; + var i = 0; + for (var j = 0; j < 10000; ++j) { + f32[i + 1] = f32[i] - 1; + SECTION += 1; + } +} +test(); diff --git a/js/src/jit-test/tests/gc/bug1116306.js b/js/src/jit-test/tests/gc/bug1116306.js new file mode 100644 index 0000000000..5a9718f19c --- /dev/null +++ b/js/src/jit-test/tests/gc/bug1116306.js @@ -0,0 +1,8 @@ +const dbg = new Debugger(); +const g = newGlobal({newCompartment: true}); +dbg.addDebuggee(g); +dbg.memory.trackingAllocationSites = true; +g.eval("this.alloc = {}"); +verifyprebarriers(); +schedulegc(3); +dbg.memory.drainAllocationsLog(); diff --git a/js/src/jit-test/tests/gc/bug1146213.js b/js/src/jit-test/tests/gc/bug1146213.js new file mode 100644 index 0000000000..7e4b246e07 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug1146213.js @@ -0,0 +1,10 @@ +// |jit-test| skip-if: !(getBuildConfiguration()['has-gczeal']) || helperThreadCount() === 0 +setGCCallback({ + action: "majorGC", +}); +schedulezone(this) +gcslice(3) +var lfGlobal = newGlobal(); +lfGlobal.offThreadCompileToStencil(""); +var stencil = lfGlobal.finishOffThreadStencil(); +lfGlobal.evalStencil(stencil); diff --git a/js/src/jit-test/tests/gc/bug1191756.js b/js/src/jit-test/tests/gc/bug1191756.js new file mode 100644 index 0000000000..ebc82341e9 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug1191756.js @@ -0,0 +1,18 @@ +// |jit-test| skip-if: typeof 'oomAtAllocation' === 'undefined' + +function fn(i) { + if (i == 3) + return ["isFinite"].map(function (i) {}); + return []; +} + +try { + fn(0); + fn(1); + fn(2); + oomAtAllocation(50); + fn(3); +} catch(e) { + // Ignore oom +} + diff --git a/js/src/jit-test/tests/gc/bug1246607.js b/js/src/jit-test/tests/gc/bug1246607.js new file mode 100644 index 0000000000..1fbe9e5208 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug1246607.js @@ -0,0 +1,15 @@ +// |jit-test| skip-if: typeof oomTest !== 'function' || typeof Intl !== 'object' + +oomTest(() => { + try { + new Intl.DateTimeFormat; + x1 = 0; + } catch (e) { + switch (1) { + case 0: + let s; + case 1: + x; + } + } +}) diff --git a/js/src/jit-test/tests/gc/bug1282113.js b/js/src/jit-test/tests/gc/bug1282113.js new file mode 100644 index 0000000000..88d128a0ff --- /dev/null +++ b/js/src/jit-test/tests/gc/bug1282113.js @@ -0,0 +1,6 @@ +Object.getOwnPropertyNames(this); +setGCCallback({ + action: "majorGC", + phases: "begin" +}); +selectforgc(this); diff --git a/js/src/jit-test/tests/gc/bug1283169.js b/js/src/jit-test/tests/gc/bug1283169.js new file mode 100644 index 0000000000..721293dbd4 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug1283169.js @@ -0,0 +1,5 @@ +// |jit-test| skip-if: helperThreadCount() === 0 + +gczeal(0); +startgc(45); +offThreadCompileToStencil("print(1)"); diff --git a/js/src/jit-test/tests/gc/bug1285186.js b/js/src/jit-test/tests/gc/bug1285186.js new file mode 100644 index 0000000000..f0974c6de9 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug1285186.js @@ -0,0 +1,5 @@ +// |jit-test| skip-if: helperThreadCount() === 0 +gczeal(10); +newGlobal(); +offThreadCompileToStencil("let x = 1;"); +abortgc(); diff --git a/js/src/jit-test/tests/gc/bug1285490.js b/js/src/jit-test/tests/gc/bug1285490.js new file mode 100644 index 0000000000..7cd457d30c --- /dev/null +++ b/js/src/jit-test/tests/gc/bug1285490.js @@ -0,0 +1,3 @@ +// |jit-test| skip-if: helperThreadCount() === 0 +gczeal(4); +offThreadCompileToStencil("let x = 1;"); diff --git a/js/src/jit-test/tests/gc/bug1287063.js b/js/src/jit-test/tests/gc/bug1287063.js new file mode 100644 index 0000000000..66c2f69390 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug1287063.js @@ -0,0 +1,4 @@ +// |jit-test| skip-if: helperThreadCount() === 0 + +schedulezone(""); +offThreadCompileToStencil(""); diff --git a/js/src/jit-test/tests/gc/bug1326343-gcstats.js b/js/src/jit-test/tests/gc/bug1326343-gcstats.js new file mode 100644 index 0000000000..f29306af4c --- /dev/null +++ b/js/src/jit-test/tests/gc/bug1326343-gcstats.js @@ -0,0 +1,6 @@ +// |jit-test| skip-if: !('oomTest' in this) + +setJitCompilerOption('baseline.warmup.trigger', 4); +oomTest((function () { + gcslice(0); +})) diff --git a/js/src/jit-test/tests/gc/bug1335642.js b/js/src/jit-test/tests/gc/bug1335642.js new file mode 100644 index 0000000000..6e7779ae04 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug1335642.js @@ -0,0 +1,6 @@ +var g = newGlobal(); +var b = g.eval(` +var b = /foo2/; +Object.defineProperty(b, "source", { get: () => {}}); +`); +new RegExp(b).source; diff --git a/js/src/jit-test/tests/gc/bug1335643.js b/js/src/jit-test/tests/gc/bug1335643.js new file mode 100644 index 0000000000..5ee61b0603 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug1335643.js @@ -0,0 +1,5 @@ +low = high = newGlobal({}) +high.low = low +high.eval("function a() { return saveStack(1, low) }") +set = eval("high.a()") +serialize(set) diff --git a/js/src/jit-test/tests/gc/bug1336866.js b/js/src/jit-test/tests/gc/bug1336866.js new file mode 100644 index 0000000000..5a665e55fd --- /dev/null +++ b/js/src/jit-test/tests/gc/bug1336866.js @@ -0,0 +1 @@ +JSON.stringify(this); diff --git a/js/src/jit-test/tests/gc/bug1337324.js b/js/src/jit-test/tests/gc/bug1337324.js new file mode 100644 index 0000000000..eaf4c080f0 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug1337324.js @@ -0,0 +1,6 @@ +// |jit-test| skip-if: !('oomTest' in this) +oomTest(function () { + offThreadCompileModuleToStencil(''); + var stencil = finishOffThreadStencil(); + instantiateModuleStencil(stencil); +}); diff --git a/js/src/jit-test/tests/gc/bug1471949.js b/js/src/jit-test/tests/gc/bug1471949.js new file mode 100644 index 0000000000..5f0f10f4df --- /dev/null +++ b/js/src/jit-test/tests/gc/bug1471949.js @@ -0,0 +1,5 @@ +// |jit-test| allow-oom; skip-if: !('oomAfterAllocations' in this) + +gczeal(15); +oomAfterAllocations(5); +gcslice(11); diff --git a/js/src/jit-test/tests/gc/bug1511412.js b/js/src/jit-test/tests/gc/bug1511412.js new file mode 100644 index 0000000000..0a2d6893cd --- /dev/null +++ b/js/src/jit-test/tests/gc/bug1511412.js @@ -0,0 +1,17 @@ +Object.defineProperty(this, "fuzzutils", { + value: { + orig_evaluate: evaluate, + evaluate: function(c, o) { + if (!o) { + o = {}; + } + o.catchTermination = true; + return fuzzutils.orig_evaluate(c, o); + }, + } + }); + gczeal(21, 10); + fuzzutils.evaluate(` +enableShellAllocationMetadataBuilder(); +function test() {} +`); diff --git a/js/src/jit-test/tests/gc/bug1532289.js b/js/src/jit-test/tests/gc/bug1532289.js new file mode 100644 index 0000000000..131867c415 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug1532289.js @@ -0,0 +1,11 @@ +// |jit-test| --ion-warmup-threshold=0; --ion-offthread-compile=off; skip-if: !getJitCompilerOptions()['baseline.enable'] + +// gczeal mode causes test to run too slowly with --no-baseline +gczeal(4,40); + +var x; +var y = false; + +function f(v) { x = v; while (y) {} } + +for (var z=1; z < 1e5; z++) { f(BigInt(z)); } diff --git a/js/src/jit-test/tests/gc/bug1600017.js b/js/src/jit-test/tests/gc/bug1600017.js new file mode 100644 index 0000000000..c7a748009f --- /dev/null +++ b/js/src/jit-test/tests/gc/bug1600017.js @@ -0,0 +1,21 @@ +var registry = new FinalizationRegistry(x => { + if (target1 === null) { + return; + } + + target1 = null; + + gc(); + + print("targets:", [...x]); // consume +}); + +var target1 = {}; +registry.register(target1, "target1"); + +var target2 = {}; +registry.register(target2, "target2"); + +target2 = null; + +gc(); diff --git a/js/src/jit-test/tests/gc/bug1600488-1.js b/js/src/jit-test/tests/gc/bug1600488-1.js new file mode 100644 index 0000000000..a0fb8e5ee5 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug1600488-1.js @@ -0,0 +1,14 @@ +const token = {}; +let cleanedUpValue; +const finalizationRegistry = new FinalizationRegistry(value => { + cleanedUpValue = value; +}); +{ + let object = {}; + finalizationRegistry.register(object, token, token); + object = undefined; +} +gc(); +finalizationRegistry.cleanupSome(); +assertEq(cleanedUpValue, token); +assertEq(finalizationRegistry.unregister(token), false); diff --git a/js/src/jit-test/tests/gc/bug1600488-2.js b/js/src/jit-test/tests/gc/bug1600488-2.js new file mode 100644 index 0000000000..c7de44f1a0 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug1600488-2.js @@ -0,0 +1,14 @@ +const token = {}; +let iterated; +const finalizationRegistry = new FinalizationRegistry(items => { + iterated = items.next().value; +}); +{ + let object = {}; + finalizationRegistry.register(object, token, token); + object = undefined; +} +gc(); +assertEq(finalizationRegistry.unregister(token), true); +finalizationRegistry.cleanupSome(); +assertEq(iterated, undefined); diff --git a/js/src/jit-test/tests/gc/bug1698557.js b/js/src/jit-test/tests/gc/bug1698557.js new file mode 100644 index 0000000000..2fa2302fe3 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug1698557.js @@ -0,0 +1,6 @@ +fullcompartmentchecks(true); +let g = newGlobal({sameZoneAs: this}); +for (let i = 0; i < 20; i++) { + g.Object.prototype.toString; +} +gc(); diff --git a/js/src/jit-test/tests/gc/bug1704451.js b/js/src/jit-test/tests/gc/bug1704451.js new file mode 100644 index 0000000000..d4b4d14995 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug1704451.js @@ -0,0 +1,6 @@ +// |jit-test| skip-if: !('gczeal' in this) + +enableShellAllocationMetadataBuilder(); +gczeal(9,1); +var o86 = {x76: 1, y86: 2}; +var snapshot = createShapeSnapshot(o86); diff --git a/js/src/jit-test/tests/gc/bug1709537.js b/js/src/jit-test/tests/gc/bug1709537.js new file mode 100644 index 0000000000..77f3fd1d5c --- /dev/null +++ b/js/src/jit-test/tests/gc/bug1709537.js @@ -0,0 +1,9 @@ +function f() { + var obj = []; + for (var count = 20000; count > 15900; count--) { + obj[count] = 2; + } + assertEq(Object.getOwnPropertyNames(obj).length, 4101); +} +gczeal(4); +f(); diff --git a/js/src/jit-test/tests/gc/bug888463.js b/js/src/jit-test/tests/gc/bug888463.js new file mode 100644 index 0000000000..cd7e170d28 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug888463.js @@ -0,0 +1,66 @@ +var sjcl = { + hash: {}, +}; +sjcl.bitArray = { + concat: function (a, b) { + var c = a[a.length - 1], + d = sjcl.bitArray.getPartial(c); + return d === 32 ? a.concat(b) : sjcl.bitArray.P(b, d, c | 0, a.slice(0, a.length - 1)) + }, + getPartial: function (a) { + return Math.round(a / 0x10000000000) || 32 + } +}; +sjcl.hash.sha256 = function (a) { + this.a[0] || this.w(); + this.reset() +}; +sjcl.hash.sha256.prototype = { + reset: function () { + this.n = this.N.slice(0); + this.i = []; + }, + update: function (a) { + var b, c = this.i = sjcl.bitArray.concat(this.i, a); + return this + }, + finalize: function () { + var a, b = this.i, + c = this.n; + this.C(b.splice(0, 16)); + return c + }, + N: [], + a: [], + w: function () { + function a(e) { + return (e - Math.floor(e)) * 0x100000000 | 0 + } + var b = 0, + c = 2, + d; + a: for (; b < 64; c++) { + if (b < 8) + this.N[b] = a(Math.pow(c, 0.5)); + b++ + } + }, + C: function (a) { + var b, c, d = a.slice(0), + e = this.n, + h = e[1], + i = e[2], + k = e[3], + n = e[7]; + for (a = 0; a < 64; a++) { + b = d[a + 1 & 15]; + g = b + (h & i ^ k & (h ^ i)) + (h >>> 2 ^ h >>> 13 ^ h >>> 22 ^ h << 30 ^ h << 19 ^ h << 10) | 0 + } + e[0] = e[0] + g | 0; + } +}; +var ax1 = [-1862726214, -1544935945, -1650904951, -1523200565, 1783959997, -1422527763, -1915825893, 67249414]; +var ax2 = ax1; +for (var aix = 0; aix < 200; aix++) ax1 = (new sjcl.hash.sha256(undefined)).update(ax1, undefined).finalize(); +eval("for (var aix = 0; aix < 200; aix++) ax2 = (new sjcl.hash.sha256(undefined)).update(ax2, undefined).finalize();" + + "assertEq(ax2.toString(), ax1.toString());"); diff --git a/js/src/jit-test/tests/gc/compartment-revived-gc.js b/js/src/jit-test/tests/gc/compartment-revived-gc.js new file mode 100644 index 0000000000..881f4a622a --- /dev/null +++ b/js/src/jit-test/tests/gc/compartment-revived-gc.js @@ -0,0 +1,133 @@ +// Test 'compartment revived' GCs, where we do an extra GC if there are +// compartments which we expected to die but were kept alive. + +// A global used as the destination for transplants. +let transplantTargetGlobal = newGlobal(); + +function didCompartmentRevivedGC() { + return performance.mozMemory.gc.lastStartReason === "COMPARTMENT_REVIVED"; +} + +function compartmentCount() { + let r = performance.mozMemory.gc.compartmentCount; + return r; +} + +function startIncrementalGC() { + startgc(1); + while (gcstate() === "Prepare") { + gcslice(100, {dontStart: true}); + } + assertEq(gcstate(), "Mark"); +} + +function finishIncrementalGC() { + while (gcstate() !== "NotActive") { + gcslice(100, {dontStart: true}); + } + assertEq(gcstate(), "NotActive"); +} + +// Create a new compartment and global and return the global. +function createCompartment() { + return newGlobal({newCompartment: true}); +} + +// Create a transplantable object and create a wrapper to it from a new +// compartment. Return a function to transplant the target object. +function createTransplantableWrapperTarget(wrapperGlobal) { + let {object: target, transplant} = transplantableObject(); + wrapperGlobal.wrapper = target; + return transplant; +} + +// Transplant an object to a new global by calling the transplant +// function. This remaps all wrappers pointing to the target object, +// potentially keeping dead compartments alive. +function transplantTargetAndRemapWrappers(transplant) { + transplant(transplantTargetGlobal); +} + +// Test no compartment revived GC triggered in normal cases. +function testNormal() { + gc(); + assertEq(didCompartmentRevivedGC(), false); + + startIncrementalGC(); + finishIncrementalGC(); + assertEq(didCompartmentRevivedGC(), false); + + let initialCount = compartmentCount(); + createCompartment(); + startIncrementalGC(); + finishIncrementalGC(); + assertEq(compartmentCount(), initialCount); +} + +// Test compartment revived GC is triggered by wrapper remapping. +function testCompartmentRevived1() { + let initialCount = compartmentCount(); + let compartment = createCompartment(); + let transplant = createTransplantableWrapperTarget(compartment); + compartment = null; + + startIncrementalGC(); + transplantTargetAndRemapWrappers(transplant); + finishIncrementalGC(); + + assertEq(didCompartmentRevivedGC(), true); + assertEq(compartmentCount(), initialCount); +} + +// Test no compartment revived GC is triggered for compartments transitively +// kept alive by black roots. +function testCompartmentRevived2() { + let initialCount = compartmentCount(); + let compartment = createCompartment(); + let transplant = createTransplantableWrapperTarget(compartment); + let liveCompartment = createCompartment(); + liveCompartment.wrapper = compartment; + compartment = null; + + startIncrementalGC(); + transplantTargetAndRemapWrappers(transplant); + finishIncrementalGC(); + + assertEq(didCompartmentRevivedGC(), false); + assertEq(compartmentCount(), initialCount + 2); + + liveCompartment = null; + gc(); + + assertEq(compartmentCount(), initialCount); +} + +// Test no compartment revived GC is triggered for compartments transitively +// kept alive by gray roots. +function testCompartmentRevived3() { + let initialCount = compartmentCount(); + let compartment = createCompartment(); + let transplant = createTransplantableWrapperTarget(compartment); + let liveCompartment = createCompartment(); + liveCompartment.wrapper = compartment; + liveCompartment.eval('grayRoot()[0] = this'); + liveCompartment = null; + gc(); + + startIncrementalGC(); + transplantTargetAndRemapWrappers(transplant); + finishIncrementalGC(); + + assertEq(didCompartmentRevivedGC(), false); + assertEq(compartmentCount(), initialCount + 2); + + // There's no easy way to clear gray roots for a compartment we don't have + // any reference to. +} + +gczeal(0); + +testNormal(); +testCompartmentRevived1(); +testCompartmentRevived2(); +testCompartmentRevived3(); diff --git a/js/src/jit-test/tests/gc/dedupe-02.js b/js/src/jit-test/tests/gc/dedupe-02.js new file mode 100644 index 0000000000..72b2f97f9c --- /dev/null +++ b/js/src/jit-test/tests/gc/dedupe-02.js @@ -0,0 +1,39 @@ +// AutoStableStringChars needs to prevent the owner of its chars from being +// deduplicated, even if they are held by a different string. + +gczeal(0); + +function makeExtensibleStrFrom(str) { + var left = str.substr(0, str.length/2); + var right = str.substr(str.length/2, str.length); + var ropeStr = left + right; + return ensureLinearString(ropeStr); +} + +// Make a string to deduplicate to. +var original = makeExtensibleStrFrom('{ "phbbbbbbbbbbbbbbttt!!!!??": [1] }\n\n'); + +// Construct D2 -> D1 -> base +var D2 = makeExtensibleStrFrom('{ "phbbbbbbbbbbbbbbttt!!!!??": [1] }'); +var D1 = newRope(D2, '\n', {nursery: true}); +ensureLinearString(D1); +var base = newRope(D1, '\n', {nursery: true}); +ensureLinearString(base); + +// Make an AutoStableStringChars(D2) and do a minor GC within it. (This will do +// a major GC, but it'll start out with a minor GC.) `base` would get +// deduplicated to `original`, if it weren't for AutoStableStringChars marking +// all of D2, D1, and base non-deduplicatable. + +// The first time JSON.parse runs, it will create several (14 in my test) GC +// things before getting to the point where it does an allocation while holding +// the chars pointer. Get them out of the way now. +JSON.parse(D2); + +// Cause a minor GC to happen during JSON.parse after AutoStableStringChars +// gives up its pointer. +schedulegc(1); +JSON.parse(D2); + +// Access `D2` to verify that it is not using the deduplicated chars. +print(D2); diff --git a/js/src/jit-test/tests/gc/dedupe.js b/js/src/jit-test/tests/gc/dedupe.js new file mode 100644 index 0000000000..2e2c578abf --- /dev/null +++ b/js/src/jit-test/tests/gc/dedupe.js @@ -0,0 +1,44 @@ +function str(c) { + let s = c; + for (let i = 0; i < 30; i++) + s += c; + ensureLinearString(s); + return s; +} + +function f() { + // Create some slots to write into. + const o = {owner: 'short1', s: 'short2'}; + + // Make a tenured rope. + const r1 = str("a") + str("b"); + gc(); + + // Write the first instance of our duplicate string into one of the slots + // (`owner`). This will be scanned first, and entered into deDupSet when + // tenured. + o.owner = ensureLinearString(str("a") + str("b") + str("c")); + + // Make another rope with identical contents, with a tenured subtree. + const r2 = r1 + str("c"); + + // Linearize the new rope, creating a new extensible string and a bunch of + // dependent strings replacing the rest of the rope nodes. + ensureLinearString(r2); + + // Write the new rope into a slot, so that it will be scanned next during the + // minor GC during traceSlots(). + o.s = r2; + + // Do a nursery collection. o.owner will be tenured and inserted into + // deDupSet. Then o.s aka r2 will be tenured. If things work correctly, r2 + // will be marked non-deduplicatable because it is the base of a tenured + // string r1. If not, it will be deduplicated to o.owner. + minorgc(); + + // Extract out that r1 child node. If its base was deduplicated, this will + // assert because its chars have been freed. + const s1 = r1.substr(0, 31); +} + +f(); diff --git a/js/src/jit-test/tests/gc/dedupeTenuredBase.js b/js/src/jit-test/tests/gc/dedupeTenuredBase.js new file mode 100644 index 0000000000..5dbb25df45 --- /dev/null +++ b/js/src/jit-test/tests/gc/dedupeTenuredBase.js @@ -0,0 +1,45 @@ +function str(c) { + let s = c; + for (let i = 0; i < 30; i++) { + s += c; + } + ensureLinearString(s); + return s; +} + +function f() { + // Create some slots to write into. + const o = { owner: "short1", s: "short2" }; + + // Make a tenured rope. + const r1 = str("a") + str("b"); + gc(); + + // Write the first instance of our duplicate string into one of the slots + // (`owner`). This will be scanned first, and entered into deDupSet when + // tenured. + o.owner = ensureLinearString(str("a") + str("b") + str("c")); + + // Make another rope with identical contents, with a tenured subtree. + const r2 = r1 + str("c"); + + // Linearize the new rope, creating a new extensible string and a bunch of + // dependent strings replacing the rest of the rope nodes. + ensureLinearString(r2); + + // Write the new rope into a slot, so that it will be scanned next during the + // minor GC during traceSlots(). + o.s = r2; + + // Do a nursery collection. o.owner will be tenured and inserted into + // deDupSet. Then o.s aka r2 will be tenured. If things work correctly, r2 + // will be marked non-deduplicatable because it is the base of a tenured + // string r1. If not, it will be deduplicated to o.owner. + minorgc(); + + // Extract out that r1 child node. If its base was deduplicated, this will + // assert because its chars have been freed. + const s1 = r1.substr(0, 31); +} + +f(); diff --git a/js/src/jit-test/tests/gc/deduplicateTenuringStrings.js b/js/src/jit-test/tests/gc/deduplicateTenuringStrings.js new file mode 100644 index 0000000000..1b8259cc15 --- /dev/null +++ b/js/src/jit-test/tests/gc/deduplicateTenuringStrings.js @@ -0,0 +1,205 @@ +// |jit-test| skip-if: !('stringRepresentation' in this) + +// This is to test the correctness of the string deduplication algorithm during +// the tenuring phase. Same strings below refer to the same character encoding +// (either latin1 or twobyte) and the same characters. + +// Tests: +// 1. Same strings with same flags and zones should be deduplicated for +// all linear strings except atoms and external strings. +// 2. Same strings, but from different zones should not be deduplicated. +// 3. Same strings, but with different flags should not be deduplicated. + +// We require predictable GC timing to make sure the correct +// strings are tenured together. +gczeal(0); + +var helperCode = ` +function makeInlineStr(isLatin1) { + var s = isLatin1 ? "123456789*1" : "一二三"; + return s + s; +} + +// Generic linear strings are non-atom, non-extensible, non-inline +// linear strings. +// Generic linear strings can only have latin1 characters. +function makeGenericLinearStr() { + return notes(() => 1); +} + +function makeRopeStr(isLatin1) { + var left = isLatin1 ? "1" : "一"; + var right = isLatin1 ? "123456789*123456789*123456" : + "一二三四五六七八九*一二三四五六七八"; + return left + right; +} + +function makeExtensibleStr(isLatin1) { + var r = makeRopeStr(isLatin1); + ensureLinearString(r); + return r; +} + +function makeExtensibleStrFrom(str) { + var left = str.substr(0, str.length/2); + var right = str.substr(str.length/2, str.length); + var ropeStr = left + right; + return ensureLinearString(ropeStr); +} + +function makeDependentStr(isLatin1) { + var e = makeExtensibleStr(isLatin1); + var r1 = e + "!"; + var r2 = e + r1; + ensureLinearString(r2); + return r1; +} + +function makeDependentStrFrom(str) { + var e = makeExtensibleStrFrom(str); + var r1 = e.substr(0, e.length/2) + e.substr(e.length/2, e.length); + var r2 = e + r1; + ensureLinearString(r2); + return r1; +} + +function makeExternalStr(isLatin1) { + return isLatin1 ? newString("12345678", {external: true}) : + newString("一二三", {external: true}); +} + +function tenureStringsWithSameChars(str1, str2, isDeduplicatable) { + minorgc(); + assertEq(stringRepresentation(str1) == stringRepresentation(str2), + isDeduplicatable); +} + +function assertDiffStrRepAfterMinorGC(g1, g2) { + minorgc(); + g1.eval(\`strRep = stringRepresentation(str);\`); + g2.eval(\`strRep = stringRepresentation(str);\`); + assertEq(g1.strRep == g2.strRep, false); +} +`; + +eval(helperCode); + +// test1: +// Same strings with same flags and zones should be deduplicated for all linear +// strings except atoms, external strings. +function test1(isLatin1) { + const isDeduplicatable = true; + + // Deduplicatable: + // --> Inline Strings + var str1 = makeInlineStr(isLatin1); + var str2 = makeInlineStr(isLatin1); + tenureStringsWithSameChars(str1, str2, isDeduplicatable); + + // --> Extensible Strings + str1 = makeExtensibleStr(isLatin1); + str2 = makeExtensibleStr(isLatin1); + tenureStringsWithSameChars(str1, str2, isDeduplicatable); + + // --> Dependent Strings + str1 = makeDependentStr(isLatin1); + str2 = makeDependentStr(isLatin1); + tenureStringsWithSameChars(str1, str2, isDeduplicatable); + + // --> Generic Linear Strings + if (isLatin1) { + var str1 = makeGenericLinearStr(); + var str2 = makeGenericLinearStr(); + tenureStringsWithSameChars(str1, str2, isDeduplicatable); + } + + // Non-Deduplicatable: + // --> Rope Strings + str1 = makeRopeStr(isLatin1); + str2 = makeRopeStr(isLatin1); + tenureStringsWithSameChars(str1, str2, !isDeduplicatable); + + // --> Atom strings are deduplicated already but not through string + // deduplication during tenuring. + + // --> External strings are not nursery allocated. +} + +// test2: +// Same strings, but from different zones should not be deduplicated. +function test2(isLatin1) { + var g1 = newGlobal({ newCompartment: true }); + var g2 = newGlobal({ newCompartment: true }); + + g1.eval(helperCode); + g2.eval(helperCode); + + // --> Inline Strings + g1.eval(`var str = makeInlineStr(${isLatin1}); `); + g2.eval(`var str = makeInlineStr(${isLatin1}); `); + assertDiffStrRepAfterMinorGC(g1, g2); + + // --> Extensible Strings + g1.eval(`str = makeExtensibleStr(${isLatin1}); `); + g2.eval(`str = makeExtensibleStr(${isLatin1}); `); + assertDiffStrRepAfterMinorGC(g1, g2); + + // --> Dependent Strings + g1.eval(`str = makeDependentStr(${isLatin1}); `); + g2.eval(`str = makeDependentStr(${isLatin1}); `); + assertDiffStrRepAfterMinorGC(g1, g2); + + // --> Generic Linear Strings + if (isLatin1) { + g1.eval(`str = makeGenericLinearStr();`); + g2.eval(`str = makeGenericLinearStr();`); + assertDiffStrRepAfterMinorGC(g1, g2); + } +} + +// test3: +// Same strings, but with different flags should not be deduplicated. +function test3(isLatin1) { + const isDeduplicatable = true; + + // --> Dependent String and Extensible String + var dependentStr = makeDependentStr(isLatin1); + var extensibleStr = makeExtensibleStrFrom(dependentStr); + tenureStringsWithSameChars(dependentStr, extensibleStr, !isDeduplicatable); + + if (isLatin1) { + // --> Generic Linear String and Extensible String + var genericLinearStr = makeGenericLinearStr(); + var extensibleStr = makeExtensibleStrFrom(genericLinearStr); + tenureStringsWithSameChars( + genericLinearStr, + extensibleStr, + !isDeduplicatable + ); + + // --> Generic Linear String and Dependent String + var dependentStr = makeDependentStrFrom(genericLinearStr); + tenureStringsWithSameChars( + dependentStr, + genericLinearStr, + !isDeduplicatable + ); + } + + // --> Inline strings are too short to have the same chars as the extensible + // strings, generic linear strings and dependent strings +} + +function runTests() { + var charEncoding = { TWOBYTE: 0, LATIN1: 1 }; + + test1(charEncoding.TWOBYTE); + test2(charEncoding.TWOBYTE); + test3(charEncoding.TWOBYTE); + + test1(charEncoding.LATIN1); + test2(charEncoding.LATIN1); + test3(charEncoding.LATIN1); +} + +runTests(); diff --git a/js/src/jit-test/tests/gc/elements-post-write-barrier.js b/js/src/jit-test/tests/gc/elements-post-write-barrier.js new file mode 100644 index 0000000000..04249eb727 --- /dev/null +++ b/js/src/jit-test/tests/gc/elements-post-write-barrier.js @@ -0,0 +1,27 @@ +gczeal(12); + +var length = 10000; +var array = new Array(length); +array.fill(null); + +// Promote the array to the tenured heap, if it isn't already there. +minorgc(); + +for (var i = 0; i < length; i++) { + // Exercise that barrier with some fresh nursery object references! + array[i] = {}; +} + +minorgc(); + +for (var i = length; i > 0; i--) { + array[i - 1] = {}; +} + +minorgc(); + +for (var i = 0; i < length; i++) { + array[Math.floor(Math.random() * length)] = {}; +} + +gc(); diff --git a/js/src/jit-test/tests/gc/finalizationRegistry-ccw.js b/js/src/jit-test/tests/gc/finalizationRegistry-ccw.js new file mode 100644 index 0000000000..88c98519ca --- /dev/null +++ b/js/src/jit-test/tests/gc/finalizationRegistry-ccw.js @@ -0,0 +1,81 @@ +// Test combinations of arguments in different compartments. + +gczeal(0); + +let heldValues = []; + +function ccwToObject() { + return evalcx('({})', newGlobal({newCompartment: true})); +} + +function newRegistry() { + return new FinalizationRegistry(value => { + heldValues.push(value); + }); +} + +function ccwToRegistry() { + let global = newGlobal({newCompartment: true}); + global.heldValues = heldValues; + return global.eval( + `new FinalizationRegistry(value => heldValues.push(value))`); +} + +function incrementalGC() { + startgc(1); + while (gcstate() !== "NotActive") { + gcslice(1000); + } +} + +// Test the case when the registry remains live. +for (let w of [false, true]) { + for (let x of [false, true]) { + for (let y of [false, true]) { + for (let z of [false, true]) { + let registry = w ? ccwToRegistry(w) : newRegistry(); + let target = x ? ccwToObject() : {}; + let heldValue = y ? ccwToObject() : {}; + let token = z ? ccwToObject() : {}; + registry.register(target, heldValue, token); + registry.unregister(token); + registry.register(target, heldValue, token); + target = undefined; + token = undefined; + heldValue = undefined; + incrementalGC(); + heldValues.length = 0; // Clear, don't replace. + drainJobQueue(); + assertEq(heldValues.length, 1); + } + } + } +} + +// Test the case when registry has no more references. +for (let w of [false, true]) { + for (let x of [false, true]) { + for (let y of [false, true]) { + for (let z of [false, true]) { + let registry = w ? ccwToRegistry(w) : newRegistry(); + let target = x ? ccwToObject() : {}; + let heldValue = y ? ccwToObject() : {}; + let token = z ? ccwToObject() : {}; + registry.register(target, heldValue, token); + registry.unregister(token); + registry.register(target, heldValue, token); + target = undefined; + token = undefined; + heldValue = undefined; + registry = undefined; // Remove last reference to registry. + incrementalGC(); + heldValues.length = 0; + drainJobQueue(); + // The cleanup callback may or may not be run depending on + // which order the zones are swept in, which itself depends on + // the arrangement of CCWs. + assertEq(heldValues.length <= 1, true); + } + } + } +} diff --git a/js/src/jit-test/tests/gc/finalizationRegistry-cleanupSome-recursive.js b/js/src/jit-test/tests/gc/finalizationRegistry-cleanupSome-recursive.js new file mode 100644 index 0000000000..3245c1cff3 --- /dev/null +++ b/js/src/jit-test/tests/gc/finalizationRegistry-cleanupSome-recursive.js @@ -0,0 +1,51 @@ +// Test trying to call cleanupSome recursively in callback. + +// 0: Initial state. +// 1: Attempt recursive calls. +// 2: After recursive calls. +let state = 0; + +let registry = new FinalizationRegistry(x => { + if (state === 0) { + state = 1; + try { + registry.cleanupSome(); + } catch (e) { + // Pass the test if any error was thrown. + return; + } finally { + state = 2; + } + throw new Error("expected stack overflow error"); + } + + if (state === 1) { + registry.cleanupSome(); + } +}); + +// Attempt to find the maximum supported stack depth. +var stackSize = 0; +function findStackSize(i) { + try { + stackSize = i; + findStackSize(i + 1); + } catch (e) { + return; + } +} +findStackSize(0); + +// Multiply the calculated stack size by some factor just to be on the safe side. +const exceedStackDepthLimit = stackSize * 5; + +let values = []; +for (let i = 0; i < exceedStackDepthLimit; ++i) { + let v = {}; + registry.register(v, i); + values.push(v); +} +values.length = 0; + +gc(); +drainJobQueue(); diff --git a/js/src/jit-test/tests/gc/finalizationRegistry-gray.js b/js/src/jit-test/tests/gc/finalizationRegistry-gray.js new file mode 100644 index 0000000000..5411a1a9ad --- /dev/null +++ b/js/src/jit-test/tests/gc/finalizationRegistry-gray.js @@ -0,0 +1,10 @@ +// Test gray finalization registry is correctly barrired. +target = {}; +registry = new FinalizationRegistry(value => undefined); +registry.register(target, 1); +grayRoot()[0] = registry; +registry = undefined; +gc(); // Registry is now marked gray. +target = undefined; +gc(); // Target dies, registry is queued. +drainJobQueue(); diff --git a/js/src/jit-test/tests/gc/finalizationRegistry-oom1.js b/js/src/jit-test/tests/gc/finalizationRegistry-oom1.js new file mode 100644 index 0000000000..753448a650 --- /dev/null +++ b/js/src/jit-test/tests/gc/finalizationRegistry-oom1.js @@ -0,0 +1,8 @@ +// |jit-test| skip-if: !('oomTest' in this) + +// Don't test prototype initialization etc. +new FinalizationRegistry(x => 0); + +oomTest(() => { + new FinalizationRegistry(x => 0); +}); diff --git a/js/src/jit-test/tests/gc/finalizationRegistry-oom2.js b/js/src/jit-test/tests/gc/finalizationRegistry-oom2.js new file mode 100644 index 0000000000..9d9b2a7db8 --- /dev/null +++ b/js/src/jit-test/tests/gc/finalizationRegistry-oom2.js @@ -0,0 +1,4 @@ +// |jit-test| skip-if: !('oomTest' in this) +let registry = new FinalizationRegistry(x => 0); +let token = {}; +oomTest(() => registry.register({}, 1, token)); diff --git a/js/src/jit-test/tests/gc/finalizationRegistry-oom3.js b/js/src/jit-test/tests/gc/finalizationRegistry-oom3.js new file mode 100644 index 0000000000..d606ad8ba8 --- /dev/null +++ b/js/src/jit-test/tests/gc/finalizationRegistry-oom3.js @@ -0,0 +1,5 @@ +// |jit-test| skip-if: !('oomTest' in this) +let registry = new FinalizationRegistry(x => 0); +registry.register({}, 1, {}); +let token = {}; +oomTest(() => registry.unregister(token)); diff --git a/js/src/jit-test/tests/gc/finalizationRegistry-oom4.js b/js/src/jit-test/tests/gc/finalizationRegistry-oom4.js new file mode 100644 index 0000000000..4b7ef66ba0 --- /dev/null +++ b/js/src/jit-test/tests/gc/finalizationRegistry-oom4.js @@ -0,0 +1,5 @@ +// |jit-test| skip-if: !('oomTest' in this) +let registry = new FinalizationRegistry(x => 0); +let target = {}; +let token = {}; +oomTest(() => registry.register(target, 1, token)); diff --git a/js/src/jit-test/tests/gc/finalizationRegistry-records-not-initialized.js b/js/src/jit-test/tests/gc/finalizationRegistry-records-not-initialized.js new file mode 100644 index 0000000000..327988ddc7 --- /dev/null +++ b/js/src/jit-test/tests/gc/finalizationRegistry-records-not-initialized.js @@ -0,0 +1,6 @@ +enableShellAllocationMetadataBuilder(); +evaluate(` + var registry = new FinalizationRegistry(x => 0); + gczeal(9,3); + registry.register({}, 1, {}); +`); diff --git a/js/src/jit-test/tests/gc/finalizationRegistry.js b/js/src/jit-test/tests/gc/finalizationRegistry.js new file mode 100644 index 0000000000..0450df8457 --- /dev/null +++ b/js/src/jit-test/tests/gc/finalizationRegistry.js @@ -0,0 +1,247 @@ +function checkPropertyDescriptor(obj, property, writable, enumerable, + configurable) { + let desc = Object.getOwnPropertyDescriptor(obj, property); + assertEq(typeof desc, "object"); + assertEq(desc.writable, writable); + assertEq(desc.enumerable, enumerable); + assertEq(desc.configurable, configurable); +} + +function assertThrowsTypeError(thunk) { + let error; + try { + thunk(); + } catch (e) { + error = e; + } + assertEq(error instanceof TypeError, true); +} + +// 3.1 The FinalizationRegistry Constructor +assertEq(typeof this.FinalizationRegistry, "function"); + +// 3.1.1 FinalizationRegistry ( cleanupCallback ) +assertThrowsTypeError(() => new FinalizationRegistry()); +assertThrowsTypeError(() => new FinalizationRegistry(1)); +new FinalizationRegistry(x => 0); + +// 3.2 Properties of the FinalizationRegistry Constructor +assertEq(Object.getPrototypeOf(FinalizationRegistry), Function.prototype); + +// 3.2.1 FinalizationRegistry.prototype +checkPropertyDescriptor(FinalizationRegistry, 'prototype', false, false, false); + +// 3.3 Properties of the FinalizationRegistry Prototype Object +let proto = FinalizationRegistry.prototype; +assertEq(Object.getPrototypeOf(proto), Object.prototype); + +// 3.3.1 FinalizationRegistry.prototype.constructor +assertEq(proto.constructor, FinalizationRegistry); + +// 3.3.2 FinalizationRegistry.prototype.register ( target , holdings [, unregisterToken ] ) +assertEq(proto.hasOwnProperty('register'), true); +assertEq(typeof proto.register, 'function'); + +// 3.3.3 FinalizationRegistry.prototype.unregister ( unregisterToken ) +assertEq(proto.hasOwnProperty('unregister'), true); +assertEq(typeof proto.unregister, 'function'); + +// 3.3.4 FinalizationRegistry.prototype.cleanupSome ( [ callback ] ) +assertEq(proto.hasOwnProperty('cleanupSome'), true); +assertEq(typeof proto.cleanupSome, 'function'); + +// 3.3.5 FinalizationRegistry.prototype [ @@toStringTag ] +assertEq(proto[Symbol.toStringTag], "FinalizationRegistry"); +checkPropertyDescriptor(proto, Symbol.toStringTag, false, false, true); + +// 3.4 Properties of FinalizationRegistry Instances +let registry = new FinalizationRegistry(x => 0); +assertEq(Object.getPrototypeOf(registry), proto); +assertEq(Object.getOwnPropertyNames(registry).length, 0); + +let heldValues = []; +registry = new FinalizationRegistry(value => { + heldValues.push(value); +}); + +// Test a single target. +heldValues = []; +registry.register({}, 42); +gc(); +drainJobQueue(); +assertEq(heldValues.length, 1); +assertEq(heldValues[0], 42); + +// Test multiple targets. +heldValues = []; +for (let i = 0; i < 100; i++) { + registry.register({}, i); +} +gc(); +drainJobQueue(); +assertEq(heldValues.length, 100); +heldValues = heldValues.sort((a, b) => a - b); +for (let i = 0; i < 100; i++) { + assertEq(heldValues[i], i); +} + +// Test a single object in multiple registries +heldValues = []; +let heldValues2 = []; +let registry2 = new FinalizationRegistry(value => { + heldValues2.push(value); +}); +{ + let object = {}; + registry.register(object, 1); + registry2.register(object, 2); + object = null; +} +gc(); +drainJobQueue(); +assertEq(heldValues.length, 1); +assertEq(heldValues[0], 1); +assertEq(heldValues2.length, 1); +assertEq(heldValues2[0], 2); + +// Unregister a single target. +heldValues = []; +let token = {}; +registry.register({}, 1, token); +registry.unregister(token); +gc(); +drainJobQueue(); +assertEq(heldValues.length, 0); + +// Unregister multiple targets. +heldValues = []; +let token2 = {}; +registry.register({}, 1, token); +registry.register({}, 2, token2); +registry.register({}, 3, token); +registry.register({}, 4, token2); +registry.unregister(token); +gc(); +drainJobQueue(); +assertEq(heldValues.length, 2); +heldValues = heldValues.sort((a, b) => a - b); +assertEq(heldValues[0], 2); +assertEq(heldValues[1], 4); + +// Watch object in another global. +let other = newGlobal({newCompartment: true}); +heldValues = []; +registry.register(evalcx('({})', other), 1); +gc(); +drainJobQueue(); +assertEq(heldValues.length, 1); +assertEq(heldValues[0], 1); + +// Pass heldValues from another global. +let heldValue = evalcx('{}', other); +heldValues = []; +registry.register({}, heldValue); +gc(); +drainJobQueue(); +assertEq(heldValues.length, 1); +assertEq(heldValues[0], heldValue); + +// Pass unregister token from another global. +token = evalcx('({})', other); +heldValues = []; +registry.register({}, 1, token); +gc(); +drainJobQueue(); +assertEq(heldValues.length, 1); +assertEq(heldValues[0], 1); +heldValues = []; +registry.register({}, 1, token); +registry.unregister(token); +gc(); +drainJobQueue(); +assertEq(heldValues.length, 0); + +// FinalizationRegistry is designed to be subclassable. +class MyRegistry extends FinalizationRegistry { + constructor(callback) { + super(callback); + } +} +let r2 = new MyRegistry(value => { + heldValues.push(value); +}); +heldValues = []; +r2.register({}, 42); +gc(); +drainJobQueue(); +assertEq(heldValues.length, 1); +assertEq(heldValues[0], 42); + +// Test cleanupSome. +heldValues = []; +let r5 = new FinalizationRegistry(v => heldValues.push(v)); +r5.register({}, 1); +r5.register({}, 2); +r5.register({}, 3); +gc(); +r5.cleanupSome(); +assertEq(heldValues.length, 3); +heldValues = heldValues.sort((a, b) => a - b); +assertEq(heldValues[0], 1); +assertEq(heldValues[1], 2); +assertEq(heldValues[2], 3); + +// Test trying to call cleanupSome in callback. +let r6 = new FinalizationRegistry(x => { + r6.cleanupSome(); +}); +r6.register({}, 1); +gc(); +drainJobQueue(); + +// Test trying to call cleanupSome in callback with multiple values. +let callbackCounter7 = 0; +let r7 = new FinalizationRegistry(x => { + callbackCounter7++; + r7.cleanupSome(); +}); +r7.register({}, 1); +r7.register({}, 2); +r7.register({}, 3); +r7.register({}, 4); +gc(); +drainJobQueue(); +assertEq(callbackCounter7, 4); + +// Test that targets don't keep the finalization registry alive. +let target = {}; +registry = new FinalizationRegistry(value => undefined); +registry.register(target, 1); +let weakRef = new WeakRef(registry); +registry = undefined; +assertEq(typeof weakRef.deref(), 'object'); +drainJobQueue(); +gc(); +assertEq(weakRef.deref(), undefined); +assertEq(typeof target, 'object'); + +// Test that targets don't keep the finalization registry alive when also +// used as the unregister token. +registry = new FinalizationRegistry(value => undefined); +registry.register(target, 1, target); +weakRef = new WeakRef(registry); +registry = undefined; +assertEq(typeof weakRef.deref(), 'object'); +drainJobQueue(); +gc(); +assertEq(weakRef.deref(), undefined); +assertEq(typeof target, 'object'); + +// Test that cleanup doesn't happen if the finalization registry dies. +heldValues = []; +new FinalizationRegistry(value => { + heldValues.push(value); +}).register({}, 1); +gc(); +drainJobQueue(); +assertEq(heldValues.length, 0); diff --git a/js/src/jit-test/tests/gc/gcparam.js b/js/src/jit-test/tests/gc/gcparam.js new file mode 100644 index 0000000000..a1584fc39b --- /dev/null +++ b/js/src/jit-test/tests/gc/gcparam.js @@ -0,0 +1,64 @@ +gczeal(0); + +function testGetParam(key) { + gcparam(key); +} + +function testChangeParam(key, diff) { + if (!diff) { + diff = 1; + } + + let prev = gcparam(key); + + let newValue = prev > 0 ? prev - diff : prev + diff; + gcparam(key, newValue); + assertEq(gcparam(key), newValue); + + gcparam(key, prev); + assertEq(gcparam(key), prev); +} + +testGetParam("gcBytes"); +testGetParam("gcNumber"); +testGetParam("unusedChunks"); +testGetParam("totalChunks"); +testGetParam("nurseryBytes"); +testGetParam("majorGCNumber"); +testGetParam("minorGCNumber"); +testGetParam("chunkBytes"); +testGetParam("helperThreadCount"); + +testChangeParam("maxBytes"); +testChangeParam("minNurseryBytes", 16 * 1024); +testChangeParam("maxNurseryBytes", 1024 * 1024); +testChangeParam("incrementalGCEnabled"); +testChangeParam("perZoneGCEnabled"); +testChangeParam("sliceTimeBudgetMS"); +testChangeParam("highFrequencyTimeLimit"); +testChangeParam("smallHeapSizeMax"); +testChangeParam("largeHeapSizeMin"); +testChangeParam("highFrequencySmallHeapGrowth"); +testChangeParam("highFrequencyLargeHeapGrowth"); +testChangeParam("lowFrequencyHeapGrowth"); +testChangeParam("balancedHeapLimitsEnabled"); +testChangeParam("heapGrowthFactor"); +testChangeParam("allocationThreshold"); +testChangeParam("smallHeapIncrementalLimit"); +testChangeParam("largeHeapIncrementalLimit"); +testChangeParam("minEmptyChunkCount"); +testChangeParam("maxEmptyChunkCount"); +testChangeParam("compactingEnabled"); +testChangeParam("parallelMarkingEnabled"); +testChangeParam("parallelMarkingThresholdKB"); +testChangeParam("minLastDitchGCPeriod"); +testChangeParam("nurseryFreeThresholdForIdleCollection"); +testChangeParam("nurseryFreeThresholdForIdleCollectionPercent"); +testChangeParam("nurseryTimeoutForIdleCollectionMS"); +testChangeParam("pretenureThreshold"); +testChangeParam("zoneAllocDelayKB"); +testChangeParam("mallocThresholdBase"); +testChangeParam("urgentThreshold"); +testChangeParam("nurseryTimeoutForIdleCollectionMS"); +testChangeParam("helperThreadRatio"); +testChangeParam("maxHelperThreads"); diff --git a/js/src/jit-test/tests/gc/gczeal-range.js b/js/src/jit-test/tests/gc/gczeal-range.js new file mode 100644 index 0000000000..1589a54af5 --- /dev/null +++ b/js/src/jit-test/tests/gc/gczeal-range.js @@ -0,0 +1,5 @@ +try { + gczeal(123); +} catch(e) { + assertEq(e.toString().includes("out of range"), true); +} diff --git a/js/src/jit-test/tests/gc/helper-thread-params.js b/js/src/jit-test/tests/gc/helper-thread-params.js new file mode 100644 index 0000000000..282ea82a54 --- /dev/null +++ b/js/src/jit-test/tests/gc/helper-thread-params.js @@ -0,0 +1,34 @@ +// |jit-test| skip-if: helperThreadCount() === 0 + +function assertError(thunk) { + let threw = false; + try { + thunk(); + } catch (e) { + threw = true; + } + assertEq(threw, true); +} + +let initialHelperThreads = helperThreadCount(); + +// Test that setting maxHelperThreads limits the number of threads. +gcparam("helperThreadRatio", 100); +for (let i = 1; i <= initialHelperThreads; i++) { + gcparam("maxHelperThreads", i); + assertEq(gcparam("helperThreadCount"), i); +} + +// Test that setting helperThreadRatio works as expected. +gcparam("maxHelperThreads", 1000); +for (let i = 25; i <= 400; i *= 2) { + gcparam("helperThreadRatio", i); + let ratio = i / 100; + let expected = Math.max(Math.floor(initialHelperThreads * ratio), 1); + assertEq(gcparam("helperThreadCount"), expected); + assertEq(helperThreadCount(), Math.max(initialHelperThreads, expected)); +} + +// Test that illegal settings are checked. +assertError(() => gcparam("helperThreadRatio", 0)); +assertError(() => gcparam("maxHelperThreads", 0)); diff --git a/js/src/jit-test/tests/gc/incremental-01.js b/js/src/jit-test/tests/gc/incremental-01.js new file mode 100644 index 0000000000..d4646c1656 --- /dev/null +++ b/js/src/jit-test/tests/gc/incremental-01.js @@ -0,0 +1,31 @@ +var objs; + +function init() +{ + objs = new Object(); + var x = new Object(); + objs.root1 = x; + objs.root2 = new Object(); + x.ptr = new Object(); + x = null; + + /* + * Clears out the arena lists. Otherwise all the objects above + * would be considered to be created during the incremental GC. + */ + gc(); +} + +/* + * Use eval here so that the interpreter frames end up higher on the + * stack, which avoids them being seen later on by the conservative + * scanner. + */ +eval("init()"); + +gcslice(0); // Start IGC, but don't mark anything. +selectforgc(objs.root2); +gcslice(1); +objs.root2.ptr = objs.root1.ptr; +objs.root1.ptr = null; +gcslice(); diff --git a/js/src/jit-test/tests/gc/incremental-02.js b/js/src/jit-test/tests/gc/incremental-02.js new file mode 100644 index 0000000000..a2a2c8de85 --- /dev/null +++ b/js/src/jit-test/tests/gc/incremental-02.js @@ -0,0 +1,30 @@ +var objs; + +function init() +{ + objs = new Object(); + var x = new Object(); + objs.root = x; + x.a = new Object(); + x.b = new Object(); + + /* + * Clears out the arena lists. Otherwise all the objects above + * would be considered to be created during the incremental GC. + */ + gc(); +} + +/* + * Use eval here so that the interpreter frames end up higher on the + * stack, which avoids them being seen later on by the conservative + * scanner. + */ +eval("init()"); + +gcslice(0); // Start IGC, but don't mark anything. +selectforgc(objs.root); +gcslice(1); +delete objs.root.b; +delete objs.root.a; +gcslice(); diff --git a/js/src/jit-test/tests/gc/incremental-AccessorShape-barrier.js b/js/src/jit-test/tests/gc/incremental-AccessorShape-barrier.js new file mode 100644 index 0000000000..0055e2c51f --- /dev/null +++ b/js/src/jit-test/tests/gc/incremental-AccessorShape-barrier.js @@ -0,0 +1,18 @@ +gczeal(0); +gc(); + +var o = {}; +function foo() { + var i = 0; + startgc(0); + Object.defineProperty(o, 'foo', {configurable: true, get: function g() { return i; }, + set: function s() { return i; }}); + Object.defineProperty(o, 'foo', {configurable: true, get: function g() { return i; }, + set: function s() { return i; }}); + Object.defineProperty(o, 'foo', {configurable: true, get: function g() { return i; }, + set: function s() { return i; }}); + Object.defineProperty(o, 'foo', {configurable: true, get: function g() { return i; }, + set: function s() { return i; }}); + abortgc(); +} +foo(); diff --git a/js/src/jit-test/tests/gc/incremental-abort.js b/js/src/jit-test/tests/gc/incremental-abort.js new file mode 100644 index 0000000000..ee128d1290 --- /dev/null +++ b/js/src/jit-test/tests/gc/incremental-abort.js @@ -0,0 +1,57 @@ +// |jit-test| skip-if: !getBuildConfiguration()['has-gczeal'] || (getBuildConfiguration()['osx'] && getBuildConfiguration()['arm64']) + +// Test aborting an incremental GC in all possible states + +gczeal(0); +gc(); + +// Allocate objectCount objects in zoneCount zones and run a incremental +// shrinking GC with slices with a work budget of sliceBudget until we reach +// GC state abortState at which point, abort the GC. +function testAbort(zoneCount, objectCount, sliceBudget, abortState) +{ + + var zones = []; + for (var i = 0; i < zoneCount; i++) { + var zone = newGlobal({newCompartment: true}); + evaluate("var objects; " + + "function makeObjectGraph(objectCount) { " + + " objects = []; " + + " for (var i = 0; i < objectCount; i++) " + + " objects.push({i: i}); " + + "}", + { global: zone }); + zone.makeObjectGraph(objectCount); + zones.push(zone); + } + + gc(); + + var didAbort = false; + startgc(sliceBudget, "shrinking"); + assertEq(currentgc().isShrinking, true); + while (gcstate() !== "NotActive") { + if (gcstate() == abortState) { + abortgc(); + didAbort = true; + break; + } + + gcslice(sliceBudget); + } + + assertEq(gcstate(), "NotActive"); + if (abortState) + assertEq(didAbort, true); + + return zones; +} + +gczeal(0); +testAbort(10, 10000, 10000); +testAbort(10, 10000, 10000, "Mark"); +testAbort(10, 10000, 1000, "Sweep"); +testAbort(10, 10000, 10000, "Compact"); +// Note: we do not yield automatically before Finalize or Decommit, as they +// yield internally. Thus, we may not witness an incremental state in this +// phase and cannot test it explicitly. diff --git a/js/src/jit-test/tests/gc/incremental-compacting.js b/js/src/jit-test/tests/gc/incremental-compacting.js new file mode 100644 index 0000000000..f051cf60ea --- /dev/null +++ b/js/src/jit-test/tests/gc/incremental-compacting.js @@ -0,0 +1,43 @@ +// |jit-test| skip-if: !("gcstate" in this && "gczeal" in this)
+
+// Exercise incremental compacting GC
+// Run with MOZ_GCTIMER to see the timings
+
+gczeal(0);
+
+function testCompacting(zoneCount, objectCount, sliceCount)
+{
+ // Allocate objectCount objects in zoneCount zones
+ // On linux64 debug builds we will move them all
+ // Run compacting GC with multiple slices
+
+ var zones = [];
+ for (var i = 0; i < zoneCount; i++) {
+ var zone = newGlobal();
+ evaluate("var objects; " +
+ "function makeObjectGraph(objectCount) { " +
+ " objects = []; " +
+ " for (var i = 0; i < objectCount; i++) " +
+ " objects.push({ serial: i }); " +
+ "}",
+ { global: zone });
+ zone.makeObjectGraph(objectCount);
+ zones.push(zone);
+ }
+
+ // Finish any alloc-triggered incremental GC
+ if (gcstate() !== "NotActive")
+ gc();
+
+ startgc(sliceCount, "shrinking");
+ while (gcstate() !== "NotActive") {
+ gcslice(sliceCount);
+ }
+
+ return zones;
+}
+
+testCompacting(1, 100000, 100000);
+testCompacting(2, 100000, 100000);
+testCompacting(4, 50000, 100000);
+testCompacting(2, 100000, 50000);
diff --git a/js/src/jit-test/tests/gc/incremental-state.js b/js/src/jit-test/tests/gc/incremental-state.js new file mode 100644 index 0000000000..f20b722234 --- /dev/null +++ b/js/src/jit-test/tests/gc/incremental-state.js @@ -0,0 +1,96 @@ +// |jit-test| skip-if: !hasFunction["gczeal"] + +function assert(x) { + assertEq(true, x); +} + +function waitForState(state) { + while (gcstate() !== state && gcstate() !== "NotActive") { + gcslice(100); + } +} + +// Test expected state changes during collection. +gczeal(0); + +// Non-incremental GC. +gc(); +assertEq(gcstate(), "NotActive"); + +// Incremental GC in minimal slice. Note that finalization always uses zero- +// sized slices while background finalization is on-going, so we need to loop. +gcslice(1000000); +assert(gcstate() !== "Mark"); +finishgc(); +assertEq(gcstate(), "NotActive"); + +// Incremental GC in multiple slices: if marking takes more than one slice, +// we yield before we start sweeping. +gczeal(0); +gcslice(1); +waitForState("Mark"); +assertEq(gcstate(), "Mark"); +gcslice(1000000); +assertEq(gcstate(), "Mark"); +gcslice(1000000); +assert(gcstate() !== "Mark"); +finishgc(); + +// Zeal mode 6: Incremental GC in two slices: +// 1) prepare +// 2) mark roots, mark and sweep +gczeal(6, 0); +gcslice(1); +assertEq(gcstate(), "Prepare"); +gcslice(1); +assertEq(gcstate(), "NotActive"); + +// Zeal mode 8: Incremental GC in two slices: +// 1) prepare and mark roots +// 2) mark and sweep +gczeal(8, 0); +gcslice(1); +assertEq(gcstate(), "Mark"); +gcslice(1); +assertEq(gcstate(), "NotActive"); + +// Zeal mode 9: Incremental GC in two slices: +// 1) prepare, mark roots and marking +// 2) new marking and sweeping +gczeal(9, 0); +gcslice(1); +assertEq(gcstate(), "Mark"); +gcslice(1); +assertEq(gcstate(), "NotActive"); + +// Zeal mode 10: Incremental GC in multiple slices (always yeilds before +// sweeping). This test uses long slices to prove that this zeal mode yields +// in sweeping, where normal IGC (above) does not. +gczeal(10, 0); +gcslice(1000000); +while (gcstate() === "Prepare") { + gcslice(1000000); +} +assertEq(gcstate(), "Sweep"); +gcslice(1000000); +assert(gcstate() !== "Sweep"); +finishgc(); + +// Two-slice zeal modes that yield once during sweeping. +for (let mode of [ 17, 19 ]) { + gczeal(mode, 0); + gcslice(1); + assertEq(gcstate(), "Sweep"); + gcslice(1); + assertEq(gcstate(), "NotActive"); +} + +// Two-slice zeal modes that yield per-zone during sweeping. +const sweepingZealModes = [ 20, 21, 22, 23 ]; +for (let mode of sweepingZealModes) { + gczeal(mode, 0); + gcslice(1); + while (gcstate() === "Sweep") + gcslice(1); + assertEq(gcstate(), "NotActive"); +} diff --git a/js/src/jit-test/tests/gc/jsscript-mark-children.js b/js/src/jit-test/tests/gc/jsscript-mark-children.js new file mode 100644 index 0000000000..c74a19c0f3 --- /dev/null +++ b/js/src/jit-test/tests/gc/jsscript-mark-children.js @@ -0,0 +1,24 @@ +// Bug 758509 changed things so that a JSScript is partially initialized when +// it is created, which is prior to bytecode generation; full initialization +// only occurs after bytecode generation. This means that +// JSScript::markChildren() must deal with partially-initialized JSScripts. +// This test forces that to happen, because each let block allocates a +// StaticBlockObject. All that should happen is that we don't crash. + +let t = 0; +gczeal(2,1); +eval("\ +let x0 = 3, y = 4;\ +{ let x = x0+0, y = 12; t += (x + y); } \ +{ let x = x0+1, y = 12; t += (x + y); } \ +{ let x = x0+2, y = 12; t += (x + y); } \ +{ let x = x0+3, y = 12; t += (x + y); } \ +{ let x = x0+4, y = 12; t += (x + y); } \ +{ let x = x0+5, y = 12; t += (x + y); } \ +{ let x = x0+6, y = 12; t += (x + y); } \ +{ let x = x0+7, y = 12; t += (x + y); } \ +{ let x = x0+8, y = 12; t += (x + y); } \ +{ let x = x0+9, y = 12; t += (x + y); } \ +t += (x0 + y);\ +assertEq(t, 202);\ +"); diff --git a/js/src/jit-test/tests/gc/marking-thread-count.js b/js/src/jit-test/tests/gc/marking-thread-count.js new file mode 100644 index 0000000000..5b90e14186 --- /dev/null +++ b/js/src/jit-test/tests/gc/marking-thread-count.js @@ -0,0 +1,12 @@ +// |jit-test| skip-if: helperThreadCount() === 0 + +let initialGCHelperThreadCount = gcparam('helperThreadCount'); + +let prevHelperThreadCount = helperThreadCount(); +for (let i of [0, 1, 4, 8, 4, 0]) { + gcparam('markingThreadCount', i); + assertEq(gcparam('markingThreadCount'), i); + assertEq(gcparam('helperThreadCount'), initialGCHelperThreadCount); + assertEq(true, helperThreadCount() >= Math.max(prevHelperThreadCount, i)); + prevHelperThreadCount = helperThreadCount(); +} diff --git a/js/src/jit-test/tests/gc/multi-01.js b/js/src/jit-test/tests/gc/multi-01.js new file mode 100644 index 0000000000..18aa40aaea --- /dev/null +++ b/js/src/jit-test/tests/gc/multi-01.js @@ -0,0 +1,9 @@ +/* Make sure we don't collect the atoms compartment unless every compartment is marked. */ + +var g = newGlobal(); +g.eval("var x = 'some-atom';"); + +schedulezone(this); +schedulezone('atoms'); +gc('zone'); +print(g.x); diff --git a/js/src/jit-test/tests/gc/multi-02.js b/js/src/jit-test/tests/gc/multi-02.js new file mode 100644 index 0000000000..fedb51ce92 --- /dev/null +++ b/js/src/jit-test/tests/gc/multi-02.js @@ -0,0 +1,10 @@ +/* Exercise the path where we want to collect a new compartment in the middle of incremental GC. */ + +var g1 = newGlobal(); +var g2 = newGlobal(); + +schedulezone(g1); +gcslice(0); // Start IGC, but don't mark anything. +schedulezone(g2); +gcslice(1); +gcslice(); diff --git a/js/src/jit-test/tests/gc/multi-03.js b/js/src/jit-test/tests/gc/multi-03.js new file mode 100644 index 0000000000..0212838dce --- /dev/null +++ b/js/src/jit-test/tests/gc/multi-03.js @@ -0,0 +1,11 @@ +/* Exercise the path where we want to collect a new compartment in the middle of incremental GC. */ + +var g1 = newGlobal(); +var g2 = newGlobal(); + +schedulezone(g1); +schedulezone(g2); +gcslice(0); // Start IGC, but don't mark anything. +schedulezone(g1); +gcslice(1); +gcslice(); diff --git a/js/src/jit-test/tests/gc/oomInArrayProtoTest.js b/js/src/jit-test/tests/gc/oomInArrayProtoTest.js new file mode 100644 index 0000000000..b99669d92d --- /dev/null +++ b/js/src/jit-test/tests/gc/oomInArrayProtoTest.js @@ -0,0 +1,22 @@ +// |jit-test| skip-if: !('oomTest' in this) + +function arrayProtoOutOfRange() { + function f(obj) { + return typeof obj[15]; + } + + function test() { + var a = [1, 2]; + a.__proto__ = {15: 1337}; + var b = [1, 2, 3, 4]; + + for (var i = 0; i < 200; i++) { + var r = f(i % 2 ? a : b); + assertEq(r, i % 2 ? "number" : "undefined"); + } + } + + test(); +} + +oomTest(arrayProtoOutOfRange); diff --git a/js/src/jit-test/tests/gc/oomInByteSize.js b/js/src/jit-test/tests/gc/oomInByteSize.js new file mode 100644 index 0000000000..9566b9cb49 --- /dev/null +++ b/js/src/jit-test/tests/gc/oomInByteSize.js @@ -0,0 +1,18 @@ +// |jit-test| skip-if: !('oomTest' in this) + +oomTest(() => byteSize({})); +oomTest(() => byteSize({ w: 1, x: 2, y: 3 })); +oomTest(() => byteSize({ w:1, x:2, y:3, z:4, a:6, 0:0, 1:1, 2:2 })); +oomTest(() => byteSize([1, 2, 3])); +oomTest(() => byteSize(function () {})); + +function f1() { + return 42; +} +oomTest(() => byteSizeOfScript(f1)); + +oomTest(() => byteSize("1234567")); +oomTest(() => byteSize("千早ぶる神代")); + +let s = Symbol(); +oomTest(() => byteSize(s)); diff --git a/js/src/jit-test/tests/gc/oomInDebugger.js b/js/src/jit-test/tests/gc/oomInDebugger.js new file mode 100644 index 0000000000..c1904f573a --- /dev/null +++ b/js/src/jit-test/tests/gc/oomInDebugger.js @@ -0,0 +1,4 @@ +// |jit-test| skip-if: !('oomTest' in this) + +var g = newGlobal(); +oomTest(() => Debugger(g)); diff --git a/js/src/jit-test/tests/gc/oomInDtoa.js b/js/src/jit-test/tests/gc/oomInDtoa.js new file mode 100644 index 0000000000..83ded51cbb --- /dev/null +++ b/js/src/jit-test/tests/gc/oomInDtoa.js @@ -0,0 +1,3 @@ +// |jit-test| skip-if: !('oomTest' in this) + +oomTest(function() { return 1e300; }) diff --git a/js/src/jit-test/tests/gc/oomInExceptionHandlerBailout.js b/js/src/jit-test/tests/gc/oomInExceptionHandlerBailout.js new file mode 100644 index 0000000000..d5c8f29b27 --- /dev/null +++ b/js/src/jit-test/tests/gc/oomInExceptionHandlerBailout.js @@ -0,0 +1,14 @@ +// |jit-test| skip-if: !('oomTest' in this) +oomTest(() => { + let x = 0; + try { + for (let i = 0; i < 100; i++) { + if (i == 99) + throw "foo"; + x += i; + } + } catch (e) { + x = 0; + } + return x; +}); diff --git a/js/src/jit-test/tests/gc/oomInFindPath.js b/js/src/jit-test/tests/gc/oomInFindPath.js new file mode 100644 index 0000000000..4b3d95688c --- /dev/null +++ b/js/src/jit-test/tests/gc/oomInFindPath.js @@ -0,0 +1,18 @@ +// |jit-test| skip-if: !('oomTest' in this) + +var o = { w: { x: { y: { z: {} } } } }; +oomTest(() => findPath(o, o.w.x.y.z)); + +var a = [ , o ]; +oomTest(() => findPath(a, o)); + +function C() {} +C.prototype.obj = {}; +var c = new C; + +oomTest(() => findPath(c, c.obj)); + +function f(x) { return function g(y) { return x+y; }; } +var o = {} +var gc = f(o); +oomTest(() => findPath(gc, o)); diff --git a/js/src/jit-test/tests/gc/oomInFormatStackDump.js b/js/src/jit-test/tests/gc/oomInFormatStackDump.js new file mode 100644 index 0000000000..ce68e47f9e --- /dev/null +++ b/js/src/jit-test/tests/gc/oomInFormatStackDump.js @@ -0,0 +1,3 @@ +// |jit-test| skip-if: !('oomTest' in this) + +oomTest(() => getBacktrace({args: true, locals: true, thisprops: true})); diff --git a/js/src/jit-test/tests/gc/oomInGetJumpLabelForBranch.js b/js/src/jit-test/tests/gc/oomInGetJumpLabelForBranch.js new file mode 100644 index 0000000000..a568fc592f --- /dev/null +++ b/js/src/jit-test/tests/gc/oomInGetJumpLabelForBranch.js @@ -0,0 +1,3 @@ +// |jit-test| skip-if: !('oomTest' in this) + +oomTest(() => getBacktrace({thisprops: gc() && delete addDebuggee.enabled})); diff --git a/js/src/jit-test/tests/gc/oomInNewGlobal.js b/js/src/jit-test/tests/gc/oomInNewGlobal.js new file mode 100644 index 0000000000..c45737e143 --- /dev/null +++ b/js/src/jit-test/tests/gc/oomInNewGlobal.js @@ -0,0 +1,3 @@ +// |jit-test| skip-if: !('oomTest' in this) + +oomTest(newGlobal); diff --git a/js/src/jit-test/tests/gc/oomInOffTheadCompile.js b/js/src/jit-test/tests/gc/oomInOffTheadCompile.js new file mode 100644 index 0000000000..d4e0d4135e --- /dev/null +++ b/js/src/jit-test/tests/gc/oomInOffTheadCompile.js @@ -0,0 +1,15 @@ +// |jit-test| skip-if: !('oomTest' in this) || helperThreadCount() === 0 + +oomTest(() => { + offThreadCompileToStencil( + ` + function f(x) { + if (x == 0) + return "foobar"; + return 1 + f(x - 1); + } + f(5); + `); + var stencil = finishOffThreadStencil(); + evalStencil(stencil); +}); diff --git a/js/src/jit-test/tests/gc/oomInOffTheadCompile2.js b/js/src/jit-test/tests/gc/oomInOffTheadCompile2.js new file mode 100644 index 0000000000..1cac5ee859 --- /dev/null +++ b/js/src/jit-test/tests/gc/oomInOffTheadCompile2.js @@ -0,0 +1,7 @@ +// |jit-test| skip-if: !('oomTest' in this) || helperThreadCount() === 0 + +oomTest(() => { + offThreadCompileToStencil("function a(x) {"); + var stencil = finishOffThreadStencil(); + evalStencil(stencil); +}); diff --git a/js/src/jit-test/tests/gc/oomInOffTheadCompile3.js b/js/src/jit-test/tests/gc/oomInOffTheadCompile3.js new file mode 100644 index 0000000000..6535676b72 --- /dev/null +++ b/js/src/jit-test/tests/gc/oomInOffTheadCompile3.js @@ -0,0 +1,17 @@ +// |jit-test| skip-if: !('oomTest' in this) || helperThreadCount() === 0 + +oomTest(() => { + offThreadCompileToStencil(` + function f(x) { + class of extends ("ABCDEFGHIJK") { + test() { return true; }; + static get() {}; + static get() {}; + } + return 1 + f(x - 1); + } + return g("try{}catch(e){}", n); + `); + var stencil = finishOffThreadStencil(); + evalStencil(stencil); +}); diff --git a/js/src/jit-test/tests/gc/oomInParseAsmJS.js b/js/src/jit-test/tests/gc/oomInParseAsmJS.js new file mode 100644 index 0000000000..72216e1c2c --- /dev/null +++ b/js/src/jit-test/tests/gc/oomInParseAsmJS.js @@ -0,0 +1,16 @@ +// |jit-test| skip-if: !('oomTest' in this) + +function parseAsmJS() { + eval(`function m(stdlib) + { + "use asm"; + var abs = stdlib.Math.abs; + function f(d) + { + d = +d; + return (~~(5.0 - +abs(d)))|0; + } + return f; + }`); +} +oomTest(parseAsmJS); diff --git a/js/src/jit-test/tests/gc/oomInParseFunction.js b/js/src/jit-test/tests/gc/oomInParseFunction.js new file mode 100644 index 0000000000..b1c1bd6297 --- /dev/null +++ b/js/src/jit-test/tests/gc/oomInParseFunction.js @@ -0,0 +1,3 @@ +// |jit-test| skip-if: !('oomTest' in this) + +oomTest(() => eval("function f() {}")); diff --git a/js/src/jit-test/tests/gc/oomInRegExp.js b/js/src/jit-test/tests/gc/oomInRegExp.js new file mode 100644 index 0000000000..b58f0ac50d --- /dev/null +++ b/js/src/jit-test/tests/gc/oomInRegExp.js @@ -0,0 +1,5 @@ +// |jit-test| skip-if: !('oomTest' in this) + +oomTest(() => assertEq("foobar\xff5baz\u1200".search(/bar\u0178\d/i), 3)); +oomTest(() => assertEq((/(?!(?!(?!6)[\Wc]))/i).test(), false)); +oomTest(() => assertEq((/bar\u0178\d/i).exec("foobar\xff5baz\u1200") != null, true)); diff --git a/js/src/jit-test/tests/gc/oomInRegExp2.js b/js/src/jit-test/tests/gc/oomInRegExp2.js new file mode 100644 index 0000000000..c35075b375 --- /dev/null +++ b/js/src/jit-test/tests/gc/oomInRegExp2.js @@ -0,0 +1,5 @@ +// |jit-test| skip-if: !('oomTest' in this) + +oomTest(() => assertEq("foobar\xff5baz\u1200".search(/bar\u0178\d/i), 3), {keepFailing: true}); +oomTest(() => assertEq((/(?!(?!(?!6)[\Wc]))/i).test(), false), {keepFailing: true}); +oomTest(() => assertEq((/bar\u0178\d/i).exec("foobar\xff5baz\u1200") != null, true), {keepFailing: true}); diff --git a/js/src/jit-test/tests/gc/oomInRegExpAlternativeGeneration.js b/js/src/jit-test/tests/gc/oomInRegExpAlternativeGeneration.js new file mode 100644 index 0000000000..b2a8ea7588 --- /dev/null +++ b/js/src/jit-test/tests/gc/oomInRegExpAlternativeGeneration.js @@ -0,0 +1,16 @@ +// |jit-test| allow-oom; allow-unhandlable-oom +// Bug 1234402 +// Unhandlable OOM in AlternativeGeneration::AlternativeGeneration. + +if (typeof oomAfterAllocations == "function" && helperThreadCount() > 0) { + offThreadCompileToStencil(` +[null, "", ""].forEach(function(locales) { +try { +Intl.NumberFormat(locales) +} catch (e) {} +oomAfterAllocations(100); +}) +`); + var stencil = finishOffThreadStencil(); + evalStencil(stencil); +} diff --git a/js/src/jit-test/tests/gc/oomInWeakMap.js b/js/src/jit-test/tests/gc/oomInWeakMap.js new file mode 100644 index 0000000000..522dc24738 --- /dev/null +++ b/js/src/jit-test/tests/gc/oomInWeakMap.js @@ -0,0 +1,6 @@ +// |jit-test| skip-if: !('oomTest' in this) + +oomTest(function () { + eval(`var wm = new WeakMap(); + wm.set({}, 'FOO').get(false);`); +}); diff --git a/js/src/jit-test/tests/gc/pretenure-array-long-lived.js b/js/src/jit-test/tests/gc/pretenure-array-long-lived.js new file mode 100644 index 0000000000..77ea7c0efc --- /dev/null +++ b/js/src/jit-test/tests/gc/pretenure-array-long-lived.js @@ -0,0 +1,17 @@ +// Allocate many long-lived objects and check that we pretenure them. + +load(libdir + "pretenure.js"); + +setupPretenureTest(); + +allocateArrays(nurseryCount, true); // Warm up. + +let { minor, major } = runTestAndCountCollections( + () => allocateArrays(tenuredCount, true) +); + +// Check that after the warm up period we 'only' do major GCs (we allow one +// nursery collection). +print(`${minor} minor GCs, ${major} major GCs`); +assertEq(minor <= 1, true); +assertEq(major >= 5, true); diff --git a/js/src/jit-test/tests/gc/pretenure-array-long-then-short-lived.js b/js/src/jit-test/tests/gc/pretenure-array-long-then-short-lived.js new file mode 100644 index 0000000000..3f7b30507b --- /dev/null +++ b/js/src/jit-test/tests/gc/pretenure-array-long-then-short-lived.js @@ -0,0 +1,28 @@ +// Allocate many objects, changing the lifetime from long-lived to short lived +// and check that we recover. + +load(libdir + "pretenure.js"); + +setupPretenureTest(); + +// Phase 1: long lived. + +allocateArrays(nurseryCount, true); +let { minor, major } = runTestAndCountCollections( + () => allocateArrays(tenuredCount, true) +); + +print(`${minor} minor GCs, ${major} major GCs`); +assertEq(minor <= 1, true); +assertEq(major >= 5, true); + +// Phase 2: short lived. + +allocateArrays(tenuredCount, false); +({ minor, major } = runTestAndCountCollections( + () => allocateArrays(nurseryCount * 5, false) +)); + +print(`${minor} minor GCs, ${major} major GCs`); +assertEq(minor >= 5, true); +assertEq(major == 0, true); diff --git a/js/src/jit-test/tests/gc/pretenure-array-short-lived.js b/js/src/jit-test/tests/gc/pretenure-array-short-lived.js new file mode 100644 index 0000000000..6a7c814c02 --- /dev/null +++ b/js/src/jit-test/tests/gc/pretenure-array-short-lived.js @@ -0,0 +1,16 @@ +// Allocate many short-lived objects and check that we don't pretenure them. + +load(libdir + "pretenure.js"); + +setupPretenureTest(); + +allocateArrays(nurseryCount, false); // Warm up. + +let { minor, major } = runTestAndCountCollections( + () => allocateArrays(nurseryCount * 5, false) +); + +// Check that after the warm up period we only do minor GCs. +print(`${minor} minor GCs, ${major} major GCs`); +assertEq(minor >= 5, true); +assertEq(major == 0, true); diff --git a/js/src/jit-test/tests/gc/pretenure-array-short-then-long-lived.js b/js/src/jit-test/tests/gc/pretenure-array-short-then-long-lived.js new file mode 100644 index 0000000000..1cef28c89c --- /dev/null +++ b/js/src/jit-test/tests/gc/pretenure-array-short-then-long-lived.js @@ -0,0 +1,28 @@ +// Allocate many objects, changing the lifetime from short-lived to long-lived +// and check that we recover. + +load(libdir + "pretenure.js"); + +setupPretenureTest(); + +// Phase 1: short lived. + +allocateArrays(nurseryCount, false); +let { minor, major } = runTestAndCountCollections( + () => allocateArrays(tenuredCount, false) +); + +print(`${minor} minor GCs, ${major} major GCs`); +assertEq(minor >= 5, true); +assertEq(major == 0, true); + +// Phase 2: long lived. + +allocateArrays(tenuredCount, true); +({ minor, major } = runTestAndCountCollections( + () => allocateArrays(tenuredCount * 5, true) +)); + +print(`${minor} minor GCs, ${major} major GCs`); +assertEq(minor <= 1, true); +assertEq(major >= 5, true); diff --git a/js/src/jit-test/tests/gc/pretenure-object-long-lived.js b/js/src/jit-test/tests/gc/pretenure-object-long-lived.js new file mode 100644 index 0000000000..4b741e3ebe --- /dev/null +++ b/js/src/jit-test/tests/gc/pretenure-object-long-lived.js @@ -0,0 +1,17 @@ +// Allocate many long-lived objects and check that we pretenure them. + +load(libdir + "pretenure.js"); + +setupPretenureTest(); + +allocateObjects(nurseryCount, true); // Warm up. + +let { minor, major } = runTestAndCountCollections( + () => allocateObjects(tenuredCount, true) +); + +// Check that after the warm up period we 'only' do major GCs (we allow one +// nursery collection). +print(`${minor} minor GCs, ${major} major GCs`); +assertEq(minor <= 1, true); +assertEq(major >= 5, true); diff --git a/js/src/jit-test/tests/gc/pretenure-object-long-then-short-lived.js b/js/src/jit-test/tests/gc/pretenure-object-long-then-short-lived.js new file mode 100644 index 0000000000..280ce3a5bd --- /dev/null +++ b/js/src/jit-test/tests/gc/pretenure-object-long-then-short-lived.js @@ -0,0 +1,28 @@ +// Allocate many objects, changing the lifetime from long-lived to short lived +// and check that we recover. + +load(libdir + "pretenure.js"); + +setupPretenureTest(); + +// Phase 1: long lived. + +allocateObjects(nurseryCount, true); +let { minor, major } = runTestAndCountCollections( + () => allocateObjects(tenuredCount, true) +); + +print(`${minor} minor GCs, ${major} major GCs`); +assertEq(minor <= 1, true); +assertEq(major >= 5, true); + +// Phase 2: short lived. + +allocateObjects(tenuredCount, false); +({ minor, major } = runTestAndCountCollections( + () => allocateObjects(nurseryCount * 5, false) +)); + +print(`${minor} minor GCs, ${major} major GCs`); +assertEq(minor >= 5, true); +assertEq(major == 0, true); diff --git a/js/src/jit-test/tests/gc/pretenure-object-short-lived.js b/js/src/jit-test/tests/gc/pretenure-object-short-lived.js new file mode 100644 index 0000000000..0e7dc3b65b --- /dev/null +++ b/js/src/jit-test/tests/gc/pretenure-object-short-lived.js @@ -0,0 +1,16 @@ +// Allocate many short-lived objects and check that we don't pretenure them. + +load(libdir + "pretenure.js"); + +setupPretenureTest(); + +allocateObjects(nurseryCount, false); // Warm up. + +let { minor, major } = runTestAndCountCollections( + () => allocateObjects(nurseryCount * 5, false) +); + +// Check that after the warm up period we only do minor GCs. +print(`${minor} minor GCs, ${major} major GCs`); +assertEq(minor >= 5, true); +assertEq(major == 0, true); diff --git a/js/src/jit-test/tests/gc/pretenure-object-short-then-long-lived.js b/js/src/jit-test/tests/gc/pretenure-object-short-then-long-lived.js new file mode 100644 index 0000000000..0de2440594 --- /dev/null +++ b/js/src/jit-test/tests/gc/pretenure-object-short-then-long-lived.js @@ -0,0 +1,28 @@ +// Allocate many objects, changing the lifetime from short-lived to long-lived +// and check that we recover. + +load(libdir + "pretenure.js"); + +setupPretenureTest(); + +// Phase 1: short lived. + +allocateObjects(nurseryCount, false); +let { minor, major } = runTestAndCountCollections( + () => allocateObjects(tenuredCount, false) +); + +print(`${minor} minor GCs, ${major} major GCs`); +assertEq(minor >= 5, true); +assertEq(major == 0, true); + +// Phase 2: long lived. + +allocateObjects(tenuredCount, true); +({ minor, major } = runTestAndCountCollections( + () => allocateObjects(tenuredCount * 5, true) +)); + +print(`${minor} minor GCs, ${major} major GCs`); +assertEq(minor <= 1, true); +assertEq(major >= 5, true); diff --git a/js/src/jit-test/tests/gc/pretenuring.js b/js/src/jit-test/tests/gc/pretenuring.js new file mode 100644 index 0000000000..9c13954d3d --- /dev/null +++ b/js/src/jit-test/tests/gc/pretenuring.js @@ -0,0 +1,42 @@ +// Test nursery string allocation and pretenuring. + +gczeal(0); + +gcparam("minNurseryBytes", 4096 * 1024); +gcparam("maxNurseryBytes", 4096 * 1024); +gc(); + +// String allocation in the nursery is initially enabled. +assertEq(nurseryStringsEnabled(), true); + +// Literal strings are atoms (which are always tenured). +assertEq(isNurseryAllocated("foo"), false); + +// The result of Number.toString is nursery allocated. +assertEq(isNurseryAllocated((1234).toString()), true); + +// Ropes are nursery allocated. +let s = "bar"; +assertEq(isNurseryAllocated("foo" + s), true); + +// Dependent strings are nursery allocated. +assertEq(isNurseryAllocated("foobar".substr(1)), true); + +// The testing function 'newString' allows control over which heap is used. +assertEq(isNurseryAllocated(newString("foobar", { tenured: true })), false); +assertEq(isNurseryAllocated(newString("foobar", { tenured: false })), true); + +// Allocating lots of strings which survive nursery collection disables +// allocating strings in the nursery. +let a = []; +for (let i = 1; i < 500000; i++) { + a.push(i.toString()); +} +gc(); +assertEq(nurseryStringsEnabled(), false); + +// When a large number of strings are collected by major GC nursery allocation +// is enabled again. +a = undefined; +gc(); +assertEq(nurseryStringsEnabled(), true); diff --git a/js/src/jit-test/tests/gc/regress-1711413.js b/js/src/jit-test/tests/gc/regress-1711413.js new file mode 100644 index 0000000000..8261c47f7f --- /dev/null +++ b/js/src/jit-test/tests/gc/regress-1711413.js @@ -0,0 +1,6 @@ +// Required --no-threads to crash. + +enableShellAllocationMetadataBuilder(); +newGlobal({newCompartment:true}); +gcslice(1); +nukeAllCCWs(); diff --git a/js/src/jit-test/tests/gc/str-atom-dedupe.js b/js/src/jit-test/tests/gc/str-atom-dedupe.js new file mode 100644 index 0000000000..587f879db5 --- /dev/null +++ b/js/src/jit-test/tests/gc/str-atom-dedupe.js @@ -0,0 +1,16 @@ +// Create Latin1 string + atom. +var latin1S = "foo".repeat(50); +var obj = {}; +obj[latin1S] = 3; +assertEq(obj[latin1S], 3); + +// Create a TwoByte version, ensure it's in the StringToAtomCache. +var twoByteS = newString(latin1S, {twoByte: true}); +assertEq(obj[twoByteS], 3); + +// Create a dependent TwoByte string. +var depTwoByteS = twoByteS.slice(1); + +// Deduplication shouldn't get confused about Latin1 atom vs TwoByte strings. +minorgc(); +assertEq(obj["f" + depTwoByteS], 3); diff --git a/js/src/jit-test/tests/gc/test-root-arrays.js b/js/src/jit-test/tests/gc/test-root-arrays.js new file mode 100644 index 0000000000..2c6015f19c --- /dev/null +++ b/js/src/jit-test/tests/gc/test-root-arrays.js @@ -0,0 +1,10 @@ +gczeal(0); +let objects = [{}, {}, {}]; +blackRoot()[0] = objects[0]; +grayRoot()[0] = objects[1]; +addMarkObservers(objects); +objects = undefined; +gc(); +assertEq(getMarks()[0], "black"); +assertEq(getMarks()[1], "gray"); +assertEq(getMarks()[2], "dead"); diff --git a/js/src/jit-test/tests/gc/weak-marking-01.js b/js/src/jit-test/tests/gc/weak-marking-01.js new file mode 100644 index 0000000000..a132398510 --- /dev/null +++ b/js/src/jit-test/tests/gc/weak-marking-01.js @@ -0,0 +1,220 @@ +// These tests will be using object literals as keys, and we want some of them +// to be dead after being inserted into a WeakMap. That means we must wrap +// everything in functions because it seems like the toplevel script hangs onto +// its object literals. + +gczeal(0); +gcparam('minNurseryBytes', 1024 * 1024); +gcparam('maxNurseryBytes', 1024 * 1024); + +function startIncrementalGC() { + startgc(1, 'shrinking'); + while (gcstate() === "Prepare") { + gcslice(1); + } +} + +function syncIncrementalGC() { + startIncrementalGC(); + finishgc(); +} + +// All reachable keys should be found, and the rest should be swept. +function basicSweeping() { + gc(); + + var wm1 = new WeakMap(); + wm1.set({'name': 'obj1'}, {'name': 'val1'}); + var hold = {'name': 'obj2'}; + wm1.set(hold, {'name': 'val2'}); + wm1.set({'name': 'obj3'}, {'name': 'val3'}); + + syncIncrementalGC(); + + assertEq(wm1.get(hold).name, 'val2'); + assertEq(nondeterministicGetWeakMapKeys(wm1).length, 1); +} + +basicSweeping(); + +// Keep values alive even when they are only referenced by (live) WeakMap values. +function weakGraph() { + gc(); + + var wm1 = new WeakMap(); + var obj1 = {'name': 'obj1'}; + var obj2 = {'name': 'obj2'}; + var obj3 = {'name': 'obj3'}; + var obj4 = {'name': 'obj4'}; + var clear = {'name': ''}; // Make the interpreter forget about the last obj created + + wm1.set(obj2, obj3); + wm1.set(obj3, obj1); + wm1.set(obj4, obj1); // This edge will be cleared + obj1 = obj3 = obj4 = undefined; + + syncIncrementalGC(); + + assertEq(obj2.name, "obj2"); + assertEq(wm1.get(obj2).name, "obj3"); + assertEq(wm1.get(wm1.get(obj2)).name, "obj1"); + print(nondeterministicGetWeakMapKeys(wm1).map(o => o.name).join(",")); + assertEq(nondeterministicGetWeakMapKeys(wm1).length, 2); +} + +weakGraph(); + +// ...but the weakmap itself has to stay alive, too. +function deadWeakMap() { + gc(); + + var wm1 = new WeakMap(); + var obj1 = makeFinalizeObserver(); + var obj2 = {'name': 'obj2'}; + var obj3 = {'name': 'obj3'}; + var obj4 = {'name': 'obj4'}; + var clear = {'name': ''}; // Make the interpreter forget about the last obj created + + wm1.set(obj2, obj3); + wm1.set(obj3, obj1); + wm1.set(obj4, obj1); // This edge will be cleared + var initialCount = finalizeCount(); + obj1 = obj3 = obj4 = undefined; + wm1 = undefined; + + syncIncrementalGC(); + + assertEq(obj2.name, "obj2"); + assertEq(finalizeCount(), initialCount + 1); +} + +deadWeakMap(); + +// WeakMaps do not strongly reference their keys or values. (WeakMaps hold a +// collection of (strong) references to *edges* from keys to values. If the +// WeakMap is not live, then its edges are of course not live either. An edge +// holds neither its key nor its value live; it just holds a strong ref from +// the key to the value. So if the key is live, the value is live too, but the +// edge itself has no references to anything.) +function deadKeys() { + gc(); + + var wm1 = new WeakMap(); + var obj1 = makeFinalizeObserver(); + var obj2 = {'name': 'obj2'}; + var obj3 = makeFinalizeObserver(); + var clear = {}; // Make the interpreter forget about the last obj created + + wm1.set(obj1, obj1); + wm1.set(obj3, obj2); + obj1 = obj3 = undefined; + var initialCount = finalizeCount(); + + syncIncrementalGC(); + + assertEq(finalizeCount(), initialCount + 2); + assertEq(nondeterministicGetWeakMapKeys(wm1).length, 0); +} + +deadKeys(); + +// The weakKeys table has to grow if it encounters enough new unmarked weakmap +// keys. Trigger this to happen during weakmap marking. +// +// There's some trickiness involved in getting it to test the right thing, +// because if a key is marked before the weakmap, then it won't get entered +// into the weakKeys table. This chains through multiple weakmap layers to +// ensure that the objects can't get marked before the weakmaps. +function weakKeysRealloc() { + gc(); + + var wm1 = new WeakMap; + var wm2 = new WeakMap; + var wm3 = new WeakMap; + var obj1 = {'name': 'obj1'}; + var obj2 = {'name': 'obj2'}; + wm1.set(obj1, wm2); + wm2.set(obj2, wm3); + for (var i = 0; i < 10000; i++) { + wm3.set(Object.create(null), wm2); + } + wm3.set(Object.create(null), makeFinalizeObserver()); + + wm2 = undefined; + wm3 = undefined; + obj2 = undefined; + + var initialCount = finalizeCount(); + syncIncrementalGC(); + assertEq(finalizeCount(), initialCount + 1); +} + +weakKeysRealloc(); + +// The weakKeys table is populated during regular marking. When a key is later +// deleted, both it and its delegate should be removed from weakKeys. +// Otherwise, it will hold its value live if it gets marked, and our table +// traversals will include non-keys, etc. +function deletedKeys() { + gc(); + + var wm = new WeakMap; + var g = newGlobal(); + + for (var i = 0; i < 1000; i++) + wm.set(g.Object.create(null), i); + + startIncrementalGC(); + + for (var key of nondeterministicGetWeakMapKeys(wm)) { + if (wm.get(key) % 2) + wm.delete(key); + } + + gc(); +} + +deletedKeys(); + +// Test adding keys during incremental GC. +function incrementalAdds() { + gc(); + + var initialCount = finalizeCount(); + + var wm1 = new WeakMap; + var wm2 = new WeakMap; + var wm3 = new WeakMap; + var obj1 = {'name': 'obj1'}; + var obj2 = {'name': 'obj2'}; + wm1.set(obj1, wm2); + wm2.set(obj2, wm3); + for (var i = 0; i < 10000; i++) { + wm3.set(Object.create(null), wm2); + } + wm3.set(Object.create(null), makeFinalizeObserver()); + obj2 = undefined; + + var obj3 = []; + startIncrementalGC(); + var M = 10; + var N = 800; + for (var j = 0; j < M; j++) { + for (var i = 0; i < N; i++) + wm3.set(Object.create(null), makeFinalizeObserver()); // Should be swept + for (var i = 0; i < N; i++) { + obj3.push({'name': 'obj3'}); + wm1.set(obj3[obj3.length - 1], makeFinalizeObserver()); // Should not be swept + } + gcslice(); + } + + wm2 = undefined; + wm3 = undefined; + + gc(); + print("initialCount = " + initialCount); + assertEq(finalizeCount(), initialCount + 1 + M * N); +} + +incrementalAdds(); diff --git a/js/src/jit-test/tests/gc/weak-marking-02.js b/js/src/jit-test/tests/gc/weak-marking-02.js new file mode 100644 index 0000000000..b29da74496 --- /dev/null +++ b/js/src/jit-test/tests/gc/weak-marking-02.js @@ -0,0 +1,338 @@ +// |jit-test| allow-unhandlable-oom + +// These tests will be using object literals as keys, and we want some of them +// to be dead after being inserted into a WeakMap. That means we must wrap +// everything in functions because it seems like the toplevel script hangs onto +// its object literals. + +// Cross-compartment WeakMap keys work by storing a cross-compartment wrapper +// in the WeakMap, and the actual "delegate" object in the target compartment +// is the thing whose liveness is checked. + +gczeal(0); + +var g2 = newGlobal({newCompartment: true}); +g2.eval('function genObj(name) { return {"name": name} }'); + +function basicSweeping() { + var wm1 = new WeakMap(); + wm1.set({'name': 'obj1'}, {'name': 'val1'}); + var hold = g2.genObj('obj2'); + wm1.set(hold, {'name': 'val2'}); + wm1.set({'name': 'obj3'}, {'name': 'val3'}); + var obj4 = g2.genObj('obj4'); + wm1.set(obj4, {'name': 'val3'}); + obj4 = undefined; + + startgc(100000, 'shrinking'); + gcslice(); + assertEq(wm1.get(hold).name, 'val2'); + assertEq(nondeterministicGetWeakMapKeys(wm1).length, 1); +} + +basicSweeping(); + +// Same, but behind an additional WM layer, to avoid ordering problems (not +// that I've checked that basicSweeping even has any problems.) + +function basicSweeping2() { + var wm1 = new WeakMap(); + wm1.set({'name': 'obj1'}, {'name': 'val1'}); + var hold = g2.genObj('obj2'); + wm1.set(hold, {'name': 'val2'}); + wm1.set({'name': 'obj3'}, {'name': 'val3'}); + var obj4 = g2.genObj('obj4'); + wm1.set(obj4, {'name': 'val3'}); + obj4 = undefined; + + var base1 = {'name': 'base1'}; + var base2 = {'name': 'base2'}; + var wm_base1 = new WeakMap(); + var wm_base2 = new WeakMap(); + wm_base1.set(base1, wm_base2); + wm_base2.set(base2, wm1); + wm1 = wm_base2 = undefined; + + startgc(100000, 'shrinking'); + gcslice(); + + assertEq(nondeterministicGetWeakMapKeys(wm_base1).length, 1); + wm_base2 = wm_base1.get(base1); + assertEq(nondeterministicGetWeakMapKeys(wm_base2).length, 1); + assertEq(nondeterministicGetWeakMapKeys(wm_base1)[0], base1); + assertEq(nondeterministicGetWeakMapKeys(wm_base2)[0], base2); + wm_base2 = wm_base1.get(base1); + wm1 = wm_base2.get(base2); + assertEq(wm1.get(hold).name, 'val2'); + assertEq(nondeterministicGetWeakMapKeys(wm1).length, 1); +} + +basicSweeping2(); + +// Scatter the weakmap, the keys, and the values among different compartments. + +function tripleZoneMarking() { + var g1 = newGlobal({newCompartment: true}); + var g2 = newGlobal({newCompartment: true}); + var g3 = newGlobal({newCompartment: true}); + + var wm = g1.eval("new WeakMap()"); + var key = g2.eval("({'name': 'obj1'})"); + var value = g3.eval("({'name': 'val1'})"); + g1 = g2 = g3 = undefined; + wm.set(key, value); + + // Make all of it only reachable via a weakmap in the main test compartment, + // so that all of this happens during weak marking mode. Use the weakmap as + // its own key, so we know that the weakmap will get traced before the key + // and therefore will populate the weakKeys table and all of that jazz. + var base_wm = new WeakMap(); + base_wm.set(base_wm, [ wm, key ]); + + wm = key = value = undefined; + + startgc(100000, 'shrinking'); + gcslice(); + + var keys = nondeterministicGetWeakMapKeys(base_wm); + assertEq(keys.length, 1); + var [ wm, key ] = base_wm.get(keys[0]); + assertEq(key.name, "obj1"); + value = wm.get(key); + assertEq(value.name, "val1"); +} + +tripleZoneMarking(); + +// Same as above, but this time use enqueueMark to enforce ordering. + +function tripleZoneMarking2() { + var g1 = newGlobal(); + var g2 = newGlobal(); + var g3 = newGlobal(); + + var wm = g1.eval("wm = new WeakMap()"); + var key = g2.eval("key = ({'name': 'obj1'})"); + var value = g3.eval("({'name': 'val1'})"); + wm.set(key, value); + + enqueueMark("enter-weak-marking-mode"); + g1.eval("enqueueMark(wm)"); // weakmap + g2.eval("enqueueMark(key)"); // delegate + g1.wm = g2.key = undefined; + g1 = g2 = g3 = undefined; + wm = key = value = undefined; + + gc(); + + var [ dummy, weakmap, keywrapper ] = getMarkQueue(); + assertEq(keywrapper.name, "obj1"); + value = weakmap.get(keywrapper); + assertEq(value.name, "val1"); + + clearMarkQueue(); +} + +if (this.enqueueMark) + tripleZoneMarking2(); + +function enbugger() { + var g = newGlobal({newCompartment: true}); + var dbg = new Debugger; + g.eval("function debuggee_f() { return 1; }"); + g.eval("function debuggee_g() { return 1; }"); + dbg.addDebuggee(g); + var [ s ] = dbg.findScripts({global: g}).filter(s => s.displayName == "debuggee_f"); + var [ s2 ] = dbg.findScripts({global: g}).filter(s => s.displayName == "debuggee_g"); + g.eval("debuggee_f = null"); + gc(); + dbg.removeAllDebuggees(); + gc(); + assertEq(s.displayName, "debuggee_f"); + + var wm = new WeakMap; + var obj = Object.create(null); + var obj2 = Object.create(null); + wm.set(obj, s); + wm.set(obj2, obj); + wm.set(s2, obj2); + s = s2 = obj = obj2 = null; + + gc(); +} + +enbugger(); + +// Want to test: zone edges +// Make map with cross-zone delegate. Collect the zones one at a time. +function zone_edges() { + var g3 = newGlobal(); + g3.eval('function genObj(name) { return {"name": name} }'); + + var wm1 = new WeakMap(); + var hold = g2.genObj('obj1'); + var others = [g2.genObj('key2'), g2.genObj('key3')]; + wm1.set(hold, {'name': g3.genObj('val1'), 'others': others}); + others = null; + + var m = new Map; + m.set(m, hold); + hold = null; + + const zones = [ this, g2, g3 ]; + for (let zonebits = 0; zonebits < 2 ** zones.length; zonebits++) { + for (let z in zones) { + if (zonebits & (1 << z)) + schedulezone(zones[z]); + } + startgc(1); + wm1.set(wm1.get(m.get(m)).others[0], g2.genObj('val2')); + gcslice(1000000); + wm1.set(wm1.get(m.get(m)).others[1], g2.genObj('val3')); + gc(); + assertEq(wm1.get(m.get(m)).name.name, 'val1'); + assertEq(wm1.get(m.get(m)).others[0].name, 'key2'); + assertEq(wm1.get(wm1.get(m.get(m)).others[0]).name, 'val2'); + assertEq(wm1.get(m.get(m)).others[1].name, 'key3'); + assertEq(wm1.get(wm1.get(m.get(m)).others[1]).name, 'val3'); + assertEq(nondeterministicGetWeakMapKeys(wm1).length, 3); + } + + // Do it again, with nuking. + const wm2 = g2.eval("new WeakMap"); + wm2.set(wm1.get(m.get(m)).others[0], Object.create(null)); + for (let zonebits = 0; zonebits < 2 ** zones.length; zonebits++) { + for (let z in zones) { + if (zonebits & (1 << z)) + schedulezone(zones[z]); + } + startgc(1); + wm1.set(wm1.get(m.get(m)).others[0], g2.genObj('val2')); + gcslice(1000000); + wm1.set(wm1.get(m.get(m)).others[1], g2.genObj('val3')); + nukeCCW(wm1.get(wm1.get(m.get(m)).others[0])); + nukeCCW(wm1.get(wm1.get(m.get(m)).others[1])); + gc(); + assertEq(wm1.get(m.get(m)).name.name, 'val1'); + assertEq(wm1.get(m.get(m)).others[0].name, 'key2'); + assertEq(wm1.get(m.get(m)).others[1].name, 'key3'); + assertEq(nondeterministicGetWeakMapKeys(wm1).length, 3); + } +} + +zone_edges(); + +// Stress test: lots of cross-zone and same-zone cross-compartment edges, and +// exercise the barriers. +function stress(opt) { + printErr(JSON.stringify(opt)); + + var g1 = this; + var g2 = newGlobal({sameZoneAs: g1}); + var g3 = newGlobal(); + + var globals = g1.globals = g2.globals = g3.globals = [ g1, g2, g3 ]; + g1.name = 'main'; + g2.name = 'same-zone'; + g3.name = 'other-zone'; + g1.names = g2.names = g3.names = [ g1.name, g2.name, g3.name ]; + + // Basic setup: + // + // Three different globals, each with a weakmap and an object. Each global's + // weakmap contains all 3 objects (1 from each global) as keys. Internally, + // that means that each weakmap will contain one local object and two + // cross-compartment wrappers. + // + // Now duplicate that 3 times. The first weakmap will be unmodified. The + // second weakmap will have its keys updated to different values. The third + // weakmap will have its keys deleted. + + for (const i in globals) { + const g = globals[i]; + g.eval('function genObj(name) { return {"name": name} }'); + g.eval("weakmap0 = new WeakMap()"); + g.eval("weakmap1 = new WeakMap()"); + g.eval("weakmap2 = new WeakMap()"); + g.eval(`obj = genObj('global-${names[i]}-object')`); + for (const j in [0, 1, 2]) { + g.eval(`weakmap${j}.set(genObj('global-${names[i]}-key}'), genObj("value"))`); + } + } + + for (const i in globals) { + const g = globals[i]; + for (const j in globals) { + for (const k in [0, 1, 2]) { + g.eval(`weakmap${k}.set(globals[${j}].obj, genObj('value-${i}-${j}'))`); + } + } + } + + // Construct object keys to retrieve the weakmaps with. + for (const g of globals) { + g.eval(`plain = genObj("plain")`); + g.eval(`update = genObj("update")`); + g.eval(`remove = genObj("remove")`); + } + + // Put the weakmaps in another WeakMap. + for (const g of globals) { + g.eval(`weakmaps = new WeakMap(); + weakmaps.set(plain, weakmap0); + weakmaps.set(update, weakmap1); + weakmaps.set(remove, weakmap2);`); + } + + // Eliminate the edges from the global to the object being used as a key. But + // assuming we want the key to be live (nothing else points to it), hide it + // behind another weakmap layer. + for (const g of globals) { + if (opt.live) { + g.eval("keyholder = genObj('key-holder')"); + g.eval("weakmaps.set(keyholder, obj)"); + } + g.eval("obj = null"); + } + + // If we want a layer of indirection, remove the edges from the globals to + // their weakmaps. But note that the original purpose of this test *wants* + // the weakmaps themselves to be visited early, so that gcWeakKeys will be + // populated with not-yet-marked keys and the barriers will need to update + // entries there. + if (opt.indirect) { + for (const g of globals) { + g.eval("weakmap0 = weakmap1 = weakmap2 = null"); + } + } + + // Start an incremental GC. TODO: need a zeal mode to yield before entering + // weak marking mode. + startgc(1); + + // Do the mutations. + if (opt.live) { + for (const g of globals) { + g.eval("weakmaps.get(update).set(weakmaps.get(keyholder), genObj('val'))"); + g.eval("weakmaps.get(remove).delete(weakmaps.get(keyholder))"); + } + } + + if (opt.nuke) { + for (const g of globals) { + if (g.name != 'main') + g.eval("nukeAllCCWs()"); + } + } + + // Finish the GC. + gc(); +} + +for (const live of [true, false]) { + for (const indirect of [true, false]) { + for (const nuke of [true, false]) { + stress({live, indirect, nuke}); + } + } +} diff --git a/js/src/jit-test/tests/gc/weak-marking-03.js b/js/src/jit-test/tests/gc/weak-marking-03.js new file mode 100644 index 0000000000..f1f7c85c1b --- /dev/null +++ b/js/src/jit-test/tests/gc/weak-marking-03.js @@ -0,0 +1,696 @@ +// |jit-test| allow-unhandlable-oom + +// weakmap marking tests that use the testing mark queue to force an ordering +// of marking. + +// We are carefully controlling the sequence of GC events. +gczeal(0); + +// If a command-line parameter is given, use it as a substring restriction on +// the tests to run. +var testRestriction = scriptArgs[0]; +printErr(`testRestriction is ${testRestriction || '(run all tests)'}`); + +function runtest(func) { + if (testRestriction && ! func.name.includes(testRestriction)) { + print("\Skipping " + func.name); + } else { + print("\nRunning " + func.name); + func(); + } +} + +function reportMarks(prefix = "") { + const marks = getMarks(); + const current = currentgc(); + const markstr = marks.join("/"); + print(`${prefix}[${current.incrementalState}/${current.sweepGroup}@${current.queuePos}] ${markstr}`); + return markstr; +} + +function startGCMarking() { + startgc(100000); + while (gcstate() === "Prepare") { + gcslice(100000); + } +} + +function purgeKey() { + const m = new WeakMap(); + const vals = {}; + vals.key = Object.create(null); + vals.val = Object.create(null); + m.set(vals.key, vals.val); + + minorgc(); + + addMarkObservers([m, vals.key, vals.val]); + + enqueueMark(m); + enqueueMark("yield"); + + enqueueMark(vals.key); + enqueueMark("yield"); + + vals.key = vals.val = null; + + startGCMarking(); + // getMarks() returns map/key/value + assertEq(getMarks().join("/"), "black/unmarked/unmarked", + "marked the map black"); + + gcslice(100000); + assertEq(getMarks().join("/"), "black/black/unmarked", + "key is now marked"); + + // Trigger purgeWeakKey: the key is in weakkeys (because it was unmarked when + // the map was marked), and now we're removing it. + m.delete(nondeterministicGetWeakMapKeys(m)[0]); + + finishgc(); // Finish the GC + assertEq(getMarks().join("/"), "black/black/black", + "at end, value is marked too"); + + clearMarkQueue(); + clearMarkObservers(); +} + +if (this.enqueueMark) + runtest(purgeKey); + +function removeKey() { + reportMarks("removeKey start: "); + + const m = new WeakMap(); + const vals = {}; + vals.key = Object.create(null); + vals.val = Object.create(null); + m.set(vals.key, vals.val); + + minorgc(); + + addMarkObservers([m, vals.key, vals.val]); + + enqueueMark(m); + enqueueMark("yield"); + + startGCMarking(); + reportMarks("first: "); + var marks = getMarks(); + assertEq(marks[0], "black", "map is black"); + assertEq(marks[1], "unmarked", "key not marked yet"); + assertEq(marks[2], "unmarked", "value not marked yet"); + m.delete(vals.key); + + finishgc(); // Finish the GC + reportMarks("done: "); + marks = getMarks(); + assertEq(marks[0], "black", "map is black"); + assertEq(marks[1], "black", "key is black"); + assertEq(marks[2], "black", "value is black"); + + // Do it again, but this time, remove all other roots. + m.set(vals.key, vals.val); + vals.key = vals.val = null; + startgc(10000); + while (gcstate() !== "Mark") { + gcslice(100000); + } + marks = getMarks(); + assertEq(marks[0], "black", "map is black"); + assertEq(marks[1], "unmarked", "key not marked yet"); + assertEq(marks[2], "unmarked", "value not marked yet"); + + // This was meant to test the weakmap deletion barrier, which would remove + // the key from weakkeys. Unfortunately, JS-exposed WeakMaps now have a read + // barrier on lookup that marks the key, and deletion requires a lookup. + m.delete(nondeterministicGetWeakMapKeys(m)[0]); + + finishgc(); + marks = getMarks(); + assertEq(marks[0], "black", "map is black"); + assertEq(marks[1], "black", "key was blackened by lookup read barrier during deletion"); + assertEq(marks[2], "black", "value is black because map and key are black"); + + clearMarkQueue(); + clearMarkObservers(); +} + +if (this.enqueueMark) + runtest(removeKey); + +// Test: +// 1. mark the map +// - that inserts the delegate into weakKeys +// 2. nuke the CCW key +// - removes the delegate from weakKeys +// 3. mark the key +// 4. enter weak marking mode +// +// The problem it's attempting to recreate is that entering weak marking mode +// will no longer mark the value, because there's no delegate to trigger it, +// and the key was not added to weakKeys (because at the time the map was +// scanned, the key had a delegate, so it was added to weakKeys instead.) +function nukeMarking() { + const g1 = newGlobal({newCompartment: true}); + + const vals = {}; + vals.map = new WeakMap(); + vals.key = g1.eval("Object.create(null)"); + vals.val = Object.create(null); + vals.map.set(vals.key, vals.val); + vals.val = null; + gc(); + + // Set up the sequence of marking events. + enqueueMark(vals.map); + enqueueMark("yield"); + // We will nuke the key's delegate here. + enqueueMark(vals.key); + enqueueMark("enter-weak-marking-mode"); + + // Okay, run through the GC now. + startgc(1000000); + while (gcstate() !== "Mark") { + gcslice(100000); + } + assertEq(gcstate(), "Mark", "expected to yield after marking map"); + // We should have marked the map and then yielded back here. + nukeCCW(vals.key); + // Finish up the GC. + gcslice(); + + clearMarkQueue(); +} + +if (this.enqueueMark) + runtest(nukeMarking); + +// Similar to the above, but trying to get a different failure: +// - start marking +// - find a map, add its key to ephemeronEdges +// - nuke the key (and all other CCWs between the key -> delegate zones) +// - when sweeping, we will no longer have any edges between the key +// and delegate zones. So they will be placed in separate sweep groups. +// - for this test, the delegate zone must be swept after the key zone +// - make sure we don't try to mark back in the key zone (due to an +// ephemeron edge) while sweeping the delegate zone. In a DEBUG build, +// this would assert. +function nukeMarkingSweepGroups() { + // Create g1 before host, because that will result in the right zone + // ordering to trigger the bug. + const g1 = newGlobal({newCompartment: true}); + const host = newGlobal({newCompartment: true}); + host.g1 = g1; + host.eval(` + const vals = {}; + vals.map = new WeakMap(); + vals.key = g1.eval("Object.create(null)"); + vals.val = Object.create(null); + vals.map.set(vals.key, vals.val); + vals.val = null; + gc(); + + // Set up the sequence of marking events. + enqueueMark(vals.map); + enqueueMark("yield"); + // We will nuke the key's delegate here. + enqueueMark(vals.key); + enqueueMark("enter-weak-marking-mode"); + + // Okay, run through the GC now. + startgc(1); + while (gcstate() === "Prepare") { + gcslice(100000); + } + assertEq(gcstate(), "Mark", "expected to yield after marking map"); + // We should have marked the map and then yielded back here. + nukeAllCCWs(); + // Finish up the GC. + while (gcstate() === "Mark") { + gcslice(1000); + } + gcslice(); + + clearMarkQueue(); + `); +} + +if (this.enqueueMark) + runtest(nukeMarkingSweepGroups); + +function transplantMarking() { + const g1 = newGlobal({newCompartment: true}); + + const vals = {}; + vals.map = new WeakMap(); + let {object, transplant} = transplantableObject(); + vals.key = object; + object = null; + vals.val = Object.create(null); + vals.map.set(vals.key, vals.val); + vals.val = null; + gc(); + + // Set up the sequence of marking events. + enqueueMark(vals.map); + enqueueMark("yield"); + // We will transplant the key here. + enqueueMark(vals.key); + enqueueMark("enter-weak-marking-mode"); + + // Okay, run through the GC now. + startgc(1000000); + while (gcstate() !== "Mark") { + gcslice(100000); + } + assertEq(gcstate(), "Mark", "expected to yield after marking map"); + // We should have marked the map and then yielded back here. + transplant(g1); + // Finish up the GC. + gcslice(); + + clearMarkQueue(); +} + +if (this.enqueueMark) + runtest(transplantMarking); + +// 1. Mark the map +// => add delegate to weakKeys +// 2. Mark the delegate black +// => do nothing because we are not in weak marking mode +// 3. Mark the key gray +// => mark value gray, not that we really care +// 4. Enter weak marking mode +// => black delegate darkens the key from gray to black +function grayMarkingMapFirst() { + const g = newGlobal({newCompartment: true}); + const vals = {}; + vals.map = new WeakMap(); + vals.key = g.eval("Object.create(null)"); + vals.val = Object.create(null); + vals.map.set(vals.key, vals.val); + + g.delegate = vals.key; + g.eval("dummy = Object.create(null)"); + g.eval("grayRoot().push(delegate, dummy)"); + addMarkObservers([vals.map, vals.key]); + g.addMarkObservers([vals.key, g.dummy]); + addMarkObservers([vals.val]); + + gc(); + + enqueueMark(vals.map); + enqueueMark("yield"); // checkpoint 1 + + g.enqueueMark(vals.key); + enqueueMark("yield"); // checkpoint 2 + + vals.val = null; + vals.key = null; + g.delegate = null; + g.dummy = null; + + const showmarks = () => { + print("[map,key,delegate,graydummy,value] marked " + JSON.stringify(getMarks())); + }; + + print("Starting incremental GC"); + startGCMarking(); + // Checkpoint 1, after marking map + showmarks(); + var marks = getMarks(); + assertEq(marks[0], "black", "map is black"); + assertEq(marks[1], "unmarked", "key is not marked yet"); + assertEq(marks[2], "unmarked", "delegate is not marked yet"); + + gcslice(100000); + // Checkpoint 2, after marking delegate + showmarks(); + marks = getMarks(); + assertEq(marks[0], "black", "map is black"); + assertEq(marks[1], "unmarked", "key is not marked yet"); + assertEq(marks[2], "black", "delegate is black"); + + gcslice(); + // GC complete. Key was marked black (b/c of delegate), then gray marking saw + // it was already black and skipped it. + showmarks(); + marks = getMarks(); + assertEq(marks[0], "black", "map is black"); + assertEq(marks[1], "black", "delegate marked key black because of weakmap"); + assertEq(marks[2], "black", "delegate is still black"); + assertEq(marks[3], "gray", "basic gray marking is working"); + assertEq(marks[4], "black", "black map + black delegate => black value"); + + clearMarkQueue(); + clearMarkObservers(); + grayRoot().length = 0; + g.eval("grayRoot().length = 0"); +} + +if (this.enqueueMark) + runtest(grayMarkingMapFirst); + +function grayMarkingMapLast() { + const g = newGlobal({newCompartment: true}); + const vals = {}; + vals.map = new WeakMap(); + vals.key = g.eval("Object.create(null)"); + vals.val = Object.create(null); + vals.map.set(vals.key, vals.val); + + vals.map2 = new WeakMap(); + vals.key2 = g.eval("Object.create(null)"); + vals.val2 = Object.create(null); + vals.map2.set(vals.key2, vals.val2); + + g.delegate = vals.key; + g.eval("grayRoot().push(delegate)"); + addMarkObservers([vals.map, vals.key]); + g.addMarkObservers([vals.key]); + addMarkObservers([vals.val]); + + grayRoot().push(vals.key2); + addMarkObservers([vals.map2, vals.key2]); + g.addMarkObservers([vals.key2]); + addMarkObservers([vals.val2]); + + const labels = ["map", "key", "delegate", "value", "map2", "key2", "delegate2", "value2"]; + + gc(); + + g.enqueueMark(vals.key); + g.enqueueMark(vals.key2); + enqueueMark("yield"); // checkpoint 1 + + vals.val = null; + vals.key = null; + g.delegate = null; + + vals.map2 = null; // Important! Second map is never marked, keeps nothing alive. + vals.key2 = null; + vals.val2 = null; + g.delegate2 = null; + + const labeledMarks = () => { + const info = {}; + const marks = getMarks(); + for (let i = 0; i < labels.length; i++) + info[labels[i]] = marks[i]; + return info; + }; + + const showmarks = () => { + print("Marks:"); + for (const [label, mark] of Object.entries(labeledMarks())) + print(` ${label}: ${mark}`); + }; + + print("Starting incremental GC"); + startGCMarking(); + // Checkpoint 1, after marking key + showmarks(); + var marks = labeledMarks(); + assertEq(marks.map, "unmarked", "map is unmarked"); + assertEq(marks.key, "unmarked", "key is not marked yet"); + assertEq(marks.delegate, "black", "delegate is black"); + assertEq(marks.map2, "unmarked", "map2 is unmarked"); + assertEq(marks.key2, "unmarked", "key2 is not marked yet"); + assertEq(marks.delegate2, "black", "delegate2 is black"); + + gcslice(); + // GC complete. When entering weak marking mode, black delegate propagated to + // key. + showmarks(); + marks = labeledMarks(); + assertEq(marks.map, "black", "map is black"); + assertEq(marks.key, "black", "delegate marked key black because of weakmap"); + assertEq(marks.delegate, "black", "delegate is still black"); + assertEq(marks.value, "black", "black map + black delegate => black value"); + assertEq(marks.map2, "dead", "map2 is dead"); + assertEq(marks.key2, "gray", "key2 marked gray, map2 had no effect"); + assertEq(marks.delegate2, "black", "delegate artificially marked black via mark queue"); + assertEq(marks.value2, "dead", "dead map + black delegate => dead value"); + + clearMarkQueue(); + clearMarkObservers(); + grayRoot().length = 0; + g.eval("grayRoot().length = 0"); + + return vals; // To prevent the JIT from optimizing out vals. +} + +if (this.enqueueMark) + runtest(grayMarkingMapLast); + +function grayMapKey() { + const vals = {}; + vals.m = new WeakMap(); + vals.key = Object.create(null); + vals.val = Object.create(null); + vals.m.set(vals.key, vals.val); + + // Maps are allocated black, so we won't be able to mark it gray during the + // first GC. + gc(); + + addMarkObservers([vals.m, vals.key, vals.val]); + + // Wait until we can mark gray (ie, sweeping). Mark the map gray and yield. + // This should happen all in one slice. + enqueueMark("set-color-gray"); + enqueueMark(vals.m); + enqueueMark("unset-color"); + enqueueMark("yield"); + + // Make the weakmap no longer reachable from the roots, so we can mark it + // gray. + vals.m = null; + + enqueueMark(vals.key); + enqueueMark("yield"); + + vals.key = vals.val = null; + + startGCMarking(); + assertEq(getMarks().join("/"), "gray/unmarked/unmarked", + "marked the map gray"); + + gcslice(100000); + assertEq(getMarks().join("/"), "gray/black/unmarked", + "key is now marked black"); + + finishgc(); // Finish the GC + + assertEq(getMarks().join("/"), "gray/black/gray", + "at end: black/gray => gray"); + + clearMarkQueue(); + clearMarkObservers(); +} + +if (this.enqueueMark) + runtest(grayMapKey); + +function grayKeyMap() { + const vals = {}; + vals.m = new WeakMap(); + vals.key = Object.create(null); + vals.val = Object.create(null); + vals.m.set(vals.key, vals.val); + + addMarkObservers([vals.m, vals.key, vals.val]); + + enqueueMark(vals.key); + enqueueMark("yield"); + + // Wait until we are gray marking. + enqueueMark("set-color-gray"); + enqueueMark(vals.m); + enqueueMark("unset-color"); + enqueueMark("yield"); + + enqueueMark("set-color-black"); + enqueueMark(vals.m); + enqueueMark("unset-color"); + + // Make the weakmap no longer reachable from the roots, so we can mark it + // gray. + vals.m = null; + + vals.key = vals.val = null; + + // Only mark this zone, to avoid interference from other tests that may have + // created additional zones. + schedulezone(vals); + + startGCMarking(); + // getMarks() returns map/key/value + reportMarks("1: "); + assertEq(getMarks().join("/"), "unmarked/black/unmarked", + "marked key black"); + + // We always yield before sweeping (in the absence of zeal), so we will see + // the unmarked state another time. + gcslice(100000); + reportMarks("2: "); + assertEq(getMarks().join("/"), "unmarked/black/unmarked", + "marked key black, yield before sweeping"); + + gcslice(100000); + reportMarks("3: "); + assertEq(getMarks().join("/"), "gray/black/gray", + "marked the map gray, which marked the value when map scanned"); + + finishgc(); // Finish the GC + reportMarks("4: "); + assertEq(getMarks().join("/"), "black/black/black", + "further marked the map black, so value should also be blackened"); + + clearMarkQueue(); + clearMarkObservers(); +} + +if (this.enqueueMark) + runtest(grayKeyMap); + +// Cause a key to be marked black *during gray marking*, by first marking a +// delegate black, then marking the map and key gray. When the key is scanned, +// it should be seen to be a CCW of a black delegate and so should itself be +// marked black. +// +// The bad behavior being prevented is: +// +// 1. You wrap an object in a CCW and use it as a weakmap key to some +// information. +// 2. You keep a strong reference to the object (in its compartment). +// 3. The only references to the CCW are gray, and are in fact part of a cycle. +// 4. The CC runs and discards the CCW. +// 5. You look up the object in the weakmap again. This creates a new wrapper +// to use as a key. It is not in the weakmap, so the information you stored +// before is not found. (It may have even been collected, if you had no +// other references to it.) +// +function blackDuringGray() { + const g = newGlobal({newCompartment: true}); + const vals = {}; + vals.map = new WeakMap(); + vals.key = g.eval("Object.create(null)"); + vals.val = Object.create(null); + vals.map.set(vals.key, vals.val); + + g.delegate = vals.key; + addMarkObservers([vals.map, vals.key]); + g.addMarkObservers([vals.key]); + addMarkObservers([vals.val]); + // Mark observers: map, key, delegate, value + + gc(); + + g.enqueueMark(vals.key); // Mark the delegate black + enqueueMark("yield"); // checkpoint 1 + + // Mark the map gray. This will scan through all entries, find our key, and + // mark it black because its delegate is black. + enqueueMark("set-color-gray"); + enqueueMark(vals.map); // Mark the map gray + + vals.map = null; + vals.val = null; + vals.key = null; + g.delegate = null; + + const showmarks = () => { + print("[map,key,delegate,value] marked " + JSON.stringify(getMarks())); + }; + + print("Starting incremental GC"); + startGCMarking(); + // Checkpoint 1, after marking delegate black + showmarks(); + var marks = getMarks(); + assertEq(marks[0], "unmarked", "map is not marked yet"); + assertEq(marks[1], "unmarked", "key is not marked yet"); + assertEq(marks[2], "black", "delegate is black"); + assertEq(marks[3], "unmarked", "values is not marked yet"); + + finishgc(); + showmarks(); + marks = getMarks(); + assertEq(marks[0], "gray", "map is gray"); + assertEq(marks[1], "gray", "gray map + black delegate should mark key gray"); + assertEq(marks[2], "black", "delegate is still black"); + assertEq(marks[3], "gray", "gray map + gray key => gray value"); + + clearMarkQueue(); + clearMarkObservers(); + grayRoot().length = 0; + g.eval("grayRoot().length = 0"); +} + +if (this.enqueueMark) + runtest(blackDuringGray); + +// Same as above, except relying on the implicit edge from delegate -> key. +function blackDuringGrayImplicit() { + const g = newGlobal({newCompartment: true}); + const vals = {}; + vals.map = new WeakMap(); + vals.key = g.eval("Object.create(null)"); + vals.val = Object.create(null); + vals.map.set(vals.key, vals.val); + + g.delegate = vals.key; + addMarkObservers([vals.map, vals.key]); + g.addMarkObservers([vals.key]); + addMarkObservers([vals.val]); + // Mark observers: map, key, delegate, value + + gc(); + + // Mark the map gray. This will scan through all entries, find our key, and + // add implicit edges from delegate -> key and delegate -> value. + enqueueMark("set-color-gray"); + enqueueMark(vals.map); // Mark the map gray + enqueueMark("yield"); // checkpoint 1 + + enqueueMark("set-color-black"); + g.enqueueMark(vals.key); // Mark the delegate black, propagating to key. + + vals.map = null; + vals.val = null; + vals.key = null; + g.delegate = null; + + const showmarks = () => { + print("[map,key,delegate,value] marked " + JSON.stringify(getMarks())); + }; + + print("Starting incremental GC"); + startGCMarking(); + // Checkpoint 1, after marking map gray + showmarks(); + var marks = getMarks(); + assertEq(marks[0], "gray", "map is gray"); + assertEq(marks[1], "unmarked", "key is not marked yet"); + assertEq(marks[2], "unmarked", "delegate is not marked yet"); + assertEq(marks[3], "unmarked", "value is not marked yet"); + + finishgc(); + showmarks(); + marks = getMarks(); + assertEq(marks[0], "gray", "map is gray"); + assertEq(marks[1], "gray", "gray map + black delegate should mark key gray"); + assertEq(marks[2], "black", "delegate is black"); + assertEq(marks[3], "gray", "gray map + gray key => gray value via delegate -> value"); + + clearMarkQueue(); + clearMarkObservers(); + grayRoot().length = 0; + g.eval("grayRoot().length = 0"); +} + +if (this.enqueueMark) + runtest(blackDuringGrayImplicit); diff --git a/js/src/jit-test/tests/gc/weak-marking-varying.js b/js/src/jit-test/tests/gc/weak-marking-varying.js new file mode 100644 index 0000000000..76dc39dfb2 --- /dev/null +++ b/js/src/jit-test/tests/gc/weak-marking-varying.js @@ -0,0 +1,90 @@ +// |jit-test| allow-unhandlable-oom + +// Crash test. Try lots of different combinations of mark colors and +// sequencing, and rely on the in-code asserts to detect problems. + +// This test requires precise control over GC timing. The SM(cgc) job will fail +// without this, because startgc() asserts if an incremental GC is already in +// progress. +gczeal(0); + +function global(where) { + if (where == 'same-realm') + return globalThis; + if (where == 'same-compartment') + return newGlobal(); + if (where == 'same-zone') + newGlobal({sameZoneAs: {}}); + return newGlobal({newCompartment: true}); +} + +function varying(mapColor, keyColor, delegateColor, order, where) { + const vals = {}; + const g = global(where); + + vals.m = new WeakMap(); + vals.key = g.eval("Object.create(null)"); + vals.val = Object.create(null); + vals.m.set(vals.key, vals.val); + + addMarkObservers([vals.m, vals.key]); + g.delegate = vals.key; + g.eval('addMarkObservers([delegate])'); + g.delegate = null; + addMarkObservers([vals.val]); + + for (const action of order) { + if (action == 'key' && ['black', 'gray'].includes(keyColor)) { + enqueueMark(`set-color-${keyColor}`); + enqueueMark(vals.key); + enqueueMark("unset-color"); + enqueueMark("yield"); + } else if (action == 'map' && ['black', 'gray'].includes(mapColor)) { + enqueueMark(`set-color-${mapColor}`); + enqueueMark(vals.m); + enqueueMark("drain"); + enqueueMark("unset-color"); + enqueueMark("yield"); + } else if (action == 'delegate' && ['black', 'gray'].includes(delegateColor)) { + g.delegate = vals.key; + g.eval(`enqueueMark("set-color-${delegateColor}")`); + g.eval('enqueueMark(delegate)'); + g.eval('enqueueMark("unset-color")'); + g.eval('enqueueMark("yield")'); + g.delegate = null; + } + } + + vals.m = vals.key = vals.val = null; + + if (delegateColor == 'uncollected') + schedulezone({}); + startgc(100000); + print(' ' + getMarks().join("/")); + gcslice(100000); + print(' ' + getMarks().join("/")); + finishgc(); + print(' ' + getMarks().join("/")); + + clearMarkQueue(); + clearMarkObservers(); +} + +if (this.enqueueMark) { + for (const mapColor of ['gray', 'black']) { + for (const keyColor of ['gray', 'black', 'unmarked']) { + for (const delegateColor of ['gray', 'black', 'unmarked', 'uncollected']) { + for (const order of [['map', 'key'], + ['key', 'map'], + ['map', 'delegate'], + ['delegate', 'map']]) + { + for (const where of ['same-realm', 'same-compartment', 'same-zone', 'other-zone']) { + print(`\nRunning variant map/key/delegate=${mapColor}/${keyColor}/${delegateColor}, key is ${where}, order: ${order.join(" ")}`); + varying(mapColor, keyColor, delegateColor, order, where); + } + } + } + } + } +} diff --git a/js/src/jit-test/tests/gc/weakRef_in_promise.js b/js/src/jit-test/tests/gc/weakRef_in_promise.js new file mode 100644 index 0000000000..2448ebcd8a --- /dev/null +++ b/js/src/jit-test/tests/gc/weakRef_in_promise.js @@ -0,0 +1,18 @@ +// https://github.com/tc39/proposal-weakrefs/issues/39 +// Weakref should keep the target until the end of current Job, that includes +// microtask(Promise). +let wr; +{ + let obj = {}; + wr = new WeakRef(obj); + obj = null; +} +// obj is out of block scope now, should be GCed. + +gc(); + +assertEq(undefined == wr.deref(), false); +Promise.resolve().then(() => { + assertEq(undefined == wr.deref(), false); +}); + diff --git a/js/src/jit-test/tests/gc/weakRefs-basic.js b/js/src/jit-test/tests/gc/weakRefs-basic.js new file mode 100644 index 0000000000..6900ae0c52 --- /dev/null +++ b/js/src/jit-test/tests/gc/weakRefs-basic.js @@ -0,0 +1,81 @@ +assertEq('WeakRef' in this, true); + +function checkPropertyDescriptor(obj, property, writable, enumerable, + configurable) { + let desc = Object.getOwnPropertyDescriptor(obj, property); + assertEq(typeof desc, "object"); + assertEq(desc.writable, writable); + assertEq(desc.enumerable, enumerable); + assertEq(desc.configurable, configurable); +} + +function assertThrowsTypeError(thunk) { + let error; + try { + thunk(); + } catch (e) { + error = e; + } + assertEq(error instanceof TypeError, true); +} + +assertEq(typeof this.WeakRef, "function"); + +// https://tc39.es/proposal-weakrefs/ + +// 1.1.1.1 +// If NewTarget is undefined, throw a TypeError exception. +assertThrowsTypeError(() => new WeakRef()); + +// 1.1.1.2 +// If Type(target) is not Object, throw a TypeError exception. +assertThrowsTypeError(() => new WeakRef(1)); +assertThrowsTypeError(() => new WeakRef(true)); +assertThrowsTypeError(() => new WeakRef("string")); +assertThrowsTypeError(() => new WeakRef(Symbol())); +assertThrowsTypeError(() => new WeakRef(null)); +assertThrowsTypeError(() => new WeakRef(undefined)); +new WeakRef({}); + +// 1.2 +// The WeakRef constructor has a [[Prototype]] internal slot whose value is the +// intrinsic object %FunctionPrototype%. +assertEq(Object.getPrototypeOf(WeakRef), Function.prototype); + +// 1.2.1 +// The initial value of WeakRef.prototype is the intrinsic %WeakRefPrototype% +// object. +// This property has the attributes { [[Writable]]: false, [[Enumerable]]: false +// , [[Configurable]]: false }. +checkPropertyDescriptor(WeakRef, 'prototype', false, false, false); + +// 1.3 +// The WeakRef prototype object has a [[Prototype]] internal slot whose value is +// the intrinsic object %ObjectPrototype%. +let proto = WeakRef.prototype; +assertEq(Object.getPrototypeOf(proto), Object.prototype); + +// 1.3.1 +// The initial value of WeakRef.prototype.constructor is the intrinsic object +// %WeakRef%. +assertEq(proto.constructor, WeakRef); + +// 1.3.2 +// WeakRef.prototype.deref () +assertEq(proto.hasOwnProperty('deref'), true); +assertEq(typeof proto.deref, 'function'); + +// 1.3.3 +// The initial value of the @@toStringTag property is the String value +// "WeakRef". +// This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, +// [[Configurable]]: true }. +assertEq(proto[Symbol.toStringTag], "WeakRef"); +checkPropertyDescriptor(proto, Symbol.toStringTag, false, false, true); + +// 1.4 +// WeakRef instances are ordinary objects that inherit properties from the +// WeakRef prototype +let weakRef = new WeakRef({}); +assertEq(Object.getPrototypeOf(weakRef), proto); + diff --git a/js/src/jit-test/tests/gc/weakRefs.js b/js/src/jit-test/tests/gc/weakRefs.js new file mode 100644 index 0000000000..cb7dcb829a --- /dev/null +++ b/js/src/jit-test/tests/gc/weakRefs.js @@ -0,0 +1,122 @@ +// https://tc39.es/proposal-weakrefs/#sec-keepduringjob +// When the abstract operation KeepDuringJob is called with a target object +// reference, it adds the target to an identity Set that will point strongly at +// the target until the end of the current Job. +// +// https://tc39.es/proposal-weakrefs/#sec-weakref-invariants +// When WeakRef.prototype.deref is called, the referent (if it's not already +// dead) is kept alive so that subsequent, synchronous accesses also return the +// object. + +function testSameCompartmentWeakRef( + targetReachable, + weakRefReachable) { + + let target = {}; + + let weakref = new WeakRef(target); + assertEq(weakref.deref(), target); + + if (!targetReachable) { + target = undefined; + } + + if (!weakRefReachable) { + weakRef = undefined; + } + + clearKeptObjects(); + gc(); + + if (weakRefReachable) { + if (targetReachable) { + assertEq(weakref.deref(), target); + } else { + assertEq(weakref.deref(), undefined); + } + } +} + +let serial = 0; + +function testCrossCompartmentWeakRef( + targetReachable, + weakRefReachable, + collectTargetZone, + collectWeakRefZone, + sameZone) { + + gc(); + + let id = serial++; + let global = newGlobal(sameZone ? {sameZoneAs: this} : {newCompartment: true}); + global.eval('var target = {};'); + global.target.id = id; + + let weakref = new WeakRef(global.target); + assertEq(weakref.deref(), global.target); + + if (!targetReachable) { + global.target = undefined; + } + + if (!weakRefReachable) { + weakRef = undefined; + } + + if (collectTargetZone || collectWeakRefZone) { + clearKeptObjects(); + + if (collectTargetZone) { + schedulezone(global); + } + if (collectWeakRefZone) { + schedulezone(this); + } + + // Incremental GC so we use sweep groups. Shrinking GC to test updating + // pointers. + startgc(1, 'shrinking'); + while (gcstate() !== 'NotActive') { + gcslice(1000, {dontStart: true}); + } + } + + if (!(collectWeakRefZone && !weakRefReachable)) { + if (collectTargetZone && !targetReachable) { + assertEq(weakref.deref(), undefined); + } else if (targetReachable) { + assertEq(weakref.deref(), global.target); + } else { + // Target is not strongly reachable but hasn't been collected yet. We + // can get it back through deref() but must check it based on properties. + assertEq(weakref.deref() !== undefined, true); + assertEq(weakref.deref().id, id); + } + } +} + +gczeal(0); + +for (let targetReachable of [true, false]) { + for (let weakRefReachable of [true, false]) { + testSameCompartmentWeakRef(targetReachable, weakRefReachable); + } +} + +for (let targetReachable of [true, false]) { + for (let weakRefReachable of [true, false]) { + for (let collectTargetZone of [true, false]) { + for (let collectWeakRefZone of [true, false]) { + for (let sameZone of [true, false]) { + if (sameZone && (collectTargetZone != collectWeakRefZone)) { + continue; + } + + testCrossCompartmentWeakRef(targetReachable, weakRefReachable, + collectTargetZone, collectWeakRefZone, sameZone); + } + } + } + } +} diff --git a/js/src/jit-test/tests/gc/weakmap-expose.js b/js/src/jit-test/tests/gc/weakmap-expose.js new file mode 100644 index 0000000000..45ebc7d5a9 --- /dev/null +++ b/js/src/jit-test/tests/gc/weakmap-expose.js @@ -0,0 +1,43 @@ +// Test that WeakMap.get() doesn't return a gray GC thing. + +function checkNotGray(value) { + // Assigning a gray GC thing to an object propery will assert. + let test = {}; + test.property = value; +} + +// 1. Make a black WeakMap with a gray key and gray value. + +gczeal(0); + +let key = {}; +let value = {}; + +let map = new WeakMap(); +map.set(key, value); + +let gray = grayRoot(); +gray.key = key; + +addMarkObservers([map, key, value]); + +gray = null; +key = null; +value = null; + +gc(); + +let marks = getMarks(); +assertEq(marks[0], "black"); +assertEq(marks[1], "gray"); +assertEq(marks[2], "gray"); + +// 2. Get our key back, which will expose it and mark it black. + +key = nondeterministicGetWeakMapKeys(map)[0]; +checkNotGray(key); + +// 3. Look up the value in the map and check it's not gray. + +value = map.get(key); +checkNotGray(value); diff --git a/js/src/jit-test/tests/gc/weakmap-nursery-value.js b/js/src/jit-test/tests/gc/weakmap-nursery-value.js new file mode 100644 index 0000000000..9c61b56632 --- /dev/null +++ b/js/src/jit-test/tests/gc/weakmap-nursery-value.js @@ -0,0 +1,35 @@ +// Bug 1715471 + +// This test relies on triggering GC at precise points. GC zeal modes +// interfere with this. +gczeal(0); + +// This test requires enqueueMark, which is only available in a debugging +// build. +if (!this.enqueueMark) quit(0); + +var wm = new WeakMap(); + +// Force materialization of the map struct. Otherwise it would be allocated +// black during the incremental GC when we do wm.set. +wm.set({}, {}); + +var tenuredKey = Object.create(null); +gc(); // Tenure the WeakMap and key. + +// Enter the GC, since otherwise the minor GC at the beginning would tenure our +// value before it had a chance to be entered into the ephemeron edge list. +startgc(1); +while (gcstate() === "Prepare") gcslice(1); + +// Create a WeakMap entry with a value in the nursery. +var nurseryValue = Object.create(null); +wm.set(tenuredKey, nurseryValue); + +// We want to mark the weakmap first, before the key or value are marked, so +// that the tenuredKey -> nurseryValue edge will be added to the ephemeron edge +// table. +enqueueMark(wm); + +gcslice(1000); +minorgc(); diff --git a/js/src/jit-test/tests/gc/weakmark-remap.js b/js/src/jit-test/tests/gc/weakmark-remap.js new file mode 100644 index 0000000000..f3e8e8f995 --- /dev/null +++ b/js/src/jit-test/tests/gc/weakmark-remap.js @@ -0,0 +1,16 @@ +g1 = newGlobal({ sameZoneAs: this }); + +// Turn on the object metadata builder because it will create internal weakmap +// entries for all objects created from here on. +enableShellAllocationMetadataBuilder(); + +// Construct a CCW for g1.Object, which will internally create the object +// metadata table and add an entry to it. +g1.Object; + +// Begin an incremental GC +gczeal(0); +startgc(1); + +// Call a scary function that does weird stuff. +recomputeWrappers(); diff --git a/js/src/jit-test/tests/gc/weakmark-remap2.js b/js/src/jit-test/tests/gc/weakmark-remap2.js new file mode 100644 index 0000000000..19e7338b5d --- /dev/null +++ b/js/src/jit-test/tests/gc/weakmark-remap2.js @@ -0,0 +1,21 @@ +gczeal(0); + +var g1 = newGlobal({ sameZoneAs: this }); + +// Turn on the object metadata builder because it will create internal weakmap +// entries for all objects created from here on. +enableShellAllocationMetadataBuilder(); + +// Construct a pair of error objects. Perhaps this fills out the metadata table? +new Error(); +new Error(); + +// Construct a CCW for g1.Object, which will internally create the object +// metadata table and add an entry to it. +try { g1.Object } catch(e) { } + +// Begin an incremental GC +startgc(1); + +// Call a scary function that does weird stuff. +recomputeWrappers(); |