|
| 1 | +// The property cache is invalidated when a prototype gains a property, and |
| 2 | +// not when an ordinary object does. These are the shapes where the second |
| 3 | +// half could go wrong: a site that has already cached where it found a |
| 4 | +// property, and then something adds one that should shadow it, or moves it. |
| 5 | +// |
| 6 | +// Every answer here is the language's, and none of them depends on a cache. |
| 7 | + |
| 8 | +let failures = 0; |
| 9 | +function check(name, actual, expected) { |
| 10 | + if (!Object.is(actual, expected)) { |
| 11 | + console.log("FAIL", name, "expected", expected, "got", actual); |
| 12 | + failures += 1; |
| 13 | + } |
| 14 | +} |
| 15 | +// One call site per case, so each has its own cache entry, and each is warmed |
| 16 | +// before the mutation it is meant to notice. |
| 17 | +const warm = (f, x, n) => { for (let i = 0; i < n; i++) f(x); }; |
| 18 | + |
| 19 | +// --- a new own property shadows an inherited one ------------------------- |
| 20 | +{ |
| 21 | + const proto = { m: "proto" }; |
| 22 | + const o = Object.create(proto); |
| 23 | + const read = (x) => x.m; |
| 24 | + warm(read, o, 200); |
| 25 | + check("inherited before", read(o), "proto"); |
| 26 | + o.m = "own"; |
| 27 | + check("own shadows inherited", read(o), "own"); |
| 28 | +} |
| 29 | + |
| 30 | +// --- a nearer prototype shadows a further one ---------------------------- |
| 31 | +{ |
| 32 | + const far = { m: "far" }; |
| 33 | + const near = Object.create(far); |
| 34 | + const o = Object.create(near); |
| 35 | + const read = (x) => x.m; |
| 36 | + warm(read, o, 200); |
| 37 | + check("found at depth 2", read(o), "far"); |
| 38 | + near.m = "near"; |
| 39 | + check("depth 1 shadows depth 2", read(o), "near"); |
| 40 | +} |
| 41 | + |
| 42 | +// --- Object.prototype, reached from a plain object ----------------------- |
| 43 | +{ |
| 44 | + const o = { a: 1 }; |
| 45 | + const read = (x) => x.zz_probe; |
| 46 | + check("absent before", read(o), undefined); |
| 47 | + warm(read, o, 200); |
| 48 | + Object.prototype.zz_probe = "proto"; |
| 49 | + check("added to Object.prototype", read(o), "proto"); |
| 50 | + const mid = { zz_probe: "own" }; |
| 51 | + check("own beats Object.prototype", read(mid), "own"); |
| 52 | + delete Object.prototype.zz_probe; |
| 53 | + check("deleted again", read(o), undefined); |
| 54 | +} |
| 55 | + |
| 56 | +// --- a constructor prototype gaining a method ---------------------------- |
| 57 | +{ |
| 58 | + function Foo() { this.a = 1; } |
| 59 | + Foo.prototype.m = function () { return "one"; }; |
| 60 | + const f = new Foo(); |
| 61 | + const call = (x) => x.m(); |
| 62 | + warm(call, f, 200); |
| 63 | + check("prototype method", call(f), "one"); |
| 64 | + Foo.prototype.m = function () { return "two"; }; |
| 65 | + check("replaced on the prototype", call(f), "two"); |
| 66 | + f.m = function () { return "own"; }; |
| 67 | + check("own method shadows it", call(f), "own"); |
| 68 | +} |
| 69 | + |
| 70 | +// --- a fresh object assigned as a prototype after the site is warm ------- |
| 71 | +{ |
| 72 | + function Bar() {} |
| 73 | + const b = new Bar(); |
| 74 | + const read = (x) => x.k; |
| 75 | + Object.prototype.k = "object"; |
| 76 | + warm(read, b, 200); |
| 77 | + check("from Object.prototype", read(b), "object"); |
| 78 | + Bar.prototype.k = "bar"; |
| 79 | + check("Bar.prototype shadows it", read(b), "bar"); |
| 80 | + delete Object.prototype.k; |
| 81 | + check("still Bar's", read(b), "bar"); |
| 82 | +} |
| 83 | + |
| 84 | +// --- setPrototypeOf under a warm site ------------------------------------ |
| 85 | +{ |
| 86 | + const a = { m: "a" }, c = { m: "c" }; |
| 87 | + const o = Object.create(a); |
| 88 | + const read = (x) => x.m; |
| 89 | + warm(read, o, 200); |
| 90 | + check("through a", read(o), "a"); |
| 91 | + Object.setPrototypeOf(o, c); |
| 92 | + check("through c", read(o), "c"); |
| 93 | +} |
| 94 | + |
| 95 | +// --- ordinary objects growing, which must not disturb anything ---------- |
| 96 | +{ |
| 97 | + const proto = { m: "proto" }; |
| 98 | + const read = (x) => x.m; |
| 99 | + const first = Object.create(proto); |
| 100 | + warm(read, first, 200); |
| 101 | + let last = null; |
| 102 | + for (let i = 0; i < 500; i++) { |
| 103 | + // Each of these adds four properties to an ordinary object, which is |
| 104 | + // what used to flush the cache 2000 times over this loop. |
| 105 | + const churn = { w: i, x: i, y: i, z: i }; |
| 106 | + if (churn.w !== i) { failures += 1; } |
| 107 | + last = Object.create(proto); |
| 108 | + last.own = i; |
| 109 | + if (read(last) !== "proto") { failures += 1; } |
| 110 | + } |
| 111 | + check("still found through the prototype", read(first), "proto"); |
| 112 | + check("and on the last one built", read(last), "proto"); |
| 113 | + proto.m = "changed"; |
| 114 | + check("changing the prototype is still seen", read(first), "changed"); |
| 115 | +} |
| 116 | + |
| 117 | +// --- accessors and non-writables on a prototype -------------------------- |
| 118 | +{ |
| 119 | + const proto = {}; |
| 120 | + Object.defineProperty(proto, "g", { get() { return "getter"; }, configurable: true }); |
| 121 | + const o = Object.create(proto); |
| 122 | + const read = (x) => x.g; |
| 123 | + warm(read, o, 200); |
| 124 | + check("inherited getter", read(o), "getter"); |
| 125 | + Object.defineProperty(proto, "g", { value: "data", configurable: true }); |
| 126 | + check("redefined as data", read(o), "data"); |
| 127 | +} |
| 128 | + |
| 129 | +// --- delete, resize and compaction -------------------------------------- |
| 130 | +{ |
| 131 | + const o = {}; |
| 132 | + for (let i = 0; i < 40; i++) o["p" + i] = i; |
| 133 | + const read = (x) => x.p39; |
| 134 | + warm(read, o, 200); |
| 135 | + check("last property", read(o), 39); |
| 136 | + for (let i = 0; i < 39; i++) delete o["p" + i]; |
| 137 | + check("survives 39 deletes", read(o), 39); |
| 138 | + for (let i = 0; i < 40; i++) o["q" + i] = i; |
| 139 | + check("survives regrowth", read(o), 39); |
| 140 | +} |
| 141 | + |
| 142 | +if (failures !== 0) throw new Error(failures + " property-cache checks failed"); |
| 143 | +console.log("property cache: all checks passed"); |
0 commit comments