blob: c48961fb6cddbb61ac4c2fa464af29ab925e0062 [file] [log] [blame]
// Copyright 2016 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/value-serializer.h"
#include <algorithm>
#include <string>
#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"
namespace v8 {
namespace {
class ValueSerializerTest : public TestWithIsolate {
protected:
ValueSerializerTest()
: serialization_context_(Context::New(isolate())),
deserialization_context_(Context::New(isolate())) {}
const Local<Context>& serialization_context() {
return serialization_context_;
}
const Local<Context>& deserialization_context() {
return deserialization_context_;
}
template <typename InputFunctor, typename OutputFunctor>
void RoundTripTest(const InputFunctor& input_functor,
const OutputFunctor& output_functor) {
EncodeTest(input_functor,
[this, &output_functor](const std::vector<uint8_t>& data) {
DecodeTest(data, output_functor);
});
}
template <typename InputFunctor, typename EncodedDataFunctor>
void EncodeTest(const InputFunctor& input_functor,
const EncodedDataFunctor& encoded_data_functor) {
Context::Scope scope(serialization_context());
TryCatch try_catch(isolate());
// TODO(jbroman): Use the public API once it exists.
Local<Value> input_value = input_functor();
i::Isolate* internal_isolate = reinterpret_cast<i::Isolate*>(isolate());
i::HandleScope handle_scope(internal_isolate);
i::ValueSerializer serializer;
serializer.WriteHeader();
ASSERT_TRUE(serializer.WriteObject(Utils::OpenHandle(*input_value))
.FromMaybe(false));
ASSERT_FALSE(try_catch.HasCaught());
encoded_data_functor(serializer.ReleaseBuffer());
}
template <typename OutputFunctor>
void DecodeTest(const std::vector<uint8_t>& data,
const OutputFunctor& output_functor) {
Context::Scope scope(deserialization_context());
TryCatch try_catch(isolate());
// TODO(jbroman): Use the public API once it exists.
i::Isolate* internal_isolate = reinterpret_cast<i::Isolate*>(isolate());
i::HandleScope handle_scope(internal_isolate);
i::ValueDeserializer deserializer(
internal_isolate,
i::Vector<const uint8_t>(&data[0], static_cast<int>(data.size())));
ASSERT_TRUE(deserializer.ReadHeader().FromMaybe(false));
Local<Value> result;
ASSERT_TRUE(ToLocal<Value>(deserializer.ReadObject(), &result));
ASSERT_FALSE(result.IsEmpty());
ASSERT_FALSE(try_catch.HasCaught());
ASSERT_TRUE(deserialization_context()
->Global()
->CreateDataProperty(deserialization_context_,
StringFromUtf8("result"), result)
.FromMaybe(false));
output_functor(result);
ASSERT_FALSE(try_catch.HasCaught());
}
void InvalidDecodeTest(const std::vector<uint8_t>& data) {
Context::Scope scope(deserialization_context());
TryCatch try_catch(isolate());
i::Isolate* internal_isolate = reinterpret_cast<i::Isolate*>(isolate());
i::HandleScope handle_scope(internal_isolate);
i::ValueDeserializer deserializer(
internal_isolate,
i::Vector<const uint8_t>(&data[0], static_cast<int>(data.size())));
Maybe<bool> header_result = deserializer.ReadHeader();
if (header_result.IsNothing()) return;
ASSERT_TRUE(header_result.ToChecked());
ASSERT_TRUE(deserializer.ReadObject().is_null());
}
Local<Value> EvaluateScriptForInput(const char* utf8_source) {
Local<String> source = StringFromUtf8(utf8_source);
Local<Script> script =
Script::Compile(serialization_context_, source).ToLocalChecked();
return script->Run(serialization_context_).ToLocalChecked();
}
bool EvaluateScriptForResultBool(const char* utf8_source) {
Local<String> source = StringFromUtf8(utf8_source);
Local<Script> script =
Script::Compile(deserialization_context_, source).ToLocalChecked();
Local<Value> value = script->Run(deserialization_context_).ToLocalChecked();
return value->BooleanValue(deserialization_context_).FromJust();
}
Local<String> StringFromUtf8(const char* source) {
return String::NewFromUtf8(isolate(), source, NewStringType::kNormal)
.ToLocalChecked();
}
static std::string Utf8Value(Local<Value> value) {
String::Utf8Value utf8(value);
return std::string(*utf8, utf8.length());
}
private:
Local<Context> serialization_context_;
Local<Context> deserialization_context_;
DISALLOW_COPY_AND_ASSIGN(ValueSerializerTest);
};
TEST_F(ValueSerializerTest, DecodeInvalid) {
// Version tag but no content.
InvalidDecodeTest({0xff});
// Version too large.
InvalidDecodeTest({0xff, 0x7f, 0x5f});
// Nonsense tag.
InvalidDecodeTest({0xff, 0x09, 0xdd});
}
TEST_F(ValueSerializerTest, RoundTripOddball) {
RoundTripTest([this]() { return Undefined(isolate()); },
[](Local<Value> value) { EXPECT_TRUE(value->IsUndefined()); });
RoundTripTest([this]() { return True(isolate()); },
[](Local<Value> value) { EXPECT_TRUE(value->IsTrue()); });
RoundTripTest([this]() { return False(isolate()); },
[](Local<Value> value) { EXPECT_TRUE(value->IsFalse()); });
RoundTripTest([this]() { return Null(isolate()); },
[](Local<Value> value) { EXPECT_TRUE(value->IsNull()); });
}
TEST_F(ValueSerializerTest, DecodeOddball) {
// What this code is expected to generate.
DecodeTest({0xff, 0x09, 0x5f},
[](Local<Value> value) { EXPECT_TRUE(value->IsUndefined()); });
DecodeTest({0xff, 0x09, 0x54},
[](Local<Value> value) { EXPECT_TRUE(value->IsTrue()); });
DecodeTest({0xff, 0x09, 0x46},
[](Local<Value> value) { EXPECT_TRUE(value->IsFalse()); });
DecodeTest({0xff, 0x09, 0x30},
[](Local<Value> value) { EXPECT_TRUE(value->IsNull()); });
// What v9 of the Blink code generates.
DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x5f, 0x00},
[](Local<Value> value) { EXPECT_TRUE(value->IsUndefined()); });
DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x54, 0x00},
[](Local<Value> value) { EXPECT_TRUE(value->IsTrue()); });
DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x46, 0x00},
[](Local<Value> value) { EXPECT_TRUE(value->IsFalse()); });
DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x30, 0x00},
[](Local<Value> value) { EXPECT_TRUE(value->IsNull()); });
// v0 (with no explicit version).
DecodeTest({0x5f, 0x00},
[](Local<Value> value) { EXPECT_TRUE(value->IsUndefined()); });
DecodeTest({0x54, 0x00},
[](Local<Value> value) { EXPECT_TRUE(value->IsTrue()); });
DecodeTest({0x46, 0x00},
[](Local<Value> value) { EXPECT_TRUE(value->IsFalse()); });
DecodeTest({0x30, 0x00},
[](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.
}
// String constants (in UTF-8) used for string encoding tests.
static const char kHelloString[] = "Hello";
static const char kQuebecString[] = "\x51\x75\xC3\xA9\x62\x65\x63";
static const char kEmojiString[] = "\xF0\x9F\x91\x8A";
TEST_F(ValueSerializerTest, RoundTripString) {
RoundTripTest([this]() { return String::Empty(isolate()); },
[](Local<Value> value) {
ASSERT_TRUE(value->IsString());
EXPECT_EQ(0, String::Cast(*value)->Length());
});
// Inside ASCII.
RoundTripTest([this]() { return StringFromUtf8(kHelloString); },
[](Local<Value> value) {
ASSERT_TRUE(value->IsString());
EXPECT_EQ(5, String::Cast(*value)->Length());
EXPECT_EQ(kHelloString, Utf8Value(value));
});
// Inside Latin-1 (i.e. one-byte string), but not ASCII.
RoundTripTest([this]() { return StringFromUtf8(kQuebecString); },
[](Local<Value> value) {
ASSERT_TRUE(value->IsString());
EXPECT_EQ(6, String::Cast(*value)->Length());
EXPECT_EQ(kQuebecString, Utf8Value(value));
});
// An emoji (decodes to two 16-bit chars).
RoundTripTest([this]() { return StringFromUtf8(kEmojiString); },
[](Local<Value> value) {
ASSERT_TRUE(value->IsString());
EXPECT_EQ(2, String::Cast(*value)->Length());
EXPECT_EQ(kEmojiString, Utf8Value(value));
});
}
TEST_F(ValueSerializerTest, DecodeString) {
// Decoding the strings above from UTF-8.
DecodeTest({0xff, 0x09, 0x53, 0x00},
[](Local<Value> value) {
ASSERT_TRUE(value->IsString());
EXPECT_EQ(0, String::Cast(*value)->Length());
});
DecodeTest({0xff, 0x09, 0x53, 0x05, 'H', 'e', 'l', 'l', 'o'},
[](Local<Value> value) {
ASSERT_TRUE(value->IsString());
EXPECT_EQ(5, String::Cast(*value)->Length());
EXPECT_EQ(kHelloString, Utf8Value(value));
});
DecodeTest({0xff, 0x09, 0x53, 0x07, 'Q', 'u', 0xc3, 0xa9, 'b', 'e', 'c'},
[](Local<Value> value) {
ASSERT_TRUE(value->IsString());
EXPECT_EQ(6, String::Cast(*value)->Length());
EXPECT_EQ(kQuebecString, Utf8Value(value));
});
DecodeTest({0xff, 0x09, 0x53, 0x04, 0xf0, 0x9f, 0x91, 0x8a},
[](Local<Value> value) {
ASSERT_TRUE(value->IsString());
EXPECT_EQ(2, String::Cast(*value)->Length());
EXPECT_EQ(kEmojiString, Utf8Value(value));
});
// And from two-byte strings (endianness dependent).
#if defined(V8_TARGET_LITTLE_ENDIAN)
DecodeTest({0xff, 0x09, 0x63, 0x00},
[](Local<Value> value) {
ASSERT_TRUE(value->IsString());
EXPECT_EQ(0, String::Cast(*value)->Length());
});
DecodeTest({0xff, 0x09, 0x63, 0x0a, 'H', '\0', 'e', '\0', 'l', '\0', 'l',
'\0', 'o', '\0'},
[](Local<Value> value) {
ASSERT_TRUE(value->IsString());
EXPECT_EQ(5, String::Cast(*value)->Length());
EXPECT_EQ(kHelloString, Utf8Value(value));
});
DecodeTest({0xff, 0x09, 0x63, 0x0c, 'Q', '\0', 'u', '\0', 0xe9, '\0', 'b',
'\0', 'e', '\0', 'c', '\0'},
[](Local<Value> value) {
ASSERT_TRUE(value->IsString());
EXPECT_EQ(6, String::Cast(*value)->Length());
EXPECT_EQ(kQuebecString, Utf8Value(value));
});
DecodeTest({0xff, 0x09, 0x63, 0x04, 0x3d, 0xd8, 0x4a, 0xdc},
[](Local<Value> value) {
ASSERT_TRUE(value->IsString());
EXPECT_EQ(2, String::Cast(*value)->Length());
EXPECT_EQ(kEmojiString, Utf8Value(value));
});
#endif
// TODO(jbroman): The same for big-endian systems.
}
TEST_F(ValueSerializerTest, DecodeInvalidString) {
// UTF-8 string with too few bytes available.
InvalidDecodeTest({0xff, 0x09, 0x53, 0x10, 'v', '8'});
#if defined(V8_TARGET_LITTLE_ENDIAN)
// Two-byte string with too few bytes available.
InvalidDecodeTest({0xff, 0x09, 0x63, 0x10, 'v', '\0', '8', '\0'});
// Two-byte string with an odd byte length.
InvalidDecodeTest({0xff, 0x09, 0x63, 0x03, 'v', '\0', '8'});
#endif
// TODO(jbroman): The same for big-endian systems.
}
TEST_F(ValueSerializerTest, EncodeTwoByteStringUsesPadding) {
// As long as the output has a version that Blink expects to be able to read,
// we must respect its alignment requirements. It requires that two-byte
// characters be aligned.
EncodeTest(
[this]() {
// We need a string whose length will take two bytes to encode, so that
// a padding byte is needed to keep the characters aligned. The string
// must also have a two-byte character, so that it gets the two-byte
// encoding.
std::string string(200, ' ');
string += kEmojiString;
return StringFromUtf8(string.c_str());
},
[](const std::vector<uint8_t>& data) {
// This is a sufficient but not necessary condition to be aligned.
// Note that the third byte (0x00) is padding.
const uint8_t expected_prefix[] = {0xff, 0x09, 0x00, 0x63, 0x94, 0x03};
ASSERT_GT(data.size(), sizeof(expected_prefix) / sizeof(uint8_t));
EXPECT_TRUE(std::equal(std::begin(expected_prefix),
std::end(expected_prefix), data.begin()));
});
}
} // namespace
} // namespace v8