[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);
     }
   }