Version 2.18.0-208.0.dev

Merge commit '6dd17d6ec49e3a24c3344a8854850f58a0b18087' into 'dev'
diff --git a/sdk/lib/_internal/vm/lib/string_patch.dart b/sdk/lib/_internal/vm/lib/string_patch.dart
index 0a51f33..8e353de 100644
--- a/sdk/lib/_internal/vm/lib/string_patch.dart
+++ b/sdk/lib/_internal/vm/lib/string_patch.dart
@@ -964,6 +964,26 @@
   external static String _concatRangeNative(List strings, int start, int end);
 }
 
+/// Product of two positive integers, clamped to the maximum int value on
+/// overflow or non-positive inputs.
+int _clampedPositiveProduct(int a, int b) {
+  const MAX_INT64 = (-1) >>> 1;
+
+  int product = a * b;
+
+  // `(a | b)` is negative if either is negative.
+  // `product <= 0` if `a` or `b` is zero, and in some cases of overflow.
+  if ((a | b) < 0 || product <= 0) return MAX_INT64;
+
+  // Both values are small enough that the product has no overflow.
+  if ((a | b) < (1 << 30)) return product;
+
+  // Check the product.
+  if (product ~/ a != b) return MAX_INT64;
+
+  return product;
+}
+
 @pragma("vm:entry-point")
 class _OneByteString extends _StringBase {
   factory _OneByteString._uninstantiable() {
@@ -1094,14 +1114,17 @@
   String operator *(int times) {
     if (times <= 0) return "";
     if (times == 1) return this;
-    int length = this.length;
     if (this.isEmpty) return this; // Don't clone empty string.
-    _OneByteString result = _OneByteString._allocate(length * times);
-    int index = 0;
-    for (int i = 0; i < times; i++) {
-      for (int j = 0; j < length; j++) {
-        result._setAt(index++, this.codeUnitAt(j));
-      }
+    int length = this.length;
+    int resultLength = _clampedPositiveProduct(length, times);
+    _OneByteString result = _OneByteString._allocate(resultLength);
+    // Copy `this` into `result`.
+    for (int i = 0; i < length; i++) {
+      result._setAt(i, this.codeUnitAt(i));
+    }
+    // Make more copies by copying within `result`.
+    for (int i = length; i < resultLength; i++) {
+      result._setAt(i, result.codeUnitAt(i - length));
     }
     return result;
   }
@@ -1341,6 +1364,24 @@
   bool operator ==(Object other) {
     return super == other;
   }
+
+  String operator *(int times) {
+    if (times <= 0) return "";
+    if (times == 1) return this;
+    if (this.isEmpty) return this; // Don't clone empty string.
+    int length = this.length;
+    int resultLength = _clampedPositiveProduct(length, times);
+    _TwoByteString result = _TwoByteString._allocate(resultLength);
+    // Copy `this` into `result`.
+    for (int i = 0; i < length; i++) {
+      result._setAt(i, this.codeUnitAt(i));
+    }
+    // Make more copies by copying within `result`.
+    for (int i = length; i < resultLength; i++) {
+      result._setAt(i, result.codeUnitAt(i - length));
+    }
+    return result;
+  }
 }
 
 @pragma("vm:entry-point")
diff --git a/tests/corelib/string_operator_multiply_test.dart b/tests/corelib/string_operator_multiply_test.dart
new file mode 100644
index 0000000..0dc20f8
--- /dev/null
+++ b/tests/corelib/string_operator_multiply_test.dart
@@ -0,0 +1,50 @@
+// Copyright (c) 2022, 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 "package:expect/expect.dart";
+
+main() {
+  Expect.equals('', 'a' * -11);
+  Expect.equals('', 'α' * -11);
+  Expect.equals('', '∀' * -11);
+
+  Expect.equals('', 'a' * 0);
+  Expect.equals('', 'α' * 0);
+  Expect.equals('', '∀' * 0);
+
+  Expect.equals('a', 'a' * 1);
+  Expect.equals('α', 'α' * 1);
+  Expect.equals('∀', '∀' * 1);
+
+  Expect.equals('aa', 'a' * 2);
+  Expect.equals('αα', 'α' * 2);
+  Expect.equals('∀∀', '∀' * 2);
+
+  Expect.equals('aaa', 'a' * 3);
+  Expect.equals('ααα', 'α' * 3);
+  Expect.equals('∀∀∀', '∀' * 3);
+
+  Expect.equals('', '' * 0x4000000000000000);
+
+  Expect.throws(() => 'a' * 0x4000000000000000);
+  Expect.throws(() => 'α' * 0x4000000000000000);
+  Expect.throws(() => '∀' * 0x4000000000000000);
+
+  for (final string in ['a', 'α', '∀', 'hello world', 'abc', 'α∀α']) {
+    for (final count in [0, 1, 10, 100, 255, 256, 257, 1000, 100000]) {
+      final expected = List.filled(count, string).join();
+      final actual = string * count;
+      Expect.equals(expected, actual);
+    }
+  }
+
+  // http://dartbug.com/49289
+  Expect.throws(() => 'abcd' * 0x4000000000000000);
+  Expect.throws(() => 'αxyz' * 0x4000000000000000);
+  Expect.throws(() => '∀pqr' * 0x4000000000000000);
+
+  Expect.throws(() => 'abcd' * (0x4000000000000000 + 1));
+  Expect.throws(() => 'αxyz' * (0x4000000000000000 + 1));
+  Expect.throws(() => '∀pqr' * (0x4000000000000000 + 1));
+}
diff --git a/tests/corelib_2/string_operator_multiply_test.dart b/tests/corelib_2/string_operator_multiply_test.dart
new file mode 100644
index 0000000..0dc20f8
--- /dev/null
+++ b/tests/corelib_2/string_operator_multiply_test.dart
@@ -0,0 +1,50 @@
+// Copyright (c) 2022, 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 "package:expect/expect.dart";
+
+main() {
+  Expect.equals('', 'a' * -11);
+  Expect.equals('', 'α' * -11);
+  Expect.equals('', '∀' * -11);
+
+  Expect.equals('', 'a' * 0);
+  Expect.equals('', 'α' * 0);
+  Expect.equals('', '∀' * 0);
+
+  Expect.equals('a', 'a' * 1);
+  Expect.equals('α', 'α' * 1);
+  Expect.equals('∀', '∀' * 1);
+
+  Expect.equals('aa', 'a' * 2);
+  Expect.equals('αα', 'α' * 2);
+  Expect.equals('∀∀', '∀' * 2);
+
+  Expect.equals('aaa', 'a' * 3);
+  Expect.equals('ααα', 'α' * 3);
+  Expect.equals('∀∀∀', '∀' * 3);
+
+  Expect.equals('', '' * 0x4000000000000000);
+
+  Expect.throws(() => 'a' * 0x4000000000000000);
+  Expect.throws(() => 'α' * 0x4000000000000000);
+  Expect.throws(() => '∀' * 0x4000000000000000);
+
+  for (final string in ['a', 'α', '∀', 'hello world', 'abc', 'α∀α']) {
+    for (final count in [0, 1, 10, 100, 255, 256, 257, 1000, 100000]) {
+      final expected = List.filled(count, string).join();
+      final actual = string * count;
+      Expect.equals(expected, actual);
+    }
+  }
+
+  // http://dartbug.com/49289
+  Expect.throws(() => 'abcd' * 0x4000000000000000);
+  Expect.throws(() => 'αxyz' * 0x4000000000000000);
+  Expect.throws(() => '∀pqr' * 0x4000000000000000);
+
+  Expect.throws(() => 'abcd' * (0x4000000000000000 + 1));
+  Expect.throws(() => 'αxyz' * (0x4000000000000000 + 1));
+  Expect.throws(() => '∀pqr' * (0x4000000000000000 + 1));
+}
diff --git a/tools/VERSION b/tools/VERSION
index e69cd69..4b92fd0 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 18
 PATCH 0
-PRERELEASE 207
+PRERELEASE 208
 PRERELEASE_PATCH 0
\ No newline at end of file