// Copyright 2015 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 "test/cctest/cctest.h" #include "include/v8.h" #include "src/api/api.h" #include "src/objects/objects-inl.h" namespace i = v8::internal; // The goal is to avoid the callback. static void UnreachableCallback( const v8::FunctionCallbackInfo& info) { UNREACHABLE(); } TEST(CachedAccessor) { // TurboFan support for fast accessors is not implemented; turbofanned // code uses the slow accessor which breaks this test's expectations. v8::internal::FLAG_always_opt = false; LocalContext env; v8::Isolate* isolate = env->GetIsolate(); v8::HandleScope scope(isolate); // Create 'foo' class, with a hidden property. v8::Local foo = v8::ObjectTemplate::New(isolate); v8::Local priv = v8::Private::ForApi(isolate, v8_str("Foo#draft")); foo->SetAccessorProperty(v8_str("draft"), v8::FunctionTemplate::NewWithCache( isolate, UnreachableCallback, priv, v8::Local())); // Create 'obj', instance of 'foo'. v8::Local obj = foo->NewInstance(env.local()).ToLocalChecked(); // Install the private property on the instance. CHECK(obj->SetPrivate(isolate->GetCurrentContext(), priv, v8::Undefined(isolate)) .FromJust()); CHECK(env->Global()->Set(env.local(), v8_str("obj"), obj).FromJust()); // Access cached accessor. ExpectUndefined("obj.draft"); // Set hidden property. CHECK(obj->SetPrivate(isolate->GetCurrentContext(), priv, v8_str("Shhh, I'm private!")) .FromJust()); ExpectString("obj.draft", "Shhh, I'm private!"); // Stress the accessor to use the IC. ExpectString( "var result = '';" "for (var i = 0; i < 10; ++i) { " " result = obj.draft; " "} " "result; ", "Shhh, I'm private!"); } TEST(CachedAccessorTurboFan) { i::FLAG_allow_natives_syntax = true; // v8::internal::FLAG_always_opt = false; LocalContext env; v8::Isolate* isolate = env->GetIsolate(); v8::HandleScope scope(isolate); // Create 'foo' class, with a hidden property. v8::Local foo = v8::ObjectTemplate::New(isolate); v8::Local priv = v8::Private::ForApi(isolate, v8_str("Foo#draft")); // Install the private property on the template. // foo->SetPrivate(priv, v8::Undefined(isolate)); foo->SetAccessorProperty(v8_str("draft"), v8::FunctionTemplate::NewWithCache( isolate, UnreachableCallback, priv, v8::Local())); // Create 'obj', instance of 'foo'. v8::Local obj = foo->NewInstance(env.local()).ToLocalChecked(); // Install the private property on the instance. CHECK(obj->SetPrivate(isolate->GetCurrentContext(), priv, v8::Undefined(isolate)) .FromJust()); CHECK(env->Global()->Set(env.local(), v8_str("obj"), obj).FromJust()); // Access surrogate accessor. ExpectUndefined("obj.draft"); // Set hidden property. CHECK(obj->SetPrivate(env.local(), priv, v8::Integer::New(isolate, 123)) .FromJust()); // Test ICs. CompileRun( "function f() {" " var x;" " for (var i = 0; i < 100; i++) {" " x = obj.draft;" " }" " return x;" "};" "%PrepareFunctionForOptimization(f);"); ExpectInt32("f()", 123); // Reset hidden property. CHECK(obj->SetPrivate(env.local(), priv, v8::Integer::New(isolate, 456)) .FromJust()); // Test TurboFan. CompileRun("%OptimizeFunctionOnNextCall(f);"); ExpectInt32("f()", 456); CHECK(obj->SetPrivate(env.local(), priv, v8::Integer::New(isolate, 456)) .FromJust()); // Test non-global ICs. CompileRun( "function g() {" " var x = obj;" " var r = 0;" " for (var i = 0; i < 100; i++) {" " r = x.draft;" " }" " return r;" "};" "%PrepareFunctionForOptimization(g);"); ExpectInt32("g()", 456); // Reset hidden property. CHECK(obj->SetPrivate(env.local(), priv, v8::Integer::New(isolate, 789)) .FromJust()); // Test non-global access in TurboFan. CompileRun("%OptimizeFunctionOnNextCall(g);"); ExpectInt32("g()", 789); } TEST(CachedAccessorOnGlobalObject) { i::FLAG_allow_natives_syntax = true; LocalContext env; v8::Isolate* isolate = env->GetIsolate(); v8::HandleScope scope(isolate); v8::Local templ = v8::FunctionTemplate::New(CcTest::isolate()); v8::Local object_template = templ->InstanceTemplate(); v8::Local priv = v8::Private::ForApi(isolate, v8_str("Foo#draft")); object_template->SetAccessorProperty( v8_str("draft"), v8::FunctionTemplate::NewWithCache(isolate, UnreachableCallback, priv, v8::Local())); v8::Local ctx = v8::Context::New(CcTest::isolate(), nullptr, object_template); v8::Local obj = ctx->Global(); // Install the private property on the instance. CHECK(obj->SetPrivate(isolate->GetCurrentContext(), priv, v8::Undefined(isolate)) .FromJust()); { v8::Context::Scope context_scope(ctx); // Access surrogate accessor. ExpectUndefined("draft"); // Set hidden property. CHECK(obj->SetPrivate(env.local(), priv, v8::Integer::New(isolate, 123)) .FromJust()); // Test ICs. CompileRun( "function f() {" " var x;" " for (var i = 0; i < 100; i++) {" " x = draft;" " }" " return x;" "}" "%PrepareFunctionForOptimization(f);"); ExpectInt32("f()", 123); // Reset hidden property. CHECK(obj->SetPrivate(env.local(), priv, v8::Integer::New(isolate, 456)) .FromJust()); // Test TurboFan. CompileRun("%OptimizeFunctionOnNextCall(f);"); ExpectInt32("f()", 456); CHECK(obj->SetPrivate(env.local(), priv, v8::Integer::New(isolate, 456)) .FromJust()); // Test non-global ICs. CompileRun( "var x = this;" "function g() {" " var r = 0;" " for (var i = 0; i < 100; i++) {" " r = x.draft;" " }" " return r;" "}" "%PrepareFunctionForOptimization(g);"); ExpectInt32("g()", 456); // Reset hidden property. CHECK(obj->SetPrivate(env.local(), priv, v8::Integer::New(isolate, 789)) .FromJust()); // Test non-global access in TurboFan. CompileRun("%OptimizeFunctionOnNextCall(g);"); ExpectInt32("g()", 789); } } namespace { // Getter return value should be non-null to trigger lazy property paths. static void Getter(v8::Local name, const v8::PropertyCallbackInfo& info) { info.GetReturnValue().Set(v8_str("return value")); } static void StringGetter(v8::Local name, const v8::PropertyCallbackInfo& info) {} static int set_accessor_call_count = 0; static void Setter(v8::Local name, v8::Local value, const v8::PropertyCallbackInfo& info) { set_accessor_call_count++; } } // namespace // Re-declaration of non-configurable accessors should throw. TEST(RedeclareAccessor) { v8::HandleScope scope(CcTest::isolate()); LocalContext env; v8::Local templ = v8::FunctionTemplate::New(CcTest::isolate()); v8::Local object_template = templ->InstanceTemplate(); object_template->SetAccessor( v8_str("foo"), nullptr, Setter, v8::Local(), v8::AccessControl::DEFAULT, v8::PropertyAttribute::DontDelete); v8::Local ctx = v8::Context::New(CcTest::isolate(), nullptr, object_template); // Declare function. v8::Local code = v8_str("function foo() {};"); v8::TryCatch try_catch(CcTest::isolate()); v8::Script::Compile(ctx, code).ToLocalChecked()->Run(ctx).IsEmpty(); CHECK(try_catch.HasCaught()); } // Accessors can be whitelisted as side-effect-free via SetAccessor. TEST(AccessorSetHasNoSideEffect) { LocalContext env; v8::Isolate* isolate = env->GetIsolate(); v8::HandleScope scope(isolate); v8::Local context = isolate->GetCurrentContext(); v8::Local templ = v8::ObjectTemplate::New(isolate); v8::Local obj = templ->NewInstance(env.local()).ToLocalChecked(); CHECK(env->Global()->Set(env.local(), v8_str("obj"), obj).FromJust()); obj->SetAccessor(context, v8_str("foo"), Getter).ToChecked(); CHECK(v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo"), true).IsEmpty()); obj->SetAccessor(context, v8_str("foo"), Getter, nullptr, v8::MaybeLocal(), v8::AccessControl::DEFAULT, v8::PropertyAttribute::None, v8::SideEffectType::kHasNoSideEffect) .ToChecked(); v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo"), true).ToLocalChecked(); // Check that setter is not whitelisted. v8::TryCatch try_catch(isolate); CHECK(v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo = 1"), true) .IsEmpty()); CHECK(try_catch.HasCaught()); CHECK_NE(1, v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo"), false) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); CHECK_EQ(0, set_accessor_call_count); } // Set accessors can be whitelisted as side-effect-free via SetAccessor. TEST(SetAccessorSetSideEffectReceiverCheck1) { LocalContext env; v8::Isolate* isolate = env->GetIsolate(); v8::HandleScope scope(isolate); v8::Local templ = v8::ObjectTemplate::New(isolate); v8::Local obj = templ->NewInstance(env.local()).ToLocalChecked(); CHECK(env->Global()->Set(env.local(), v8_str("obj"), obj).FromJust()); obj->SetAccessor(env.local(), v8_str("foo"), Getter, Setter, v8::MaybeLocal(), v8::AccessControl::DEFAULT, v8::PropertyAttribute::None, v8::SideEffectType::kHasNoSideEffect, v8::SideEffectType::kHasSideEffectToReceiver) .ToChecked(); CHECK(v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo"), true) .ToLocalChecked() ->Equals(env.local(), v8_str("return value")) .FromJust()); v8::TryCatch try_catch(isolate); CHECK(v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo = 1"), true) .IsEmpty()); CHECK(try_catch.HasCaught()); CHECK_EQ(0, set_accessor_call_count); } static void ConstructCallback(const v8::FunctionCallbackInfo& info) { } TEST(SetAccessorSetSideEffectReceiverCheck2) { LocalContext env; v8::Isolate* isolate = env->GetIsolate(); v8::HandleScope scope(isolate); i::FLAG_enable_one_shot_optimization = false; v8::Local templ = v8::FunctionTemplate::New( isolate, ConstructCallback, v8::Local(), v8::Local(), 0, v8::ConstructorBehavior::kAllow, v8::SideEffectType::kHasNoSideEffect); templ->InstanceTemplate()->SetAccessor( v8_str("bar"), Getter, Setter, v8::Local(), v8::AccessControl::DEFAULT, v8::PropertyAttribute::None, v8::Local(), v8::SideEffectType::kHasSideEffectToReceiver, v8::SideEffectType::kHasSideEffectToReceiver); CHECK(env->Global() ->Set(env.local(), v8_str("f"), templ->GetFunction(env.local()).ToLocalChecked()) .FromJust()); CHECK(v8::debug::EvaluateGlobal(isolate, v8_str("new f().bar"), true) .ToLocalChecked() ->Equals(env.local(), v8_str("return value")) .FromJust()); v8::debug::EvaluateGlobal(isolate, v8_str("new f().bar = 1"), true) .ToLocalChecked(); CHECK_EQ(1, set_accessor_call_count); } // Accessors can be whitelisted as side-effect-free via SetNativeDataProperty. TEST(AccessorSetNativeDataPropertyHasNoSideEffect) { LocalContext env; v8::Isolate* isolate = env->GetIsolate(); v8::HandleScope scope(isolate); v8::Local context = isolate->GetCurrentContext(); v8::Local templ = v8::ObjectTemplate::New(isolate); v8::Local obj = templ->NewInstance(env.local()).ToLocalChecked(); CHECK(env->Global()->Set(env.local(), v8_str("obj"), obj).FromJust()); obj->SetNativeDataProperty(context, v8_str("foo"), Getter).ToChecked(); CHECK(v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo"), true).IsEmpty()); obj->SetNativeDataProperty( context, v8_str("foo"), Getter, nullptr, v8::Local(), v8::PropertyAttribute::None, v8::SideEffectType::kHasNoSideEffect) .ToChecked(); v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo"), true).ToLocalChecked(); // Check that setter is not whitelisted. v8::TryCatch try_catch(isolate); CHECK(v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo = 1"), true) .IsEmpty()); CHECK(try_catch.HasCaught()); CHECK_NE(1, v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo"), false) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); } // Accessors can be whitelisted as side-effect-free via SetLazyDataProperty. TEST(AccessorSetLazyDataPropertyHasNoSideEffect) { LocalContext env; v8::Isolate* isolate = env->GetIsolate(); v8::HandleScope scope(isolate); v8::Local context = isolate->GetCurrentContext(); v8::Local templ = v8::ObjectTemplate::New(isolate); v8::Local obj = templ->NewInstance(env.local()).ToLocalChecked(); CHECK(env->Global()->Set(env.local(), v8_str("obj"), obj).FromJust()); obj->SetLazyDataProperty(context, v8_str("foo"), Getter).ToChecked(); CHECK(v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo"), true).IsEmpty()); obj->SetLazyDataProperty(context, v8_str("foo"), Getter, v8::Local(), v8::PropertyAttribute::None, v8::SideEffectType::kHasNoSideEffect) .ToChecked(); v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo"), true).ToLocalChecked(); // Check that setter is not whitelisted. v8::TryCatch try_catch(isolate); CHECK(v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo = 1"), true) .IsEmpty()); CHECK(try_catch.HasCaught()); CHECK_NE(1, v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo"), false) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); } TEST(ObjectTemplateSetAccessorHasNoSideEffect) { LocalContext env; v8::Isolate* isolate = env->GetIsolate(); v8::HandleScope scope(isolate); v8::Local templ = v8::ObjectTemplate::New(isolate); templ->SetAccessor(v8_str("foo"), StringGetter); templ->SetAccessor( v8_str("foo2"), StringGetter, nullptr, v8::Local(), v8::AccessControl::DEFAULT, v8::PropertyAttribute::None, v8::Local(), v8::SideEffectType::kHasNoSideEffect); v8::Local obj = templ->NewInstance(env.local()).ToLocalChecked(); CHECK(env->Global()->Set(env.local(), v8_str("obj"), obj).FromJust()); CHECK(v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo"), true).IsEmpty()); v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo2"), true).ToLocalChecked(); // Check that setter is not whitelisted. v8::TryCatch try_catch(isolate); CHECK(v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo2 = 1"), true) .IsEmpty()); CHECK(try_catch.HasCaught()); CHECK_NE(1, v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo2"), false) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); } TEST(ObjectTemplateSetNativePropertyHasNoSideEffect) { LocalContext env; v8::Isolate* isolate = env->GetIsolate(); v8::HandleScope scope(isolate); v8::Local templ = v8::ObjectTemplate::New(isolate); templ->SetNativeDataProperty(v8_str("foo"), Getter); templ->SetNativeDataProperty( v8_str("foo2"), Getter, nullptr, v8::Local(), v8::PropertyAttribute::None, v8::Local(), v8::AccessControl::DEFAULT, v8::SideEffectType::kHasNoSideEffect); v8::Local obj = templ->NewInstance(env.local()).ToLocalChecked(); CHECK(env->Global()->Set(env.local(), v8_str("obj"), obj).FromJust()); CHECK(v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo"), true).IsEmpty()); v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo2"), true).ToLocalChecked(); // Check that setter is not whitelisted. v8::TryCatch try_catch(isolate); CHECK(v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo2 = 1"), true) .IsEmpty()); CHECK(try_catch.HasCaught()); CHECK_NE(1, v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo2"), false) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); } TEST(ObjectTemplateSetLazyPropertyHasNoSideEffect) { LocalContext env; v8::Isolate* isolate = env->GetIsolate(); v8::HandleScope scope(isolate); v8::Local templ = v8::ObjectTemplate::New(isolate); templ->SetLazyDataProperty(v8_str("foo"), Getter); templ->SetLazyDataProperty(v8_str("foo2"), Getter, v8::Local(), v8::PropertyAttribute::None, v8::SideEffectType::kHasNoSideEffect); v8::Local obj = templ->NewInstance(env.local()).ToLocalChecked(); CHECK(env->Global()->Set(env.local(), v8_str("obj"), obj).FromJust()); CHECK(v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo"), true).IsEmpty()); v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo2"), true).ToLocalChecked(); // Check that setter is not whitelisted. v8::TryCatch try_catch(isolate); CHECK(v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo2 = 1"), true) .IsEmpty()); CHECK(try_catch.HasCaught()); CHECK_NE(1, v8::debug::EvaluateGlobal(isolate, v8_str("obj.foo2"), false) .ToLocalChecked() ->Int32Value(env.local()) .FromJust()); }