// Copyright 2018 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 "include/v8-function.h" #include "src/api/api-inl.h" #include "src/web-snapshot/web-snapshot.h" #include "test/cctest/cctest-utils.h" #include "test/cctest/cctest.h" namespace v8 { namespace internal { namespace { void TestWebSnapshotExtensive( const char* snapshot_source, const char* test_source, std::function)> tester, uint32_t string_count, uint32_t symbol_count, uint32_t builtin_object_count, uint32_t map_count, uint32_t context_count, uint32_t function_count, uint32_t object_count, uint32_t array_count) { CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); WebSnapshotData snapshot_data; { v8::HandleScope scope(isolate); v8::Local new_context = CcTest::NewContext(); v8::Context::Scope context_scope(new_context); CompileRun(snapshot_source); v8::Local exports = v8::PrimitiveArray::New(isolate, 1); v8::Local str = v8::String::NewFromUtf8(isolate, "foo").ToLocalChecked(); exports->Set(isolate, 0, str); WebSnapshotSerializer serializer(isolate); CHECK(serializer.TakeSnapshot(new_context, exports, snapshot_data)); CHECK(!serializer.has_error()); CHECK_NOT_NULL(snapshot_data.buffer); CHECK_EQ(string_count, serializer.string_count()); CHECK_EQ(symbol_count, serializer.symbol_count()); CHECK_EQ(map_count, serializer.map_count()); CHECK_EQ(builtin_object_count, serializer.builtin_object_count()); CHECK_EQ(context_count, serializer.context_count()); CHECK_EQ(function_count, serializer.function_count()); CHECK_EQ(object_count, serializer.object_count()); CHECK_EQ(array_count, serializer.array_count()); } { v8::HandleScope scope(isolate); v8::Local new_context = CcTest::NewContext(); v8::Context::Scope context_scope(new_context); WebSnapshotDeserializer deserializer(isolate, snapshot_data.buffer, snapshot_data.buffer_size); CHECK(deserializer.Deserialize()); CHECK(!deserializer.has_error()); tester(isolate, new_context); CHECK_EQ(string_count, deserializer.string_count()); CHECK_EQ(symbol_count, deserializer.symbol_count()); CHECK_EQ(map_count, deserializer.map_count()); CHECK_EQ(builtin_object_count, deserializer.builtin_object_count()); CHECK_EQ(context_count, deserializer.context_count()); CHECK_EQ(function_count, deserializer.function_count()); CHECK_EQ(object_count, deserializer.object_count()); CHECK_EQ(array_count, deserializer.array_count()); } } void TestWebSnapshot(const char* snapshot_source, const char* test_source, const char* expected_result, uint32_t string_count, uint32_t symbol_count, uint32_t map_count, uint32_t builtin_object_count, uint32_t context_count, uint32_t function_count, uint32_t object_count, uint32_t array_count) { TestWebSnapshotExtensive( snapshot_source, test_source, [test_source, expected_result](v8::Isolate* isolate, v8::Local new_context) { v8::Local result = CompileRun(test_source).As(); CHECK(result->Equals(new_context, v8_str(expected_result)).FromJust()); }, string_count, symbol_count, map_count, builtin_object_count, context_count, function_count, object_count, array_count); } } // namespace TEST(Minimal) { const char* snapshot_source = "var foo = {'key': 'lol'};"; const char* test_source = "foo.key"; const char* expected_result = "lol"; uint32_t kStringCount = 2; // 'foo', 'Object.prototype'; 'key' is in-place. uint32_t kSymbolCount = 0; uint32_t kBuiltinObjectCount = 1; uint32_t kMapCount = 1; uint32_t kContextCount = 0; uint32_t kFunctionCount = 0; uint32_t kObjectCount = 1; uint32_t kArrayCount = 0; TestWebSnapshot(snapshot_source, test_source, expected_result, kStringCount, kSymbolCount, kBuiltinObjectCount, kMapCount, kContextCount, kFunctionCount, kObjectCount, kArrayCount); } TEST(EmptyObject) { const char* snapshot_source = "var foo = {}"; const char* test_source = "foo"; uint32_t kStringCount = 2; // 'foo', 'Object.prototype' uint32_t kSymbolCount = 0; uint32_t kBuiltinObjectCount = 1; uint32_t kMapCount = 1; uint32_t kContextCount = 0; uint32_t kFunctionCount = 0; uint32_t kObjectCount = 1; uint32_t kArrayCount = 0; std::function)> tester = [test_source](v8::Isolate* isolate, v8::Local new_context) { v8::Local result = CompileRun(test_source).As(); Handle foo(v8::Utils::OpenHandle(*result)); Isolate* i_isolate = reinterpret_cast(isolate); CHECK_EQ(foo->map(), i_isolate->native_context()->object_function().initial_map()); }; TestWebSnapshotExtensive(snapshot_source, test_source, tester, kStringCount, kSymbolCount, kBuiltinObjectCount, kMapCount, kContextCount, kFunctionCount, kObjectCount, kArrayCount); } TEST(Numbers) { const char* snapshot_source = "var foo = {'a': 6,\n" " 'b': -11,\n" " 'c': 11.6,\n" " 'd': NaN,\n" " 'e': Number.POSITIVE_INFINITY,\n" " 'f': Number.NEGATIVE_INFINITY,\n" "}"; const char* test_source = "foo"; uint32_t kStringCount = 2; // 'foo', 'Object.prototype'; 'a'...'f' are in-place. uint32_t kSymbolCount = 0; uint32_t kBuiltinObjectCount = 1; uint32_t kMapCount = 1; uint32_t kContextCount = 0; uint32_t kFunctionCount = 0; uint32_t kObjectCount = 1; uint32_t kArrayCount = 0; std::function)> tester = [test_source](v8::Isolate* isolate, v8::Local new_context) { v8::Local result = CompileRun(test_source).As(); int32_t a = result->Get(new_context, v8_str("a")) .ToLocalChecked() .As() ->Value(); CHECK_EQ(a, 6); int32_t b = result->Get(new_context, v8_str("b")) .ToLocalChecked() .As() ->Value(); CHECK_EQ(b, -11); double c = result->Get(new_context, v8_str("c")) .ToLocalChecked() .As() ->Value(); CHECK_EQ(c, 11.6); double d = result->Get(new_context, v8_str("d")) .ToLocalChecked() .As() ->Value(); CHECK(std::isnan(d)); double e = result->Get(new_context, v8_str("e")) .ToLocalChecked() .As() ->Value(); CHECK_EQ(e, std::numeric_limits::infinity()); double f = result->Get(new_context, v8_str("f")) .ToLocalChecked() .As() ->Value(); CHECK_EQ(f, -std::numeric_limits::infinity()); }; TestWebSnapshotExtensive(snapshot_source, test_source, tester, kStringCount, kSymbolCount, kBuiltinObjectCount, kMapCount, kContextCount, kFunctionCount, kObjectCount, kArrayCount); } TEST(Oddballs) { const char* snapshot_source = "var foo = {'a': false,\n" " 'b': true,\n" " 'c': null,\n" " 'd': undefined,\n" "}"; const char* test_source = "foo"; // 'foo', 'Object.prototype'; 'a'...'d' are in-place. uint32_t kStringCount = 2; uint32_t kSymbolCount = 0; uint32_t kBuiltinObjectCount = 1; uint32_t kMapCount = 1; uint32_t kContextCount = 0; uint32_t kFunctionCount = 0; uint32_t kObjectCount = 1; uint32_t kArrayCount = 0; std::function)> tester = [test_source](v8::Isolate* isolate, v8::Local new_context) { v8::Local result = CompileRun(test_source).As(); Local a = result->Get(new_context, v8_str("a")).ToLocalChecked(); CHECK(a->IsFalse()); Local b = result->Get(new_context, v8_str("b")).ToLocalChecked(); CHECK(b->IsTrue()); Local c = result->Get(new_context, v8_str("c")).ToLocalChecked(); CHECK(c->IsNull()); Local d = result->Get(new_context, v8_str("d")).ToLocalChecked(); CHECK(d->IsUndefined()); }; TestWebSnapshotExtensive(snapshot_source, test_source, tester, kStringCount, kSymbolCount, kBuiltinObjectCount, kMapCount, kContextCount, kFunctionCount, kObjectCount, kArrayCount); } TEST(Function) { const char* snapshot_source = "var foo = {'key': function() { return '11525'; }};"; const char* test_source = "foo.key()"; const char* expected_result = "11525"; // 'foo', 'Object.prototype', function source code. 'key' is in-place. uint32_t kStringCount = 3; uint32_t kSymbolCount = 0; uint32_t kBuiltinObjectCount = 1; uint32_t kMapCount = 1; uint32_t kContextCount = 0; uint32_t kFunctionCount = 1; uint32_t kObjectCount = 1; uint32_t kArrayCount = 0; TestWebSnapshot(snapshot_source, test_source, expected_result, kStringCount, kSymbolCount, kBuiltinObjectCount, kMapCount, kContextCount, kFunctionCount, kObjectCount, kArrayCount); } TEST(InnerFunctionWithContext) { const char* snapshot_source = "var foo = {'key': (function() {\n" " let result = '11525';\n" " function inner() { return result; }\n" " return inner;\n" " })()};"; const char* test_source = "foo.key()"; const char* expected_result = "11525"; // Strings: 'foo', 'result', 'Object.prototype'. function source code (inner). // 'key' is in-place. uint32_t kStringCount = 4; uint32_t kSymbolCount = 0; uint32_t kBuiltinObjectCount = 1; uint32_t kMapCount = 1; uint32_t kContextCount = 1; uint32_t kFunctionCount = 1; uint32_t kObjectCount = 1; uint32_t kArrayCount = 0; TestWebSnapshot(snapshot_source, test_source, expected_result, kStringCount, kSymbolCount, kBuiltinObjectCount, kMapCount, kContextCount, kFunctionCount, kObjectCount, kArrayCount); } TEST(InnerFunctionWithContextAndParentContext) { const char* snapshot_source = "var foo = {'key': (function() {\n" " let part1 = '11';\n" " function inner() {\n" " let part2 = '525';\n" " function innerinner() {\n" " return part1 + part2;\n" " }\n" " return innerinner;\n" " }\n" " return inner();\n" " })()};"; const char* test_source = "foo.key()"; const char* expected_result = "11525"; // Strings: 'foo', 'Object.prototype', function source code (innerinner), // 'part1', 'part2'. uint32_t kStringCount = 5; uint32_t kSymbolCount = 0; uint32_t kBuiltinObjectCount = 1; uint32_t kMapCount = 1; uint32_t kContextCount = 2; uint32_t kFunctionCount = 1; uint32_t kObjectCount = 1; uint32_t kArrayCount = 0; TestWebSnapshot(snapshot_source, test_source, expected_result, kStringCount, kSymbolCount, kBuiltinObjectCount, kMapCount, kContextCount, kFunctionCount, kObjectCount, kArrayCount); } TEST(RegExp) { const char* snapshot_source = "var foo = {'re': /ab+c/gi}"; const char* test_source = "foo"; // 'foo', 'Object.prototype', RegExp pattern, RegExp flags uint32_t kStringCount = 4; uint32_t kSymbolCount = 0; uint32_t kBuiltinObjectCount = 1; uint32_t kMapCount = 1; uint32_t kContextCount = 0; uint32_t kFunctionCount = 0; uint32_t kObjectCount = 1; uint32_t kArrayCount = 0; std::function)> tester = [test_source](v8::Isolate* isolate, v8::Local new_context) { v8::Local result = CompileRun(test_source).As(); Local re = result->Get(new_context, v8_str("re")) .ToLocalChecked() .As(); CHECK(re->IsRegExp()); CHECK(re->GetSource()->Equals(new_context, v8_str("ab+c")).FromJust()); CHECK_EQ(v8::RegExp::kGlobal | v8::RegExp::kIgnoreCase, re->GetFlags()); v8::Local match = re->Exec(new_context, v8_str("aBc")).ToLocalChecked(); CHECK(match->IsArray()); v8::Local no_match = re->Exec(new_context, v8_str("ac")).ToLocalChecked(); CHECK(no_match->IsNull()); }; TestWebSnapshotExtensive(snapshot_source, test_source, tester, kStringCount, kSymbolCount, kBuiltinObjectCount, kMapCount, kContextCount, kFunctionCount, kObjectCount, kArrayCount); } TEST(RegExpNoFlags) { const char* snapshot_source = "var foo = {'re': /ab+c/}"; const char* test_source = "foo"; // 'foo', , 'Object.prototype RegExp pattern, RegExp flags uint32_t kStringCount = 4; uint32_t kSymbolCount = 0; uint32_t kBuiltinObjectCount = 1; uint32_t kMapCount = 1; uint32_t kContextCount = 0; uint32_t kFunctionCount = 0; uint32_t kObjectCount = 1; uint32_t kArrayCount = 0; std::function)> tester = [test_source](v8::Isolate* isolate, v8::Local new_context) { v8::Local result = CompileRun(test_source).As(); Local re = result->Get(new_context, v8_str("re")) .ToLocalChecked() .As(); CHECK(re->IsRegExp()); CHECK(re->GetSource()->Equals(new_context, v8_str("ab+c")).FromJust()); CHECK_EQ(v8::RegExp::kNone, re->GetFlags()); v8::Local match = re->Exec(new_context, v8_str("abc")).ToLocalChecked(); CHECK(match->IsArray()); v8::Local no_match = re->Exec(new_context, v8_str("ac")).ToLocalChecked(); CHECK(no_match->IsNull()); }; TestWebSnapshotExtensive(snapshot_source, test_source, tester, kStringCount, kSymbolCount, kBuiltinObjectCount, kMapCount, kContextCount, kFunctionCount, kObjectCount, kArrayCount); } TEST(SFIDeduplication) { CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); WebSnapshotData snapshot_data; { v8::Local new_context = CcTest::NewContext(); v8::Context::Scope context_scope(new_context); const char* snapshot_source = "let foo = {};\n" "foo.outer = function(a) {\n" " return function() {\n" " return a;\n" " }\n" "}\n" "foo.inner = foo.outer('hi');"; CompileRun(snapshot_source); v8::Local exports = v8::PrimitiveArray::New(isolate, 1); v8::Local str = v8::String::NewFromUtf8(isolate, "foo").ToLocalChecked(); exports->Set(isolate, 0, str); WebSnapshotSerializer serializer(isolate); CHECK(serializer.TakeSnapshot(new_context, exports, snapshot_data)); CHECK(!serializer.has_error()); CHECK_NOT_NULL(snapshot_data.buffer); } { v8::Local new_context = CcTest::NewContext(); v8::Context::Scope context_scope(new_context); WebSnapshotDeserializer deserializer(isolate, snapshot_data.buffer, snapshot_data.buffer_size); CHECK(deserializer.Deserialize()); CHECK(!deserializer.has_error()); const char* get_inner = "foo.inner"; const char* create_new_inner = "foo.outer()"; // Verify that foo.inner and the JSFunction which is the result of calling // foo.outer() after deserialization share the SFI. v8::Local v8_inner1 = CompileRun(get_inner).As(); v8::Local v8_inner2 = CompileRun(create_new_inner).As(); Handle inner1 = Handle::cast(Utils::OpenHandle(*v8_inner1)); Handle inner2 = Handle::cast(Utils::OpenHandle(*v8_inner2)); CHECK_EQ(inner1->shared(), inner2->shared()); } } TEST(SFIDeduplicationClasses) { CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); WebSnapshotData snapshot_data; { v8::Local new_context = CcTest::NewContext(); v8::Context::Scope context_scope(new_context); const char* snapshot_source = "let foo = {};\n" "foo.create = function(a) {\n" " return class {\n" " constructor(x) {this.x = x;};\n" " }\n" "}\n" "foo.class = foo.create('hi');"; CompileRun(snapshot_source); v8::Local exports = v8::PrimitiveArray::New(isolate, 1); v8::Local str = v8::String::NewFromUtf8(isolate, "foo").ToLocalChecked(); exports->Set(isolate, 0, str); WebSnapshotSerializer serializer(isolate); CHECK(serializer.TakeSnapshot(new_context, exports, snapshot_data)); CHECK(!serializer.has_error()); CHECK_NOT_NULL(snapshot_data.buffer); } { v8::Local new_context = CcTest::NewContext(); v8::Context::Scope context_scope(new_context); WebSnapshotDeserializer deserializer(isolate, snapshot_data.buffer, snapshot_data.buffer_size); CHECK(deserializer.Deserialize()); CHECK(!deserializer.has_error()); const char* get_class = "foo.class"; const char* create_new_class = "foo.create()"; // Verify that foo.inner and the JSFunction which is the result of calling // foo.outer() after deserialization share the SFI. v8::Local v8_class1 = CompileRun(get_class).As(); v8::Local v8_class2 = CompileRun(create_new_class).As(); Handle class1 = Handle::cast(Utils::OpenHandle(*v8_class1)); Handle class2 = Handle::cast(Utils::OpenHandle(*v8_class2)); CHECK_EQ(class1->shared(), class2->shared()); } } TEST(SFIDeduplicationAfterBytecodeFlushing) { FLAG_stress_flush_code = true; FLAG_flush_bytecode = true; CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); WebSnapshotData snapshot_data; { v8::HandleScope scope(isolate); v8::Local new_context = CcTest::NewContext(); v8::Context::Scope context_scope(new_context); const char* snapshot_source = "let foo = {};\n" "foo.outer = function() {\n" " let a = 'hello';\n" " return function() {\n" " return a;\n" " }\n" "}\n" "foo.inner = foo.outer();"; CompileRun(snapshot_source); v8::Local exports = v8::PrimitiveArray::New(isolate, 1); v8::Local str = v8::String::NewFromUtf8(isolate, "foo").ToLocalChecked(); exports->Set(isolate, 0, str); WebSnapshotSerializer serializer(isolate); CHECK(serializer.TakeSnapshot(new_context, exports, snapshot_data)); CHECK(!serializer.has_error()); CHECK_NOT_NULL(snapshot_data.buffer); } CcTest::CollectAllGarbage(); CcTest::CollectAllGarbage(); { v8::HandleScope scope(isolate); v8::Local new_context = CcTest::NewContext(); v8::Context::Scope context_scope(new_context); WebSnapshotDeserializer deserializer(isolate, snapshot_data.buffer, snapshot_data.buffer_size); CHECK(deserializer.Deserialize()); CHECK(!deserializer.has_error()); const char* get_outer = "foo.outer"; const char* get_inner = "foo.inner"; const char* create_new_inner = "foo.outer()"; v8::Local v8_outer = CompileRun(get_outer).As(); Handle outer = Handle::cast(Utils::OpenHandle(*v8_outer)); CHECK(!outer->shared().is_compiled()); v8::Local v8_inner1 = CompileRun(get_inner).As(); v8::Local v8_inner2 = CompileRun(create_new_inner).As(); Handle inner1 = Handle::cast(Utils::OpenHandle(*v8_inner1)); Handle inner2 = Handle::cast(Utils::OpenHandle(*v8_inner2)); CHECK(outer->shared().is_compiled()); CHECK_EQ(inner1->shared(), inner2->shared()); // Force bytecode flushing of "foo.outer". CcTest::CollectAllGarbage(); CcTest::CollectAllGarbage(); CHECK(!outer->shared().is_compiled()); // Create another inner function. v8::Local v8_inner3 = CompileRun(create_new_inner).As(); Handle inner3 = Handle::cast(Utils::OpenHandle(*v8_inner3)); // Check that it shares the SFI with the original inner function which is in // the snapshot. CHECK_EQ(inner1->shared(), inner3->shared()); } } TEST(SFIDeduplicationAfterBytecodeFlushingClasses) { FLAG_stress_flush_code = true; FLAG_flush_bytecode = true; CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); WebSnapshotData snapshot_data; { v8::HandleScope scope(isolate); v8::Local new_context = CcTest::NewContext(); v8::Context::Scope context_scope(new_context); const char* snapshot_source = "let foo = {};\n" "foo.create = function(a) {\n" " return class {\n" " constructor(x) {this.x = x;};\n" " }\n" "}\n" "foo.class = foo.create('hi');"; CompileRun(snapshot_source); v8::Local exports = v8::PrimitiveArray::New(isolate, 1); v8::Local str = v8::String::NewFromUtf8(isolate, "foo").ToLocalChecked(); exports->Set(isolate, 0, str); WebSnapshotSerializer serializer(isolate); CHECK(serializer.TakeSnapshot(new_context, exports, snapshot_data)); CHECK(!serializer.has_error()); CHECK_NOT_NULL(snapshot_data.buffer); } CcTest::CollectAllGarbage(); CcTest::CollectAllGarbage(); { v8::HandleScope scope(isolate); v8::Local new_context = CcTest::NewContext(); v8::Context::Scope context_scope(new_context); WebSnapshotDeserializer deserializer(isolate, snapshot_data.buffer, snapshot_data.buffer_size); CHECK(deserializer.Deserialize()); CHECK(!deserializer.has_error()); const char* get_create = "foo.create"; const char* get_class = "foo.class"; const char* create_new_class = "foo.create()"; v8::Local v8_create = CompileRun(get_create).As(); Handle create = Handle::cast(Utils::OpenHandle(*v8_create)); CHECK(!create->shared().is_compiled()); v8::Local v8_class1 = CompileRun(get_class).As(); v8::Local v8_class2 = CompileRun(create_new_class).As(); Handle class1 = Handle::cast(Utils::OpenHandle(*v8_class1)); Handle class2 = Handle::cast(Utils::OpenHandle(*v8_class2)); CHECK(create->shared().is_compiled()); CHECK_EQ(class1->shared(), class2->shared()); // Force bytecode flushing of "foo.outer". CcTest::CollectAllGarbage(); CcTest::CollectAllGarbage(); CHECK(!create->shared().is_compiled()); // Create another inner function. v8::Local v8_class3 = CompileRun(create_new_class).As(); Handle class3 = Handle::cast(Utils::OpenHandle(*v8_class3)); // Check that it shares the SFI with the original inner function which is in // the snapshot. CHECK_EQ(class1->shared(), class3->shared()); } } TEST(SFIDeduplicationOfFunctionsNotInSnapshot) { CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); WebSnapshotData snapshot_data; { v8::Local new_context = CcTest::NewContext(); v8::Context::Scope context_scope(new_context); const char* snapshot_source = "let foo = {};\n" "foo.outer = function(a) {\n" " return function() {\n" " return a;\n" " }\n" "}\n"; CompileRun(snapshot_source); v8::Local exports = v8::PrimitiveArray::New(isolate, 1); v8::Local str = v8::String::NewFromUtf8(isolate, "foo").ToLocalChecked(); exports->Set(isolate, 0, str); WebSnapshotSerializer serializer(isolate); CHECK(serializer.TakeSnapshot(new_context, exports, snapshot_data)); CHECK(!serializer.has_error()); CHECK_NOT_NULL(snapshot_data.buffer); } { v8::Local new_context = CcTest::NewContext(); v8::Context::Scope context_scope(new_context); WebSnapshotDeserializer deserializer(isolate, snapshot_data.buffer, snapshot_data.buffer_size); CHECK(deserializer.Deserialize()); CHECK(!deserializer.has_error()); const char* create_new_inner = "foo.outer()"; // Verify that repeated invocations of foo.outer() return functions which // share the SFI. v8::Local v8_inner1 = CompileRun(create_new_inner).As(); v8::Local v8_inner2 = CompileRun(create_new_inner).As(); Handle inner1 = Handle::cast(Utils::OpenHandle(*v8_inner1)); Handle inner2 = Handle::cast(Utils::OpenHandle(*v8_inner2)); CHECK_EQ(inner1->shared(), inner2->shared()); } } namespace { void VerifyFunctionKind(const v8::Local& result, const v8::Local& context, const char* property_name, FunctionKind expected_kind) { v8::Local v8_function = result->Get(context, v8_str(property_name)) .ToLocalChecked() .As(); Handle function = Handle::cast(Utils::OpenHandle(*v8_function)); CHECK_EQ(function->shared().kind(), expected_kind); } } // namespace TEST(FunctionKinds) { const char* snapshot_source = "var foo = {a: function() {},\n" " b: () => {},\n" " c: async function() {},\n" " d: async () => {},\n" " e: function*() {},\n" " f: async function*() {}\n" "}"; const char* test_source = "foo"; // 'foo', 'Object.prototype', source code. 'a'...'f' in-place. uint32_t kStringCount = 3; uint32_t kSymbolCount = 0; uint32_t kBuiltinObjectCount = 1; uint32_t kMapCount = 1; uint32_t kContextCount = 0; uint32_t kFunctionCount = 6; uint32_t kObjectCount = 1; uint32_t kArrayCount = 0; std::function)> tester = [test_source](v8::Isolate* isolate, v8::Local new_context) { v8::Local result = CompileRun(test_source).As(); // Verify all FunctionKinds. VerifyFunctionKind(result, new_context, "a", FunctionKind::kNormalFunction); VerifyFunctionKind(result, new_context, "b", FunctionKind::kArrowFunction); VerifyFunctionKind(result, new_context, "c", FunctionKind::kAsyncFunction); VerifyFunctionKind(result, new_context, "d", FunctionKind::kAsyncArrowFunction); VerifyFunctionKind(result, new_context, "e", FunctionKind::kGeneratorFunction); VerifyFunctionKind(result, new_context, "f", FunctionKind::kAsyncGeneratorFunction); }; TestWebSnapshotExtensive(snapshot_source, test_source, tester, kStringCount, kSymbolCount, kBuiltinObjectCount, kMapCount, kContextCount, kFunctionCount, kObjectCount, kArrayCount); } // Test that concatenating JS code to the snapshot works. TEST(Concatenation) { CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); const char* snapshot_source = "var foo = {a: 1};\n"; const char* source_to_append = "var bar = {a: 10};"; const char* test_source = "foo.a + bar.a"; uint32_t kObjectCount = 1; WebSnapshotData snapshot_data; { v8::HandleScope scope(isolate); v8::Local new_context = CcTest::NewContext(); v8::Context::Scope context_scope(new_context); CompileRun(snapshot_source); v8::Local exports = v8::PrimitiveArray::New(isolate, 1); v8::Local str = v8::String::NewFromUtf8(isolate, "foo").ToLocalChecked(); exports->Set(isolate, 0, str); WebSnapshotSerializer serializer(isolate); CHECK(serializer.TakeSnapshot(new_context, exports, snapshot_data)); CHECK(!serializer.has_error()); CHECK_NOT_NULL(snapshot_data.buffer); CHECK_EQ(kObjectCount, serializer.object_count()); } auto buffer_size = snapshot_data.buffer_size + strlen(source_to_append); std::unique_ptr buffer(new uint8_t[buffer_size]); memcpy(buffer.get(), snapshot_data.buffer, snapshot_data.buffer_size); memcpy(buffer.get() + snapshot_data.buffer_size, source_to_append, strlen(source_to_append)); { v8::HandleScope scope(isolate); v8::Local new_context = CcTest::NewContext(); v8::Context::Scope context_scope(new_context); WebSnapshotDeserializer deserializer(isolate, buffer.get(), buffer_size); CHECK(deserializer.Deserialize()); CHECK(!deserializer.has_error()); CHECK_EQ(kObjectCount, deserializer.object_count()); v8::Local result = CompileRun(test_source).As(); CHECK_EQ(11, result->Value()); } } // Test that errors from invalid concatenated code are handled correctly. TEST(ConcatenationErrors) { CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); const char* snapshot_source = "var foo = {a: 1};\n"; const char* source_to_append = "wontparse+[)"; uint32_t kObjectCount = 1; WebSnapshotData snapshot_data; { v8::HandleScope scope(isolate); v8::Local new_context = CcTest::NewContext(); v8::Context::Scope context_scope(new_context); CompileRun(snapshot_source); v8::Local exports = v8::PrimitiveArray::New(isolate, 1); v8::Local str = v8::String::NewFromUtf8(isolate, "foo").ToLocalChecked(); exports->Set(isolate, 0, str); WebSnapshotSerializer serializer(isolate); CHECK(serializer.TakeSnapshot(new_context, exports, snapshot_data)); CHECK(!serializer.has_error()); CHECK_NOT_NULL(snapshot_data.buffer); CHECK_EQ(kObjectCount, serializer.object_count()); } auto buffer_size = snapshot_data.buffer_size + strlen(source_to_append); std::unique_ptr buffer(new uint8_t[buffer_size]); memcpy(buffer.get(), snapshot_data.buffer, snapshot_data.buffer_size); memcpy(buffer.get() + snapshot_data.buffer_size, source_to_append, strlen(source_to_append)); { v8::HandleScope scope(isolate); v8::Local new_context = CcTest::NewContext(); v8::Context::Scope context_scope(new_context); WebSnapshotDeserializer deserializer(isolate, buffer.get(), buffer_size); CHECK(!deserializer.Deserialize()); } } TEST(CompactedSourceCode) { CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); Isolate* i_isolate = CcTest::i_isolate(); v8::HandleScope scope(isolate); WebSnapshotData snapshot_data; { v8::Local new_context = CcTest::NewContext(); v8::Context::Scope context_scope(new_context); const char* snapshot_source = "function foo() { 'foo' }\n" "function bar() { 'bar' }\n" "function baz() { 'baz' }\n" "let e = [foo, bar, baz]"; CompileRun(snapshot_source); v8::Local exports = v8::PrimitiveArray::New(isolate, 1); v8::Local str = v8::String::NewFromUtf8(isolate, "e").ToLocalChecked(); exports->Set(isolate, 0, str); WebSnapshotSerializer serializer(isolate); CHECK(serializer.TakeSnapshot(new_context, exports, snapshot_data)); CHECK(!serializer.has_error()); CHECK_NOT_NULL(snapshot_data.buffer); } { v8::Local new_context = CcTest::NewContext(); v8::Context::Scope context_scope(new_context); WebSnapshotDeserializer deserializer(isolate, snapshot_data.buffer, snapshot_data.buffer_size); CHECK(deserializer.Deserialize()); CHECK(!deserializer.has_error()); const char* get_function = "e[0]"; // Verify that the source code got compacted. v8::Local v8_function = CompileRun(get_function).As(); Handle function = Handle::cast(Utils::OpenHandle(*v8_function)); Handle function_script_source = handle(String::cast(Script::cast(function->shared().script()).source()), i_isolate); const char* raw_expected_source = "() { 'foo' }() { 'bar' }() { 'baz' }"; Handle expected_source = Utils::OpenHandle( *v8::String::NewFromUtf8(isolate, raw_expected_source).ToLocalChecked(), i_isolate); CHECK(function_script_source->Equals(*expected_source)); } } TEST(InPlaceStringsInArrays) { const char* snapshot_source = "var foo = ['one', 'two', 'three'];"; const char* test_source = "foo.join('');"; const char* expected_result = "onetwothree"; uint32_t kStringCount = 1; // 'foo'; Other strings are in-place. uint32_t kSymbolCount = 0; uint32_t kBuiltinObjectCount = 0; uint32_t kMapCount = 0; uint32_t kContextCount = 0; uint32_t kFunctionCount = 0; uint32_t kObjectCount = 0; uint32_t kArrayCount = 1; TestWebSnapshot(snapshot_source, test_source, expected_result, kStringCount, kSymbolCount, kBuiltinObjectCount, kMapCount, kContextCount, kFunctionCount, kObjectCount, kArrayCount); } TEST(RepeatedInPlaceStringsInArrays) { const char* snapshot_source = "var foo = ['one', 'two', 'one'];"; const char* test_source = "foo.join('');"; const char* expected_result = "onetwoone"; uint32_t kStringCount = 2; // 'foo', 'one'; Other strings are in-place. uint32_t kSymbolCount = 0; uint32_t kBuiltinObjectCount = 0; uint32_t kMapCount = 0; uint32_t kContextCount = 0; uint32_t kFunctionCount = 0; uint32_t kObjectCount = 0; uint32_t kArrayCount = 1; TestWebSnapshot(snapshot_source, test_source, expected_result, kStringCount, kSymbolCount, kBuiltinObjectCount, kMapCount, kContextCount, kFunctionCount, kObjectCount, kArrayCount); } TEST(InPlaceStringsInObjects) { const char* snapshot_source = "var foo = {a: 'one', b: 'two', c: 'three'};"; const char* test_source = "foo.a + foo.b + foo.c;"; const char* expected_result = "onetwothree"; // 'foo', 'Object.prototype'. Other strings are in-place. uint32_t kStringCount = 2; uint32_t kSymbolCount = 0; uint32_t kBuiltinObjectCount = 1; uint32_t kMapCount = 1; uint32_t kContextCount = 0; uint32_t kFunctionCount = 0; uint32_t kObjectCount = 1; uint32_t kArrayCount = 0; TestWebSnapshot(snapshot_source, test_source, expected_result, kStringCount, kSymbolCount, kBuiltinObjectCount, kMapCount, kContextCount, kFunctionCount, kObjectCount, kArrayCount); } TEST(RepeatedInPlaceStringsInObjects) { const char* snapshot_source = "var foo = {a: 'one', b: 'two', c: 'one'};"; const char* test_source = "foo.a + foo.b + foo.c;"; const char* expected_result = "onetwoone"; // 'foo', 'one', 'Object.prototype'. Other strings are in-place. uint32_t kStringCount = 3; uint32_t kSymbolCount = 0; uint32_t kBuiltinObjectCount = 1; uint32_t kMapCount = 1; uint32_t kContextCount = 0; uint32_t kFunctionCount = 0; uint32_t kObjectCount = 1; uint32_t kArrayCount = 0; TestWebSnapshot(snapshot_source, test_source, expected_result, kStringCount, kSymbolCount, kBuiltinObjectCount, kMapCount, kContextCount, kFunctionCount, kObjectCount, kArrayCount); } TEST(BuiltinObjects) { const char* snapshot_source = "var foo = {a: Error.prototype};"; const char* test_source = "foo.a == Error.prototype ? \"pass\" : \"fail\""; const char* expected_result = "pass"; // 'foo', 'Error.prototype', 'Object.prototype'. Other strings are in-place. uint32_t kStringCount = 3; uint32_t kSymbolCount = 0; uint32_t kBuiltinObjectCount = 2; uint32_t kMapCount = 1; uint32_t kContextCount = 0; uint32_t kFunctionCount = 0; uint32_t kObjectCount = 1; uint32_t kArrayCount = 0; TestWebSnapshot(snapshot_source, test_source, expected_result, kStringCount, kSymbolCount, kBuiltinObjectCount, kMapCount, kContextCount, kFunctionCount, kObjectCount, kArrayCount); } TEST(BuiltinObjectsDeduplicated) { const char* snapshot_source = "var foo = {a: Error.prototype, b: Error.prototype}"; const char* test_source = "foo.a === Error.prototype ? \"pass\" : \"fail\""; const char* expected_result = "pass"; // 'foo', 'Error.prototype', 'Object.prototype'. Other strings are in-place. uint32_t kStringCount = 3; uint32_t kSymbolCount = 0; uint32_t kBuiltinObjectCount = 2; uint32_t kMapCount = 1; uint32_t kContextCount = 0; uint32_t kFunctionCount = 0; uint32_t kObjectCount = 1; uint32_t kArrayCount = 0; TestWebSnapshot(snapshot_source, test_source, expected_result, kStringCount, kSymbolCount, kBuiltinObjectCount, kMapCount, kContextCount, kFunctionCount, kObjectCount, kArrayCount); } } // namespace internal } // namespace v8