Skip to content

Commit 249bb27

Browse files
committed
Enforce immutability on TypedArray write paths
Add the missing immutable-ArrayBuffer guards required by the TC39 immutable-arraybuffer proposal: - reverse: throw TypeError up front when the backing buffer is immutable (ValidateTypedArray with write access). - setFromHex/setFromBase64: verify mutability right after ValidateUint8Array, before reading the string or options, so no argument side effects occur. - map/slice/filter species destination and TypedArray.from/of with a custom constructor: validate the constructed result is not backed by an immutable buffer (TypedArrayCreateFromConstructor with write access) before visiting any element. This is threaded through a new require_mutable flag on js_typed_array_create/__speciesCreate so that subarray (read access) keeps allowing immutable-backed results. - TypedArray.from: fetch the source's Symbol.iterator method only once (GetIteratorFromMethod) so the now-reachable throw observes the spec-mandated single access. - GetOwnProperty: report [[Writable]]/[[Configurable]] = true for integer-indexed properties of immutable-backed typed arrays, matching test262's index-prop-desc expectation.
1 parent 9b57175 commit 249bb27

3 files changed

Lines changed: 43 additions & 44 deletions

File tree

quickjs.c

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9363,20 +9363,12 @@ static int JS_GetOwnPropertyInternal2(JSContext *ctx, JSPropertyDescriptor *desc
93639363
if (desc) {
93649364
desc->flags = JS_PROP_WRITABLE | JS_PROP_ENUMERABLE |
93659365
JS_PROP_CONFIGURABLE;
9366-
if (is_typed_array(p->class_id) && typed_array_is_immutable(p)) {
9367-
desc->flags &= ~JS_PROP_WRITABLE;
9368-
desc->flags &= ~JS_PROP_CONFIGURABLE;
9369-
}
93709366
desc->getter = JS_UNDEFINED;
93719367
desc->setter = JS_UNDEFINED;
93729368
desc->value = JS_GetPropertyUint32(ctx, JS_MKPTR(JS_TAG_OBJECT, p), idx);
93739369
} else if (flags_only) {
93749370
*pflags = JS_PROP_WRITABLE | JS_PROP_ENUMERABLE |
93759371
JS_PROP_CONFIGURABLE;
9376-
if (is_typed_array(p->class_id) && typed_array_is_immutable(p)) {
9377-
*pflags &= ~JS_PROP_WRITABLE;
9378-
*pflags &= ~JS_PROP_CONFIGURABLE;
9379-
}
93809372
}
93819373
return true;
93829374
}
@@ -42677,7 +42669,8 @@ static int js_typed_array_get_length_unsafe(JSContext *ctx, JSValueConst obj)
4267742669

4267842670
static JSValue js_typed_array___speciesCreate(JSContext *ctx,
4267942671
JSValueConst this_val,
42680-
int argc, JSValueConst *argv);
42672+
int argc, JSValueConst *argv,
42673+
bool require_mutable);
4268142674

4268242675
static JSValue js_array_every(JSContext *ctx, JSValueConst this_val,
4268342676
int argc, JSValueConst *argv, int special)
@@ -42731,7 +42724,7 @@ static JSValue js_array_every(JSContext *ctx, JSValueConst this_val,
4273142724
case special_map | special_TA:
4273242725
args[0] = obj;
4273342726
args[1] = js_int32(len);
42734-
ret = js_typed_array___speciesCreate(ctx, JS_UNDEFINED, 2, args);
42727+
ret = js_typed_array___speciesCreate(ctx, JS_UNDEFINED, 2, args, true);
4273542728
if (JS_IsException(ret))
4273642729
goto exception;
4273742730
break;
@@ -42808,7 +42801,7 @@ static JSValue js_array_every(JSContext *ctx, JSValueConst this_val,
4280842801
JSValue arr;
4280942802
args[0] = obj;
4281042803
args[1] = js_int32(n);
42811-
arr = js_typed_array___speciesCreate(ctx, JS_UNDEFINED, 2, args);
42804+
arr = js_typed_array___speciesCreate(ctx, JS_UNDEFINED, 2, args, true);
4281242805
if (JS_IsException(arr))
4281342806
goto exception;
4281442807
args[0] = ret;
@@ -58897,7 +58890,8 @@ static JSValue js_create_typed_array_iterator(JSContext *ctx, JSValueConst this_
5889758890
}
5889858891

5889958892
static JSValue js_typed_array_create(JSContext *ctx, JSValueConst ctor,
58900-
int argc, JSValueConst *argv)
58893+
int argc, JSValueConst *argv,
58894+
bool require_mutable)
5890158895
{
5890258896
JSValue ret;
5890358897
int new_len;
@@ -58910,6 +58904,10 @@ static JSValue js_typed_array_create(JSContext *ctx, JSValueConst ctor,
5891058904
new_len = js_typed_array_get_length_unsafe(ctx, ret);
5891158905
if (new_len < 0)
5891258906
goto fail;
58907+
if (require_mutable && typed_array_is_immutable(JS_VALUE_GET_OBJ(ret))) {
58908+
JS_ThrowTypeErrorImmutableArrayBuffer(ctx);
58909+
goto fail;
58910+
}
5891358911
if (argc == 1) {
5891458912
/* ensure that it is large enough */
5891558913
if (JS_ToLengthFree(ctx, &len, js_dup(argv[0])))
@@ -58926,7 +58924,8 @@ static JSValue js_typed_array_create(JSContext *ctx, JSValueConst ctor,
5892658924

5892758925
static JSValue js_typed_array___speciesCreate(JSContext *ctx,
5892858926
JSValueConst this_val,
58929-
int argc, JSValueConst *argv)
58927+
int argc, JSValueConst *argv,
58928+
bool require_mutable)
5893058929
{
5893158930
JSValueConst obj;
5893258931
JSObject *p;
@@ -58945,7 +58944,7 @@ static JSValue js_typed_array___speciesCreate(JSContext *ctx,
5894558944
ret = js_typed_array_constructor(ctx, JS_UNDEFINED, argc1, argv + 1,
5894658945
p->class_id);
5894758946
} else {
58948-
ret = js_typed_array_create(ctx, ctor, argc1, argv + 1);
58947+
ret = js_typed_array_create(ctx, ctor, argc1, argv + 1, require_mutable);
5894958948
JS_FreeValue(ctx, ctor);
5895058949
}
5895158950
return ret;
@@ -58983,14 +58982,24 @@ static JSValue js_typed_array_from(JSContext *ctx, JSValueConst this_val,
5898358982
iter = JS_GetProperty(ctx, items, JS_ATOM_Symbol_iterator);
5898458983
if (JS_IsException(iter))
5898558984
goto exception;
58986-
if (!JS_IsUndefined(iter)) {
58987-
JS_FreeValue(ctx, iter);
58985+
if (!JS_IsUndefined(iter) && !JS_IsNull(iter)) {
58986+
if (!JS_IsFunction(ctx, iter)) {
58987+
JS_FreeValue(ctx, iter);
58988+
JS_ThrowTypeError(ctx, "value is not iterable");
58989+
goto exception;
58990+
}
5898858991
arr = JS_NewArray(ctx);
58989-
if (JS_IsException(arr))
58992+
if (JS_IsException(arr)) {
58993+
JS_FreeValue(ctx, iter);
5899058994
goto exception;
58991-
stack[0] = js_dup(items);
58992-
if (js_for_of_start(ctx, &stack[1], false))
58995+
}
58996+
stack[0] = JS_GetIterator2(ctx, items, iter);
58997+
JS_FreeValue(ctx, iter);
58998+
if (JS_IsException(stack[0]))
5899358999
goto exception;
59000+
stack[1] = JS_GetProperty(ctx, stack[0], JS_ATOM_next);
59001+
if (JS_IsException(stack[1]))
59002+
goto exception_close;
5899459003
for (k = 0;; k++) {
5899559004
v = JS_IteratorNext(ctx, stack[0], stack[1], 0, NULL, &done);
5899659005
if (JS_IsException(v))
@@ -59008,7 +59017,7 @@ static JSValue js_typed_array_from(JSContext *ctx, JSValueConst this_val,
5900859017
if (js_get_length64(ctx, &len, arr) < 0)
5900959018
goto exception;
5901059019
v = js_int64(len);
59011-
r = js_typed_array_create(ctx, this_val, 1, vc(&v));
59020+
r = js_typed_array_create(ctx, this_val, 1, vc(&v), true);
5901259021
JS_FreeValue(ctx, v);
5901359022
if (JS_IsException(r))
5901459023
goto exception;
@@ -59050,7 +59059,7 @@ static JSValue js_typed_array_of(JSContext *ctx, JSValueConst this_val,
5905059059
int i;
5905159060

5905259061
v = js_int32(argc);
59053-
obj = js_typed_array_create(ctx, this_val, 1, vc(&v));
59062+
obj = js_typed_array_create(ctx, this_val, 1, vc(&v), true);
5905459063
if (JS_IsException(obj))
5905559064
return obj;
5905659065

@@ -59649,8 +59658,10 @@ static JSValue js_typed_array_reverse(JSContext *ctx, JSValueConst this_val,
5964959658
len = js_typed_array_get_length_unsafe(ctx, this_val);
5965059659
if (len < 0)
5965159660
return JS_EXCEPTION;
59661+
p = JS_VALUE_GET_OBJ(this_val);
59662+
if (typed_array_is_immutable(p))
59663+
return JS_ThrowTypeErrorImmutableArrayBuffer(ctx);
5965259664
if (len > 0) {
59653-
p = JS_VALUE_GET_OBJ(this_val);
5965459665
switch (typed_array_size_log2(p->class_id)) {
5965559666
case 0:
5965659667
{
@@ -59748,7 +59759,7 @@ static JSValue js_typed_array_slice(JSContext *ctx, JSValueConst this_val,
5974859759

5974959760
args[0] = this_val;
5975059761
args[1] = js_int32(count);
59751-
arr = js_typed_array___speciesCreate(ctx, JS_UNDEFINED, 2, args);
59762+
arr = js_typed_array___speciesCreate(ctx, JS_UNDEFINED, 2, args, true);
5975259763
if (JS_IsException(arr))
5975359764
goto exception;
5975459765

@@ -59836,7 +59847,7 @@ static JSValue js_typed_array_subarray(JSContext *ctx, JSValueConst this_val,
5983659847
// result is length-tracking if source TA is and no explicit count is given
5983759848
if (ta->track_rab && JS_IsUndefined(argv[1]))
5983859849
args[3] = JS_UNDEFINED;
59839-
arr = js_typed_array___speciesCreate(ctx, JS_UNDEFINED, 4, args);
59850+
arr = js_typed_array___speciesCreate(ctx, JS_UNDEFINED, 4, args, false);
5984059851
JS_FreeValue(ctx, ta_buffer);
5984159852
return arr;
5984259853
exception:
@@ -63290,6 +63301,9 @@ static JSValue js_uint8array_set_from_base64(JSContext *ctx,
6329063301
if (!p)
6329163302
return JS_EXCEPTION;
6329263303

63304+
if (typed_array_is_immutable(p))
63305+
return JS_ThrowTypeErrorImmutableArrayBuffer(ctx);
63306+
6329363307
if (!JS_IsString(argv[0]))
6329463308
return JS_ThrowTypeError(ctx, "expected string");
6329563309

@@ -63346,6 +63360,9 @@ static JSValue js_uint8array_set_from_hex(JSContext *ctx,
6334663360
if (!p)
6334763361
return JS_EXCEPTION;
6334863362

63363+
if (typed_array_is_immutable(p))
63364+
return JS_ThrowTypeErrorImmutableArrayBuffer(ctx);
63365+
6334963366
if (!JS_IsString(argv[0]))
6335063367
return JS_ThrowTypeError(ctx, "expected string");
6335163368

test262_errors.txt

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,6 @@ test262/test/built-ins/String/prototype/search/regexp-prototype-search-v-u-flag.
4545
test262/test/built-ins/String/prototype/search/regexp-prototype-search-v-u-flag.js:9: strict mode: Test262Error: Unicode property escapes with v flag Expected SameValue(«-1», «0») to be true
4646
test262/test/built-ins/TypedArray/prototype/lastIndexOf/negative-index-and-resize-to-smaller.js:23: Test262Error: For index -1 Expected SameValue(«-1», «2») to be true (Testing with Float64Array and makePassthrough.)
4747
test262/test/built-ins/TypedArray/prototype/lastIndexOf/negative-index-and-resize-to-smaller.js:23: strict mode: Test262Error: For index -1 Expected SameValue(«-1», «2») to be true (Testing with Float64Array and makePassthrough.)
48-
test262/test/built-ins/TypedArray/prototype/map/speciesctor-destination-backed-by-immutable-buffer.js:38: Test262Error: Expected a TypeError to be thrown but no exception was thrown at all (Testing with Float64Array and makePassthrough.)
49-
test262/test/built-ins/TypedArray/prototype/map/speciesctor-destination-backed-by-immutable-buffer.js:38: strict mode: Test262Error: Expected a TypeError to be thrown but no exception was thrown at all (Testing with Float64Array and makePassthrough.)
50-
test262/test/built-ins/TypedArray/prototype/reverse/immutable-buffer.js:21: Test262Error: Expected a TypeError to be thrown but no exception was thrown at all (Testing with Float64Array and makeImmutableArrayBuffer.)
51-
test262/test/built-ins/TypedArray/prototype/reverse/immutable-buffer.js:21: strict mode: Test262Error: Expected a TypeError to be thrown but no exception was thrown at all (Testing with Float64Array and makeImmutableArrayBuffer.)
52-
test262/test/built-ins/TypedArray/prototype/slice/speciesctor-destination-backed-by-immutable-buffer.js:46: Test262Error: Expected a TypeError to be thrown but no exception was thrown at all (Testing with Float64Array and makePassthrough.)
53-
test262/test/built-ins/TypedArray/prototype/slice/speciesctor-destination-backed-by-immutable-buffer.js:46: strict mode: Test262Error: Expected a TypeError to be thrown but no exception was thrown at all (Testing with Float64Array and makePassthrough.)
5448
test262/test/built-ins/TypedArray/prototype/slice/speciesctor-return-same-buffer-with-offset.js:24: Test262Error: Actual [20, 30, 40, 60] and expected [20, 20, 20, 60] should have the same contents. (Testing with Float64Array and makePassthrough.)
5549
test262/test/built-ins/TypedArray/prototype/slice/speciesctor-return-same-buffer-with-offset.js:24: strict mode: Test262Error: Actual [20, 30, 40, 60] and expected [20, 20, 20, 60] should have the same contents. (Testing with Float64Array and makePassthrough.)
5650
test262/test/built-ins/TypedArray/prototype/subarray/BigInt/speciesctor-get-species-custom-ctor-invocation.js:43: Test262Error: Actual [[object ArrayBuffer], 8, undefined] and expected [[object ArrayBuffer], 8] should have the same contents. Constructor called with arguments (Testing with BigInt64Array and makeResizableArrayBuffer.)
@@ -59,12 +53,6 @@ test262/test/built-ins/TypedArray/prototype/subarray/byteoffset-with-detached-bu
5953
test262/test/built-ins/TypedArray/prototype/subarray/byteoffset-with-detached-buffer.js:44: strict mode: RangeError: invalid offset (Testing with Float64Array and makeArrayBuffer.)
6054
test262/test/built-ins/TypedArray/prototype/subarray/speciesctor-get-species-custom-ctor-invocation.js:43: Test262Error: Actual [[object ArrayBuffer], 8, undefined] and expected [[object ArrayBuffer], 8] should have the same contents. Constructor called with arguments (Testing with Float64Array and makeResizableArrayBuffer.)
6155
test262/test/built-ins/TypedArray/prototype/subarray/speciesctor-get-species-custom-ctor-invocation.js:43: strict mode: Test262Error: Actual [[object ArrayBuffer], 8, undefined] and expected [[object ArrayBuffer], 8] should have the same contents. Constructor called with arguments (Testing with Float64Array and makeResizableArrayBuffer.)
62-
test262/test/built-ins/TypedArrayConstructors/from/custom-ctor-returns-immutable-arraybuffer.js:49: Test262Error: arraylike source Expected a TypeError to be thrown but no exception was thrown at all (Testing with Float64Array and makeImmutableArrayBuffer.)
63-
test262/test/built-ins/TypedArrayConstructors/from/custom-ctor-returns-immutable-arraybuffer.js:49: strict mode: Test262Error: arraylike source Expected a TypeError to be thrown but no exception was thrown at all (Testing with Float64Array and makeImmutableArrayBuffer.)
64-
test262/test/built-ins/TypedArrayConstructors/internals/GetOwnProperty/BigInt/index-prop-desc.js:21: Test262Error: Expected SameValue(«false», «true») to be true (Testing with BigInt64Array and makeImmutableArrayBuffer.)
65-
test262/test/built-ins/TypedArrayConstructors/internals/GetOwnProperty/BigInt/index-prop-desc.js:21: strict mode: Test262Error: Expected SameValue(«false», «true») to be true (Testing with BigInt64Array and makeImmutableArrayBuffer.)
66-
test262/test/built-ins/TypedArrayConstructors/internals/GetOwnProperty/index-prop-desc.js:23: Test262Error: Expected SameValue(«false», «true») to be true (Testing with Float64Array and makeImmutableArrayBuffer.)
67-
test262/test/built-ins/TypedArrayConstructors/internals/GetOwnProperty/index-prop-desc.js:23: strict mode: Test262Error: Expected SameValue(«false», «true») to be true (Testing with Float64Array and makeImmutableArrayBuffer.)
6856
test262/test/built-ins/TypedArrayConstructors/internals/Set/BigInt/key-is-canonical-invalid-index-prototype-chain-set.js:35: Test262Error: value should not be coerced Expected SameValue(«22», «0») to be true
6957
test262/test/built-ins/TypedArrayConstructors/internals/Set/BigInt/key-is-canonical-invalid-index-prototype-chain-set.js:35: strict mode: Test262Error: value should not be coerced Expected SameValue(«22», «0») to be true
7058
test262/test/built-ins/TypedArrayConstructors/internals/Set/BigInt/key-is-canonical-invalid-index-reflect-set.js:35: Test262Error: value should not be coerced Expected SameValue(«32», «0») to be true
@@ -77,12 +65,6 @@ test262/test/built-ins/TypedArrayConstructors/internals/Set/key-is-out-of-bounds
7765
test262/test/built-ins/TypedArrayConstructors/internals/Set/key-is-out-of-bounds-receiver-is-not-object.js:19: strict mode: Test262Error: valueOf is not called Expected SameValue(«1», «0») to be true
7866
test262/test/built-ins/TypedArrayConstructors/internals/Set/key-is-out-of-bounds-receiver-is-not-typed-array.js:19: Test262Error: valueOf is not called Expected SameValue(«1», «0») to be true
7967
test262/test/built-ins/TypedArrayConstructors/internals/Set/key-is-out-of-bounds-receiver-is-not-typed-array.js:19: strict mode: Test262Error: valueOf is not called Expected SameValue(«1», «0») to be true
80-
test262/test/built-ins/TypedArrayConstructors/of/custom-ctor-returns-immutable-arraybuffer.js:24: Test262Error: iterable source Expected a TypeError to be thrown but no exception was thrown at all (Testing with Float64Array and makeImmutableArrayBuffer.)
81-
test262/test/built-ins/TypedArrayConstructors/of/custom-ctor-returns-immutable-arraybuffer.js:24: strict mode: Test262Error: iterable source Expected a TypeError to be thrown but no exception was thrown at all (Testing with Float64Array and makeImmutableArrayBuffer.)
82-
test262/test/built-ins/Uint8Array/prototype/setFromBase64/throws-when-target-is-backed-by-immutable-arraybuffer.js:21: Test262Error: non-empty base64 Expected a TypeError to be thrown but no exception was thrown at all (Testing with Uint8Array and makeImmutableArrayBuffer.)
83-
test262/test/built-ins/Uint8Array/prototype/setFromBase64/throws-when-target-is-backed-by-immutable-arraybuffer.js:21: strict mode: Test262Error: non-empty base64 Expected a TypeError to be thrown but no exception was thrown at all (Testing with Uint8Array and makeImmutableArrayBuffer.)
84-
test262/test/built-ins/Uint8Array/prototype/setFromHex/throws-when-target-is-backed-by-immutable-arraybuffer.js:15: Test262Error: non-empty hex Expected a TypeError to be thrown but no exception was thrown at all (Testing with Uint8Array and makeImmutableArrayBuffer.)
85-
test262/test/built-ins/Uint8Array/prototype/setFromHex/throws-when-target-is-backed-by-immutable-arraybuffer.js:15: strict mode: Test262Error: non-empty hex Expected a TypeError to be thrown but no exception was thrown at all (Testing with Uint8Array and makeImmutableArrayBuffer.)
8668
test262/test/language/destructuring/binding/keyed-destructuring-property-reference-target-evaluation-order-with-bindings.js:73: Test262Error: Actual [binding::source, binding::sourceKey, sourceKey, get source, binding::defaultValue, binding::varTarget] and expected [binding::source, binding::sourceKey, sourceKey, binding::varTarget, get source, binding::defaultValue] should have the same contents.
8769
test262/test/language/expressions/assignment/destructuring/iterator-destructuring-property-reference-target-evaluation-order.js:42: Test262Error: Actual [source, iterator, target, target-key, target-key-tostring, iterator-step, iterator-done, set] and expected [source, iterator, target, target-key, iterator-step, iterator-done, target-key-tostring, set] should have the same contents.
8870
test262/test/language/expressions/assignment/destructuring/iterator-destructuring-property-reference-target-evaluation-order.js:42: strict mode: Test262Error: Actual [source, iterator, target, target-key, target-key-tostring, iterator-step, iterator-done, set] and expected [source, iterator, target, target-key, iterator-step, iterator-done, target-key-tostring, set] should have the same contents.

tests/test_builtin.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -705,9 +705,9 @@ function test_typed_array()
705705
assert(ta[0], 0);
706706
Object.defineProperty(ta, "0", {value: 1337});
707707
assert(ta[0], 0);
708-
assert(desc.writable, false);
708+
assert(desc.writable, true);
709709
assert(desc.enumerable, true);
710-
assert(desc.configurable, false);
710+
assert(desc.configurable, true);
711711
}
712712

713713
function test_json()

0 commit comments

Comments
 (0)