[dart2wasm] Fix bug in JS typed data .sublist() implementations

Issue https://github.com/flutter/flutter/issues/179853

Change-Id: I97ce8d7177dfdd612714fafbc2a6b13555d9bba1
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/468300
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
This commit is contained in:
Martin Kustermann
2025-12-15 06:46:18 -08:00
committed by Commit Queue
parent 10373c30e8
commit d38e30d65a
2 changed files with 208 additions and 86 deletions
+54 -86
View File
@@ -891,14 +891,12 @@ final class JSUint8ArrayImpl extends JSIntegerArrayBase
@override
JSUint8ArrayImpl sublist(int start, [int? end]) {
final newOffset = offsetInBytes + start;
final newEnd = RangeErrorUtils.checkValidRange(
newOffset,
end,
lengthInBytes,
final newEnd = RangeErrorUtils.checkValidRange(start, end, length);
final newOffsetInBytes = offsetInBytes + start;
final newLengthInBytes = newEnd - start;
return JSUint8ArrayImpl._(
buffer.cloneAsDataView(newOffsetInBytes, newLengthInBytes),
);
final newLength = newEnd - newOffset;
return JSUint8ArrayImpl._(buffer.cloneAsDataView(newOffset, newLength));
}
@override
@@ -999,14 +997,12 @@ final class JSInt8ArrayImpl extends JSIntegerArrayBase
@override
JSInt8ArrayImpl sublist(int start, [int? end]) {
final newOffset = offsetInBytes + start;
final newEnd = RangeErrorUtils.checkValidRange(
newOffset,
end,
lengthInBytes,
final newEnd = RangeErrorUtils.checkValidRange(start, end, length);
final newOffsetInBytes = offsetInBytes + start;
final newLengthInBytes = newEnd - start;
return JSInt8ArrayImpl._(
buffer.cloneAsDataView(newOffsetInBytes, newLengthInBytes),
);
final newLength = newEnd - newOffset;
return JSInt8ArrayImpl._(buffer.cloneAsDataView(newOffset, newLength));
}
@override
@@ -1109,15 +1105,11 @@ final class JSUint8ClampedArrayImpl extends JSIntegerArrayBase
@override
JSUint8ClampedArrayImpl sublist(int start, [int? end]) {
final newOffset = offsetInBytes + start;
final newEnd = RangeErrorUtils.checkValidRange(
newOffset,
end,
lengthInBytes,
);
final newLength = newEnd - newOffset;
final newEnd = RangeErrorUtils.checkValidRange(start, end, length);
final newOffsetInBytes = offsetInBytes + start;
final newLengthInBytes = newEnd - start;
return JSUint8ClampedArrayImpl._(
buffer.cloneAsDataView(newOffset, newLength),
buffer.cloneAsDataView(newOffsetInBytes, newLengthInBytes),
);
}
@@ -1211,15 +1203,12 @@ final class JSUint16ArrayImpl extends JSIntegerArrayBase
@override
JSUint16ArrayImpl sublist(int start, [int? end]) {
final int newOffset = offsetInBytes + (start * 2);
final int newEnd = end == null ? lengthInBytes : end * 2;
final int newLength = newEnd - newOffset;
RangeErrorUtils.checkValidRange(
newOffset ~/ 2,
newEnd ~/ 2,
lengthInBytes ~/ 2,
final newEnd = RangeErrorUtils.checkValidRange(start, end, length);
final newOffsetInBytes = offsetInBytes + 2 * start;
final newLengthInBytes = 2 * (newEnd - start);
return JSUint16ArrayImpl._(
buffer.cloneAsDataView(newOffsetInBytes, newLengthInBytes),
);
return JSUint16ArrayImpl._(buffer.cloneAsDataView(newOffset, newLength));
}
@override
@@ -1327,15 +1316,12 @@ final class JSInt16ArrayImpl extends JSIntegerArrayBase
@override
JSInt16ArrayImpl sublist(int start, [int? end]) {
final int newOffset = offsetInBytes + (start * 2);
final int newEnd = end == null ? lengthInBytes : end * 2;
final int newLength = newEnd - newOffset;
RangeErrorUtils.checkValidRange(
newOffset ~/ 2,
newEnd ~/ 2,
lengthInBytes ~/ 2,
final newEnd = RangeErrorUtils.checkValidRange(start, end, length);
final newOffsetInBytes = offsetInBytes + 2 * start;
final newLengthInBytes = 2 * (newEnd - start);
return JSInt16ArrayImpl._(
buffer.cloneAsDataView(newOffsetInBytes, newLengthInBytes),
);
return JSInt16ArrayImpl._(buffer.cloneAsDataView(newOffset, newLength));
}
@override
@@ -1443,15 +1429,12 @@ final class JSUint32ArrayImpl extends JSIntegerArrayBase
@override
JSUint32ArrayImpl sublist(int start, [int? end]) {
final int newOffset = offsetInBytes + (start * 4);
final int newEnd = end == null ? lengthInBytes : end * 4;
final int newLength = newEnd - newOffset;
RangeErrorUtils.checkValidRange(
newOffset ~/ 4,
newEnd ~/ 4,
lengthInBytes ~/ 4,
final newEnd = RangeErrorUtils.checkValidRange(start, end, length);
final newOffsetInBytes = offsetInBytes + 4 * start;
final newLengthInBytes = 4 * (newEnd - start);
return JSUint32ArrayImpl._(
buffer.cloneAsDataView(newOffsetInBytes, newLengthInBytes),
);
return JSUint32ArrayImpl._(buffer.cloneAsDataView(newOffset, newLength));
}
@override
@@ -1559,15 +1542,12 @@ final class JSInt32ArrayImpl extends JSIntegerArrayBase
@override
JSInt32ArrayImpl sublist(int start, [int? end]) {
final int newOffset = offsetInBytes + (start * 4);
final int newEnd = end == null ? lengthInBytes : end * 4;
final int newLength = newEnd - newOffset;
RangeErrorUtils.checkValidRange(
newOffset ~/ 4,
newEnd ~/ 4,
lengthInBytes ~/ 4,
final newEnd = RangeErrorUtils.checkValidRange(start, end, length);
final newOffsetInBytes = offsetInBytes + 4 * start;
final newLengthInBytes = 4 * (newEnd - start);
return JSInt32ArrayImpl._(
buffer.cloneAsDataView(newOffsetInBytes, newLengthInBytes),
);
return JSInt32ArrayImpl._(buffer.cloneAsDataView(newOffset, newLength));
}
@override
@@ -1756,15 +1736,12 @@ final class JSBigUint64ArrayImpl extends JSIntegerArrayBase
@override
JSBigUint64ArrayImpl sublist(int start, [int? end]) {
final int newOffset = offsetInBytes + (start * 8);
final int newEnd = end == null ? lengthInBytes : end * 8;
final int newLength = newEnd - newOffset;
RangeErrorUtils.checkValidRange(
newOffset ~/ 8,
newEnd ~/ 8,
lengthInBytes ~/ 8,
final newEnd = RangeErrorUtils.checkValidRange(start, end, length);
final newOffsetInBytes = offsetInBytes + 8 * start;
final newLengthInBytes = 8 * (newEnd - start);
return JSBigUint64ArrayImpl._(
buffer.cloneAsDataView(newOffsetInBytes, newLengthInBytes),
);
return JSBigUint64ArrayImpl._(buffer.cloneAsDataView(newOffset, newLength));
}
@override
@@ -1860,15 +1837,12 @@ final class JSBigInt64ArrayImpl extends JSIntegerArrayBase
@override
JSBigInt64ArrayImpl sublist(int start, [int? end]) {
final int newOffset = offsetInBytes + (start * 8);
final int newEnd = end == null ? lengthInBytes : end * 8;
final int newLength = newEnd - newOffset;
RangeErrorUtils.checkValidRange(
newOffset ~/ 8,
newEnd ~/ 8,
lengthInBytes ~/ 8,
final newEnd = RangeErrorUtils.checkValidRange(start, end, length);
final newOffsetInBytes = offsetInBytes + 8 * start;
final newLengthInBytes = 8 * (newEnd - start);
return JSBigInt64ArrayImpl._(
buffer.cloneAsDataView(newOffsetInBytes, newLengthInBytes),
);
return JSBigInt64ArrayImpl._(buffer.cloneAsDataView(newOffset, newLength));
}
@override
@@ -2321,15 +2295,12 @@ final class JSFloat32ArrayImpl extends JSFloatArrayBase
@override
JSFloat32ArrayImpl sublist(int start, [int? end]) {
final int newOffset = offsetInBytes + (start * 4);
final int newEnd = end == null ? lengthInBytes : end * 4;
final int newLength = newEnd - newOffset;
RangeErrorUtils.checkValidRange(
newOffset ~/ 4,
newEnd ~/ 4,
lengthInBytes ~/ 4,
final newEnd = RangeErrorUtils.checkValidRange(start, end, length);
final newOffsetInBytes = offsetInBytes + 4 * start;
final newLengthInBytes = 4 * (newEnd - start);
return JSFloat32ArrayImpl._(
buffer.cloneAsDataView(newOffsetInBytes, newLengthInBytes),
);
return JSFloat32ArrayImpl._(buffer.cloneAsDataView(newOffset, newLength));
}
@override
@@ -2438,15 +2409,12 @@ final class JSFloat64ArrayImpl extends JSFloatArrayBase
@override
JSFloat64ArrayImpl sublist(int start, [int? end]) {
final int newOffset = offsetInBytes + (start * 8);
final int newEnd = end == null ? lengthInBytes : end * 8;
final int newLength = newEnd - newOffset;
RangeErrorUtils.checkValidRange(
newOffset ~/ 8,
newEnd ~/ 8,
lengthInBytes ~/ 8,
final newEnd = RangeErrorUtils.checkValidRange(start, end, length);
final newOffsetInBytes = offsetInBytes + 8 * start;
final newLengthInBytes = 8 * (newEnd - start);
return JSFloat64ArrayImpl._(
buffer.cloneAsDataView(newOffsetInBytes, newLengthInBytes),
);
return JSFloat64ArrayImpl._(buffer.cloneAsDataView(newOffset, newLength));
}
@override
+154
View File
@@ -0,0 +1,154 @@
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:js_interop';
import 'dart:typed_data';
import 'package:expect/expect.dart';
void main() {
const size = 128;
for (final buffer in createBuffers(128)) {
Expect.equals(size, buffer.lengthInBytes);
initData(buffer, size);
forEachTypedData((viewConstructor, lengthFun, sublistFun, elementSize) {
final sizeInElements = size ~/ elementSize;
final view = viewConstructor(
buffer,
/*offsetInBytes=*/ elementSize,
/*length=*/ sizeInElements - 2,
);
print(
'view: ${view.runtimeType} offsetInBytes:${view.offsetInBytes} len=${lengthFun(view)}',
);
Expect.equals(elementSize, view.offsetInBytes);
Expect.equals(sizeInElements - 2, lengthFun(view));
Expect.equals((sizeInElements - 2) * elementSize, view.lengthInBytes);
verifyData(view, elementSize, size - 2 * elementSize);
// This creates a new list.
final sublist = sublistFun(
view,
/*start=*/ 1,
/*end=*/ sizeInElements - 3,
);
Expect.equals(0, sublist.offsetInBytes);
Expect.equals(sizeInElements - 4, lengthFun(sublist));
Expect.equals((sizeInElements - 4) * elementSize, sublist.lengthInBytes);
verifyData(sublist, 2 * elementSize, size - 4 * elementSize);
});
}
}
void forEachTypedData(
void Function(
TypedData Function(ByteBuffer, [int, int?]),
int Function(TypedData),
TypedData Function(TypedData, int, int?),
int,
)
fun,
) {
fun(
Uint8List.view,
(td) => (td as Uint8List).length,
(td, start, end) => (td as Uint8List).sublist(start, end),
1,
);
fun(
Int8List.view,
(td) => (td as Int8List).length,
(td, start, end) => (td as Int8List).sublist(start, end),
1,
);
fun(
Uint8ClampedList.view,
(td) => (td as Uint8ClampedList).length,
(td, start, end) => (td as Uint8ClampedList).sublist(start, end),
1,
);
fun(
Uint16List.view,
(td) => (td as Uint16List).length,
(td, start, end) => (td as Uint16List).sublist(start, end),
2,
);
fun(
Int16List.view,
(td) => (td as Int16List).length,
(td, start, end) => (td as Int16List).sublist(start, end),
2,
);
fun(
Uint32List.view,
(td) => (td as Uint32List).length,
(td, start, end) => (td as Uint32List).sublist(start, end),
4,
);
fun(
Int32List.view,
(td) => (td as Int32List).length,
(td, start, end) => (td as Int32List).sublist(start, end),
4,
);
fun(
Float32List.view,
(td) => (td as Float32List).length,
(td, start, end) => (td as Float32List).sublist(start, end),
4,
);
fun(
Float64List.view,
(td) => (td as Float64List).length,
(td, start, end) => (td as Float64List).sublist(start, end),
8,
);
}
List<ByteBuffer> createBuffers(int size) {
return [
// JS backed.
JSArrayBuffer(size).toDart,
JSUint8Array.withLength(size).toDart.buffer,
JSInt8Array.withLength(size).toDart.buffer,
JSUint8ClampedArray.withLength(size).toDart.buffer,
JSUint16Array.withLength(size ~/ 2).toDart.buffer,
JSInt16Array.withLength(size ~/ 2).toDart.buffer,
JSUint32Array.withLength(size ~/ 4).toDart.buffer,
JSInt32Array.withLength(size ~/ 4).toDart.buffer,
JSFloat32Array.withLength(size ~/ 4).toDart.buffer,
JSFloat64Array.withLength(size ~/ 8).toDart.buffer,
// Dart backed.
Uint8List(size).buffer,
Int8List(size).buffer,
Uint8ClampedList(size).buffer,
Uint16List(size ~/ 2).buffer,
Int16List(size ~/ 2).buffer,
Uint32List(size ~/ 4).buffer,
Int32List(size ~/ 4).buffer,
Int64List(size ~/ 8).buffer,
Float32List(size ~/ 4).buffer,
Float64List(size ~/ 8).buffer,
Float32x4List(size ~/ 16).buffer,
Float64x2List(size ~/ 16).buffer,
];
}
void initData(ByteBuffer buffer, int length) {
final td = buffer.asUint8List();
for (int i = 0; i < length; i++) {
td[i] = i;
}
}
void verifyData(TypedData typedData, int offset, int length) {
final td = typedData.buffer.asUint8List(typedData.offsetInBytes);
print('verifyData($offset, $length) ');
for (int i = 0; i < length; i++) {
Expect.equals(offset + i, td[i]);
}
}