[bigint] Implement NoSideEffectsToString

When our various debugging and error reporting facilities want to
perform a side effect free conversion of a value (which could be
a BigInt) to a String, then the usual BigInt::ToString is not a
great fit because it reacts to termination requests.
This patch adds a method BigInt::NoSideEffectsToString, which uses
a low upper bound instead of termination requests.

Fixed: chromium:1406774
Change-Id: Ibc5d37027823e4a03c470f1dd0a63c16c552850c
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4177099
Auto-Submit: Jakob Kummerow <jkummerow@chromium.org>
Commit-Queue: Clemens Backes <clemensb@chromium.org>
Reviewed-by: Clemens Backes <clemensb@chromium.org>
Cr-Commit-Position: refs/heads/main@{#85433}
This commit is contained in:
Jakob Kummerow 2023-01-19 19:00:34 +01:00 committed by V8 LUCI CQ
parent 11c4c8e3d2
commit b4ae834223
4 changed files with 82 additions and 20 deletions

View File

@ -922,6 +922,26 @@ ComparisonResult BigInt::CompareToDouble(Handle<BigInt> x, double y) {
return ComparisonResult::kEqual;
}
namespace {
void RightTrimString(Isolate* isolate, Handle<SeqOneByteString> string,
int chars_allocated, int chars_written) {
DCHECK_LE(chars_written, chars_allocated);
if (chars_written == chars_allocated) return;
string->set_length(chars_written, kReleaseStore);
int string_size =
ALIGN_TO_ALLOCATION_ALIGNMENT(SeqOneByteString::SizeFor(chars_allocated));
int needed_size =
ALIGN_TO_ALLOCATION_ALIGNMENT(SeqOneByteString::SizeFor(chars_written));
if (needed_size < string_size && !isolate->heap()->IsLargeObject(*string)) {
isolate->heap()->NotifyObjectSizeChange(*string, string_size, needed_size,
ClearRecordedSlots::kNo,
UpdateInvalidatedObjectSize::kNo);
}
}
} // namespace
MaybeHandle<String> BigInt::ToString(Isolate* isolate, Handle<BigInt> bigint,
int radix, ShouldThrow should_throw) {
if (bigint->is_zero()) {
@ -995,18 +1015,7 @@ MaybeHandle<String> BigInt::ToString(Isolate* isolate, Handle<BigInt> bigint,
// Right-trim any over-allocation (which can happen due to conservative
// estimates).
if (chars_written < chars_allocated) {
result->set_length(chars_written, kReleaseStore);
int string_size = ALIGN_TO_ALLOCATION_ALIGNMENT(
SeqOneByteString::SizeFor(chars_allocated));
int needed_size =
ALIGN_TO_ALLOCATION_ALIGNMENT(SeqOneByteString::SizeFor(chars_written));
if (needed_size < string_size && !isolate->heap()->IsLargeObject(*result)) {
isolate->heap()->NotifyObjectSizeChange(*result, string_size, needed_size,
ClearRecordedSlots::kNo,
UpdateInvalidatedObjectSize::kNo);
}
}
RightTrimString(isolate, result, chars_allocated, chars_written);
#if DEBUG
// Verify that all characters have been written.
DCHECK(result->length() == chars_written);
@ -1019,6 +1028,37 @@ MaybeHandle<String> BigInt::ToString(Isolate* isolate, Handle<BigInt> bigint,
return result;
}
Handle<String> BigInt::NoSideEffectsToString(Isolate* isolate,
Handle<BigInt> bigint) {
if (bigint->is_zero()) {
return isolate->factory()->zero_string();
}
// The threshold is chosen such that the operation will be fast enough to
// not need interrupt checks. This function is meant for producing human-
// readable error messages, so super-long results aren't useful anyway.
if (bigint->length() > 100) {
return isolate->factory()->NewStringFromStaticChars(
"<a very large BigInt>");
}
int chars_allocated =
bigint::ToStringResultLength(GetDigits(bigint), 10, bigint->sign());
DCHECK_LE(chars_allocated, String::kMaxLength);
Handle<SeqOneByteString> result = isolate->factory()
->NewRawOneByteString(chars_allocated)
.ToHandleChecked();
int chars_written = chars_allocated;
DisallowGarbageCollection no_gc;
char* characters = reinterpret_cast<char*>(result->GetChars(no_gc));
std::unique_ptr<bigint::Processor, bigint::Processor::Destroyer>
non_interruptible_processor(
bigint::Processor::New(new bigint::Platform()));
non_interruptible_processor->ToString(characters, &chars_written,
GetDigits(bigint), 10, bigint->sign());
RightTrimString(isolate, result, chars_allocated, chars_written);
return result;
}
MaybeHandle<BigInt> BigInt::FromNumber(Isolate* isolate,
Handle<Object> number) {
DCHECK(number->IsNumber());

View File

@ -278,6 +278,12 @@ class BigInt : public BigIntBase {
static MaybeHandle<String> ToString(Isolate* isolate, Handle<BigInt> bigint,
int radix = 10,
ShouldThrow should_throw = kThrowOnError);
// Like the above, but adapted for the needs of producing error messages:
// doesn't care about termination requests, and returns a default string
// for inputs beyond a relatively low upper bound.
static Handle<String> NoSideEffectsToString(Isolate* isolate,
Handle<BigInt> bigint);
// "The Number value for x", see:
// https://tc39.github.io/ecma262/#sec-ecmascript-language-types-number-type
// Returns a Smi or HeapNumber.

View File

@ -487,14 +487,7 @@ MaybeHandle<String> Object::NoSideEffectsToMaybeString(Isolate* isolate,
} while (currInput->IsJSProxy());
return NoSideEffectsToString(isolate, currInput);
} else if (input->IsBigInt()) {
MaybeHandle<String> maybe_string =
BigInt::ToString(isolate, Handle<BigInt>::cast(input), 10, kDontThrow);
Handle<String> result;
if (maybe_string.ToHandle(&result)) return result;
// BigInt-to-String conversion can fail on 32-bit platforms where
// String::kMaxLength is too small to fit this BigInt.
return isolate->factory()->NewStringFromStaticChars(
"<a very large BigInt>");
return BigInt::NoSideEffectsToString(isolate, Handle<BigInt>::cast(input));
} else if (input->IsFunction()) {
// -- F u n c t i o n
Handle<String> fun_str;

View File

@ -0,0 +1,23 @@
// Copyright 2023 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --stack-size=100
function worker_code(arr) {
postMessage("worker starting");
const large_bigint = 2n ** 100_000n;
function f() {
// Run until near the stack limit.
try { f(); } catch (e) {
postMessage("stack limit reached");
postMessage(arr[large_bigint]);
}
}
onmessage = f;
}
let w = new Worker(worker_code, { "arguments": [], "type": "function" });
assertEquals("worker starting", w.getMessage());
w.postMessage("");
assertEquals("stack limit reached", w.getMessage());
w.terminate();