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