[runtime] Avoid SIGSEGV on String.*

Correctness changes:

Fix SEGV by checking for overflow in computing the length of the
repeated string.

Performance changes:

Use unnested loops to fill the string instead of nested loops.

Implement `operator *` for _TwoByteString in the same way.

(The default implementation using StringBuffer causes a lot of
allocations. For example, `"α" * 10000` repeatedly adds the small
string to the StringBuffer, which repeatedly compresses a sequence of
small strings to make a bigger string to stop the list of parts
becoming too large. This compression creates a lot of small strings
with the same contents.)

Bug: 49289
Change-Id: I06c3d91b531d7e4fffc8de9f3bada3eb62ad185f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/249122
Reviewed-by: Lasse Nielsen <lrn@google.com>
Commit-Queue: Stephen Adams <sra@google.com>
This commit is contained in:
Stephen Adams
2022-06-19 17:02:41 +00:00
committed by Commit Bot
parent e78399ac52
commit 6dd17d6ec4
3 changed files with 148 additions and 7 deletions
+48 -7
View File
@@ -964,6 +964,26 @@ abstract class _StringBase implements String {
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 @@ class _OneByteString extends _StringBase {
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 @@ class _TwoByteString extends _StringBase {
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")
@@ -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));
}
@@ -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));
}