// Copyright 2022 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/codegen/compiler.h" #include #include #include #include "include/v8-function.h" #include "include/v8-local-handle.h" #include "include/v8-profiler.h" #include "include/v8-script.h" #include "src/api/api-inl.h" #include "src/codegen/compilation-cache.h" #include "src/codegen/script-details.h" #include "src/heap/factory.h" #include "src/objects/allocation-site-inl.h" #include "src/objects/objects-inl.h" #include "src/objects/shared-function-info.h" #include "test/unittests/test-utils.h" #include "testing/gtest/include/gtest/gtest.h" namespace v8 { using CompilerTest = TestWithContext; namespace internal { static Handle GetGlobalProperty(const char* name) { Isolate* isolate = reinterpret_cast(v8::Isolate::GetCurrent()); return JSReceiver::GetProperty(isolate, isolate->global_object(), name) .ToHandleChecked(); } static void SetGlobalProperty(const char* name, Object value) { Isolate* isolate = reinterpret_cast(v8::Isolate::GetCurrent()); Handle object(value, isolate); Handle internalized_name = isolate->factory()->InternalizeUtf8String(name); Handle global(isolate->context().global_object(), isolate); Runtime::SetObjectProperty(isolate, global, internalized_name, object, StoreOrigin::kMaybeKeyed, Just(kDontThrow)) .Check(); } static Handle Compile(const char* source) { Isolate* isolate = reinterpret_cast(v8::Isolate::GetCurrent()); Handle source_code = isolate->factory() ->NewStringFromUtf8(base::CStrVector(source)) .ToHandleChecked(); Handle shared = Compiler::GetSharedFunctionInfoForScript( isolate, source_code, ScriptDetails(), v8::ScriptCompiler::kNoCompileOptions, ScriptCompiler::kNoCacheNoReason, NOT_NATIVES_CODE) .ToHandleChecked(); return Factory::JSFunctionBuilder{isolate, shared, isolate->native_context()} .Build(); } static double Inc(Isolate* isolate, int x) { const char* source = "result = %d + 1;"; base::EmbeddedVector buffer; SNPrintF(buffer, source, x); Handle fun = Compile(buffer.begin()); if (fun.is_null()) return -1; Handle global(isolate->context().global_object(), isolate); Execution::CallScript(isolate, fun, global, isolate->factory()->empty_fixed_array()) .Check(); return GetGlobalProperty("result")->Number(); } TEST_F(CompilerTest, Inc) { v8::HandleScope scope(isolate()); EXPECT_EQ(4.0, Inc(i_isolate(), 3)); } static double Add(Isolate* isolate, int x, int y) { Handle fun = Compile("result = x + y;"); if (fun.is_null()) return -1; SetGlobalProperty("x", Smi::FromInt(x)); SetGlobalProperty("y", Smi::FromInt(y)); Handle global(isolate->context().global_object(), isolate); Execution::CallScript(isolate, fun, global, isolate->factory()->empty_fixed_array()) .Check(); return GetGlobalProperty("result")->Number(); } TEST_F(CompilerTest, Add) { v8::HandleScope scope(isolate()); EXPECT_EQ(5.0, Add(i_isolate(), 2, 3)); } static double Abs(Isolate* isolate, int x) { Handle fun = Compile("if (x < 0) result = -x; else result = x;"); if (fun.is_null()) return -1; SetGlobalProperty("x", Smi::FromInt(x)); Handle global(isolate->context().global_object(), isolate); Execution::CallScript(isolate, fun, global, isolate->factory()->empty_fixed_array()) .Check(); return GetGlobalProperty("result")->Number(); } TEST_F(CompilerTest, Abs) { v8::HandleScope scope(isolate()); EXPECT_EQ(3.0, Abs(i_isolate(), -3)); } static double Sum(Isolate* isolate, int n) { Handle fun = Compile("s = 0; while (n > 0) { s += n; n -= 1; }; result = s;"); if (fun.is_null()) return -1; SetGlobalProperty("n", Smi::FromInt(n)); Handle global(isolate->context().global_object(), isolate); Execution::CallScript(isolate, fun, global, isolate->factory()->empty_fixed_array()) .Check(); return GetGlobalProperty("result")->Number(); } TEST_F(CompilerTest, Sum) { v8::HandleScope scope(isolate()); EXPECT_EQ(5050.0, Sum(i_isolate(), 100)); } using CompilerPrintTest = WithPrintExtensionMixin; TEST_F(CompilerPrintTest, Print) { v8::HandleScope scope(isolate()); const char* extension_names[1] = { WithPrintExtensionMixin::kPrintExtensionName}; v8::ExtensionConfiguration config(1, extension_names); v8::Local context = v8::Context::New(isolate(), &config); v8::Context::Scope context_scope(context); const char* source = "for (n = 0; n < 100; ++n) print(n, 1, 2);"; Handle fun = Compile(source); if (fun.is_null()) return; Handle global(i_isolate()->context().global_object(), i_isolate()); Execution::CallScript(i_isolate(), fun, global, i_isolate()->factory()->empty_fixed_array()) .Check(); } // The following test method stems from my coding efforts today. It // tests all the functionality I have added to the compiler today TEST_F(CompilerTest, Stuff) { v8::HandleScope scope(isolate()); const char* source = "r = 0;\n" "a = new Object;\n" "if (a == a) r+=1;\n" // 1 "if (a != new Object()) r+=2;\n" // 2 "a.x = 42;\n" "if (a.x == 42) r+=4;\n" // 4 "function foo() { var x = 87; return x; }\n" "if (foo() == 87) r+=8;\n" // 8 "function bar() { var x; x = 99; return x; }\n" "if (bar() == 99) r+=16;\n" // 16 "function baz() { var x = 1, y, z = 2; y = 3; return x + y + z; }\n" "if (baz() == 6) r+=32;\n" // 32 "function Cons0() { this.x = 42; this.y = 87; }\n" "if (new Cons0().x == 42) r+=64;\n" // 64 "if (new Cons0().y == 87) r+=128;\n" // 128 "function Cons2(x, y) { this.sum = x + y; }\n" "if (new Cons2(3,4).sum == 7) r+=256;"; // 256 Handle fun = Compile(source); EXPECT_TRUE(!fun.is_null()); Handle global(i_isolate()->context().global_object(), i_isolate()); Execution::CallScript(i_isolate(), fun, global, i_isolate()->factory()->empty_fixed_array()) .Check(); EXPECT_EQ(511.0, GetGlobalProperty("r")->Number()); } TEST_F(CompilerTest, UncaughtThrow) { v8::HandleScope scope(isolate()); const char* source = "throw 42;"; Handle fun = Compile(source); EXPECT_TRUE(!fun.is_null()); Isolate* isolate = fun->GetIsolate(); Handle global(isolate->context().global_object(), isolate); EXPECT_TRUE(Execution::CallScript(isolate, fun, global, isolate->factory()->empty_fixed_array()) .is_null()); EXPECT_EQ(42.0, isolate->pending_exception().Number()); } using CompilerC2JSFramesTest = WithPrintExtensionMixin; // Tests calling a builtin function from C/C++ code, and the builtin function // performs GC. It creates a stack frame looks like following: // | C (PerformGC) | // | JS-to-C | // | JS | // | C-to-JS | TEST_F(CompilerC2JSFramesTest, C2JSFrames) { v8_flags.expose_gc = true; v8::HandleScope scope(isolate()); const char* extension_names[2] = { "v8/gc", WithPrintExtensionMixin::kPrintExtensionName}; v8::ExtensionConfiguration config(2, extension_names); v8::Local context = v8::Context::New(isolate(), &config); v8::Context::Scope context_scope(context); const char* source = "function foo(a) { gc(), print(a); }"; Handle fun0 = Compile(source); EXPECT_TRUE(!fun0.is_null()); Isolate* isolate = fun0->GetIsolate(); // Run the generated code to populate the global object with 'foo'. Handle global(isolate->context().global_object(), isolate); Execution::CallScript(isolate, fun0, global, isolate->factory()->empty_fixed_array()) .Check(); Handle fun1 = JSReceiver::GetProperty(isolate, isolate->global_object(), "foo") .ToHandleChecked(); EXPECT_TRUE(fun1->IsJSFunction()); Handle argv[] = { isolate->factory()->InternalizeString(base::StaticCharVector("hello"))}; Execution::Call(isolate, Handle::cast(fun1), global, arraysize(argv), argv) .Check(); } // Regression 236. Calling InitLineEnds on a Script with undefined // source resulted in crash. TEST_F(CompilerTest, Regression236) { Factory* factory = i_isolate()->factory(); v8::HandleScope scope(isolate()); Handle