[vm, compiler] Consistently produce OutOfMemoryErrors for large variable-length object allocations.

Bug: https://github.com/dart-lang/sdk/issues/38575
Change-Id: I3f93488511519ac1dca04f91465efad3d2a0c66d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/118886
Commit-Queue: Ryan Macnak <rmacnak@google.com>
Reviewed-by: Aart Bik <ajcbik@google.com>
This commit is contained in:
Ryan Macnak
2019-09-30 20:19:03 +00:00
committed by commit-bot@chromium.org
parent 5f32afdd4c
commit 8a5e2a688b
15 changed files with 239 additions and 75 deletions
+11 -2
View File
@@ -287,8 +287,17 @@ DEFINE_NATIVE_ENTRY(OneByteString_splitWithCharCode, 0, 2) {
}
DEFINE_NATIVE_ENTRY(OneByteString_allocate, 0, 1) {
GET_NON_NULL_NATIVE_ARGUMENT(Smi, length_obj, arguments->NativeArgAt(0));
return OneByteString::New(length_obj.Value(), Heap::kNew);
GET_NON_NULL_NATIVE_ARGUMENT(Integer, length_obj, arguments->NativeArgAt(0));
const int64_t length = length_obj.AsInt64Value();
if ((length < 0) || (length > OneByteString::kMaxElements)) {
// Assume that negative lengths are the result of wrapping in code in
// string_patch.dart.
const Instance& exception =
Instance::Handle(thread->isolate()->object_store()->out_of_memory());
Exceptions::Throw(thread, exception);
UNREACHABLE();
}
return OneByteString::New(static_cast<intptr_t>(length), Heap::kNew);
}
DEFINE_NATIVE_ENTRY(OneByteString_allocateFromOneByteList, 0, 3) {
+11 -4
View File
@@ -9,6 +9,7 @@
#include "vm/exceptions.h"
#include "vm/native_entry.h"
#include "vm/object.h"
#include "vm/object_store.h"
namespace dart {
@@ -189,12 +190,18 @@ DEFINE_NATIVE_ENTRY(TypedData_setRange, 0, 7) {
// Argument 0 is type arguments and is ignored.
#define TYPED_DATA_NEW(name) \
DEFINE_NATIVE_ENTRY(TypedData_##name##_new, 0, 2) { \
GET_NON_NULL_NATIVE_ARGUMENT(Smi, length, arguments->NativeArgAt(1)); \
GET_NON_NULL_NATIVE_ARGUMENT(Integer, length, arguments->NativeArgAt(1)); \
const intptr_t cid = kTypedData##name##Cid; \
const intptr_t len = length.Value(); \
const intptr_t max = TypedData::MaxElements(cid); \
LengthCheck(len, max); \
return TypedData::New(cid, len); \
const int64_t len = length.AsInt64Value(); \
if (len < 0) { \
Exceptions::ThrowRangeError("length", length, 0, max); \
} else if (len > max) { \
const Instance& exception = Instance::Handle( \
zone, thread->isolate()->object_store()->out_of_memory()); \
Exceptions::Throw(thread, exception); \
} \
return TypedData::New(cid, static_cast<intptr_t>(len)); \
}
#define TYPED_DATA_NEW_NATIVE(name) TYPED_DATA_NEW(name)
+13 -13
View File
@@ -1936,19 +1936,22 @@ void AsmIntrinsifier::OneByteString_getHashCode(Assembler* assembler,
__ Ret();
}
// Allocates one-byte string of length 'end - start'. The content is not
// initialized.
// 'length-reg' (R2) contains tagged length.
// Allocates a _OneByteString. The content is not initialized.
// 'length-reg' (R2) contains the desired length as a _Smi or _Mint.
// Returns new string as tagged pointer in R0.
static void TryAllocateOnebyteString(Assembler* assembler,
static void TryAllocateOneByteString(Assembler* assembler,
Label* ok,
Label* failure) {
const Register length_reg = R2;
Label fail;
// _Mint length: call to runtime to produce error.
__ BranchIfNotSmi(length_reg, failure);
// Negative length: call to runtime to produce error.
__ cmp(length_reg, Operand(0));
__ b(failure, LT);
NOT_IN_PRODUCT(__ LoadAllocationStatsAddress(R0, kOneByteStringCid));
NOT_IN_PRODUCT(__ MaybeTraceAllocation(R0, failure));
__ mov(R8, Operand(length_reg)); // Save the length register.
// TODO(koda): Protect against negative length and overflow here.
__ SmiUntag(length_reg);
const intptr_t fixed_size_plus_alignment_padding =
target::String::InstanceSize() +
@@ -1962,7 +1965,7 @@ static void TryAllocateOnebyteString(Assembler* assembler,
// length_reg: allocation size.
__ adds(R1, R0, Operand(length_reg));
__ b(&fail, CS); // Fail on unsigned overflow.
__ b(failure, CS); // Fail on unsigned overflow.
// Check if the allocation fits into the remaining space.
// R0: potential new object start.
@@ -1970,7 +1973,7 @@ static void TryAllocateOnebyteString(Assembler* assembler,
// R2: allocation size.
__ ldr(NOTFP, Address(THR, target::Thread::end_offset()));
__ cmp(R1, Operand(NOTFP));
__ b(&fail, CS);
__ b(failure, CS);
// Successfully allocated the object(s), now update top to point to
// next object start and initialize the object.
@@ -2010,9 +2013,6 @@ static void TryAllocateOnebyteString(Assembler* assembler,
NOT_IN_PRODUCT(__ IncrementAllocationStatsWithSize(R4, R2));
__ b(ok);
__ Bind(&fail);
__ b(failure);
}
// Arg0: OneByteString (receiver).
@@ -2033,7 +2033,7 @@ void AsmIntrinsifier::OneByteString_substringUnchecked(Assembler* assembler,
__ b(normal_ir_body, NE); // 'start', 'end' not Smi.
__ sub(R2, R2, Operand(TMP));
TryAllocateOnebyteString(assembler, &ok, normal_ir_body);
TryAllocateOneByteString(assembler, &ok, normal_ir_body);
__ Bind(&ok);
// R0: new string as tagged pointer.
// Copy string.
@@ -2092,7 +2092,7 @@ void AsmIntrinsifier::OneByteString_allocate(Assembler* assembler,
Label* normal_ir_body) {
__ ldr(R2, Address(SP, 0 * target::kWordSize)); // Length.
Label ok;
TryAllocateOnebyteString(assembler, &ok, normal_ir_body);
TryAllocateOneByteString(assembler, &ok, normal_ir_body);
__ Bind(&ok);
__ Ret();
+12 -12
View File
@@ -1999,15 +1999,18 @@ void AsmIntrinsifier::OneByteString_getHashCode(Assembler* assembler,
__ ret();
}
// Allocates one-byte string of length 'end - start'. The content is not
// initialized.
// 'length-reg' (R2) contains tagged length.
// Allocates a _OneByteString. The content is not initialized.
// 'length-reg' (R2) contains the desired length as a _Smi or _Mint.
// Returns new string as tagged pointer in R0.
static void TryAllocateOnebyteString(Assembler* assembler,
static void TryAllocateOneByteString(Assembler* assembler,
Label* ok,
Label* failure) {
const Register length_reg = R2;
Label fail;
// _Mint length: call to runtime to produce error.
__ BranchIfNotSmi(length_reg, failure);
// negative length: call to runtime to produce error.
__ tbnz(failure, length_reg, compiler::target::kBitsPerWord - 1);
NOT_IN_PRODUCT(__ MaybeTraceAllocation(kOneByteStringCid, R0, failure));
__ mov(R6, length_reg); // Save the length register.
// TODO(koda): Protect against negative length and overflow here.
@@ -2030,7 +2033,7 @@ static void TryAllocateOnebyteString(Assembler* assembler,
// length_reg: allocation size.
__ adds(R1, R0, Operand(length_reg));
__ b(&fail, CS); // Fail on unsigned overflow.
__ b(failure, CS); // Fail on unsigned overflow.
// Check if the allocation fits into the remaining space.
// R0: potential new object start.
@@ -2038,7 +2041,7 @@ static void TryAllocateOnebyteString(Assembler* assembler,
// R2: allocation size.
__ ldr(R7, Address(THR, target::Thread::end_offset()));
__ cmp(R1, Operand(R7));
__ b(&fail, CS);
__ b(failure, CS);
// Successfully allocated the object(s), now update top to point to
// next object start and initialize the object.
@@ -2072,9 +2075,6 @@ static void TryAllocateOnebyteString(Assembler* assembler,
__ StoreIntoObjectNoBarrier(
R0, FieldAddress(R0, target::String::length_offset()), R6);
__ b(ok);
__ Bind(&fail);
__ b(failure);
}
// Arg0: OneByteString (receiver).
@@ -2094,7 +2094,7 @@ void AsmIntrinsifier::OneByteString_substringUnchecked(Assembler* assembler,
__ BranchIfNotSmi(R3, normal_ir_body); // 'start', 'end' not Smi.
__ sub(R2, R2, Operand(TMP));
TryAllocateOnebyteString(assembler, &ok, normal_ir_body);
TryAllocateOneByteString(assembler, &ok, normal_ir_body);
__ Bind(&ok);
// R0: new string as tagged pointer.
// Copy string.
@@ -2155,7 +2155,7 @@ void AsmIntrinsifier::OneByteString_allocate(Assembler* assembler,
Label ok;
__ ldr(R2, Address(SP, 0 * target::kWordSize)); // Length.
TryAllocateOnebyteString(assembler, &ok, normal_ir_body);
TryAllocateOneByteString(assembler, &ok, normal_ir_body);
__ Bind(&ok);
__ ret();
+11 -5
View File
@@ -1951,13 +1951,19 @@ void AsmIntrinsifier::OneByteString_getHashCode(Assembler* assembler,
__ ret();
}
// Allocates one-byte string of length 'end - start'. The content is not
// initialized. 'length-reg' contains tagged length.
// Allocates a _OneByteString. The content is not initialized.
// 'length-reg' contains the desired length as a _Smi or _Mint.
// Returns new string as tagged pointer in EAX.
static void TryAllocateOnebyteString(Assembler* assembler,
static void TryAllocateOneByteString(Assembler* assembler,
Label* ok,
Label* failure,
Register length_reg) {
// _Mint length: call to runtime to produce error.
__ BranchIfNotSmi(length_reg, failure);
// negative length: call to runtime to produce error.
__ cmpl(length_reg, Immediate(0));
__ j(LESS, failure);
NOT_IN_PRODUCT(
__ MaybeTraceAllocation(kOneByteStringCid, EAX, failure, false));
if (length_reg != EDI) {
@@ -2048,7 +2054,7 @@ void AsmIntrinsifier::OneByteString_substringUnchecked(Assembler* assembler,
__ j(NOT_ZERO, normal_ir_body); // 'start', 'end' not Smi.
__ subl(EDI, Address(ESP, +kStartIndexOffset));
TryAllocateOnebyteString(assembler, &ok, normal_ir_body, EDI);
TryAllocateOneByteString(assembler, &ok, normal_ir_body, EDI);
__ Bind(&ok);
// EAX: new string as tagged pointer.
// Copy string.
@@ -2098,7 +2104,7 @@ void AsmIntrinsifier::OneByteString_allocate(Assembler* assembler,
Label* normal_ir_body) {
__ movl(EDI, Address(ESP, +1 * target::kWordSize)); // Length.
Label ok;
TryAllocateOnebyteString(assembler, &ok, normal_ir_body, EDI);
TryAllocateOneByteString(assembler, &ok, normal_ir_body, EDI);
// EDI: Start address to copy from (untagged).
__ Bind(&ok);
+11 -5
View File
@@ -1977,13 +1977,19 @@ void AsmIntrinsifier::OneByteString_getHashCode(Assembler* assembler,
__ ret();
}
// Allocates one-byte string of length 'end - start'. The content is not
// initialized. 'length-reg' contains tagged length.
// Allocates a _OneByteString. The content is not initialized.
// 'length-reg' contains the desired length as a _Smi or _Mint.
// Returns new string as tagged pointer in RAX.
static void TryAllocateOnebyteString(Assembler* assembler,
static void TryAllocateOneByteString(Assembler* assembler,
Label* ok,
Label* failure,
Register length_reg) {
// _Mint length: call to runtime to produce error.
__ BranchIfNotSmi(length_reg, failure);
// negative length: call to runtime to produce error.
__ cmpq(length_reg, Immediate(0));
__ j(LESS, failure);
NOT_IN_PRODUCT(__ MaybeTraceAllocation(kOneByteStringCid, failure, false));
if (length_reg != RDI) {
__ movq(RDI, length_reg);
@@ -2076,7 +2082,7 @@ void AsmIntrinsifier::OneByteString_substringUnchecked(Assembler* assembler,
__ j(NOT_ZERO, normal_ir_body); // 'start', 'end' not Smi.
__ subq(RDI, Address(RSP, +kStartIndexOffset));
TryAllocateOnebyteString(assembler, &ok, normal_ir_body, RDI);
TryAllocateOneByteString(assembler, &ok, normal_ir_body, RDI);
__ Bind(&ok);
// RAX: new string as tagged pointer.
// Copy string.
@@ -2126,7 +2132,7 @@ void AsmIntrinsifier::OneByteString_allocate(Assembler* assembler,
Label* normal_ir_body) {
__ movq(RDI, Address(RSP, +1 * target::kWordSize)); // Length.v=
Label ok;
TryAllocateOnebyteString(assembler, &ok, normal_ir_body, RDI);
TryAllocateOneByteString(assembler, &ok, normal_ir_body, RDI);
// RDI: Start address to copy from (untagged).
__ Bind(&ok);
-6
View File
@@ -1005,12 +1005,6 @@ void Exceptions::ThrowUnsupportedError(const char* msg) {
Exceptions::ThrowByType(Exceptions::kUnsupported, args);
}
void Exceptions::ThrowRangeErrorMsg(const char* msg) {
const Array& args = Array::Handle(Array::New(1));
args.SetAt(0, String::Handle(String::New(msg)));
Exceptions::ThrowByType(Exceptions::kRangeMsg, args);
}
void Exceptions::ThrowCompileTimeError(const LanguageError& error) {
const Array& args = Array::Handle(Array::New(1));
args.SetAt(0, String::Handle(error.FormatMessage()));
-1
View File
@@ -83,7 +83,6 @@ class Exceptions : AllStatic {
const Integer& argument_value,
intptr_t expected_from,
intptr_t expected_to);
DART_NORETURN static void ThrowRangeErrorMsg(const char* msg);
DART_NORETURN static void ThrowUnsupportedError(const char* msg);
DART_NORETURN static void ThrowCompileTimeError(const LanguageError& error);
+5 -1
View File
@@ -262,6 +262,9 @@ DART_FORCE_INLINE static bool TryAllocate(Thread* thread,
intptr_t class_id,
intptr_t instance_size,
RawObject** result) {
ASSERT(instance_size > 0);
ASSERT(Utils::IsAligned(instance_size, kObjectAlignment));
const uword start = thread->top();
#ifndef PRODUCT
auto table = thread->isolate()->shared_class_table();
@@ -269,7 +272,8 @@ DART_FORCE_INLINE static bool TryAllocate(Thread* thread,
return false;
}
#endif
if (LIKELY((start + instance_size) < thread->end())) {
const intptr_t remaining = thread->end() - start;
if (LIKELY(remaining >= instance_size)) {
thread->set_top(start + instance_size);
#ifndef PRODUCT
table->UpdateAllocatedNew(class_id, instance_size);
+27 -3
View File
@@ -1835,10 +1835,22 @@ TEST_CASE(ArrayLengthNegativeOne) {
TEST_CASE(ArrayLengthSmiMin) {
TestIllegalArrayLength(kSmiMin);
}
TEST_CASE(ArrayLengthOneTooMany) {
const intptr_t kOneTooMany = Array::kMaxElements + 1;
ASSERT(kOneTooMany >= 0);
TestIllegalArrayLength(kOneTooMany);
char buffer[1024];
Utils::SNPrint(buffer, sizeof(buffer),
"main() {\n"
" return new List(%" Pd
");\n"
"}\n",
kOneTooMany);
Dart_Handle lib = TestCase::LoadTestScript(buffer, NULL);
EXPECT_VALID(lib);
Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, NULL);
EXPECT_ERROR(result, "Out of Memory");
}
TEST_CASE(ArrayLengthMaxElements) {
@@ -1876,7 +1888,7 @@ static void TestIllegalTypedDataLength(const char* class_name,
EXPECT_VALID(lib);
Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, NULL);
Utils::SNPrint(buffer, sizeof(buffer), "%" Pd, length);
EXPECT_ERROR(result, "Invalid argument(s)");
EXPECT_ERROR(result, "RangeError (length): Invalid value");
EXPECT_ERROR(result, buffer);
}
@@ -1890,7 +1902,19 @@ TEST_CASE(Int8ListLengthOneTooMany) {
const intptr_t kOneTooMany =
TypedData::MaxElements(kTypedDataInt8ArrayCid) + 1;
ASSERT(kOneTooMany >= 0);
TestIllegalTypedDataLength("Int8List", kOneTooMany);
char buffer[1024];
Utils::SNPrint(buffer, sizeof(buffer),
"import 'dart:typed_data';\n"
"main() {\n"
" return new Int8List(%" Pd
");\n"
"}\n",
kOneTooMany);
Dart_Handle lib = TestCase::LoadTestScript(buffer, NULL);
EXPECT_VALID(lib);
Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, NULL);
EXPECT_ERROR(result, "Out of Memory");
}
TEST_CASE(Int8ListLengthMaxElements) {
+22 -22
View File
@@ -248,29 +248,29 @@ DEFINE_RUNTIME_ENTRY(AllocateArray, 2) {
args.SetAt(2, String::Handle(zone, String::New("is not an integer")));
Exceptions::ThrowByType(Exceptions::kArgumentValue, args);
}
if (length.IsSmi()) {
const intptr_t len = Smi::Cast(length).Value();
if (Array::IsValidLength(len)) {
const Array& array = Array::Handle(zone, Array::New(len, Heap::kNew));
arguments.SetReturn(array);
TypeArguments& element_type =
TypeArguments::CheckedHandle(zone, arguments.ArgAt(1));
// An Array is raw or takes one type argument. However, its type argument
// vector may be longer than 1 due to a type optimization reusing the type
// argument vector of the instantiator.
ASSERT(element_type.IsNull() ||
(element_type.Length() >= 1 && element_type.IsInstantiated()));
array.SetTypeArguments(element_type); // May be null.
return;
}
const int64_t len = Integer::Cast(length).AsInt64Value();
if (len < 0) {
// Throw: new RangeError.range(length, 0, Array::kMaxElements, "length");
Exceptions::ThrowRangeError("length", Integer::Cast(length), 0,
Array::kMaxElements);
}
// Throw: new RangeError.range(length, 0, Array::kMaxElements, "length");
const Array& args = Array::Handle(zone, Array::New(4));
args.SetAt(0, length);
args.SetAt(1, Integer::Handle(zone, Integer::New(0)));
args.SetAt(2, Integer::Handle(zone, Integer::New(Array::kMaxElements)));
args.SetAt(3, Symbols::Length());
Exceptions::ThrowByType(Exceptions::kRange, args);
if (len > Array::kMaxElements) {
const Instance& exception = Instance::Handle(
zone, thread->isolate()->object_store()->out_of_memory());
Exceptions::Throw(thread, exception);
}
const Array& array =
Array::Handle(zone, Array::New(static_cast<intptr_t>(len), Heap::kNew));
arguments.SetReturn(array);
TypeArguments& element_type =
TypeArguments::CheckedHandle(zone, arguments.ArgAt(1));
// An Array is raw or takes one type argument. However, its type argument
// vector may be longer than 1 due to a type optimization reusing the type
// argument vector of the instantiator.
ASSERT(element_type.IsNull() ||
(element_type.Length() >= 1 && element_type.IsInstantiated()));
array.SetTypeArguments(element_type); // May be null.
}
// Helper returning the token position of the Dart caller.
+2 -1
View File
@@ -91,7 +91,8 @@ void testConstructor() {
testGrowable(new List<int>.filled(5, null, growable: true));
Expect.throwsArgumentError(() => new List<int>(-1), "-1");
// There must be limits. Fix this test if we ever allow 2^63 elements.
Expect.throwsArgumentError(() => new List<int>(0x7ffffffffffff000), "bignum");
Expect.throws(() => new List<int>(0x7ffffffffffff000),
(e) => e is OutOfMemoryError || e is ArgumentError, "bignum");
Expect.throwsArgumentError(() => new List<int>(null), "null");
testThrowsOrTypeError(
() => new List([] as Object), // Cast to avoid warning.
@@ -0,0 +1,33 @@
// Copyright (c) 2019, 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';
const interestingLengths = <int>[
0x3FFFFFFF00000000,
0x3FFFFFFFFFFFF000,
0x3FFFFFFFFFFFFF00,
0x3FFFFFFFFFFFFFF0,
0x3FFFFFFFFFFFFFFE,
0x3FFFFFFFFFFFFFFF,
0x7FFFFFFF00000000,
0x7FFFFFFFFFFFF000,
0x7FFFFFFFFFFFFF00,
0x7FFFFFFFFFFFFFF0,
0x7FFFFFFFFFFFFFFE,
0x7FFFFFFFFFFFFFFF,
];
main() {
for (int interestingLength in interestingLengths) {
for (int elementLength in <int>[1, 2, 3, 4, 5, 6, 7, 8, 9]) {
print(interestingLength ~/ elementLength);
Expect.throws(() {
var array = new List(interestingLength ~/ elementLength);
print(array.first);
}, (e) => e is OutOfMemoryError);
}
}
}
@@ -0,0 +1,44 @@
// Copyright (c) 2019, 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';
const interestingLengths = <int>[
0x3FFFFFFF00000000,
0x3FFFFFFFFFFFFFF0,
0x3FFFFFFFFFFFFFFE,
0x3FFFFFFFFFFFFFFF,
0x7FFFFFFF00000000,
0x7FFFFFFFFFFFFFF0,
0x7FFFFFFFFFFFFFFE,
0x7FFFFFFFFFFFFFFF,
];
main() {
for (int interestingLength in interestingLengths) {
print(interestingLength);
Expect.throws(() {
var bytearray = new Uint8List(interestingLength);
print(bytearray.first);
}, (e) => e is OutOfMemoryError);
Expect.throws(() {
var bytearray = new Uint8ClampedList(interestingLength);
print(bytearray.first);
}, (e) => e is OutOfMemoryError);
Expect.throws(() {
var bytearray = new Int8List(interestingLength);
print(bytearray.first);
}, (e) => e is OutOfMemoryError);
Expect.throws(() {
var bytearray = new ByteData(interestingLength);
print(bytearray.getUint8(0));
}, (e) => e is OutOfMemoryError);
}
}
@@ -0,0 +1,37 @@
// Copyright (c) 2019, 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';
const interestingLengths = <int>[
0x3FFFFFFF00000000,
0x3FFFFFFFFFFFFFF0,
0x3FFFFFFFFFFFFFFE,
0x3FFFFFFFFFFFFFFF,
0x7FFFFFFF00000000,
0x7FFFFFFFFFFFFFF0,
0x7FFFFFFFFFFFFFFE,
0x7FFFFFFFFFFFFFFF,
];
main() {
for (int interestingLength in interestingLengths) {
print(interestingLength);
Expect.throws(() {
var oneByteString = "v";
oneByteString *= interestingLength;
}, (e) => e is OutOfMemoryError);
Expect.throws(() {
var oneByteString = "v";
oneByteString = oneByteString.padLeft(interestingLength);
}, (e) => e is OutOfMemoryError);
Expect.throws(() {
var oneByteString = "v";
oneByteString = oneByteString.padRight(interestingLength);
}, (e) => e is OutOfMemoryError);
}
}