Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions quickjs.c
Original file line number Diff line number Diff line change
Expand Up @@ -3948,12 +3948,12 @@ static JSValue JS_AtomIsNumericIndex1(JSContext *ctx, JSAtom atom)
if (c == '0' && len == 2)
goto minus_zero;
}
/* XXX: should test NaN, but the tests do not check it */
if (!is_num(c)) {
/* XXX: String should be normalized, therefore 8-bit only */
const uint16_t nfinity16[7] = { 'n', 'f', 'i', 'n', 'i', 't', 'y' };
if (!(c =='I' && (r_end - r) == 8 &&
!memcmp(r + 1, nfinity16, sizeof(nfinity16))))
!memcmp(r + 1, nfinity16, sizeof(nfinity16))) &&
!(c == 'N' && len == 3 && r[1] == 'a' && r[2] == 'N'))
return JS_UNDEFINED;
}
} else {
Expand All @@ -3974,7 +3974,8 @@ static JSValue JS_AtomIsNumericIndex1(JSContext *ctx, JSAtom atom)
}
if (!is_num(c)) {
if (!(c =='I' && (r_end - r) == 8 &&
!memcmp(r + 1, "nfinity", 7)))
!memcmp(r + 1, "nfinity", 7)) &&
!(c == 'N' && len == 3 && r[1] == 'a' && r[2] == 'N'))
return JS_UNDEFINED;
}
}
Expand Down Expand Up @@ -10635,6 +10636,17 @@ static int JS_SetPropertyInternal2(JSContext *ctx, JSValueConst obj, JSAtom prop
if (ret < 0)
goto fail;
typed_array_oob:
/* An integer index that is out of bounds is not a
property, so a store that requires the property to
exist already has nothing to write to. This is a
`with` binding that vanished between the reference
being taken and the store: SetMutableBinding throws
a ReferenceError (step 3) before reaching [[Set]],
so the value is not coerced either. */
if (unlikely(flags & JS_PROP_NO_ADD)) {
JS_ThrowReferenceErrorNotDefined(ctx, prop);
goto fail;
}
/* [[Set]] (10.4.5.5): an out-of-bounds or non-canonical
integer index only coerces the value (step i, via
TypedArraySetElement) when the receiver is the typed
Expand Down
1 change: 0 additions & 1 deletion test262_errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,5 @@ test262/test/language/statements/using/initializer-disposed-at-end-of-imported-m
test262/test/language/statements/with/get-binding-value-call-with-proxy-env.js:39: Test262Error: Actual [has:Object, get:Symbol(Symbol.unscopables), get:Object] and expected [has:Object, get:Symbol(Symbol.unscopables), has:Object, get:Object] should have the same contents.
test262/test/language/statements/with/get-binding-value-idref-with-proxy-env.js:39: Test262Error: Actual [has:Object, get:Symbol(Symbol.unscopables), get:Object] and expected [has:Object, get:Symbol(Symbol.unscopables), has:Object, get:Object] should have the same contents.
test262/test/language/statements/with/get-mutable-binding-binding-deleted-in-get-unscopables-strict-mode.js:21: Test262Error: Expected a ReferenceError to be thrown but no exception was thrown at all
test262/test/language/statements/with/set-mutable-binding-binding-deleted-with-typed-array-in-proto-chain.js:20: Test262Error: Expected SameValue(芦[object Object]禄, 芦undefined禄) to be true
test262/test/language/statements/with/set-mutable-binding-idref-compound-assign-with-proxy-env.js:58: Test262Error: Actual [has:p, get:Symbol(Symbol.unscopables), get:p, set:p, getOwnPropertyDescriptor:p, defineProperty:p] and expected [has:p, get:Symbol(Symbol.unscopables), has:p, get:p, has:p, set:p, getOwnPropertyDescriptor:p, defineProperty:p] should have the same contents.
test262/test/language/statements/with/set-mutable-binding-idref-with-proxy-env.js:50: Test262Error: Actual [has:p, get:Symbol(Symbol.unscopables), set:p, getOwnPropertyDescriptor:p, defineProperty:p] and expected [has:p, get:Symbol(Symbol.unscopables), has:p, set:p, getOwnPropertyDescriptor:p, defineProperty:p] should have the same contents.
192 changes: 192 additions & 0 deletions tests/canonical-numeric-index-nan.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import { assert } from "./assert.js";

/* CanonicalNumericIndexString("NaN") is NaN, not undefined: ToString(NaN) is
"NaN". On an integer-indexed exotic object that makes "NaN" an (always out
of range) numeric index rather than an ordinary property, so it never
reaches the ordinary object machinery or the prototype chain. */

const ta = new Int32Array(4);

/* the strings that are canonical numeric index strings but never a valid
index, so every one of them is handled by the integer-indexed path */
const canonical = ["NaN", "Infinity", "-Infinity", "-0", "1.5", "-1", "4",
"1e+21"];
/* ... and lookalikes that are not canonical: ToString(ToNumber(s)) !== s */
const ordinary = ["-NaN", "nan", "NAN", "Nan", "NaN ", " NaN", "+NaN", "NaNa",
"aNaN", "NaN\0", "+Infinity", "0x1", "01", "1.50", "1e21",
""];

/* [[Get]] returns undefined and does not consult the prototype */
{
const proto = {};
for (const k of canonical.concat(ordinary))
proto[k] = "from proto";
const t = new Int32Array(4);
Object.setPrototypeOf(t, proto);

for (const k of canonical)
assert(t[k], undefined, `get ${k}`);
for (const k of ordinary)
assert(t[k], "from proto", `get ${k}`);

/* the number NaN as a key goes through the same string */
assert(t[NaN], undefined);
assert(t[0 / 0], undefined);
}

/* [[HasProperty]] is false and does not consult the prototype */
{
const proto = {};
for (const k of canonical.concat(ordinary))
proto[k] = 1;
const t = new Int32Array(4);
Object.setPrototypeOf(t, proto);

for (const k of canonical)
assert(k in t, false, `in ${k}`);
for (const k of ordinary)
assert(k in t, true, `in ${k}`);
assert(NaN in t, false);
}

/* [[Set]] is silently dropped, and no ordinary property appears */
{
const t = new Int32Array(4);
for (const k of canonical) {
t[k] = 123;
assert(t[k], undefined, `set ${k}`);
assert(Object.prototype.hasOwnProperty.call(t, k), false, `own ${k}`);
assert(Reflect.set(t, k, 123), true, `Reflect.set ${k}`);
assert(t[k], undefined, `after Reflect.set ${k}`);
}
for (const k of ordinary) {
t[k] = 123;
assert(t[k], 123, `set ${k}`);
assert(Object.prototype.hasOwnProperty.call(t, k), true, `own ${k}`);
}
assert(Object.keys(t).indexOf("NaN"), -1);

/* strict mode does not turn the dropped write into a TypeError */
(function() {
"use strict";
const s = new Int32Array(4);
s.NaN = 1;
s[NaN] = 2;
assert(s.NaN, undefined);
})();
}

/* [[GetOwnProperty]] reports nothing */
{
const t = new Int32Array(4);
for (const k of canonical)
assert(Object.getOwnPropertyDescriptor(t, k), undefined, `gopd ${k}`);
assert(Object.getOwnPropertyNames(t).indexOf("NaN"), -1);
}

/* [[DefineOwnProperty]] fails */
{
const t = new Int32Array(4);
for (const k of canonical) {
assert(Reflect.defineProperty(t, k, { value: 1 }), false,
`defineProperty ${k}`);
let threw = false;
try {
Object.defineProperty(t, k, { value: 1 });
} catch (e) {
threw = e instanceof TypeError;
}
assert(threw, true, `Object.defineProperty ${k}`);
}
/* the non canonical ones define normally */
for (const k of ordinary)
assert(Reflect.defineProperty(t, k, { value: 1 }), true,
`defineProperty ${k}`);
}

/* [[Delete]] on an absent numeric index succeeds */
{
const t = new Int32Array(4);
for (const k of canonical)
assert(delete t[k], true, `delete ${k}`);
assert(delete t[NaN], true);
}

/* every typed array flavour shares the integer-indexed behaviour, including
an out of bounds view, a zero length one and a bigint one */
{
const ctors = [Int8Array, Uint8Array, Uint8ClampedArray, Int16Array,
Uint16Array, Int32Array, Uint32Array, Float16Array,
Float32Array, Float64Array, BigInt64Array, BigUint64Array];
for (const Ctor of ctors) {
const t = new Ctor(0);
assert(t.NaN, undefined, Ctor.name);
assert("NaN" in t, false, Ctor.name);
assert(Reflect.defineProperty(t, "NaN", { value: 1 }), false, Ctor.name);
}
}

/* only integer-indexed exotic objects are affected: ordinary objects, plain
arrays and strings keep "NaN" as an ordinary property */
{
const o = { NaN: 1 };
assert(o.NaN, 1);
assert("NaN" in o, true);
assert(Reflect.defineProperty(o, "NaN", { value: 2 }), true);
assert(o.NaN, 2);

const a = [];
a.NaN = 3;
assert(a.NaN, 3);
assert("NaN" in a, true);
assert(a.length, 0);

assert(Object.getOwnPropertyDescriptor("abc", "NaN"), undefined);
assert("NaN" in Object("abc"), false);
const so = Object("abc");
so.NaN = 4;
assert(so.NaN, 4);

/* an ArrayBuffer is not integer-indexed either */
const b = new ArrayBuffer(8);
b.NaN = 5;
assert(b.NaN, 5);
}

/* A canonical numeric index is not a property, so a store that requires the
property to already exist has nothing to write to. That is what a `with`
binding deleted between the reference being taken and the store looks
like: SetMutableBinding throws a ReferenceError rather than letting the
integer-indexed path swallow the write. `with` needs sloppy mode, which a
module is not, so the scenario is built through an indirect eval. */
{
function vanishing(name) {
const key = JSON.stringify(name);
return (0, eval)(`(function() {
var env = Object.create(new Int32Array(10));
Object.defineProperty(env, ${key}, { configurable: true, value: 100 });
var caught = null;
with (env) {
try {
(function() {
"use strict";
${name} = (delete env[${key}], 0);
})();
} catch (e) { caught = e; }
}
return [caught, Object.getOwnPropertyDescriptor(env, ${key})];
})()`);
}

for (const name of ["NaN", "Infinity"]) {
const [caught, desc] = vanishing(name);
assert(caught instanceof ReferenceError, true, name);
assert(desc, undefined, name);
}

/* an ordinary property name reaches the same ReferenceError by the
ordinary route, so the two agree */
const [caught, desc] = vanishing("ordinary");
assert(caught instanceof ReferenceError, true, "ordinary");
assert(desc, undefined, "ordinary");
}
Loading