Commit 3872ed65 authored by Adam Klein's avatar Adam Klein Committed by Commit Bot

[bigint] Support parsing of BigInt literals

Reuses the existing logic for BigInt.parseInt, adapted slightly
to allow octal and binary radix prefixes (and to support parsing
of a raw character buffer, rather than a v8::internal::String).

Bug: v8:6791
Change-Id: I41904b2204721eac452e0765fa9ff0ab26ee343b
Reviewed-on: https://chromium-review.googlesource.com/711334
Commit-Queue: Adam Klein <adamk@chromium.org>
Reviewed-by: 's avatarLeszek Swirski <leszeks@chromium.org>
Reviewed-by: 's avatarMarja Hölttä <marja@chromium.org>
Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#48560}
parent c076667b
......@@ -213,6 +213,17 @@ bool AstValue::BooleanValue() const {
return DoubleToBoolean(number_);
case SMI:
return smi_ != 0;
case BIGINT: {
size_t length = strlen(bigint_buffer_);
DCHECK_GT(length, 0);
if (length == 1 && bigint_buffer_[0] == '0') return false;
// Skip over any radix prefix; BigInts with length > 1 only
// begin with zero if they include a radix.
for (size_t i = (bigint_buffer_[0] == '0') ? 2 : 0; i < length; ++i) {
if (bigint_buffer_[i] != '0') return true;
}
return false;
}
case BOOLEAN:
return bool_;
case NULL_TYPE:
......@@ -247,6 +258,11 @@ void AstValue::Internalize(Isolate* isolate) {
case SMI:
set_value(handle(Smi::FromInt(smi_), isolate));
break;
case BIGINT:
// TODO(adamk): Don't check-fail on conversion failure; instead
// check for errors during parsing and throw at that point.
set_value(StringToBigInt(isolate, bigint_buffer_).ToHandleChecked());
break;
case BOOLEAN:
if (bool_) {
set_value(isolate->factory()->true_value());
......@@ -396,6 +412,11 @@ const AstValue* AstValueFactory::NewSmi(uint32_t number) {
return AddValue(value);
}
const AstValue* AstValueFactory::NewBigInt(const char* number) {
AstValue* value = new (zone_) AstValue(number);
return AddValue(value);
}
#define GENERATE_VALUE_GETTER(value, initializer) \
if (!value) { \
value = AddValue(new (zone_) AstValue(initializer)); \
......
......@@ -217,10 +217,11 @@ class AstValue : public ZoneObject {
bool IsPropertyName() const;
bool BooleanValue() const;
V8_EXPORT_PRIVATE bool BooleanValue() const;
bool IsSmi() const { return type_ == SMI; }
bool IsHeapNumber() const { return type_ == NUMBER; }
bool IsBigInt() const { return type_ == BIGINT; }
bool IsFalse() const { return type_ == BOOLEAN && !bool_; }
bool IsTrue() const { return type_ == BOOLEAN && bool_; }
bool IsUndefined() const { return type_ == UNDEFINED; }
......@@ -249,6 +250,7 @@ class AstValue : public ZoneObject {
SYMBOL,
NUMBER,
SMI,
BIGINT,
BOOLEAN,
NULL_TYPE,
UNDEFINED,
......@@ -270,6 +272,10 @@ class AstValue : public ZoneObject {
smi_ = i;
}
explicit AstValue(const char* n) : type_(BIGINT), next_(nullptr) {
bigint_buffer_ = n;
}
explicit AstValue(bool b) : type_(BOOLEAN), next_(nullptr) { bool_ = b; }
explicit AstValue(Type t) : type_(t), next_(nullptr) {
......@@ -292,6 +298,7 @@ class AstValue : public ZoneObject {
int smi_;
bool bool_;
AstSymbol symbol_;
const char* bigint_buffer_;
};
};
......@@ -430,6 +437,7 @@ class AstValueFactory {
const AstValue* NewSymbol(AstSymbol symbol);
V8_EXPORT_PRIVATE const AstValue* NewNumber(double number);
const AstValue* NewSmi(uint32_t number);
V8_EXPORT_PRIVATE const AstValue* NewBigInt(const char* number);
const AstValue* NewBoolean(bool b);
const AstValue* NewStringList(ZoneList<const AstRawString*>* strings);
const AstValue* NewNull();
......
......@@ -3134,6 +3134,10 @@ class AstNodeFactory final BASE_EMBEDDED {
return new (zone_) Literal(ast_value_factory_->NewSmi(number), pos);
}
Literal* NewBigIntLiteral(const char* buffer, int pos) {
return new (zone_) Literal(ast_value_factory_->NewBigInt(buffer), pos);
}
Literal* NewBooleanLiteral(bool b, int pos) {
return new (zone_) Literal(ast_value_factory_->NewBoolean(b), pos);
}
......
......@@ -184,6 +184,11 @@ class StringToIntHelper {
: isolate_(isolate), subject_(subject), radix_(radix) {
DCHECK(subject->IsFlat());
}
// Used for parsing BigInt literals, where the input is a Zone-allocated
// buffer of one-byte digits, along with an optional radix prefix.
StringToIntHelper(Isolate* isolate, const uint8_t* subject, int length)
: isolate_(isolate), raw_one_byte_subject_(subject), length_(length) {}
virtual ~StringToIntHelper() {}
protected:
......@@ -197,11 +202,26 @@ class StringToIntHelper {
// Subclasses may override this.
virtual void HandleSpecialCases() {}
bool IsOneByte() const {
return raw_one_byte_subject_ != nullptr ||
subject_->IsOneByteRepresentationUnderneath();
}
Vector<const uint8_t> GetOneByteVector() {
if (raw_one_byte_subject_ != nullptr) {
return Vector<const uint8_t>(raw_one_byte_subject_, length_);
}
return subject_->GetFlatContent().ToOneByteVector();
}
Vector<const uc16> GetTwoByteVector() {
return subject_->GetFlatContent().ToUC16Vector();
}
// Subclasses get access to internal state:
enum State { kRunning, kError, kJunk, kZero, kDone };
Isolate* isolate() { return isolate_; }
Handle<String> subject() { return subject_; }
int radix() { return radix_; }
int cursor() { return cursor_; }
int length() { return length_; }
......@@ -209,6 +229,14 @@ class StringToIntHelper {
State state() { return 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:
template <class Char>
void DetectRadixInternal(Char current, int length);
......@@ -217,7 +245,8 @@ class StringToIntHelper {
Isolate* isolate_;
Handle<String> subject_;
int radix_;
const uint8_t* raw_one_byte_subject_ = nullptr;
int radix_ = 0;
int cursor_ = 0;
int length_ = 0;
bool negative_ = false;
......@@ -228,12 +257,11 @@ class StringToIntHelper {
void StringToIntHelper::ParseInt() {
{
DisallowHeapAllocation no_gc;
String::FlatContent flat = subject_->GetFlatContent();
if (flat.IsOneByte()) {
Vector<const uint8_t> vector = flat.ToOneByteVector();
if (IsOneByte()) {
Vector<const uint8_t> vector = GetOneByteVector();
DetectRadixInternal(vector.start(), vector.length());
} else {
Vector<const uc16> vector = flat.ToUC16Vector();
Vector<const uc16> vector = GetTwoByteVector();
DetectRadixInternal(vector.start(), vector.length());
}
}
......@@ -243,13 +271,12 @@ void StringToIntHelper::ParseInt() {
if (state_ != kRunning) return;
{
DisallowHeapAllocation no_gc;
String::FlatContent flat = subject_->GetFlatContent();
if (flat.IsOneByte()) {
Vector<const uint8_t> vector = flat.ToOneByteVector();
if (IsOneByte()) {
Vector<const uint8_t> vector = GetOneByteVector();
DCHECK_EQ(length_, vector.length());
ParseInternal(vector.start());
} else {
Vector<const uc16> vector = flat.ToUC16Vector();
Vector<const uc16> vector = GetTwoByteVector();
DCHECK_EQ(length_, vector.length());
ParseInternal(vector.start());
}
......@@ -292,6 +319,16 @@ void StringToIntHelper::DetectRadixInternal(Char current, int length) {
radix_ = 16;
++current;
if (current == end) return set_state(kJunk);
} else if (AllowOctalRadixPrefix() &&
(*current == 'o' || *current == 'O')) {
radix_ = 8;
++current;
DCHECK(current != end);
} else if (AllowBinaryRadixPrefix() &&
(*current == 'b' || *current == 'B')) {
radix_ = 2;
++current;
DCHECK(current != end);
} else {
leading_zero_ = true;
}
......@@ -419,14 +456,13 @@ class NumberParseIntHelper : public StringToIntHelper {
bool is_power_of_two = base::bits::IsPowerOfTwo(radix());
if (!is_power_of_two && radix() != 10) return;
DisallowHeapAllocation no_gc;
String::FlatContent flat = subject()->GetFlatContent();
if (flat.IsOneByte()) {
Vector<const uint8_t> vector = flat.ToOneByteVector();
if (IsOneByte()) {
Vector<const uint8_t> vector = GetOneByteVector();
DCHECK_EQ(length(), vector.length());
result_ = is_power_of_two ? HandlePowerOfTwoCase(vector.start())
: HandleBaseTenCase(vector.start());
} else {
Vector<const uc16> vector = flat.ToUC16Vector();
Vector<const uc16> vector = GetTwoByteVector();
DCHECK_EQ(length(), vector.length());
result_ = is_power_of_two ? HandlePowerOfTwoCase(vector.start())
: HandleBaseTenCase(vector.start());
......@@ -802,9 +838,15 @@ double StringToInt(Isolate* isolate, Handle<String> string, int radix) {
class BigIntParseIntHelper : public StringToIntHelper {
public:
// Used for BigInt.parseInt API, where the input is a Heap-allocated String.
BigIntParseIntHelper(Isolate* isolate, Handle<String> string, int radix)
: StringToIntHelper(isolate, string, radix) {}
// Used for parsing BigInt literals, where the input is a buffer of
// one-byte ASCII digits, along with an optional radix prefix.
BigIntParseIntHelper(Isolate* isolate, const uint8_t* string, int length)
: StringToIntHelper(isolate, string, length) {}
MaybeHandle<BigInt> GetResult() {
ParseInt();
switch (state()) {
......@@ -855,6 +897,12 @@ MaybeHandle<BigInt> StringToBigInt(Isolate* isolate, Handle<String> string,
return helper.GetResult();
}
MaybeHandle<BigInt> StringToBigInt(Isolate* isolate, const char* string) {
BigIntParseIntHelper helper(isolate, reinterpret_cast<const uint8_t*>(string),
static_cast<int>(strlen(string)));
return helper.GetResult();
}
const char* DoubleToCString(double v, Vector<char> buffer) {
switch (FPCLASSIFY_NAMESPACE::fpclassify(v)) {
case FP_NAN: return "NaN";
......
......@@ -108,6 +108,13 @@ double StringToInt(Isolate* isolate, Handle<String> string, int radix);
MaybeHandle<BigInt> StringToBigInt(Isolate* isolate, Handle<String> string,
int radix);
// This version expects a zero-terminated character array. Radix will
// be inferred from string prefix (case-insensitive):
// 0x -> hex
// 0o -> octal
// 0b -> binary
MaybeHandle<BigInt> StringToBigInt(Isolate* isolate, const char* string);
const int kDoubleToCStringMinBufferSize = 100;
// Converts a double to a string value according to ECMA-262 9.8.1.
......
......@@ -600,7 +600,7 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::LoadLiteral(
return LoadTheHole();
} else if (ast_value->IsString()) {
return LoadLiteral(ast_value->AsString());
} else if (ast_value->IsHeapNumber()) {
} else if (ast_value->IsHeapNumber() || ast_value->IsBigInt()) {
size_t entry = GetConstantPoolEntry(ast_value);
OutputLdaConstant(entry);
return *this;
......@@ -1448,7 +1448,7 @@ size_t BytecodeArrayBuilder::GetConstantPoolEntry(
}
size_t BytecodeArrayBuilder::GetConstantPoolEntry(const AstValue* heap_number) {
DCHECK(heap_number->IsHeapNumber());
DCHECK(heap_number->IsHeapNumber() || heap_number->IsBigInt());
return constant_array_builder()->Insert(heap_number);
}
......
......@@ -191,12 +191,12 @@ size_t ConstantArrayBuilder::Insert(const AstRawString* raw_string) {
}
size_t ConstantArrayBuilder::Insert(const AstValue* heap_number) {
// This method only accepts heap numbers. Other types of ast value should
// either be passed through as raw values (in the case of strings), use the
// singleton Insert methods (in the case of symbols), or skip the constant
// pool entirely and use bytecodes with immediate values (Smis, booleans,
// undefined, etc.).
DCHECK(heap_number->IsHeapNumber());
// This method only accepts heap numbers and BigInts. Other types of
// AstValue should either be passed through as raw values (in the
// case of strings), use the singleton Insert methods (in the case
// of symbols), or skip the constant pool entirely and use bytecodes
// with immediate values (Smis, booleans, undefined, etc.).
DCHECK(heap_number->IsHeapNumber() || heap_number->IsBigInt());
return constants_map_
.LookupOrInsert(reinterpret_cast<intptr_t>(heap_number),
static_cast<uint32_t>(base::hash_value(heap_number)),
......@@ -340,7 +340,7 @@ Handle<Object> ConstantArrayBuilder::Entry::ToHandle(Isolate* isolate) const {
case Tag::kRawString:
return raw_string_->string();
case Tag::kHeapNumber:
DCHECK(heap_number_->IsHeapNumber());
DCHECK(heap_number_->IsHeapNumber() || heap_number_->IsBigInt());
return heap_number_->value();
case Tag::kScope:
return scope_->scope_info();
......
......@@ -298,6 +298,13 @@ class ParserBase {
#undef ALLOW_ACCESSORS
bool allow_harmony_bigint() const {
return scanner()->allow_harmony_bigint();
}
void set_allow_harmony_bigint(bool allow) {
scanner()->set_allow_harmony_bigint(allow);
}
uintptr_t stack_limit() const { return stack_limit_; }
void set_stack_limit(uintptr_t stack_limit) { stack_limit_ = stack_limit; }
......@@ -1549,6 +1556,7 @@ void ParserBase<Impl>::GetUnexpectedTokenMessage(
break;
case Token::SMI:
case Token::NUMBER:
case Token::BIGINT:
*message = MessageTemplate::kUnexpectedTokenNumber;
break;
case Token::STRING:
......@@ -1780,6 +1788,7 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParsePrimaryExpression(
case Token::FALSE_LITERAL:
case Token::SMI:
case Token::NUMBER:
case Token::BIGINT:
BindingPatternUnexpectedToken();
return impl()->ExpressionFromLiteral(Next(), beg_pos);
......@@ -4233,9 +4242,10 @@ template <typename Impl>
bool ParserBase<Impl>::IsTrivialExpression() {
Token::Value peek_token = peek();
if (peek_token == Token::SMI || peek_token == Token::NUMBER ||
peek_token == Token::NULL_LITERAL || peek_token == Token::TRUE_LITERAL ||
peek_token == Token::FALSE_LITERAL || peek_token == Token::STRING ||
peek_token == Token::IDENTIFIER || peek_token == Token::THIS) {
peek_token == Token::BIGINT || peek_token == Token::NULL_LITERAL ||
peek_token == Token::TRUE_LITERAL || peek_token == Token::FALSE_LITERAL ||
peek_token == Token::STRING || peek_token == Token::IDENTIFIER ||
peek_token == Token::THIS) {
// PeekAhead() is expensive & may not always be called, so we only call it
// after checking peek().
Token::Value peek_ahead = PeekAhead();
......
......@@ -405,6 +405,9 @@ Literal* Parser::ExpressionFromLiteral(Token::Value token, int pos) {
double value = scanner()->DoubleValue();
return factory()->NewNumberLiteral(value, pos);
}
case Token::BIGINT:
return factory()->NewBigIntLiteral(
scanner()->CurrentLiteralAsCString(zone()), pos);
default:
DCHECK(false);
}
......@@ -513,6 +516,7 @@ Parser::Parser(ParseInfo* info)
set_allow_harmony_import_meta(FLAG_harmony_import_meta);
set_allow_harmony_async_iteration(FLAG_harmony_async_iteration);
set_allow_harmony_template_escapes(FLAG_harmony_template_escapes);
set_allow_harmony_bigint(FLAG_harmony_bigint);
for (int feature = 0; feature < v8::Isolate::kUseCounterFeatureCount;
++feature) {
use_counts_[feature] = 0;
......
......@@ -299,6 +299,7 @@ class V8_EXPORT_PRIVATE Parser : public NON_EXPORTED_BASE(ParserBase<Parser>) {
SET_ALLOW(harmony_async_iteration);
SET_ALLOW(harmony_template_escapes);
SET_ALLOW(harmony_restrictive_generators);
SET_ALLOW(harmony_bigint);
#undef SET_ALLOW
}
return reusable_preparser_;
......
......@@ -196,6 +196,7 @@ Scanner::Scanner(UnicodeCache* unicode_cache, int* use_counts)
octal_pos_(Location::invalid()),
octal_message_(MessageTemplate::kNone),
found_html_comment_(false),
allow_harmony_bigint_(false),
use_counts_(use_counts) {}
void Scanner::Initialize(Utf16CharacterStream* source, bool is_module) {
......@@ -934,6 +935,7 @@ void Scanner::SanityCheckTokenDesc(const TokenDesc& token) const {
case Token::FUTURE_STRICT_RESERVED_WORD:
case Token::IDENTIFIER:
case Token::NUMBER:
case Token::BIGINT:
case Token::REGEXP_LITERAL:
case Token::SMI:
case Token::STRING:
......@@ -1321,14 +1323,20 @@ Token::Value Scanner::ScanNumber(bool seen_period) {
ScanDecimalDigits(); // optional
if (c0_ == '.') {
seen_period = true;
AddLiteralCharAdvance();
ScanDecimalDigits(); // optional
}
}
}
// scan exponent, if any
if (c0_ == 'e' || c0_ == 'E') {
bool is_bigint = false;
if (allow_harmony_bigint() && c0_ == 'n' && !seen_period &&
(kind == DECIMAL || kind == HEX || kind == OCTAL || kind == BINARY)) {
is_bigint = true;
Advance();
} else if (c0_ == 'e' || c0_ == 'E') {
// scan exponent, if any
DCHECK(kind != HEX); // 'e'/'E' must be scanned as part of the hex number
if (!(kind == DECIMAL || kind == DECIMAL_WITH_LEADING_ZERO))
return Token::ILLEGAL;
......@@ -1357,7 +1365,7 @@ Token::Value Scanner::ScanNumber(bool seen_period) {
octal_pos_ = Location(start_pos, source_pos());
octal_message_ = MessageTemplate::kStrictDecimalWithLeadingZero;
}
return Token::NUMBER;
return is_bigint ? Token::BIGINT : Token::NUMBER;
}
......@@ -1787,6 +1795,16 @@ double Scanner::DoubleValue() {
ALLOW_HEX | ALLOW_OCTAL | ALLOW_IMPLICIT_OCTAL | ALLOW_BINARY);
}
const char* Scanner::CurrentLiteralAsCString(Zone* zone) const {
DCHECK(is_literal_one_byte());
Vector<const uint8_t> vector = literal_one_byte_string();
int length = vector.length();
char* buffer = zone->NewArray<char>(length + 1);
memcpy(buffer, vector.start(), length);
buffer[length] = '\0';
return buffer;
}
bool Scanner::IsDuplicateSymbol(DuplicateFinder* duplicate_finder,
AstValueFactory* ast_value_factory) const {
DCHECK_NOT_NULL(duplicate_finder);
......
......@@ -260,6 +260,8 @@ class Scanner {
double DoubleValue();
const char* CurrentLiteralAsCString(Zone* zone) const;
inline bool CurrentMatches(Token::Value token) const {
DCHECK(Token::IsKeyword(token));
return current_.token == token;
......@@ -356,6 +358,9 @@ class Scanner {
bool FoundHtmlComment() const { return found_html_comment_; }
bool allow_harmony_bigint() const { return allow_harmony_bigint_; }
void set_allow_harmony_bigint(bool allow) { allow_harmony_bigint_ = allow; }
private:
// Scoped helper for saving & restoring scanner error state.
// This is used for tagged template literals, in which normally forbidden
......@@ -801,6 +806,9 @@ class Scanner {
// Whether this scanner encountered an HTML comment.
bool found_html_comment_;
// Whether to recognize BIGINT tokens.
bool allow_harmony_bigint_;
int* use_counts_;
MessageTemplate::Template scanner_error_;
......
......@@ -147,6 +147,7 @@ namespace internal {
T(NUMBER, nullptr, 0) \
T(SMI, nullptr, 0) \
T(STRING, nullptr, 0) \
T(BIGINT, NULL, 0) \
\
/* Identifiers (not keywords or future reserved words). */ \
T(IDENTIFIER, nullptr, 0) \
......
......@@ -399,6 +399,26 @@ const six = BigInt(6);
assertThrows(() => +One, TypeError);
}
// Literals
{
// Invalid literals.
assertThrows("00n", SyntaxError);
assertThrows("01n", SyntaxError);
assertThrows("0bn", SyntaxError);
assertThrows("0on", SyntaxError);
assertThrows("0xn", SyntaxError);
assertThrows("1.n", SyntaxError);
assertThrows("1.0n", SyntaxError);
assertThrows("1e25n", SyntaxError);
// Various radixes.
assertTrue(12345n === BigInt(12345));
assertTrue(0xabcden === BigInt(0xabcde));
assertTrue(0xAbCdEn === BigInt(0xabcde));
assertTrue(0o54321n === BigInt(0o54321));
assertTrue(0b1010101n === BigInt(0b1010101));
}
// Binary ops.
{
assertTrue(one + two === three);
......
......@@ -165,6 +165,7 @@ v8_source_set("unittests_sources") {
"libplatform/worker-thread-unittest.cc",
"locked-queue-unittest.cc",
"object-unittest.cc",
"parser/ast-value-unittest.cc",
"parser/preparser-unittest.cc",
"register-configuration-unittest.cc",
"run-all-unittests.cc",
......
// Copyright 2017 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.
#include "src/ast/ast-value-factory.h"
#include "src/heap/heap-inl.h"
#include "src/isolate-inl.h"
#include "src/zone/zone.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
namespace internal {
class AstValueTest : public TestWithIsolateAndZone {
protected:
AstValueTest()
: ast_value_factory_(zone(), i_isolate()->ast_string_constants(),
i_isolate()->heap()->HashSeed()) {}
AstValueFactory ast_value_factory_;
};
TEST_F(AstValueTest, BigIntBooleanValue) {
EXPECT_FALSE(ast_value_factory_.NewBigInt("0")->BooleanValue());
EXPECT_FALSE(ast_value_factory_.NewBigInt("0b0")->BooleanValue());
EXPECT_FALSE(ast_value_factory_.NewBigInt("0o0")->BooleanValue());
EXPECT_FALSE(ast_value_factory_.NewBigInt("0x0")->BooleanValue());
EXPECT_FALSE(ast_value_factory_.NewBigInt("0b000")->BooleanValue());
EXPECT_FALSE(ast_value_factory_.NewBigInt("0o00000")->BooleanValue());
EXPECT_FALSE(ast_value_factory_.NewBigInt("0x000000000")->BooleanValue());
EXPECT_TRUE(ast_value_factory_.NewBigInt("3")->BooleanValue());
EXPECT_TRUE(ast_value_factory_.NewBigInt("0b1")->BooleanValue());
EXPECT_TRUE(ast_value_factory_.NewBigInt("0o6")->BooleanValue());
EXPECT_TRUE(ast_value_factory_.NewBigInt("0xa")->BooleanValue());
EXPECT_TRUE(ast_value_factory_.NewBigInt("0b0000001")->BooleanValue());
EXPECT_TRUE(ast_value_factory_.NewBigInt("0o00005000")->BooleanValue());
EXPECT_TRUE(ast_value_factory_.NewBigInt("0x0000d00c0")->BooleanValue());
}
} // namespace internal
} // namespace v8
......@@ -138,6 +138,7 @@
'libplatform/worker-thread-unittest.cc',
'locked-queue-unittest.cc',
'object-unittest.cc',
'parser/ast-value-unittest.cc',
'parser/preparser-unittest.cc',
'register-configuration-unittest.cc',
'run-all-unittests.cc',
......
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