Commit ead5abac authored by Adam Klein's avatar Adam Klein Committed by Commit Bot

[bigint] Correct StringToBigInt radix and junk handling

Fix two more places where StringToBigInt differs from parseInt:
  - Binary and octal radix prefixes are supported
  - Trailing non-whitespace junk is not allowed

This is done with a new Behavior enum in BigIntParseIntHelper,
along with a couple of bool configuration flags in StringToIntHelper.

Bug: v8:6791, v8:7038
Change-Id: Ib91bfc5ccb04ad0dd6c99bc81e19c1239264a469
Reviewed-on: https://chromium-review.googlesource.com/764595Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Reviewed-by: 's avatarGeorg Neis <neis@chromium.org>
Commit-Queue: Adam Klein <adamk@chromium.org>
Cr-Commit-Position: refs/heads/master@{#49335}
parent 4091f2b3
...@@ -184,6 +184,12 @@ class StringToIntHelper { ...@@ -184,6 +184,12 @@ class StringToIntHelper {
DCHECK(subject->IsFlat()); DCHECK(subject->IsFlat());
} }
// Used for the StringToBigInt operation.
StringToIntHelper(Isolate* isolate, Handle<String> subject)
: isolate_(isolate), subject_(subject) {
DCHECK(subject->IsFlat());
}
// Used for parsing BigInt literals, where the input is a Zone-allocated // Used for parsing BigInt literals, where the input is a Zone-allocated
// buffer of one-byte digits, along with an optional radix prefix. // buffer of one-byte digits, along with an optional radix prefix.
StringToIntHelper(Isolate* isolate, const uint8_t* subject, int length) StringToIntHelper(Isolate* isolate, const uint8_t* subject, int length)
...@@ -201,6 +207,13 @@ class StringToIntHelper { ...@@ -201,6 +207,13 @@ class StringToIntHelper {
// Subclasses may override this. // Subclasses may override this.
virtual void HandleSpecialCases() {} virtual void HandleSpecialCases() {}
// Subclass constructors should call these for configuration before calling
// ParseInt().
void set_allow_binary_and_octal_prefixes() {
allow_binary_and_octal_prefixes_ = true;
}
void set_disallow_trailing_junk() { allow_trailing_junk_ = false; }
bool IsOneByte() const { bool IsOneByte() const {
return raw_one_byte_subject_ != nullptr || return raw_one_byte_subject_ != nullptr ||
subject_->IsOneByteRepresentationUnderneath(); subject_->IsOneByteRepresentationUnderneath();
...@@ -220,22 +233,17 @@ class StringToIntHelper { ...@@ -220,22 +233,17 @@ class StringToIntHelper {
// Subclasses get access to internal state: // Subclasses get access to internal state:
enum State { kRunning, kError, kJunk, kEmpty, kZero, kDone }; enum State { kRunning, kError, kJunk, kEmpty, kZero, kDone };
enum class Sign { kNegative, kPositive, kNone };
Isolate* isolate() { return isolate_; } Isolate* isolate() { return isolate_; }
int radix() { return radix_; } int radix() { return radix_; }
int cursor() { return cursor_; } int cursor() { return cursor_; }
int length() { return length_; } int length() { return length_; }
bool negative() { return negative_; } bool negative() { return sign_ == Sign::kNegative; }
Sign sign() { return sign_; }
State state() { return state_; } State state() { return state_; }
void set_state(State state) { state_ = state; } void set_state(State state) { state_ = state; }
bool AllowOctalRadixPrefix() const {
return raw_one_byte_subject_ != nullptr;
}
bool AllowBinaryRadixPrefix() const {
return raw_one_byte_subject_ != nullptr;
}
private: private:
template <class Char> template <class Char>
void DetectRadixInternal(Char current, int length); void DetectRadixInternal(Char current, int length);
...@@ -248,8 +256,10 @@ class StringToIntHelper { ...@@ -248,8 +256,10 @@ class StringToIntHelper {
int radix_ = 0; int radix_ = 0;
int cursor_ = 0; int cursor_ = 0;
int length_ = 0; int length_ = 0;
bool negative_ = false; Sign sign_ = Sign::kNone;
bool leading_zero_ = false; bool leading_zero_ = false;
bool allow_binary_and_octal_prefixes_ = false;
bool allow_trailing_junk_ = true;
State state_ = kRunning; State state_ = kRunning;
}; };
...@@ -300,12 +310,13 @@ void StringToIntHelper::DetectRadixInternal(Char current, int length) { ...@@ -300,12 +310,13 @@ void StringToIntHelper::DetectRadixInternal(Char current, int length) {
if (current == end) { if (current == end) {
return set_state(kJunk); return set_state(kJunk);
} }
sign_ = Sign::kPositive;
} else if (*current == '-') { } else if (*current == '-') {
++current; ++current;
if (current == end) { if (current == end) {
return set_state(kJunk); return set_state(kJunk);
} }
negative_ = true; sign_ = Sign::kNegative;
} }
if (radix_ == 0) { if (radix_ == 0) {
...@@ -318,12 +329,12 @@ void StringToIntHelper::DetectRadixInternal(Char current, int length) { ...@@ -318,12 +329,12 @@ void StringToIntHelper::DetectRadixInternal(Char current, int length) {
radix_ = 16; radix_ = 16;
++current; ++current;
if (current == end) return set_state(kJunk); if (current == end) return set_state(kJunk);
} else if (AllowOctalRadixPrefix() && } else if (allow_binary_and_octal_prefixes_ &&
(*current == 'o' || *current == 'O')) { (*current == 'o' || *current == 'O')) {
radix_ = 8; radix_ = 8;
++current; ++current;
DCHECK(current != end); DCHECK(current != end);
} else if (AllowBinaryRadixPrefix() && } else if (allow_binary_and_octal_prefixes_ &&
(*current == 'b' || *current == 'B')) { (*current == 'b' || *current == 'B')) {
radix_ = 2; radix_ = 2;
++current; ++current;
...@@ -420,6 +431,11 @@ void StringToIntHelper::ParseInternal(Char start) { ...@@ -420,6 +431,11 @@ void StringToIntHelper::ParseInternal(Char start) {
ResultMultiplyAdd(multiplier, part); ResultMultiplyAdd(multiplier, part);
} while (!done); } while (!done);
if (!allow_trailing_junk_ &&
AdvanceToNonspace(isolate_->unicode_cache(), &current, end)) {
return set_state(kJunk);
}
return set_state(kDone); return set_state(kDone);
} }
...@@ -838,30 +854,39 @@ double StringToInt(Isolate* isolate, Handle<String> string, int radix) { ...@@ -838,30 +854,39 @@ double StringToInt(Isolate* isolate, Handle<String> string, int radix) {
class BigIntParseIntHelper : public StringToIntHelper { class BigIntParseIntHelper : public StringToIntHelper {
public: public:
// Configures what to return for empty or whitespace-only input strings. enum class Behavior { kParseInt, kStringToBigInt, kLiteral };
enum class EmptyStringResult { kSyntaxError, kZero, kUnreachable };
// Used for BigInt.parseInt API, where the input is a Heap-allocated String. // Used for BigInt.parseInt API, where the input is a Heap-allocated String.
BigIntParseIntHelper(Isolate* isolate, Handle<String> string, int radix, BigIntParseIntHelper(Isolate* isolate, Handle<String> string, int radix)
EmptyStringResult empty_string_result,
ShouldThrow should_throw)
: StringToIntHelper(isolate, string, radix), : StringToIntHelper(isolate, string, radix),
empty_string_result_(empty_string_result), behavior_(Behavior::kParseInt) {}
should_throw_(should_throw) {}
// Used for StringToBigInt operation (BigInt constructor and == operator).
BigIntParseIntHelper(Isolate* isolate, Handle<String> string)
: StringToIntHelper(isolate, string),
behavior_(Behavior::kStringToBigInt) {
set_allow_binary_and_octal_prefixes();
set_disallow_trailing_junk();
}
// Used for parsing BigInt literals, where the input is a buffer of // Used for parsing BigInt literals, where the input is a buffer of
// one-byte ASCII digits, along with an optional radix prefix. // one-byte ASCII digits, along with an optional radix prefix.
BigIntParseIntHelper(Isolate* isolate, const uint8_t* string, int length) BigIntParseIntHelper(Isolate* isolate, const uint8_t* string, int length)
: StringToIntHelper(isolate, string, length), : StringToIntHelper(isolate, string, length),
empty_string_result_(EmptyStringResult::kUnreachable), behavior_(Behavior::kLiteral) {
should_throw_(kDontThrow) {} set_allow_binary_and_octal_prefixes();
}
MaybeHandle<BigInt> GetResult() { MaybeHandle<BigInt> GetResult() {
ParseInt(); ParseInt();
if (behavior_ == Behavior::kStringToBigInt && sign() != Sign::kNone &&
radix() != 10) {
return MaybeHandle<BigInt>();
}
if (state() == kEmpty) { if (state() == kEmpty) {
if (empty_string_result_ == EmptyStringResult::kSyntaxError) { if (behavior_ == Behavior::kParseInt) {
set_state(kJunk); set_state(kJunk);
} else if (empty_string_result_ == EmptyStringResult::kZero) { } else if (behavior_ == Behavior::kStringToBigInt) {
set_state(kZero); set_state(kZero);
} else { } else {
UNREACHABLE(); UNREACHABLE();
...@@ -869,18 +894,18 @@ class BigIntParseIntHelper : public StringToIntHelper { ...@@ -869,18 +894,18 @@ class BigIntParseIntHelper : public StringToIntHelper {
} }
switch (state()) { switch (state()) {
case kJunk: case kJunk:
if (should_throw_ == kThrowOnError) { if (should_throw() == kThrowOnError) {
THROW_NEW_ERROR(isolate(), THROW_NEW_ERROR(isolate(),
NewSyntaxError(MessageTemplate::kBigIntInvalidString), NewSyntaxError(MessageTemplate::kBigIntInvalidString),
BigInt); BigInt);
} else { } else {
DCHECK_EQ(should_throw_, kDontThrow); DCHECK_EQ(should_throw(), kDontThrow);
return MaybeHandle<BigInt>(); return MaybeHandle<BigInt>();
} }
case kZero: case kZero:
return isolate()->factory()->NewBigIntFromInt(0); return isolate()->factory()->NewBigIntFromInt(0);
case kError: case kError:
DCHECK_EQ(should_throw_ == kThrowOnError, DCHECK_EQ(should_throw() == kThrowOnError,
isolate()->has_pending_exception()); isolate()->has_pending_exception());
return MaybeHandle<BigInt>(); return MaybeHandle<BigInt>();
case kDone: case kDone:
...@@ -901,8 +926,9 @@ class BigIntParseIntHelper : public StringToIntHelper { ...@@ -901,8 +926,9 @@ class BigIntParseIntHelper : public StringToIntHelper {
// Optimization opportunity: Would it makes sense to scan for trailing // Optimization opportunity: Would it makes sense to scan for trailing
// junk before allocating the result? // junk before allocating the result?
int charcount = length() - cursor(); int charcount = length() - cursor();
// TODO(adamk): Pretenure if this is for a literal.
MaybeHandle<BigInt> maybe = MaybeHandle<BigInt> maybe =
BigInt::AllocateFor(isolate(), radix(), charcount, should_throw_); BigInt::AllocateFor(isolate(), radix(), charcount, should_throw());
if (!maybe.ToHandle(&result_)) { if (!maybe.ToHandle(&result_)) {
set_state(kError); set_state(kError);
} }
...@@ -914,23 +940,22 @@ class BigIntParseIntHelper : public StringToIntHelper { ...@@ -914,23 +940,22 @@ class BigIntParseIntHelper : public StringToIntHelper {
} }
private: private:
ShouldThrow should_throw() const {
return behavior_ == Behavior::kParseInt ? kThrowOnError : kDontThrow;
}
Handle<BigInt> result_; Handle<BigInt> result_;
EmptyStringResult empty_string_result_; Behavior behavior_;
ShouldThrow should_throw_;
}; };
MaybeHandle<BigInt> BigIntParseInt(Isolate* isolate, Handle<String> string, MaybeHandle<BigInt> BigIntParseInt(Isolate* isolate, Handle<String> string,
int radix) { int radix) {
BigIntParseIntHelper helper( BigIntParseIntHelper helper(isolate, string, radix);
isolate, string, radix,
BigIntParseIntHelper::EmptyStringResult::kSyntaxError, kThrowOnError);
return helper.GetResult(); return helper.GetResult();
} }
MaybeHandle<BigInt> StringToBigInt(Isolate* isolate, Handle<String> string) { MaybeHandle<BigInt> StringToBigInt(Isolate* isolate, Handle<String> string) {
BigIntParseIntHelper helper(isolate, string, 10, BigIntParseIntHelper helper(isolate, string);
BigIntParseIntHelper::EmptyStringResult::kZero,
kDontThrow);
return helper.GetResult(); return helper.GetResult();
} }
......
...@@ -35,11 +35,22 @@ const six = BigInt(6); ...@@ -35,11 +35,22 @@ const six = BigInt(6);
assertThrows(() => BigInt(null), TypeError); assertThrows(() => BigInt(null), TypeError);
assertThrows(() => BigInt({}), SyntaxError); assertThrows(() => BigInt({}), SyntaxError);
assertThrows(() => BigInt("foo"), SyntaxError); assertThrows(() => BigInt("foo"), SyntaxError);
assertThrows(() => BigInt("1j"), SyntaxError);
assertThrows(() => BigInt("0b1ju"), SyntaxError);
assertThrows(() => BigInt("0o1jun"), SyntaxError);
assertThrows(() => BigInt("0x1junk"), SyntaxError);
}{ }{
assertSame(BigInt(true), 1n); assertSame(BigInt(true), 1n);
assertSame(BigInt(false), 0n); assertSame(BigInt(false), 0n);
assertSame(BigInt(""), 0n); assertSame(BigInt(""), 0n);
assertSame(BigInt(" 42"), 42n); assertSame(BigInt(" 42"), 42n);
assertSame(BigInt("0b101010"), 42n);
assertSame(BigInt(" 0b101011"), 43n);
assertSame(BigInt("0x2a "), 42n);
assertSame(BigInt(" 0x2b"), 43n);
assertSame(BigInt("0o52"), 42n);
assertSame(BigInt(" 0o53\n"), 43n);
assertSame(BigInt(-0), 0n); assertSame(BigInt(-0), 0n);
assertSame(BigInt(42), 42n); assertSame(BigInt(42), 42n);
assertSame(BigInt(42n), 42n); assertSame(BigInt(42n), 42n);
......
...@@ -160,6 +160,18 @@ const six = BigInt(6); ...@@ -160,6 +160,18 @@ const six = BigInt(6);
assertFalse(Symbol() == zero); assertFalse(Symbol() == zero);
assertFalse(zero == Symbol()); assertFalse(zero == Symbol());
assertTrue(one == "0b1");
assertTrue(" 0b1" == one);
assertTrue(one == "0o1");
assertTrue("0o1 " == one);
assertTrue(one == "\n0x1");
assertTrue("0x1" == one);
assertFalse(one == "1j");
assertFalse(one == "0b1ju");
assertFalse(one == "0o1jun");
assertFalse(one == "0x1junk");
}{ }{
assertFalse(%NotEqual(zero, zero)); assertFalse(%NotEqual(zero, zero));
assertFalse(%NotEqual(zero, another_zero)); assertFalse(%NotEqual(zero, another_zero));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment