// 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 ValueDeserializer::Delegate* GetDeserializerDelegate() { return nullptr; } virtual void BeforeDecode(ValueDeserializer*) {} Local RoundTripTest(Local input_value) { std::vector encoded = EncodeTest(input_value); return DecodeTest(encoded); } // Variant for the common case where a script is used to build the original // value. Local RoundTripTest(const char* source) { return RoundTripTest(EvaluateScriptForInput(source)); } // Variant which uses JSON.parse/stringify to check the result. void RoundTripJSON(const char* source) { Local input_value = JSON::Parse(serialization_context_, StringFromUtf8(source)) .ToLocalChecked(); Local result = RoundTripTest(input_value); ASSERT_TRUE(result->IsObject()); EXPECT_EQ(source, Utf8Value(JSON::Stringify(deserialization_context_, result.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>(); } std::pair buffer = serializer.Release(); std::vector result(buffer.first, buffer.first + buffer.second); free(buffer.first); return Just(std::move(result)); } std::vector EncodeTest(Local input_value) { Context::Scope scope(serialization_context()); TryCatch try_catch(isolate()); std::vector buffer; // Ideally we would use GTest's ASSERT_* macros here and below. However, // those only work in functions returning {void}, and they only terminate // the current function, but not the entire current test (so we would need // additional manual checks whether it is okay to proceed). Given that our // test driver starts a new process for each test anyway, it is acceptable // to just use a CHECK (which would kill the process on failure) instead. CHECK(DoEncode(input_value).To(&buffer)); CHECK(!try_catch.HasCaught()); return buffer; } v8::Local InvalidEncodeTest(Local input_value) { Context::Scope scope(serialization_context()); TryCatch try_catch(isolate()); CHECK(DoEncode(input_value).IsNothing()); return try_catch.Message(); } v8::Local InvalidEncodeTest(const char* source) { return InvalidEncodeTest(EvaluateScriptForInput(source)); } Local DecodeTest(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); CHECK(deserializer.ReadHeader(context).FromMaybe(false)); Local result; CHECK(deserializer.ReadValue(context).ToLocal(&result)); CHECK(!result.IsEmpty()); CHECK(!try_catch.HasCaught()); CHECK(context->Global() ->CreateDataProperty(context, StringFromUtf8("result"), result) .FromMaybe(false)); CHECK(!try_catch.HasCaught()); return result; } Local DecodeTestForVersion0(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); CHECK(deserializer.ReadHeader(context).FromMaybe(false)); CHECK_EQ(0u, deserializer.GetWireFormatVersion()); Local result; CHECK(deserializer.ReadValue(context).ToLocal(&result)); CHECK(!result.IsEmpty()); CHECK(!try_catch.HasCaught()); CHECK(context->Global() ->CreateDataProperty(context, StringFromUtf8("result"), result) .FromMaybe(false)); CHECK(!try_catch.HasCaught()); return result; } 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; } CHECK(header_result.ToChecked()); CHECK(deserializer.ReadValue(context).IsEmpty()); EXPECT_TRUE(try_catch.HasCaught()); } Local EvaluateScriptForInput(const char* utf8_source) { Context::Scope scope(serialization_context_); Local source = StringFromUtf8(utf8_source); Local