Skip to content

Commit bc2ad3e

Browse files
committed
refactor(harden): Duplicate make-hardener test for shallow
1 parent bd1cca1 commit bc2ad3e

File tree

1 file changed

+322
-0
lines changed

1 file changed

+322
-0
lines changed
Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
// @ts-nocheck
2+
3+
import test from 'ava';
4+
import { makeHardener } from '../make-hardener.js';
5+
6+
const { stringify: q } = JSON;
7+
8+
test('makeHardener', t => {
9+
const h = makeHardener({ traversePrototypes: true });
10+
const o = { a: {} };
11+
t.is(h(o), o);
12+
t.true(Object.isFrozen(o));
13+
t.true(Object.isFrozen(o.a));
14+
});
15+
16+
test('harden the same thing twice', t => {
17+
const h = makeHardener({ traversePrototypes: true });
18+
const o = { a: {} };
19+
t.is(h(o), o);
20+
t.is(h(o), o);
21+
t.true(Object.isFrozen(o));
22+
t.true(Object.isFrozen(o.a));
23+
});
24+
25+
test('harden objects with cycles', t => {
26+
const h = makeHardener({ traversePrototypes: true });
27+
const o = { a: {} };
28+
o.a.foo = o;
29+
t.is(h(o), o);
30+
t.true(Object.isFrozen(o));
31+
t.true(Object.isFrozen(o.a));
32+
});
33+
34+
test('harden overlapping objects', t => {
35+
const h = makeHardener({ traversePrototypes: true });
36+
const o1 = { a: {} };
37+
const o2 = { a: o1.a };
38+
t.is(h(o1), o1);
39+
t.true(Object.isFrozen(o1));
40+
t.true(Object.isFrozen(o1.a));
41+
t.falsy(Object.isFrozen(o2));
42+
t.is(h(o2), o2);
43+
t.true(Object.isFrozen(o2));
44+
});
45+
46+
test('harden up prototype chain', t => {
47+
const h = makeHardener({ traversePrototypes: true });
48+
const a = { a: 1 };
49+
const b = { b: 1, __proto__: a };
50+
const c = { c: 1, __proto__: b };
51+
52+
h(c);
53+
t.true(Object.isFrozen(a));
54+
});
55+
56+
test('harden tolerates objects with null prototypes', t => {
57+
const h = makeHardener({ traversePrototypes: true });
58+
const o = { a: 1 };
59+
Object.setPrototypeOf(o, null);
60+
t.is(h(o), o);
61+
t.true(Object.isFrozen(o));
62+
t.true(Object.isFrozen(o.a));
63+
});
64+
65+
test('harden typed arrays', t => {
66+
const typedArrayConstructors = [
67+
BigInt64Array,
68+
BigUint64Array,
69+
Float32Array,
70+
Float64Array,
71+
Int16Array,
72+
Int32Array,
73+
Int8Array,
74+
Uint16Array,
75+
Uint32Array,
76+
Uint8Array,
77+
Uint8ClampedArray,
78+
];
79+
80+
for (const TypedArray of typedArrayConstructors) {
81+
const h = makeHardener({ traversePrototypes: true });
82+
const a = new TypedArray(1);
83+
84+
t.is(h(a), a, `harden ${TypedArray}`);
85+
t.true(Object.isSealed(a));
86+
const descriptor = Object.getOwnPropertyDescriptor(a, '0');
87+
t.is(descriptor.value, a[0]);
88+
// Failed in Node.js 14 and earlier due to an engine bug:
89+
t.is(
90+
descriptor.configurable,
91+
true,
92+
'hardened typed array indexed property remains configurable',
93+
);
94+
// Note that indexes of typed arrays are exceptionally writable for hardened objects.
95+
t.is(
96+
descriptor.writable,
97+
true,
98+
'hardened typed array indexed property is writable',
99+
);
100+
t.is(
101+
descriptor.enumerable,
102+
true,
103+
'hardened typed array indexed property is enumerable',
104+
);
105+
}
106+
});
107+
108+
test('harden typed arrays and their expandos', t => {
109+
const h = makeHardener({ traversePrototypes: true });
110+
const a = new Uint8Array(1);
111+
const b = new Uint8Array(1);
112+
113+
// TODO: Use fast-check to generate arbitrary input.
114+
const expandoKeyCandidates = [
115+
'x',
116+
'length',
117+
118+
// invalid keys
119+
'-1',
120+
'-1.5',
121+
122+
// number-coercible strings that are not in canonical form
123+
// https://tc39.es/ecma262/#sec-canonicalnumericindexstring
124+
// https://tc39.es/ecma262/#prod-StringNumericLiteral
125+
'',
126+
' ',
127+
' 0',
128+
'0\t',
129+
'+0',
130+
'00',
131+
'.0',
132+
'0.',
133+
'0e0',
134+
'0b0',
135+
'0o0',
136+
'0x0',
137+
' -0',
138+
'-0\t',
139+
'-00',
140+
'-.0',
141+
'-0.',
142+
'-0e0',
143+
'-0b0',
144+
'-0o0',
145+
'-0x0',
146+
'9007199254740993', // reserializes to "9007199254740992" (Number.MAX_SAFE_INTEGER + 1)
147+
'0.0000001', // reserializes to "1e-7"
148+
'1000000000000000000000', // reserializes to "1e+21"
149+
// Exactly one of these is canonical in any given implementation.
150+
// https://tc39.es/ecma262/#sec-numeric-types-number-tostring
151+
'1.2000000000000001',
152+
'1.2000000000000002',
153+
154+
// Symbols go last because they are returned last.
155+
// https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-ownpropertykeys
156+
Symbol('unique symbol'),
157+
Symbol.for('registered symbol'),
158+
Symbol('00'),
159+
Symbol.for('00'),
160+
Symbol.match,
161+
];
162+
// Test only property keys that are actually supported by the implementation.
163+
const expandoKeys = [];
164+
for (const key of expandoKeyCandidates) {
165+
if (Reflect.defineProperty(b, key, { value: 'test', configurable: true })) {
166+
expandoKeys.push(key);
167+
}
168+
}
169+
for (const key of expandoKeys) {
170+
Object.defineProperty(a, key, {
171+
value: { a: { b: { c: 10 } } },
172+
enumerable: true,
173+
writable: true,
174+
configurable: true,
175+
});
176+
}
177+
178+
t.is(h(a), a, 'harden() must return typed array input');
179+
t.deepEqual(
180+
Reflect.ownKeys(a),
181+
['0'].concat(expandoKeys),
182+
'hardened typed array keys must exactly match pre-hardened keys',
183+
);
184+
185+
// Index properties remain writable.
186+
{
187+
const descriptor = Object.getOwnPropertyDescriptor(a, '0');
188+
t.like(
189+
descriptor,
190+
{ value: 0, writable: true, enumerable: true },
191+
'hardened typed array index property',
192+
);
193+
// Failed in Node.js 14 and earlier due to an engine bug:
194+
t.is(
195+
descriptor.configurable,
196+
true,
197+
'typed array indexed property is configurable',
198+
);
199+
// Note that indexes of typed arrays are exceptionally writable for hardened objects:
200+
}
201+
202+
// Non-index properties are locked down.
203+
for (const key of expandoKeys) {
204+
const descriptor = Object.getOwnPropertyDescriptor(a, key);
205+
t.like(
206+
descriptor,
207+
{ configurable: false, writable: false, enumerable: true },
208+
`hardened typed array expando ${q(key)}`,
209+
);
210+
t.is(
211+
descriptor.value,
212+
a[key],
213+
`hardened typed array expando ${q(key)} value identity`,
214+
);
215+
t.deepEqual(
216+
descriptor.value,
217+
{ a: { b: { c: 10 } } },
218+
`hardened typed array expando ${q(key)} value shape`,
219+
);
220+
t.true(
221+
Object.isFrozen(descriptor.value),
222+
`hardened typed array expando ${q(key)} value is frozen`,
223+
);
224+
t.true(
225+
Object.isFrozen(descriptor.value.a),
226+
`hardened typed array expando ${q(key)} value property is frozen`,
227+
);
228+
t.true(
229+
Object.isFrozen(descriptor.value.a.b),
230+
`hardened typed array expando ${q(key)} value subproperty is frozen`,
231+
);
232+
t.true(
233+
Object.isFrozen(descriptor.value.a.b.c),
234+
`hardened typed array expando ${q(key)} value sub-subproperty is frozen`,
235+
);
236+
}
237+
238+
t.true(Object.isSealed(a), 'hardened typed array is sealed');
239+
});
240+
241+
test('hardening makes writable properties readonly even if non-configurable', t => {
242+
const h = makeHardener({ traversePrototypes: true });
243+
const o = {};
244+
Object.defineProperty(o, 'x', {
245+
value: 10,
246+
writable: true,
247+
configurable: false,
248+
enumerable: false,
249+
});
250+
h(o);
251+
252+
t.deepEqual(Object.getOwnPropertyDescriptor(o, 'x'), {
253+
value: 10,
254+
writable: false,
255+
configurable: false,
256+
enumerable: false,
257+
});
258+
});
259+
260+
test('harden a typed array with a writable non-configurable expando', t => {
261+
const h = makeHardener({ traversePrototypes: true });
262+
const a = new Uint8Array(1);
263+
Object.defineProperty(a, 'x', {
264+
value: 'A',
265+
writable: true,
266+
configurable: false,
267+
enumerable: false,
268+
});
269+
270+
t.is(h(a), a);
271+
t.true(Object.isSealed(a));
272+
273+
t.deepEqual(
274+
{
275+
value: 'A',
276+
writable: false,
277+
configurable: false,
278+
enumerable: false,
279+
},
280+
Object.getOwnPropertyDescriptor(a, 'x'),
281+
);
282+
});
283+
284+
test('harden a typed array subclass', t => {
285+
const h = makeHardener({ traversePrototypes: true });
286+
287+
class Ooint8Array extends Uint8Array {
288+
oo = 'ghosts';
289+
}
290+
h(Ooint8Array);
291+
t.true(Object.isFrozen(Ooint8Array.prototype));
292+
t.true(Object.isFrozen(Object.getPrototypeOf(Ooint8Array.prototype)));
293+
294+
const a = new Ooint8Array(1);
295+
t.is(h(a), a);
296+
297+
t.deepEqual(Object.getOwnPropertyDescriptor(a, 'oo'), {
298+
value: 'ghosts',
299+
writable: false,
300+
configurable: false,
301+
enumerable: true,
302+
});
303+
t.true(Object.isSealed(a));
304+
});
305+
306+
test('harden depends on invariant: typed arrays have no storage for integer indexes beyond length', t => {
307+
const a = new Uint8Array(1);
308+
a[1] = 1;
309+
t.is(a[1], undefined);
310+
});
311+
312+
test('harden depends on invariant: typed arrays cannot have integer expandos', t => {
313+
const a = new Uint8Array(1);
314+
t.throws(() => {
315+
Object.defineProperty(a, '1', {
316+
value: 'A',
317+
writable: true,
318+
configurable: false,
319+
enumerable: false,
320+
});
321+
});
322+
});

0 commit comments

Comments
 (0)