Blink-compatible serialization of numbers.
This includes unsigned integers (encoded as base-128 varints), signed integers (ZigZag-encoded, then varint-encoded) and doubles (written in host byte order). BUG=chromium:148757 Review-Url: https://codereview.chromium.org/2232323004 Cr-Commit-Position: refs/heads/master@{#38630}
This commit is contained in:
parent
d1dcebd1f5
commit
39bbb6f22a
@ -19,13 +19,26 @@ namespace internal {
|
||||
static const uint32_t kLatestVersion = 9;
|
||||
|
||||
enum class SerializationTag : uint8_t {
|
||||
// version:uint32_t (if at beginning of data, sets version > 0)
|
||||
kVersion = 0xFF,
|
||||
// ignore
|
||||
kPadding = '\0',
|
||||
// refTableSize:uint32_t (previously used for sanity checks; safe to ignore)
|
||||
kVerifyObjectCount = '?',
|
||||
// Oddballs (no data).
|
||||
kUndefined = '_',
|
||||
kNull = '0',
|
||||
kTrue = 'T',
|
||||
kFalse = 'F',
|
||||
// Number represented as 32-bit integer, ZigZag-encoded
|
||||
// (like sint32 in protobuf)
|
||||
kInt32 = 'I',
|
||||
// Number represented as 32-bit unsigned integer, varint-encoded
|
||||
// (like uint32 in protobuf)
|
||||
kUint32 = 'U',
|
||||
// Number represented as a 64-bit double.
|
||||
// Host byte order is used (N.B. this makes the format non-portable).
|
||||
kDouble = 'N',
|
||||
};
|
||||
|
||||
ValueSerializer::ValueSerializer() {}
|
||||
@ -60,14 +73,40 @@ void ValueSerializer::WriteVarint(T value) {
|
||||
buffer_.insert(buffer_.end(), stack_buffer, next_byte);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void ValueSerializer::WriteZigZag(T value) {
|
||||
// Writes a signed integer as a varint using ZigZag encoding (i.e. 0 is
|
||||
// encoded as 0, -1 as 1, 1 as 2, -2 as 3, and so on).
|
||||
// See also https://developers.google.com/protocol-buffers/docs/encoding
|
||||
// Note that this implementation relies on the right shift being arithmetic.
|
||||
static_assert(std::is_integral<T>::value && std::is_signed<T>::value,
|
||||
"Only signed integer types can be written as zigzag.");
|
||||
using UnsignedT = typename std::make_unsigned<T>::type;
|
||||
WriteVarint((static_cast<UnsignedT>(value) << 1) ^
|
||||
(value >> (8 * sizeof(T) - 1)));
|
||||
}
|
||||
|
||||
void ValueSerializer::WriteDouble(double value) {
|
||||
// Warning: this uses host endianness.
|
||||
buffer_.insert(buffer_.end(), reinterpret_cast<const uint8_t*>(&value),
|
||||
reinterpret_cast<const uint8_t*>(&value + 1));
|
||||
}
|
||||
|
||||
Maybe<bool> ValueSerializer::WriteObject(Handle<Object> object) {
|
||||
if (object->IsSmi()) UNIMPLEMENTED();
|
||||
if (object->IsSmi()) {
|
||||
WriteSmi(Smi::cast(*object));
|
||||
return Just(true);
|
||||
}
|
||||
|
||||
DCHECK(object->IsHeapObject());
|
||||
switch (HeapObject::cast(*object)->map()->instance_type()) {
|
||||
case ODDBALL_TYPE:
|
||||
WriteOddball(Oddball::cast(*object));
|
||||
return Just(true);
|
||||
case HEAP_NUMBER_TYPE:
|
||||
case MUTABLE_HEAP_NUMBER_TYPE:
|
||||
WriteHeapNumber(HeapNumber::cast(*object));
|
||||
return Just(true);
|
||||
default:
|
||||
UNIMPLEMENTED();
|
||||
return Nothing<bool>();
|
||||
@ -96,6 +135,17 @@ void ValueSerializer::WriteOddball(Oddball* oddball) {
|
||||
WriteTag(tag);
|
||||
}
|
||||
|
||||
void ValueSerializer::WriteSmi(Smi* smi) {
|
||||
static_assert(kSmiValueSize <= 32, "Expected SMI <= 32 bits.");
|
||||
WriteTag(SerializationTag::kInt32);
|
||||
WriteZigZag<int32_t>(smi->value());
|
||||
}
|
||||
|
||||
void ValueSerializer::WriteHeapNumber(HeapNumber* number) {
|
||||
WriteTag(SerializationTag::kDouble);
|
||||
WriteDouble(number->value());
|
||||
}
|
||||
|
||||
ValueDeserializer::ValueDeserializer(Isolate* isolate,
|
||||
Vector<const uint8_t> data)
|
||||
: isolate_(isolate),
|
||||
@ -149,6 +199,30 @@ Maybe<T> ValueDeserializer::ReadVarint() {
|
||||
return Just(value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Maybe<T> ValueDeserializer::ReadZigZag() {
|
||||
// Writes a signed integer as a varint using ZigZag encoding (i.e. 0 is
|
||||
// encoded as 0, -1 as 1, 1 as 2, -2 as 3, and so on).
|
||||
// See also https://developers.google.com/protocol-buffers/docs/encoding
|
||||
static_assert(std::is_integral<T>::value && std::is_signed<T>::value,
|
||||
"Only signed integer types can be read as zigzag.");
|
||||
using UnsignedT = typename std::make_unsigned<T>::type;
|
||||
UnsignedT unsigned_value;
|
||||
if (!ReadVarint<UnsignedT>().To(&unsigned_value)) return Nothing<T>();
|
||||
return Just(static_cast<T>((unsigned_value >> 1) ^
|
||||
-static_cast<T>(unsigned_value & 1)));
|
||||
}
|
||||
|
||||
Maybe<double> ValueDeserializer::ReadDouble() {
|
||||
// Warning: this uses host endianness.
|
||||
if (position_ > end_ - sizeof(double)) return Nothing<double>();
|
||||
double value;
|
||||
memcpy(&value, position_, sizeof(double));
|
||||
position_ += sizeof(double);
|
||||
if (std::isnan(value)) value = std::numeric_limits<double>::quiet_NaN();
|
||||
return Just(value);
|
||||
}
|
||||
|
||||
MaybeHandle<Object> ValueDeserializer::ReadObject() {
|
||||
SerializationTag tag;
|
||||
if (!ReadTag().To(&tag)) return MaybeHandle<Object>();
|
||||
@ -165,6 +239,21 @@ MaybeHandle<Object> ValueDeserializer::ReadObject() {
|
||||
return isolate_->factory()->true_value();
|
||||
case SerializationTag::kFalse:
|
||||
return isolate_->factory()->false_value();
|
||||
case SerializationTag::kInt32: {
|
||||
Maybe<int32_t> number = ReadZigZag<int32_t>();
|
||||
if (number.IsNothing()) return MaybeHandle<Object>();
|
||||
return isolate_->factory()->NewNumberFromInt(number.FromJust());
|
||||
}
|
||||
case SerializationTag::kUint32: {
|
||||
Maybe<uint32_t> number = ReadVarint<uint32_t>();
|
||||
if (number.IsNothing()) return MaybeHandle<Object>();
|
||||
return isolate_->factory()->NewNumberFromUint(number.FromJust());
|
||||
}
|
||||
case SerializationTag::kDouble: {
|
||||
Maybe<double> number = ReadDouble();
|
||||
if (number.IsNothing()) return MaybeHandle<Object>();
|
||||
return isolate_->factory()->NewNumber(number.FromJust());
|
||||
}
|
||||
default:
|
||||
return MaybeHandle<Object>();
|
||||
}
|
||||
|
@ -16,9 +16,11 @@
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
class HeapNumber;
|
||||
class Isolate;
|
||||
class Object;
|
||||
class Oddball;
|
||||
class Smi;
|
||||
|
||||
enum class SerializationTag : uint8_t;
|
||||
|
||||
@ -54,9 +56,14 @@ class ValueSerializer {
|
||||
void WriteTag(SerializationTag tag);
|
||||
template <typename T>
|
||||
void WriteVarint(T value);
|
||||
template <typename T>
|
||||
void WriteZigZag(T value);
|
||||
void WriteDouble(double value);
|
||||
|
||||
// Writing V8 objects of various kinds.
|
||||
void WriteOddball(Oddball* oddball);
|
||||
void WriteSmi(Smi* smi);
|
||||
void WriteHeapNumber(HeapNumber* number);
|
||||
|
||||
std::vector<uint8_t> buffer_;
|
||||
|
||||
@ -86,6 +93,9 @@ class ValueDeserializer {
|
||||
Maybe<SerializationTag> ReadTag() WARN_UNUSED_RESULT;
|
||||
template <typename T>
|
||||
Maybe<T> ReadVarint() WARN_UNUSED_RESULT;
|
||||
template <typename T>
|
||||
Maybe<T> ReadZigZag() WARN_UNUSED_RESULT;
|
||||
Maybe<double> ReadDouble() WARN_UNUSED_RESULT;
|
||||
|
||||
Isolate* const isolate_;
|
||||
const uint8_t* position_;
|
||||
|
@ -3,8 +3,10 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "src/value-serializer.h"
|
||||
|
||||
#include "include/v8.h"
|
||||
#include "src/api.h"
|
||||
#include "src/base/build_config.h"
|
||||
#include "test/unittests/test-utils.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
@ -163,5 +165,88 @@ TEST_F(ValueSerializerTest, DecodeOddball) {
|
||||
[](Local<Value> value) { EXPECT_TRUE(value->IsNull()); });
|
||||
}
|
||||
|
||||
TEST_F(ValueSerializerTest, RoundTripNumber) {
|
||||
RoundTripTest([this]() { return Integer::New(isolate(), 42); },
|
||||
[](Local<Value> value) {
|
||||
ASSERT_TRUE(value->IsInt32());
|
||||
EXPECT_EQ(42, Int32::Cast(*value)->Value());
|
||||
});
|
||||
RoundTripTest([this]() { return Integer::New(isolate(), -31337); },
|
||||
[](Local<Value> value) {
|
||||
ASSERT_TRUE(value->IsInt32());
|
||||
EXPECT_EQ(-31337, Int32::Cast(*value)->Value());
|
||||
});
|
||||
RoundTripTest(
|
||||
[this]() {
|
||||
return Integer::New(isolate(), std::numeric_limits<int32_t>::min());
|
||||
},
|
||||
[](Local<Value> value) {
|
||||
ASSERT_TRUE(value->IsInt32());
|
||||
EXPECT_EQ(std::numeric_limits<int32_t>::min(),
|
||||
Int32::Cast(*value)->Value());
|
||||
});
|
||||
RoundTripTest([this]() { return Number::New(isolate(), -0.25); },
|
||||
[](Local<Value> value) {
|
||||
ASSERT_TRUE(value->IsNumber());
|
||||
EXPECT_EQ(-0.25, Number::Cast(*value)->Value());
|
||||
});
|
||||
RoundTripTest(
|
||||
[this]() {
|
||||
return Number::New(isolate(), std::numeric_limits<double>::quiet_NaN());
|
||||
},
|
||||
[](Local<Value> value) {
|
||||
ASSERT_TRUE(value->IsNumber());
|
||||
EXPECT_TRUE(std::isnan(Number::Cast(*value)->Value()));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ValueSerializerTest, DecodeNumber) {
|
||||
// 42 zig-zag encoded (signed)
|
||||
DecodeTest({0xff, 0x09, 0x49, 0x54},
|
||||
[](Local<Value> value) {
|
||||
ASSERT_TRUE(value->IsInt32());
|
||||
EXPECT_EQ(42, Int32::Cast(*value)->Value());
|
||||
});
|
||||
// 42 varint encoded (unsigned)
|
||||
DecodeTest({0xff, 0x09, 0x55, 0x2a},
|
||||
[](Local<Value> value) {
|
||||
ASSERT_TRUE(value->IsInt32());
|
||||
EXPECT_EQ(42, Int32::Cast(*value)->Value());
|
||||
});
|
||||
// 160 zig-zag encoded (signed)
|
||||
DecodeTest({0xff, 0x09, 0x49, 0xc0, 0x02},
|
||||
[](Local<Value> value) {
|
||||
ASSERT_TRUE(value->IsInt32());
|
||||
ASSERT_EQ(160, Int32::Cast(*value)->Value());
|
||||
});
|
||||
// 160 varint encoded (unsigned)
|
||||
DecodeTest({0xff, 0x09, 0x55, 0xa0, 0x01},
|
||||
[](Local<Value> value) {
|
||||
ASSERT_TRUE(value->IsInt32());
|
||||
ASSERT_EQ(160, Int32::Cast(*value)->Value());
|
||||
});
|
||||
#if defined(V8_TARGET_LITTLE_ENDIAN)
|
||||
// IEEE 754 doubles, little-endian byte order
|
||||
DecodeTest({0xff, 0x09, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0xbf},
|
||||
[](Local<Value> value) {
|
||||
ASSERT_TRUE(value->IsNumber());
|
||||
EXPECT_EQ(-0.25, Number::Cast(*value)->Value());
|
||||
});
|
||||
// quiet NaN
|
||||
DecodeTest({0xff, 0x09, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x7f},
|
||||
[](Local<Value> value) {
|
||||
ASSERT_TRUE(value->IsNumber());
|
||||
EXPECT_TRUE(std::isnan(Number::Cast(*value)->Value()));
|
||||
});
|
||||
// signaling NaN
|
||||
DecodeTest({0xff, 0x09, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf4, 0x7f},
|
||||
[](Local<Value> value) {
|
||||
ASSERT_TRUE(value->IsNumber());
|
||||
EXPECT_TRUE(std::isnan(Number::Cast(*value)->Value()));
|
||||
});
|
||||
#endif
|
||||
// TODO(jbroman): Equivalent test for big-endian machines.
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace v8
|
||||
|
Loading…
Reference in New Issue
Block a user