[vm] Ensure element type is checked for typed data setRange calls.
In c93f924c82, the separate setRange definitions in _TypedIntListMixin,
_TypedDoubleListMixin, _Float32x4ListMixin, _Float64x2ListMixin, and
_Int32x4ListMixin were replaced with a single definition in
_TypedListBase. In doing so, the signature was changed: now the `from`
argument is just an Iterable, instead of the more specific
Iterable<int>/Iterable<double>/etc in the original definitions.
setRange calls two helper methods, _fastSetRange, when from is also a
_TypedListBase whose elements are the same size, and _slowSetRange, for
all other cases.
In _slowSetRange, various casts and checks ensure that the setRange
method was called with a compatible element type, but _fastSetRange only
checks the _size_ of the element type, not the element type itself,
before doing a memory move between the two _TypedListBase objects. That
means via upcasting, elements can be copied from an argument with an
incompatible element type to the receiver, which would have resulted in
a TypeError being thrown before.
Change the unified setRange definition to _setRange and recreate the old
separate setRange definitions with the more specific signatures, with
the separate definitions delegating to _setRange.
TEST=vm/dart/regress_53945
Fixes: https://github.com/dart-lang/sdk/issues/53945
Cq-Include-Trybots: luci.dart.try:vm-aot-linux-debug-x64-try,vm-aot-linux-release-x64-try
Change-Id: If7eef0b2e07c63aaf776de7b26b1c2cc8c57607d
Fixed: 53945
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/334201
Reviewed-by: Daco Harkes <dacoharkes@google.com>
Commit-Queue: Tess Strickland <sstrickl@google.com>
diff --git a/runtime/tests/vm/dart/regress_53945_test.dart b/runtime/tests/vm/dart/regress_53945_test.dart
new file mode 100644
index 0000000..367f338
--- /dev/null
+++ b/runtime/tests/vm/dart/regress_53945_test.dart
@@ -0,0 +1,82 @@
+// Copyright (c) 2023, 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:typed_data";
+
+import "package:expect/expect.dart";
+
+extension ListCopy<T> on List<T> {
+ @pragma("vm:never-inline")
+ void copyToNotInlined(List<T> to) {
+ to.setRange(0, this.length, this);
+ }
+}
+
+extension ListCopyInlined<T> on List<T> {
+ @pragma("vm:prefer-inline")
+ void copyToInlined(List<T> to) {
+ to.setRange(0, this.length, this);
+ }
+}
+
+void testNotInlined() {
+ List<num> numList = Uint32List.fromList([1, 2, 3, 4]);
+
+ List<int> intList = [2, 4, 6, 8];
+ // Calls _slowSetRange (from is not a _TypedListBase)
+ numList.copyToNotInlined(intList);
+ Expect.deepEquals(numList, intList);
+
+ Uint32List uint32List = Uint32List(numList.length);
+ numList.copyToNotInlined(uint32List);
+ // Calls _fastSetRange (from is a _TypedListBase and element sizes match)
+ Expect.deepEquals(numList, uint32List);
+
+ Uint8List uint8List = Uint8List(numList.length);
+ numList.copyToNotInlined(uint8List);
+ // Calls _slowSetRange (element sizes differ)
+ Expect.deepEquals(numList, uint8List);
+
+ List<double> doubleList = [2.0, 4.0, 6.0, 8.0];
+ Expect.isTrue(doubleList.length >= numList.length);
+ // Would call _slowSetRange (from is not a _TypedListBase)
+ Expect.throws<TypeError>(() => numList.copyToNotInlined(doubleList));
+
+ Float32List float32List = Float32List(numList.length);
+ // Would call _fastSetRange (from is a _TypedListBase and element sizes match)
+ Expect.throws<TypeError>(() => numList.copyToNotInlined(float32List));
+}
+
+void testInlined() {
+ List<num> numList = Uint32List.fromList([1, 2, 3, 4]);
+
+ List<int> intList = [2, 4, 6, 8];
+ // Calls _slowSetRange (from is not a _TypedListBase)
+ numList.copyToInlined(intList);
+ Expect.deepEquals(numList, intList);
+
+ Uint32List uint32List = Uint32List(numList.length);
+ numList.copyToInlined(uint32List);
+ // Calls _fastSetRange (from is a _TypedListBase and element sizes match)
+ Expect.deepEquals(numList, uint32List);
+
+ Uint8List uint8List = Uint8List(numList.length);
+ numList.copyToInlined(uint8List);
+ // Calls _slowSetRange (element sizes differ)
+ Expect.deepEquals(numList, uint8List);
+
+ List<double> doubleList = [2.0, 4.0, 6.0, 8.0];
+ Expect.isTrue(doubleList.length >= numList.length);
+ // Would call _slowSetRange (from is not a _TypedListBase)
+ Expect.throws<TypeError>(() => numList.copyToInlined(doubleList));
+
+ Float32List float32List = Float32List(numList.length);
+ // Would call _fastSetRange (from is a _TypedListBase and element sizes match)
+ Expect.throws<TypeError>(() => numList.copyToInlined(float32List));
+}
+
+void main() {
+ testNotInlined();
+ testInlined();
+}
diff --git a/sdk/lib/_internal/vm/lib/typed_data_patch.dart b/sdk/lib/_internal/vm/lib/typed_data_patch.dart
index 10ee679..ef8159e 100644
--- a/sdk/lib/_internal/vm/lib/typed_data_patch.dart
+++ b/sdk/lib/_internal/vm/lib/typed_data_patch.dart
@@ -103,7 +103,7 @@
}
@pragma("vm:prefer-inline")
- void setRange(int start, int end, Iterable from, [int skipCount = 0]) {
+ void _setRange(int start, int end, Iterable from, [int skipCount = 0]) {
// Range check all numeric inputs.
if (0 > start || start > end || end > length) {
RangeError.checkValidRange(start, end, length); // Always throws.
@@ -114,8 +114,8 @@
}
if (from is _TypedListBase) {
- // Note: _TypedListBase is not related to Iterable<int> so there is
- // no promotion here.
+ // Note: _TypedListBase is not related to Iterable so there is no
+ // promotion here.
final fromAsTyped = unsafeCast<_TypedListBase>(from);
if (fromAsTyped.elementSizeInBytes == elementSizeInBytes) {
// Check that from has enough elements, which is assumed by
@@ -206,7 +206,7 @@
int start, int count, _TypedListBase from, int skipCount);
}
-mixin _IntListMixin implements List<int> {
+base mixin _IntListMixin on _TypedListBase implements List<int> {
int get elementSizeInBytes;
int get offsetInBytes;
_ByteBuffer get buffer;
@@ -476,9 +476,13 @@
this[i] = fillValue;
}
}
+
+ @pragma("vm:prefer-inline")
+ void setRange(int start, int end, Iterable<int> from, [int skipCount = 0]) =>
+ _setRange(start, end, from, skipCount);
}
-mixin _TypedIntListMixin<SpawnedType extends List<int>> on _IntListMixin
+base mixin _TypedIntListMixin<SpawnedType extends List<int>> on _IntListMixin
implements List<int> {
SpawnedType _createList(int length);
@@ -486,8 +490,8 @@
// The numeric inputs have already been checked, all that's left is to
// check that from has enough elements when applicable.
if (from is _TypedListBase) {
- // Note: _TypedListBase is not related to Iterable<int> so there is
- // no promotion here.
+ // Note: _TypedListBase is not related to Iterable so there is no
+ // promotion here.
final fromAsTyped = unsafeCast<_TypedListBase>(from);
if (fromAsTyped.buffer == this.buffer) {
final count = end - start;
@@ -536,7 +540,7 @@
}
}
-mixin _DoubleListMixin implements List<double> {
+base mixin _DoubleListMixin on _TypedListBase implements List<double> {
int get elementSizeInBytes;
int get offsetInBytes;
_ByteBuffer get buffer;
@@ -809,9 +813,14 @@
this[i] = fillValue;
}
}
+
+ @pragma("vm:prefer-inline")
+ void setRange(int start, int end, Iterable<double> from,
+ [int skipCount = 0]) =>
+ _setRange(start, end, from, skipCount);
}
-mixin _TypedDoubleListMixin<SpawnedType extends List<double>>
+base mixin _TypedDoubleListMixin<SpawnedType extends List<double>>
on _DoubleListMixin implements List<double> {
SpawnedType _createList(int length);
@@ -819,8 +828,8 @@
// The numeric inputs have already been checked, all that's left is to
// check that from has enough elements when applicable.
if (from is _TypedListBase) {
- // Note: _TypedListBase is not related to Iterable<int> so there is
- // no promotion here.
+ // Note: _TypedListBase is not related to Iterable so there is no
+ // promotion here.
final fromAsTyped = unsafeCast<_TypedListBase>(from);
if (fromAsTyped.buffer == this.buffer) {
final count = end - start;
@@ -869,7 +878,7 @@
}
}
-mixin _Float32x4ListMixin implements List<Float32x4> {
+base mixin _Float32x4ListMixin on _TypedListBase implements List<Float32x4> {
int get elementSizeInBytes;
int get offsetInBytes;
_ByteBuffer get buffer;
@@ -935,8 +944,8 @@
// The numeric inputs have already been checked, all that's left is to
// check that from has enough elements when applicable.
if (from is _TypedListBase) {
- // Note: _TypedListBase is not related to Iterable<int> so there is
- // no promotion here.
+ // Note: _TypedListBase is not related to Iterable so there is no
+ // promotion here.
final fromAsTyped = unsafeCast<_TypedListBase>(from);
if (fromAsTyped.buffer == this.buffer) {
final count = end - start;
@@ -1200,9 +1209,14 @@
this[i] = fillValue;
}
}
+
+ @pragma("vm:prefer-inline")
+ void setRange(int start, int end, Iterable<Float32x4> from,
+ [int skipCount = 0]) =>
+ _setRange(start, end, from, skipCount);
}
-mixin _Int32x4ListMixin implements List<Int32x4> {
+base mixin _Int32x4ListMixin on _TypedListBase implements List<Int32x4> {
int get elementSizeInBytes;
int get offsetInBytes;
_ByteBuffer get buffer;
@@ -1268,8 +1282,8 @@
// The numeric inputs have already been checked, all that's left is to
// check that from has enough elements when applicable.
if (from is _TypedListBase) {
- // Note: _TypedListBase is not related to Iterable<int> so there is
- // no promotion here.
+ // Note: _TypedListBase is not related to Iterable so there is no
+ // promotion here.
final fromAsTyped = unsafeCast<_TypedListBase>(from);
if (fromAsTyped.buffer == this.buffer) {
final count = end - start;
@@ -1532,9 +1546,14 @@
this[i] = fillValue;
}
}
+
+ @pragma("vm:prefer-inline")
+ void setRange(int start, int end, Iterable<Int32x4> from,
+ [int skipCount = 0]) =>
+ _setRange(start, end, from, skipCount);
}
-mixin _Float64x2ListMixin implements List<Float64x2> {
+base mixin _Float64x2ListMixin on _TypedListBase implements List<Float64x2> {
int get elementSizeInBytes;
int get offsetInBytes;
_ByteBuffer get buffer;
@@ -1600,8 +1619,8 @@
// The numeric inputs have already been checked, all that's left is to
// check that from has enough elements when applicable.
if (from is _TypedListBase) {
- // Note: _TypedListBase is not related to Iterable<int> so there is
- // no promotion here.
+ // Note: _TypedListBase is not related to Iterable so there is no
+ // promotion here.
final fromAsTyped = unsafeCast<_TypedListBase>(from);
if (fromAsTyped.buffer == this.buffer) {
final count = end - start;
@@ -1865,6 +1884,11 @@
this[i] = fillValue;
}
}
+
+ @pragma("vm:prefer-inline")
+ void setRange(int start, int end, Iterable<Float64x2> from,
+ [int skipCount = 0]) =>
+ _setRange(start, end, from, skipCount);
}
@pragma("vm:entry-point")
@@ -2362,14 +2386,15 @@
}
@pragma("vm:prefer-inline")
- void setRange(int start, int end, Iterable iterable, [int skipCount = 0]) {
- if (iterable is CodeUnits) {
+ @override
+ void setRange(int start, int end, Iterable<int> from, [int skipCount = 0]) {
+ if (from is CodeUnits) {
end = RangeError.checkValidRange(start, end, this.length);
int length = end - start;
int byteStart = this.offsetInBytes + start * Int16List.bytesPerElement;
- _setCodeUnits(iterable, byteStart, length, skipCount);
+ _setCodeUnits(from, byteStart, length, skipCount);
} else {
- super.setRange(start, end, iterable, skipCount);
+ super.setRange(start, end, from, skipCount);
}
}
@@ -2438,14 +2463,15 @@
}
@pragma("vm:prefer-inline")
- void setRange(int start, int end, Iterable iterable, [int skipCount = 0]) {
- if (iterable is CodeUnits) {
+ @override
+ void setRange(int start, int end, Iterable<int> from, [int skipCount = 0]) {
+ if (from is CodeUnits) {
end = RangeError.checkValidRange(start, end, this.length);
int length = end - start;
int byteStart = this.offsetInBytes + start * Uint16List.bytesPerElement;
- _setCodeUnits(iterable, byteStart, length, skipCount);
+ _setCodeUnits(from, byteStart, length, skipCount);
} else {
- super.setRange(start, end, iterable, skipCount);
+ super.setRange(start, end, from, skipCount);
}
}
@@ -4405,14 +4431,15 @@
}
@pragma("vm:prefer-inline")
- void setRange(int start, int end, Iterable iterable, [int skipCount = 0]) {
- if (iterable is CodeUnits) {
+ @override
+ void setRange(int start, int end, Iterable<int> from, [int skipCount = 0]) {
+ if (from is CodeUnits) {
end = RangeError.checkValidRange(start, end, this.length);
int length = end - start;
int byteStart = this.offsetInBytes + start * Int16List.bytesPerElement;
- _typedData._setCodeUnits(iterable, byteStart, length, skipCount);
+ _typedData._setCodeUnits(from, byteStart, length, skipCount);
} else {
- super.setRange(start, end, iterable, skipCount);
+ super.setRange(start, end, from, skipCount);
}
}
@@ -4464,14 +4491,15 @@
}
@pragma("vm:prefer-inline")
- void setRange(int start, int end, Iterable iterable, [int skipCount = 0]) {
- if (iterable is CodeUnits) {
+ @override
+ void setRange(int start, int end, Iterable<int> from, [int skipCount = 0]) {
+ if (from is CodeUnits) {
end = RangeError.checkValidRange(start, end, this.length);
int length = end - start;
int byteStart = this.offsetInBytes + start * Uint16List.bytesPerElement;
- _typedData._setCodeUnits(iterable, byteStart, length, skipCount);
+ _typedData._setCodeUnits(from, byteStart, length, skipCount);
} else {
- super.setRange(start, end, iterable, skipCount);
+ super.setRange(start, end, from, skipCount);
}
}