Commit 4dce9690 authored by jbroman's avatar jbroman Committed by Commit bot

Blink-compatible serialization of Boolean, Number and String objects.

BUG=chromium:148757

Review-Url: https://codereview.chromium.org/2265603002
Cr-Commit-Position: refs/heads/master@{#38830}
parent 6993943e
......@@ -72,6 +72,13 @@ enum class SerializationTag : uint8_t {
kEndDenseJSArray = '$',
// Date. millisSinceEpoch:double
kDate = 'D',
// Boolean object. No data.
kTrueObject = 'y',
kFalseObject = 'x',
// Number object. value:double
kNumberObject = 'n',
// String object, UTF-8 encoding. byteLength:uint32_t, then raw data.
kStringObject = 's',
};
ValueSerializer::ValueSerializer(Isolate* isolate)
......@@ -141,6 +148,7 @@ void ValueSerializer::WriteTwoByteString(Vector<const uc16> chars) {
}
uint8_t* ValueSerializer::ReserveRawBytes(size_t bytes) {
if (!bytes) return nullptr;
auto old_size = buffer_.size();
buffer_.resize(buffer_.size() + bytes);
return &buffer_[old_size];
......@@ -273,6 +281,8 @@ Maybe<bool> ValueSerializer::WriteJSReceiver(Handle<JSReceiver> receiver) {
case JS_DATE_TYPE:
WriteJSDate(JSDate::cast(*receiver));
return Just(true);
case JS_VALUE_TYPE:
return WriteJSValue(Handle<JSValue>::cast(receiver));
default:
UNIMPLEMENTED();
break;
......@@ -363,6 +373,33 @@ void ValueSerializer::WriteJSDate(JSDate* date) {
WriteDouble(date->value()->Number());
}
Maybe<bool> ValueSerializer::WriteJSValue(Handle<JSValue> value) {
Object* inner_value = value->value();
if (inner_value->IsTrue(isolate_)) {
WriteTag(SerializationTag::kTrueObject);
} else if (inner_value->IsFalse(isolate_)) {
WriteTag(SerializationTag::kFalseObject);
} else if (inner_value->IsNumber()) {
WriteTag(SerializationTag::kNumberObject);
WriteDouble(inner_value->Number());
} else if (inner_value->IsString()) {
// TODO(jbroman): Replace UTF-8 encoding with the same options available for
// ordinary strings.
WriteTag(SerializationTag::kStringObject);
v8::Local<v8::String> api_string =
Utils::ToLocal(handle(String::cast(inner_value), isolate_));
uint32_t utf8_length = api_string->Utf8Length();
WriteVarint(utf8_length);
api_string->WriteUtf8(reinterpret_cast<char*>(ReserveRawBytes(utf8_length)),
utf8_length, nullptr,
v8::String::NO_NULL_TERMINATION);
} else {
DCHECK(inner_value->IsSymbol());
return Nothing<bool>();
}
return Just(true);
}
Maybe<uint32_t> ValueSerializer::WriteJSObjectProperties(
Handle<JSObject> object, Handle<FixedArray> keys) {
uint32_t properties_written = 0;
......@@ -545,6 +582,11 @@ MaybeHandle<Object> ValueDeserializer::ReadObject() {
return ReadDenseJSArray();
case SerializationTag::kDate:
return ReadJSDate();
case SerializationTag::kTrueObject:
case SerializationTag::kFalseObject:
case SerializationTag::kNumberObject:
case SerializationTag::kStringObject:
return ReadJSValue(tag);
default:
return MaybeHandle<Object>();
}
......@@ -686,6 +728,45 @@ MaybeHandle<JSDate> ValueDeserializer::ReadJSDate() {
return date;
}
MaybeHandle<JSValue> ValueDeserializer::ReadJSValue(SerializationTag tag) {
uint32_t id = next_id_++;
Handle<JSValue> value;
switch (tag) {
case SerializationTag::kTrueObject:
value = Handle<JSValue>::cast(
isolate_->factory()->NewJSObject(isolate_->boolean_function()));
value->set_value(isolate_->heap()->true_value());
break;
case SerializationTag::kFalseObject:
value = Handle<JSValue>::cast(
isolate_->factory()->NewJSObject(isolate_->boolean_function()));
value->set_value(isolate_->heap()->false_value());
break;
case SerializationTag::kNumberObject: {
double number;
if (!ReadDouble().To(&number)) return MaybeHandle<JSValue>();
value = Handle<JSValue>::cast(
isolate_->factory()->NewJSObject(isolate_->number_function()));
Handle<Object> number_object = isolate_->factory()->NewNumber(number);
value->set_value(*number_object);
break;
}
case SerializationTag::kStringObject: {
Handle<String> string;
if (!ReadUtf8String().ToHandle(&string)) return MaybeHandle<JSValue>();
value = Handle<JSValue>::cast(
isolate_->factory()->NewJSObject(isolate_->string_function()));
value->set_value(*string);
break;
}
default:
UNREACHABLE();
return MaybeHandle<JSValue>();
}
AddObjectWithID(id, value);
return value;
}
Maybe<uint32_t> ValueDeserializer::ReadJSObjectProperties(
Handle<JSObject> object, SerializationTag end_tag) {
for (uint32_t num_properties = 0;; num_properties++) {
......
......@@ -21,6 +21,7 @@ namespace internal {
class HeapNumber;
class Isolate;
class JSDate;
class JSValue;
class Object;
class Oddball;
class Smi;
......@@ -75,6 +76,7 @@ class ValueSerializer {
Maybe<bool> WriteJSObject(Handle<JSObject> object) WARN_UNUSED_RESULT;
Maybe<bool> WriteJSArray(Handle<JSArray> array) WARN_UNUSED_RESULT;
void WriteJSDate(JSDate* date);
Maybe<bool> WriteJSValue(Handle<JSValue> value) WARN_UNUSED_RESULT;
/*
* Reads the specified keys from the object and writes key-value pairs to the
......@@ -146,6 +148,7 @@ class ValueDeserializer {
MaybeHandle<JSArray> ReadSparseJSArray() WARN_UNUSED_RESULT;
MaybeHandle<JSArray> ReadDenseJSArray() WARN_UNUSED_RESULT;
MaybeHandle<JSDate> ReadJSDate() WARN_UNUSED_RESULT;
MaybeHandle<JSValue> ReadJSValue(SerializationTag tag) WARN_UNUSED_RESULT;
/*
* Reads key-value pairs into the object until the specified end tag is
......
......@@ -83,6 +83,10 @@ class ValueSerializerTest : public TestWithIsolate {
functor(try_catch.Message());
}
void InvalidEncodeTest(const char* source) {
InvalidEncodeTest(source, [](Local<Message>) {});
}
template <typename OutputFunctor>
void DecodeTest(const std::vector<uint8_t>& data,
const OutputFunctor& output_functor) {
......@@ -1126,5 +1130,138 @@ TEST_F(ValueSerializerTest, DecodeDate) {
});
}
TEST_F(ValueSerializerTest, RoundTripValueObjects) {
RoundTripTest("new Boolean(true)", [this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Boolean.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.valueOf() === true"));
});
RoundTripTest("new Boolean(false)", [this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Boolean.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.valueOf() === false"));
});
RoundTripTest(
"({ a: new Boolean(true), get b() { return this.a; }})",
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool("result.a instanceof Boolean"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.a === result.b"));
});
RoundTripTest("new Number(-42)", [this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Number.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.valueOf() === -42"));
});
RoundTripTest("new Number(NaN)", [this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Number.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool("Number.isNaN(result.valueOf())"));
});
RoundTripTest(
"({ a: new Number(6), get b() { return this.a; }})",
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool("result.a instanceof Number"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.a === result.b"));
});
RoundTripTest("new String('Qu\\xe9bec')", [this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === String.prototype"));
EXPECT_TRUE(
EvaluateScriptForResultBool("result.valueOf() === 'Qu\\xe9bec'"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.length === 6"));
});
RoundTripTest("new String('\\ud83d\\udc4a')", [this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === String.prototype"));
EXPECT_TRUE(
EvaluateScriptForResultBool("result.valueOf() === '\\ud83d\\udc4a'"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.length === 2"));
});
RoundTripTest(
"({ a: new String(), get b() { return this.a; }})",
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool("result.a instanceof String"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.a === result.b"));
});
}
TEST_F(ValueSerializerTest, RejectsOtherValueObjects) {
// This is a roundabout way of getting an instance of Symbol.
InvalidEncodeTest("Object.valueOf.apply(Symbol())");
}
TEST_F(ValueSerializerTest, DecodeValueObjects) {
DecodeTest(
{0xff, 0x09, 0x3f, 0x00, 0x79, 0x00},
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Boolean.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.valueOf() === true"));
});
DecodeTest(
{0xff, 0x09, 0x3f, 0x00, 0x78, 0x00},
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Boolean.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.valueOf() === false"));
});
DecodeTest(
{0xff, 0x09, 0x3f, 0x00, 0x6f, 0x3f, 0x01, 0x53, 0x01, 0x61, 0x3f, 0x01,
0x79, 0x3f, 0x02, 0x53, 0x01, 0x62, 0x3f, 0x02, 0x5e, 0x01, 0x7b, 0x02},
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool("result.a instanceof Boolean"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.a === result.b"));
});
DecodeTest(
{0xff, 0x09, 0x3f, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45,
0xc0, 0x00},
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Number.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.valueOf() === -42"));
});
DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xf8, 0x7f, 0x00},
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Number.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool(
"Number.isNaN(result.valueOf())"));
});
DecodeTest(
{0xff, 0x09, 0x3f, 0x00, 0x6f, 0x3f, 0x01, 0x53, 0x01, 0x61, 0x3f,
0x01, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x40, 0x3f,
0x02, 0x53, 0x01, 0x62, 0x3f, 0x02, 0x5e, 0x01, 0x7b, 0x02},
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool("result.a instanceof Number"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.a === result.b"));
});
DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x73, 0x07, 0x51, 0x75, 0xc3, 0xa9, 0x62,
0x65, 0x63, 0x00},
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === String.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool(
"result.valueOf() === 'Qu\\xe9bec'"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.length === 6"));
});
DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x73, 0x04, 0xf0, 0x9f, 0x91, 0x8a},
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === String.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool(
"result.valueOf() === '\\ud83d\\udc4a'"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.length === 2"));
});
DecodeTest(
{0xff, 0x09, 0x3f, 0x00, 0x6f, 0x3f, 0x01, 0x53, 0x01,
0x61, 0x3f, 0x01, 0x73, 0x00, 0x3f, 0x02, 0x53, 0x01,
0x62, 0x3f, 0x02, 0x5e, 0x01, 0x7b, 0x02, 0x00},
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool("result.a instanceof String"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.a === result.b"));
});
}
} // namespace
} // namespace v8
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