// 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 #include #include "include/v8.h" #include "src/api.h" #include "src/base/build_config.h" #include "src/objects-inl.h" #include "src/wasm/wasm-objects.h" #include "test/unittests/test-utils.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" namespace v8 { namespace { using ::testing::_; using ::testing::Invoke; using ::testing::Return; class ValueSerializerTest : public TestWithIsolate { protected: ValueSerializerTest() : serialization_context_(Context::New(isolate())), deserialization_context_(Context::New(isolate())) { // Create a host object type that can be tested through // serialization/deserialization delegates below. Local function_template = v8::FunctionTemplate::New( isolate(), [](const FunctionCallbackInfo& args) { args.Holder()->SetInternalField(0, args[0]); args.Holder()->SetInternalField(1, args[1]); }); function_template->InstanceTemplate()->SetInternalFieldCount(2); function_template->InstanceTemplate()->SetAccessor( StringFromUtf8("value"), [](Local property, const PropertyCallbackInfo& args) { args.GetReturnValue().Set(args.Holder()->GetInternalField(0)); }); function_template->InstanceTemplate()->SetAccessor( StringFromUtf8("value2"), [](Local property, const PropertyCallbackInfo& args) { args.GetReturnValue().Set(args.Holder()->GetInternalField(1)); }); for (Local context : {serialization_context_, deserialization_context_}) { context->Global() ->CreateDataProperty( context, StringFromUtf8("ExampleHostObject"), function_template->GetFunction(context).ToLocalChecked()) .ToChecked(); } host_object_constructor_template_ = function_template; isolate_ = reinterpret_cast(isolate()); } ~ValueSerializerTest() { // In some cases unhandled scheduled exceptions from current test produce // that Context::New(isolate()) from next test's constructor returns NULL. // In order to prevent that, we added destructor which will clear scheduled // exceptions just for the current test from test case. if (isolate_->has_scheduled_exception()) { isolate_->clear_scheduled_exception(); } } const Local& serialization_context() { return serialization_context_; } const Local& deserialization_context() { return deserialization_context_; } bool ExpectInlineWasm() const { return expect_inline_wasm_; } void SetExpectInlineWasm(bool value) { expect_inline_wasm_ = value; } // Overridden in more specific fixtures. virtual ValueSerializer::Delegate* GetSerializerDelegate() { return nullptr; } virtual void BeforeEncode(ValueSerializer*) {} virtual void AfterEncode() {} virtual ValueDeserializer::Delegate* GetDeserializerDelegate() { return nullptr; } virtual void BeforeDecode(ValueDeserializer*) {} template void RoundTripTest(const InputFunctor& input_functor, const OutputFunctor& output_functor) { EncodeTest(input_functor, [this, &output_functor](const std::vector& data) { DecodeTest(data, output_functor); }); } // Variant for the common case where a script is used to build the original // value. template void RoundTripTest(const char* source, const OutputFunctor& output_functor) { RoundTripTest([this, source]() { return EvaluateScriptForInput(source); }, output_functor); } // Variant which uses JSON.parse/stringify to check the result. void RoundTripJSON(const char* source) { RoundTripTest( [this, source]() { return JSON::Parse(serialization_context_, StringFromUtf8(source)) .ToLocalChecked(); }, [this, source](Local value) { ASSERT_TRUE(value->IsObject()); EXPECT_EQ(source, Utf8Value(JSON::Stringify(deserialization_context_, value.As()) .ToLocalChecked())); }); } Maybe> DoEncode(Local value) { Local context = serialization_context(); ValueSerializer serializer(isolate(), GetSerializerDelegate()); BeforeEncode(&serializer); serializer.WriteHeader(); if (!serializer.WriteValue(context, value).FromMaybe(false)) { return Nothing>(); } AfterEncode(); std::pair buffer = serializer.Release(); std::vector result(buffer.first, buffer.first + buffer.second); free(buffer.first); return Just(std::move(result)); } template void EncodeTest(const InputFunctor& input_functor, const EncodedDataFunctor& encoded_data_functor) { Context::Scope scope(serialization_context()); TryCatch try_catch(isolate()); Local input_value = input_functor(); std::vector buffer; ASSERT_TRUE(DoEncode(input_value).To(&buffer)); ASSERT_FALSE(try_catch.HasCaught()); encoded_data_functor(buffer); } template void InvalidEncodeTest(const InputFunctor& input_functor, const MessageFunctor& functor) { Context::Scope scope(serialization_context()); TryCatch try_catch(isolate()); Local input_value = input_functor(); ASSERT_TRUE(DoEncode(input_value).IsNothing()); functor(try_catch.Message()); } template void InvalidEncodeTest(const char* source, const MessageFunctor& functor) { InvalidEncodeTest( [this, source]() { return EvaluateScriptForInput(source); }, functor); } void InvalidEncodeTest(const char* source) { InvalidEncodeTest(source, [](Local) {}); } template void DecodeTest(const std::vector& data, const OutputFunctor& output_functor) { Local context = deserialization_context(); Context::Scope scope(context); TryCatch try_catch(isolate()); ValueDeserializer deserializer(isolate(), &data[0], static_cast(data.size()), GetDeserializerDelegate()); deserializer.SetSupportsLegacyWireFormat(true); deserializer.SetExpectInlineWasm(ExpectInlineWasm()); BeforeDecode(&deserializer); ASSERT_TRUE(deserializer.ReadHeader(context).FromMaybe(false)); Local result; ASSERT_TRUE(deserializer.ReadValue(context).ToLocal(&result)); ASSERT_FALSE(result.IsEmpty()); ASSERT_FALSE(try_catch.HasCaught()); ASSERT_TRUE( context->Global() ->CreateDataProperty(context, StringFromUtf8("result"), result) .FromMaybe(false)); output_functor(result); ASSERT_FALSE(try_catch.HasCaught()); } template void DecodeTestForVersion0(const std::vector& data, const OutputFunctor& output_functor) { Local context = deserialization_context(); Context::Scope scope(context); TryCatch try_catch(isolate()); ValueDeserializer deserializer(isolate(), &data[0], static_cast(data.size()), GetDeserializerDelegate()); deserializer.SetSupportsLegacyWireFormat(true); deserializer.SetExpectInlineWasm(ExpectInlineWasm()); BeforeDecode(&deserializer); ASSERT_TRUE(deserializer.ReadHeader(context).FromMaybe(false)); ASSERT_EQ(0u, deserializer.GetWireFormatVersion()); Local result; ASSERT_TRUE(deserializer.ReadValue(context).ToLocal(&result)); ASSERT_FALSE(result.IsEmpty()); ASSERT_FALSE(try_catch.HasCaught()); ASSERT_TRUE( context->Global() ->CreateDataProperty(context, StringFromUtf8("result"), result) .FromMaybe(false)); output_functor(result); ASSERT_FALSE(try_catch.HasCaught()); } void InvalidDecodeTest(const std::vector& data) { Local context = deserialization_context(); Context::Scope scope(context); TryCatch try_catch(isolate()); ValueDeserializer deserializer(isolate(), &data[0], static_cast(data.size()), GetDeserializerDelegate()); deserializer.SetSupportsLegacyWireFormat(true); deserializer.SetExpectInlineWasm(ExpectInlineWasm()); BeforeDecode(&deserializer); Maybe header_result = deserializer.ReadHeader(context); if (header_result.IsNothing()) { EXPECT_TRUE(try_catch.HasCaught()); return; } ASSERT_TRUE(header_result.ToChecked()); ASSERT_TRUE(deserializer.ReadValue(context).IsEmpty()); EXPECT_TRUE(try_catch.HasCaught()); } Local EvaluateScriptForInput(const char* utf8_source) { Local source = StringFromUtf8(utf8_source); Local