// 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/objects/value-serializer.h" #include #include #include "include/v8-context.h" #include "include/v8-date.h" #include "include/v8-function.h" #include "include/v8-json.h" #include "include/v8-local-handle.h" #include "include/v8-primitive-object.h" #include "include/v8-template.h" #include "include/v8-value-serializer-version.h" #include "include/v8-value-serializer.h" #include "include/v8-wasm.h" #include "src/api/api-inl.h" #include "src/base/build_config.h" #include "src/objects/backing-store.h" #include "src/objects/js-array-buffer-inl.h" #include "src/objects/js-array-buffer.h" #include "src/objects/objects-inl.h" #include "test/common/flag-utils.h" #include "test/unittests/test-utils.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #if V8_ENABLE_WEBASSEMBLY #include "src/wasm/wasm-engine.h" #include "src/wasm/wasm-objects.h" #include "src/wasm/wasm-result.h" #endif // V8_ENABLE_WEBASSEMBLY namespace v8 { namespace { using ::testing::_; using ::testing::Invoke; using ::testing::Return; class ValueSerializerTest : public TestWithIsolate { public: ValueSerializerTest(const ValueSerializerTest&) = delete; ValueSerializerTest& operator=(const ValueSerializerTest&) = delete; 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& info) { CHECK(i::ValidateCallbackInfo(info)); info.Holder()->SetInternalField(0, info[0]); info.Holder()->SetInternalField(1, info[1]); }); function_template->InstanceTemplate()->SetInternalFieldCount(2); function_template->InstanceTemplate()->SetAccessor( StringFromUtf8("value"), [](Local property, const PropertyCallbackInfo& info) { CHECK(i::ValidateCallbackInfo(info)); info.GetReturnValue().Set(info.Holder()->GetInternalField(0)); }); function_template->InstanceTemplate()->SetAccessor( StringFromUtf8("value2"), [](Local property, const PropertyCallbackInfo& info) { CHECK(i::ValidateCallbackInfo(info)); info.GetReturnValue().Set(info.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() override { // 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_; } // 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); if (auto* delegate = GetSerializerDelegate()) delegate->FreeBufferMemory(buffer.first); else 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; } std::vector EncodeTest(const char* source) { return EncodeTest(EvaluateScriptForInput(source)); } 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); 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; } template void DecodeTestFutureVersions(std::vector&& data, Lambda test) { DecodeTestUpToVersion(v8::CurrentValueSerializerFormatVersion(), std::move(data), test); } template void DecodeTestUpToVersion(int last_version, std::vector&& data, Lambda test) { // Check that there is at least one version to test. CHECK_LE(data[1], last_version); for (int version = data[1]; version <= last_version; ++version) { data[1] = version; Local value = DecodeTest(data); test(value); } } 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); 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); 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