Skip to content

Commit d38e30d

Browse files
mkustermannCommit Queue
authored andcommitted
[dart2wasm] Fix bug in JS typed data .sublist() implementations
Issue flutter/flutter#179853 Change-Id: I97ce8d7177dfdd612714fafbc2a6b13555d9bba1 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/468300 Reviewed-by: Ömer Ağacan <[email protected]> Commit-Queue: Martin Kustermann <[email protected]>
1 parent 10373c3 commit d38e30d

File tree

2 files changed

+208
-86
lines changed

2 files changed

+208
-86
lines changed

sdk/lib/_internal/wasm/lib/js_typed_array.dart

Lines changed: 54 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -891,14 +891,12 @@ final class JSUint8ArrayImpl extends JSIntegerArrayBase
891891

892892
@override
893893
JSUint8ArrayImpl sublist(int start, [int? end]) {
894-
final newOffset = offsetInBytes + start;
895-
final newEnd = RangeErrorUtils.checkValidRange(
896-
newOffset,
897-
end,
898-
lengthInBytes,
894+
final newEnd = RangeErrorUtils.checkValidRange(start, end, length);
895+
final newOffsetInBytes = offsetInBytes + start;
896+
final newLengthInBytes = newEnd - start;
897+
return JSUint8ArrayImpl._(
898+
buffer.cloneAsDataView(newOffsetInBytes, newLengthInBytes),
899899
);
900-
final newLength = newEnd - newOffset;
901-
return JSUint8ArrayImpl._(buffer.cloneAsDataView(newOffset, newLength));
902900
}
903901

904902
@override
@@ -999,14 +997,12 @@ final class JSInt8ArrayImpl extends JSIntegerArrayBase
999997

1000998
@override
1001999
JSInt8ArrayImpl sublist(int start, [int? end]) {
1002-
final newOffset = offsetInBytes + start;
1003-
final newEnd = RangeErrorUtils.checkValidRange(
1004-
newOffset,
1005-
end,
1006-
lengthInBytes,
1000+
final newEnd = RangeErrorUtils.checkValidRange(start, end, length);
1001+
final newOffsetInBytes = offsetInBytes + start;
1002+
final newLengthInBytes = newEnd - start;
1003+
return JSInt8ArrayImpl._(
1004+
buffer.cloneAsDataView(newOffsetInBytes, newLengthInBytes),
10071005
);
1008-
final newLength = newEnd - newOffset;
1009-
return JSInt8ArrayImpl._(buffer.cloneAsDataView(newOffset, newLength));
10101006
}
10111007

10121008
@override
@@ -1109,15 +1105,11 @@ final class JSUint8ClampedArrayImpl extends JSIntegerArrayBase
11091105

11101106
@override
11111107
JSUint8ClampedArrayImpl sublist(int start, [int? end]) {
1112-
final newOffset = offsetInBytes + start;
1113-
final newEnd = RangeErrorUtils.checkValidRange(
1114-
newOffset,
1115-
end,
1116-
lengthInBytes,
1117-
);
1118-
final newLength = newEnd - newOffset;
1108+
final newEnd = RangeErrorUtils.checkValidRange(start, end, length);
1109+
final newOffsetInBytes = offsetInBytes + start;
1110+
final newLengthInBytes = newEnd - start;
11191111
return JSUint8ClampedArrayImpl._(
1120-
buffer.cloneAsDataView(newOffset, newLength),
1112+
buffer.cloneAsDataView(newOffsetInBytes, newLengthInBytes),
11211113
);
11221114
}
11231115

@@ -1211,15 +1203,12 @@ final class JSUint16ArrayImpl extends JSIntegerArrayBase
12111203

12121204
@override
12131205
JSUint16ArrayImpl sublist(int start, [int? end]) {
1214-
final int newOffset = offsetInBytes + (start * 2);
1215-
final int newEnd = end == null ? lengthInBytes : end * 2;
1216-
final int newLength = newEnd - newOffset;
1217-
RangeErrorUtils.checkValidRange(
1218-
newOffset ~/ 2,
1219-
newEnd ~/ 2,
1220-
lengthInBytes ~/ 2,
1206+
final newEnd = RangeErrorUtils.checkValidRange(start, end, length);
1207+
final newOffsetInBytes = offsetInBytes + 2 * start;
1208+
final newLengthInBytes = 2 * (newEnd - start);
1209+
return JSUint16ArrayImpl._(
1210+
buffer.cloneAsDataView(newOffsetInBytes, newLengthInBytes),
12211211
);
1222-
return JSUint16ArrayImpl._(buffer.cloneAsDataView(newOffset, newLength));
12231212
}
12241213

12251214
@override
@@ -1327,15 +1316,12 @@ final class JSInt16ArrayImpl extends JSIntegerArrayBase
13271316

13281317
@override
13291318
JSInt16ArrayImpl sublist(int start, [int? end]) {
1330-
final int newOffset = offsetInBytes + (start * 2);
1331-
final int newEnd = end == null ? lengthInBytes : end * 2;
1332-
final int newLength = newEnd - newOffset;
1333-
RangeErrorUtils.checkValidRange(
1334-
newOffset ~/ 2,
1335-
newEnd ~/ 2,
1336-
lengthInBytes ~/ 2,
1319+
final newEnd = RangeErrorUtils.checkValidRange(start, end, length);
1320+
final newOffsetInBytes = offsetInBytes + 2 * start;
1321+
final newLengthInBytes = 2 * (newEnd - start);
1322+
return JSInt16ArrayImpl._(
1323+
buffer.cloneAsDataView(newOffsetInBytes, newLengthInBytes),
13371324
);
1338-
return JSInt16ArrayImpl._(buffer.cloneAsDataView(newOffset, newLength));
13391325
}
13401326

13411327
@override
@@ -1443,15 +1429,12 @@ final class JSUint32ArrayImpl extends JSIntegerArrayBase
14431429

14441430
@override
14451431
JSUint32ArrayImpl sublist(int start, [int? end]) {
1446-
final int newOffset = offsetInBytes + (start * 4);
1447-
final int newEnd = end == null ? lengthInBytes : end * 4;
1448-
final int newLength = newEnd - newOffset;
1449-
RangeErrorUtils.checkValidRange(
1450-
newOffset ~/ 4,
1451-
newEnd ~/ 4,
1452-
lengthInBytes ~/ 4,
1432+
final newEnd = RangeErrorUtils.checkValidRange(start, end, length);
1433+
final newOffsetInBytes = offsetInBytes + 4 * start;
1434+
final newLengthInBytes = 4 * (newEnd - start);
1435+
return JSUint32ArrayImpl._(
1436+
buffer.cloneAsDataView(newOffsetInBytes, newLengthInBytes),
14531437
);
1454-
return JSUint32ArrayImpl._(buffer.cloneAsDataView(newOffset, newLength));
14551438
}
14561439

14571440
@override
@@ -1559,15 +1542,12 @@ final class JSInt32ArrayImpl extends JSIntegerArrayBase
15591542

15601543
@override
15611544
JSInt32ArrayImpl sublist(int start, [int? end]) {
1562-
final int newOffset = offsetInBytes + (start * 4);
1563-
final int newEnd = end == null ? lengthInBytes : end * 4;
1564-
final int newLength = newEnd - newOffset;
1565-
RangeErrorUtils.checkValidRange(
1566-
newOffset ~/ 4,
1567-
newEnd ~/ 4,
1568-
lengthInBytes ~/ 4,
1545+
final newEnd = RangeErrorUtils.checkValidRange(start, end, length);
1546+
final newOffsetInBytes = offsetInBytes + 4 * start;
1547+
final newLengthInBytes = 4 * (newEnd - start);
1548+
return JSInt32ArrayImpl._(
1549+
buffer.cloneAsDataView(newOffsetInBytes, newLengthInBytes),
15691550
);
1570-
return JSInt32ArrayImpl._(buffer.cloneAsDataView(newOffset, newLength));
15711551
}
15721552

15731553
@override
@@ -1756,15 +1736,12 @@ final class JSBigUint64ArrayImpl extends JSIntegerArrayBase
17561736

17571737
@override
17581738
JSBigUint64ArrayImpl sublist(int start, [int? end]) {
1759-
final int newOffset = offsetInBytes + (start * 8);
1760-
final int newEnd = end == null ? lengthInBytes : end * 8;
1761-
final int newLength = newEnd - newOffset;
1762-
RangeErrorUtils.checkValidRange(
1763-
newOffset ~/ 8,
1764-
newEnd ~/ 8,
1765-
lengthInBytes ~/ 8,
1739+
final newEnd = RangeErrorUtils.checkValidRange(start, end, length);
1740+
final newOffsetInBytes = offsetInBytes + 8 * start;
1741+
final newLengthInBytes = 8 * (newEnd - start);
1742+
return JSBigUint64ArrayImpl._(
1743+
buffer.cloneAsDataView(newOffsetInBytes, newLengthInBytes),
17661744
);
1767-
return JSBigUint64ArrayImpl._(buffer.cloneAsDataView(newOffset, newLength));
17681745
}
17691746

17701747
@override
@@ -1860,15 +1837,12 @@ final class JSBigInt64ArrayImpl extends JSIntegerArrayBase
18601837

18611838
@override
18621839
JSBigInt64ArrayImpl sublist(int start, [int? end]) {
1863-
final int newOffset = offsetInBytes + (start * 8);
1864-
final int newEnd = end == null ? lengthInBytes : end * 8;
1865-
final int newLength = newEnd - newOffset;
1866-
RangeErrorUtils.checkValidRange(
1867-
newOffset ~/ 8,
1868-
newEnd ~/ 8,
1869-
lengthInBytes ~/ 8,
1840+
final newEnd = RangeErrorUtils.checkValidRange(start, end, length);
1841+
final newOffsetInBytes = offsetInBytes + 8 * start;
1842+
final newLengthInBytes = 8 * (newEnd - start);
1843+
return JSBigInt64ArrayImpl._(
1844+
buffer.cloneAsDataView(newOffsetInBytes, newLengthInBytes),
18701845
);
1871-
return JSBigInt64ArrayImpl._(buffer.cloneAsDataView(newOffset, newLength));
18721846
}
18731847

18741848
@override
@@ -2321,15 +2295,12 @@ final class JSFloat32ArrayImpl extends JSFloatArrayBase
23212295

23222296
@override
23232297
JSFloat32ArrayImpl sublist(int start, [int? end]) {
2324-
final int newOffset = offsetInBytes + (start * 4);
2325-
final int newEnd = end == null ? lengthInBytes : end * 4;
2326-
final int newLength = newEnd - newOffset;
2327-
RangeErrorUtils.checkValidRange(
2328-
newOffset ~/ 4,
2329-
newEnd ~/ 4,
2330-
lengthInBytes ~/ 4,
2298+
final newEnd = RangeErrorUtils.checkValidRange(start, end, length);
2299+
final newOffsetInBytes = offsetInBytes + 4 * start;
2300+
final newLengthInBytes = 4 * (newEnd - start);
2301+
return JSFloat32ArrayImpl._(
2302+
buffer.cloneAsDataView(newOffsetInBytes, newLengthInBytes),
23312303
);
2332-
return JSFloat32ArrayImpl._(buffer.cloneAsDataView(newOffset, newLength));
23332304
}
23342305

23352306
@override
@@ -2438,15 +2409,12 @@ final class JSFloat64ArrayImpl extends JSFloatArrayBase
24382409

24392410
@override
24402411
JSFloat64ArrayImpl sublist(int start, [int? end]) {
2441-
final int newOffset = offsetInBytes + (start * 8);
2442-
final int newEnd = end == null ? lengthInBytes : end * 8;
2443-
final int newLength = newEnd - newOffset;
2444-
RangeErrorUtils.checkValidRange(
2445-
newOffset ~/ 8,
2446-
newEnd ~/ 8,
2447-
lengthInBytes ~/ 8,
2412+
final newEnd = RangeErrorUtils.checkValidRange(start, end, length);
2413+
final newOffsetInBytes = offsetInBytes + 8 * start;
2414+
final newLengthInBytes = 8 * (newEnd - start);
2415+
return JSFloat64ArrayImpl._(
2416+
buffer.cloneAsDataView(newOffsetInBytes, newLengthInBytes),
24482417
);
2449-
return JSFloat64ArrayImpl._(buffer.cloneAsDataView(newOffset, newLength));
24502418
}
24512419

24522420
@override
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:js_interop';
6+
import 'dart:typed_data';
7+
8+
import 'package:expect/expect.dart';
9+
10+
void main() {
11+
const size = 128;
12+
for (final buffer in createBuffers(128)) {
13+
Expect.equals(size, buffer.lengthInBytes);
14+
initData(buffer, size);
15+
forEachTypedData((viewConstructor, lengthFun, sublistFun, elementSize) {
16+
final sizeInElements = size ~/ elementSize;
17+
18+
final view = viewConstructor(
19+
buffer,
20+
/*offsetInBytes=*/ elementSize,
21+
/*length=*/ sizeInElements - 2,
22+
);
23+
print(
24+
'view: ${view.runtimeType} offsetInBytes:${view.offsetInBytes} len=${lengthFun(view)}',
25+
);
26+
Expect.equals(elementSize, view.offsetInBytes);
27+
Expect.equals(sizeInElements - 2, lengthFun(view));
28+
Expect.equals((sizeInElements - 2) * elementSize, view.lengthInBytes);
29+
verifyData(view, elementSize, size - 2 * elementSize);
30+
31+
// This creates a new list.
32+
final sublist = sublistFun(
33+
view,
34+
/*start=*/ 1,
35+
/*end=*/ sizeInElements - 3,
36+
);
37+
Expect.equals(0, sublist.offsetInBytes);
38+
Expect.equals(sizeInElements - 4, lengthFun(sublist));
39+
Expect.equals((sizeInElements - 4) * elementSize, sublist.lengthInBytes);
40+
41+
verifyData(sublist, 2 * elementSize, size - 4 * elementSize);
42+
});
43+
}
44+
}
45+
46+
void forEachTypedData(
47+
void Function(
48+
TypedData Function(ByteBuffer, [int, int?]),
49+
int Function(TypedData),
50+
TypedData Function(TypedData, int, int?),
51+
int,
52+
)
53+
fun,
54+
) {
55+
fun(
56+
Uint8List.view,
57+
(td) => (td as Uint8List).length,
58+
(td, start, end) => (td as Uint8List).sublist(start, end),
59+
1,
60+
);
61+
fun(
62+
Int8List.view,
63+
(td) => (td as Int8List).length,
64+
(td, start, end) => (td as Int8List).sublist(start, end),
65+
1,
66+
);
67+
fun(
68+
Uint8ClampedList.view,
69+
(td) => (td as Uint8ClampedList).length,
70+
(td, start, end) => (td as Uint8ClampedList).sublist(start, end),
71+
1,
72+
);
73+
fun(
74+
Uint16List.view,
75+
(td) => (td as Uint16List).length,
76+
(td, start, end) => (td as Uint16List).sublist(start, end),
77+
2,
78+
);
79+
fun(
80+
Int16List.view,
81+
(td) => (td as Int16List).length,
82+
(td, start, end) => (td as Int16List).sublist(start, end),
83+
2,
84+
);
85+
fun(
86+
Uint32List.view,
87+
(td) => (td as Uint32List).length,
88+
(td, start, end) => (td as Uint32List).sublist(start, end),
89+
4,
90+
);
91+
fun(
92+
Int32List.view,
93+
(td) => (td as Int32List).length,
94+
(td, start, end) => (td as Int32List).sublist(start, end),
95+
4,
96+
);
97+
fun(
98+
Float32List.view,
99+
(td) => (td as Float32List).length,
100+
(td, start, end) => (td as Float32List).sublist(start, end),
101+
4,
102+
);
103+
fun(
104+
Float64List.view,
105+
(td) => (td as Float64List).length,
106+
(td, start, end) => (td as Float64List).sublist(start, end),
107+
8,
108+
);
109+
}
110+
111+
List<ByteBuffer> createBuffers(int size) {
112+
return [
113+
// JS backed.
114+
JSArrayBuffer(size).toDart,
115+
JSUint8Array.withLength(size).toDart.buffer,
116+
JSInt8Array.withLength(size).toDart.buffer,
117+
JSUint8ClampedArray.withLength(size).toDart.buffer,
118+
JSUint16Array.withLength(size ~/ 2).toDart.buffer,
119+
JSInt16Array.withLength(size ~/ 2).toDart.buffer,
120+
JSUint32Array.withLength(size ~/ 4).toDart.buffer,
121+
JSInt32Array.withLength(size ~/ 4).toDart.buffer,
122+
JSFloat32Array.withLength(size ~/ 4).toDart.buffer,
123+
JSFloat64Array.withLength(size ~/ 8).toDart.buffer,
124+
125+
// Dart backed.
126+
Uint8List(size).buffer,
127+
Int8List(size).buffer,
128+
Uint8ClampedList(size).buffer,
129+
Uint16List(size ~/ 2).buffer,
130+
Int16List(size ~/ 2).buffer,
131+
Uint32List(size ~/ 4).buffer,
132+
Int32List(size ~/ 4).buffer,
133+
Int64List(size ~/ 8).buffer,
134+
Float32List(size ~/ 4).buffer,
135+
Float64List(size ~/ 8).buffer,
136+
Float32x4List(size ~/ 16).buffer,
137+
Float64x2List(size ~/ 16).buffer,
138+
];
139+
}
140+
141+
void initData(ByteBuffer buffer, int length) {
142+
final td = buffer.asUint8List();
143+
for (int i = 0; i < length; i++) {
144+
td[i] = i;
145+
}
146+
}
147+
148+
void verifyData(TypedData typedData, int offset, int length) {
149+
final td = typedData.buffer.asUint8List(typedData.offsetInBytes);
150+
print('verifyData($offset, $length) ');
151+
for (int i = 0; i < length; i++) {
152+
Expect.equals(offset + i, td[i]);
153+
}
154+
}

0 commit comments

Comments
 (0)