Skip to content

Commit cf0e18a

Browse files
committed
fix(napi/parser): NodeArray allow setting large integer properties (#11883)
Integers `>= 4294967295` are treated as string properties, not numerical array indexes. Make `NodeArray` behave the same as `Array` for such properties, and allow user to set them.
1 parent 5500d2d commit cf0e18a

File tree

2 files changed

+56
-26
lines changed

2 files changed

+56
-26
lines changed

napi/parser/raw-transfer/node-array.js

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,8 @@ const PROXY_HANDLERS = {
258258
// Return `true` for indexes which are in bounds.
259259
// e.g. `'0' in arr`.
260260
has(arr, key) {
261-
if (isIndex(key)) return key * 1 < getLength(arr);
261+
const index = toIndex(key);
262+
if (index !== null) return index < getLength(arr);
262263
return Reflect.has(arr, key);
263264
},
264265

@@ -268,7 +269,8 @@ const PROXY_HANDLERS = {
268269
// They can "unwrap" the proxy by getting `this[ARRAY]`.
269270
if (key === ARRAY) return arr;
270271
if (key === 'length') return getLength(arr);
271-
if (isIndex(key)) return getElement(arr, key * 1);
272+
const index = toIndex(key);
273+
if (index !== null) return getElement(arr, index);
272274

273275
return Reflect.get(arr, key);
274276
},
@@ -280,8 +282,9 @@ const PROXY_HANDLERS = {
280282
return { value: getLength(arr), writable: true, enumerable: false, configurable: false };
281283
}
282284

283-
if (isIndex(key)) {
284-
const value = getElement(arr, key * 1);
285+
const index = toIndex(key);
286+
if (index !== null) {
287+
const value = getElement(arr, index);
285288
if (value === void 0) return void 0;
286289
// Cannot return `configurable: false` unfortunately
287290
return { value, writable: false, enumerable: true, configurable: true };
@@ -298,14 +301,14 @@ const PROXY_HANDLERS = {
298301
// * `Object.defineProperty(arr, 'length', {value: 0})`.
299302
// * Other operations which mutate entries e.g. `arr.push(123)`.
300303
defineProperty(arr, key, descriptor) {
301-
if (key === 'length' || isIndex(key)) return false;
304+
if (key === 'length' || toIndex(key) !== null) return false;
302305
return Reflect.defineProperty(arr, key, descriptor);
303306
},
304307

305308
// Prevent deleting entries.
306309
deleteProperty(arr, key) {
307310
// Note: `Reflect.deleteProperty(arr, 'length')` already returns `false`
308-
if (isIndex(key)) return false;
311+
if (toIndex(key) !== null) return false;
309312
return Reflect.deleteProperty(arr, key);
310313
},
311314

@@ -322,16 +325,24 @@ const PROXY_HANDLERS = {
322325
};
323326

324327
/**
325-
* Check if a key is a valid array index.
328+
* Convert key to array index, if it is a valid array index.
329+
*
326330
* Only strings comprising a plain integer are valid indexes.
327331
* e.g. `"-1"`, `"01"`, `"0xFF"`, `"1e1"`, `"1 "` are not valid indexes.
332+
* Integers >= 4294967295 are not valid indexes.
328333
*
329-
* @param {*} - Key used for property lookup.
330-
* @returns {boolean} - `true` if `key` is a valid array index.
334+
* @param {string|Symbol} - Key used for property lookup.
335+
* @returns {number|null} - `key` converted to integer, if it's a valid array index, otherwise `null`.
331336
*/
332-
function isIndex(key) {
333-
// TODO: Any way to do this without a regex?
334-
return typeof key === 'string' && (key === '0' || INDEX_REGEX.test(key));
337+
function toIndex(key) {
338+
if (typeof key === 'string') {
339+
if (key === '0') return 0;
340+
if (INDEX_REGEX.test(key)) {
341+
const index = +key;
342+
if (index < 4294967295) return index;
343+
}
344+
}
345+
return null;
335346
}
336347

337348
const INDEX_REGEX = /^[1-9]\d*$/;

napi/parser/test/lazy-deserialization.test.ts

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,8 @@ describe('NodeArray', () => {
530530
expect(() => body[1] = {}).toThrow(new TypeError('Cannot redefine property: 1'));
531531
expect(() => body[2] = {})
532532
.toThrow(new TypeError("'defineProperty' on proxy: trap returned falsish for property '2'"));
533+
expect(() => body[4294967294] = {})
534+
.toThrow(new TypeError("'defineProperty' on proxy: trap returned falsish for property '4294967294'"));
533535
});
534536

535537
it('set element via `defineProperty` (throws)', () => {
@@ -540,28 +542,45 @@ describe('NodeArray', () => {
540542
.toThrow(new TypeError("'defineProperty' on proxy: trap returned falsish for property '1'"));
541543
expect(() => Object.defineProperty(body, 2, { value: {} }))
542544
.toThrow(new TypeError("'defineProperty' on proxy: trap returned falsish for property '2'"));
545+
expect(() => Object.defineProperty(body, 4294967294, { value: {} }))
546+
.toThrow(new TypeError("'defineProperty' on proxy: trap returned falsish for property '4294967294'"));
543547
});
544548

549+
const propertyKeys = [
550+
'foo',
551+
'bar',
552+
Symbol('yeah'),
553+
-1,
554+
'01',
555+
'0x1',
556+
'1 ',
557+
' 1',
558+
'1e1',
559+
'4294967295',
560+
'4294967296',
561+
'10000000000000',
562+
];
563+
545564
it('set properties', () => {
546565
const { body } = parseSyncLazy('test.js', 'let x = 1; x = 2;').program;
547566

548-
const keys = [
549-
'foo',
550-
'bar',
551-
Symbol('yeah'),
552-
-1,
553-
'01',
554-
'0x1',
555-
'1 ',
556-
' 1',
557-
'1e1',
558-
];
559-
560-
for (const [i, key] of keys.entries()) {
567+
for (const [i, key] of propertyKeys.entries()) {
561568
body[key] = i + 100;
562569
}
563570

564-
for (const [i, key] of keys.entries()) {
571+
for (const [i, key] of propertyKeys.entries()) {
572+
expect(body[key]).toBe(i + 100);
573+
}
574+
});
575+
576+
it('set properties via `defineProperty`', () => {
577+
const { body } = parseSyncLazy('test.js', 'let x = 1; x = 2;').program;
578+
579+
for (const [i, key] of propertyKeys.entries()) {
580+
Object.defineProperty(body, key, { value: i + 100 });
581+
}
582+
583+
for (const [i, key] of propertyKeys.entries()) {
565584
expect(body[key]).toBe(i + 100);
566585
}
567586
});

0 commit comments

Comments
 (0)