Upload Kmake

This commit is contained in:
Gorochu
2026-05-26 23:36:42 -07:00
parent ba051b2f74
commit 555ec72358
41615 changed files with 13344630 additions and 1 deletions

View File

@ -0,0 +1,640 @@
// Copyright 2017 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-context.h"
#include "include/v8-function.h"
#include "include/v8-isolate.h"
#include "include/v8-local-handle.h"
#include "include/v8-primitive.h"
#include "include/v8-script.h"
#include "include/v8-template.h"
#include "src/debug/debug.h"
#include "test/unittests/test-utils.h"
#include "testing/gmock-support.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
using testing::IsInt32;
using testing::IsString;
int32_t g_cross_context_int = 0;
bool g_expect_interceptor_call = false;
class AccessCheckTest : public TestWithIsolate {
public:
void CheckCanRunScriptInContext(Local<Context> context) {
HandleScope handle_scope(isolate());
Context::Scope context_scope(context);
g_expect_interceptor_call = false;
g_cross_context_int = 0;
// Running script in this context should work.
RunJS("this.foo = 42; this[23] = true;");
RunJS("this.cross_context_int = 23");
CHECK_EQ(g_cross_context_int, 23);
EXPECT_THAT(RunJS("this.cross_context_int"), IsInt32(23));
}
void CheckCrossContextAccess(Local<Context> accessing_context,
Local<Object> accessed_object) {
HandleScope handle_scope(isolate());
accessing_context->Global()
->Set(accessing_context, NewString("other"), accessed_object)
.FromJust();
Context::Scope context_scope(accessing_context);
g_expect_interceptor_call = true;
g_cross_context_int = 23;
{
TryCatch try_catch(isolate());
CHECK(TryRunJS("this.other.foo").IsEmpty());
}
{
TryCatch try_catch(isolate());
CHECK(TryRunJS("this.other[23]").IsEmpty());
}
// Intercepted properties are accessible, however.
EXPECT_THAT(RunJS("this.other.cross_context_int"), IsInt32(23));
RunJS("this.other.cross_context_int = 42");
EXPECT_THAT(RunJS("this.other[7]"), IsInt32(42));
EXPECT_THAT(RunJS("JSON.stringify(Object.getOwnPropertyNames(this.other))"),
IsString("[\"7\",\"cross_context_int\"]"));
}
void CheckCrossContextAccessWithException(Local<Context> accessing_context,
Local<Object> accessed_object) {
HandleScope handle_scope(isolate());
accessing_context->Global()
->Set(accessing_context, NewString("other"), accessed_object)
.FromJust();
Context::Scope context_scope(accessing_context);
{
TryCatch try_catch(isolate());
TryRunJS("this.other.should_throw");
CHECK(try_catch.HasCaught());
CHECK(try_catch.Exception()->IsString());
CHECK(NewString("exception")
->Equals(accessing_context, try_catch.Exception())
.FromJust());
}
{
TryCatch try_catch(isolate());
TryRunJS("this.other.should_throw = 8");
CHECK(try_catch.HasCaught());
CHECK(try_catch.Exception()->IsString());
CHECK(NewString("exception")
->Equals(accessing_context, try_catch.Exception())
.FromJust());
}
{
TryCatch try_catch(isolate());
TryRunJS("this.other[42]");
CHECK(try_catch.HasCaught());
CHECK(try_catch.Exception()->IsString());
CHECK(NewString("exception")
->Equals(accessing_context, try_catch.Exception())
.FromJust());
}
{
TryCatch try_catch(isolate());
TryRunJS("this.other[42] = 8");
CHECK(try_catch.HasCaught());
CHECK(try_catch.Exception()->IsString());
CHECK(NewString("exception")
->Equals(accessing_context, try_catch.Exception())
.FromJust());
}
}
};
namespace {
inline v8::Local<v8::String> v8_str(const char* x) {
return v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), x).ToLocalChecked();
}
inline v8::Local<v8::String> v8_str(v8::Isolate* isolate, const char* x) {
return v8::String::NewFromUtf8(isolate, x).ToLocalChecked();
}
bool AccessCheck(Local<Context> accessing_context,
Local<Object> accessed_object, Local<Value> data) {
return false;
}
MaybeLocal<Value> CompileRun(Isolate* isolate, const char* source) {
Local<String> source_string = v8_str(isolate, source);
Local<Context> context = isolate->GetCurrentContext();
Local<Script> script =
Script::Compile(context, source_string).ToLocalChecked();
return script->Run(context);
}
} // namespace
TEST_F(AccessCheckTest, GetOwnPropertyDescriptor) {
isolate()->SetFailedAccessCheckCallbackFunction(
[](v8::Local<v8::Object> host, v8::AccessType type,
v8::Local<v8::Value> data) {});
Local<ObjectTemplate> global_template = ObjectTemplate::New(isolate());
global_template->SetAccessCheckCallback(AccessCheck);
Local<FunctionTemplate> getter_template = FunctionTemplate::New(
isolate(), [](const FunctionCallbackInfo<Value>& info) { FAIL(); });
getter_template->SetAcceptAnyReceiver(false);
Local<FunctionTemplate> setter_template = FunctionTemplate::New(
isolate(), [](const FunctionCallbackInfo<v8::Value>& info) { FAIL(); });
setter_template->SetAcceptAnyReceiver(false);
global_template->SetAccessorProperty(NewString("property"), getter_template,
setter_template);
Local<Context> target_context =
Context::New(isolate(), nullptr, global_template);
Local<Context> accessing_context =
Context::New(isolate(), nullptr, global_template);
accessing_context->Global()
->Set(accessing_context, NewString("other"), target_context->Global())
.FromJust();
Context::Scope context_scope(accessing_context);
Local<String> no_access_str = NewString("no access");
Local<Value> result;
result = CompileRun(isolate(),
"var m = null; "
"try {"
" Object.getOwnPropertyDescriptor(this, 'property')"
" .get.call(other);"
"} catch(e) {"
" m = e.message;"
"};"
"m")
.ToLocalChecked();
EXPECT_TRUE(no_access_str->Equals(accessing_context, result).FromJust());
result = CompileRun(isolate(),
"var m = null; "
"try {"
" Object.getOwnPropertyDescriptor(this, 'property')"
" .set.call(other, 42);"
"} catch(e) {"
" m = e.message;"
"};"
"m")
.ToLocalChecked();
EXPECT_TRUE(no_access_str->Equals(accessing_context, result).FromJust());
}
class AccessRegressionTest : public AccessCheckTest {
protected:
i::DirectHandle<i::JSFunction> RetrieveFunctionFrom(Local<Context> context,
const char* script) {
Context::Scope context_scope(context);
Local<Value> getter = CompileRun(isolate(), script).ToLocalChecked();
EXPECT_TRUE(getter->IsFunction());
i::DirectHandle<i::JSReceiver> r =
Utils::OpenDirectHandle(*Local<Function>::Cast(getter));
EXPECT_TRUE(IsJSFunction(*r));
return i::Cast<i::JSFunction>(r);
}
};
TEST_F(AccessRegressionTest,
InstantiatedLazyAccessorPairsHaveCorrectNativeContext) {
// The setup creates two contexts and sets an object created
// in context 1 on the global of context 2.
// The object has an accessor pair {property}. Accessing the
// property descriptor of {property} causes instantiation of the
// accessor pair. The test checks that the access pair has the
// correct native context.
Local<FunctionTemplate> getter_template = FunctionTemplate::New(
isolate(), [](const FunctionCallbackInfo<Value>&) { FAIL(); });
Local<FunctionTemplate> setter_template = FunctionTemplate::New(
isolate(), [](const FunctionCallbackInfo<v8::Value>&) { FAIL(); });
Local<ObjectTemplate> object_template = ObjectTemplate::New(isolate());
object_template->SetAccessorProperty(NewString("property"), getter_template,
setter_template);
Local<Context> context1 = Context::New(isolate(), nullptr);
Local<Context> context2 = Context::New(isolate(), nullptr);
Local<Object> object =
object_template->NewInstance(context1).ToLocalChecked();
context2->Global()
->Set(context2, NewString("object_from_context1"), object)
.Check();
i::DirectHandle<i::JSFunction> getter = RetrieveFunctionFrom(
context2,
"Object.getOwnPropertyDescriptor(object_from_context1, 'property').get");
ASSERT_EQ(getter->native_context(), *Utils::OpenDirectHandle(*context1));
}
// Regression test for https://crbug.com/986063.
TEST_F(AccessRegressionTest,
InstantiatedLazyAccessorPairsHaveCorrectNativeContextDebug) {
// The setup creates two contexts and installs an object "object"
// on the global this for each context.
// The object consists of:
// - an accessor pair "property".
// - a normal function "breakfn".
//
// The test sets a break point on {object.breakfn} in the first context.
// This forces instantiation of the JSFunction for the {object.property}
// accessor pair. The test verifies afterwards that the respective
// JSFunction of the getter have the correct native context.
Local<FunctionTemplate> getter_template = FunctionTemplate::New(
isolate(), [](const FunctionCallbackInfo<Value>&) { FAIL(); });
Local<FunctionTemplate> setter_template = FunctionTemplate::New(
isolate(), [](const FunctionCallbackInfo<v8::Value>&) { FAIL(); });
Local<FunctionTemplate> break_template = FunctionTemplate::New(
isolate(), [](const FunctionCallbackInfo<v8::Value>&) { FAIL(); });
Local<Context> context1 = Context::New(isolate(), nullptr);
Local<Context> context2 = Context::New(isolate(), nullptr);
Local<ObjectTemplate> object_template = ObjectTemplate::New(isolate());
object_template->Set(isolate(), "breakfn", break_template);
object_template->SetAccessorProperty(NewString("property"), getter_template,
setter_template);
Local<Object> object1 =
object_template->NewInstance(context1).ToLocalChecked();
EXPECT_TRUE(
context1->Global()->Set(context1, NewString("object"), object1).IsJust());
Local<Object> object2 =
object_template->NewInstance(context2).ToLocalChecked();
EXPECT_TRUE(
context2->Global()->Set(context2, NewString("object"), object2).IsJust());
// Force instantiation of the JSFunction for the getter and setter
// of {object.property} by setting a break point on {object.breakfn}
{
Context::Scope context_scope(context1);
i::Isolate* iso = reinterpret_cast<i::Isolate*>(isolate());
i::DirectHandle<i::JSFunction> break_fn =
RetrieveFunctionFrom(context1, "object.breakfn");
int id;
iso->debug()->SetBreakpointForFunction(i::handle(break_fn->shared(), iso),
iso->factory()->empty_string(), &id);
}
i::DirectHandle<i::JSFunction> getter_c1 = RetrieveFunctionFrom(
context1, "Object.getOwnPropertyDescriptor(object, 'property').get");
i::DirectHandle<i::JSFunction> getter_c2 = RetrieveFunctionFrom(
context2, "Object.getOwnPropertyDescriptor(object, 'property').get");
ASSERT_EQ(getter_c1->native_context(), *Utils::OpenDirectHandle(*context1));
ASSERT_EQ(getter_c2->native_context(), *Utils::OpenDirectHandle(*context2));
}
v8::Intercepted NamedGetter(Local<Name> property,
const PropertyCallbackInfo<Value>& info) {
CHECK(g_expect_interceptor_call);
Isolate* isolate = info.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
if (!property->Equals(context, v8_str("cross_context_int")).FromJust()) {
return v8::Intercepted::kNo;
}
info.GetReturnValue().Set(g_cross_context_int);
return v8::Intercepted::kYes;
}
v8::Intercepted NamedSetter(Local<Name> property, Local<Value> value,
const PropertyCallbackInfo<void>& info) {
CHECK(g_expect_interceptor_call);
Isolate* isolate = info.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
if (!property->Equals(context, v8_str("cross_context_int")).FromJust()) {
return v8::Intercepted::kNo;
}
if (value->IsInt32()) {
g_cross_context_int = value->ToInt32(context).ToLocalChecked()->Value();
}
return v8::Intercepted::kYes;
}
v8::Intercepted NamedQuery(Local<Name> property,
const PropertyCallbackInfo<Integer>& info) {
CHECK(g_expect_interceptor_call);
Isolate* isolate = info.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
if (!property->Equals(context, v8_str("cross_context_int")).FromJust()) {
return v8::Intercepted::kNo;
}
info.GetReturnValue().Set(DontDelete);
return v8::Intercepted::kYes;
}
v8::Intercepted NamedDeleter(Local<Name> property,
const PropertyCallbackInfo<Boolean>& info) {
CHECK(g_expect_interceptor_call);
Isolate* isolate = info.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
if (!property->Equals(context, v8_str("cross_context_int")).FromJust()) {
return v8::Intercepted::kNo;
}
info.GetReturnValue().Set(false);
return v8::Intercepted::kYes;
}
void NamedEnumerator(const PropertyCallbackInfo<Array>& info) {
CHECK(g_expect_interceptor_call);
Isolate* isolate = info.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
Local<Array> names = Array::New(isolate, 1);
names->Set(context, 0, v8_str("cross_context_int")).FromJust();
info.GetReturnValue().Set(names);
}
v8::Intercepted IndexedGetter(uint32_t index,
const PropertyCallbackInfo<Value>& info) {
CHECK(g_expect_interceptor_call);
if (index == 7) {
info.GetReturnValue().Set(g_cross_context_int);
return v8::Intercepted::kYes;
}
return v8::Intercepted::kNo;
}
v8::Intercepted IndexedSetter(uint32_t index, Local<Value> value,
const PropertyCallbackInfo<void>& info) {
CHECK(g_expect_interceptor_call);
if (index == 7) {
Isolate* isolate = info.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
if (value->IsInt32()) {
g_cross_context_int = value->ToInt32(context).ToLocalChecked()->Value();
}
return v8::Intercepted::kYes;
}
return v8::Intercepted::kNo;
}
v8::Intercepted IndexedQuery(uint32_t index,
const PropertyCallbackInfo<Integer>& info) {
CHECK(g_expect_interceptor_call);
if (index == 7) {
info.GetReturnValue().Set(DontDelete);
return v8::Intercepted::kYes;
}
return v8::Intercepted::kNo;
}
v8::Intercepted IndexedDeleter(uint32_t index,
const PropertyCallbackInfo<Boolean>& info) {
CHECK(g_expect_interceptor_call);
if (index == 7) {
info.GetReturnValue().Set(false);
return v8::Intercepted::kYes;
}
return v8::Intercepted::kNo;
}
void IndexedEnumerator(const PropertyCallbackInfo<Array>& info) {
CHECK(g_expect_interceptor_call);
Isolate* isolate = info.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
Local<Array> names = Array::New(isolate, 1);
names->Set(context, 0, v8_str(isolate, "7")).FromJust();
info.GetReturnValue().Set(names);
}
v8::Intercepted MethodGetter(Local<Name> property,
const PropertyCallbackInfo<Value>& info) {
Isolate* isolate = info.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
Local<External> data = info.Data().As<External>();
Local<FunctionTemplate>& function_template =
*reinterpret_cast<Local<FunctionTemplate>*>(data->Value());
info.GetReturnValue().Set(
function_template->GetFunction(context).ToLocalChecked());
return v8::Intercepted::kYes;
}
void MethodCallback(const FunctionCallbackInfo<Value>& info) {
info.GetReturnValue().Set(8);
}
v8::Intercepted NamedGetterThrowsException(
Local<Name> property, const PropertyCallbackInfo<Value>& info) {
Isolate* isolate = info.GetIsolate();
isolate->ThrowException(v8_str(isolate, "exception"));
return v8::Intercepted::kYes;
}
v8::Intercepted NamedSetterThrowsException(
Local<Name> property, Local<Value> value,
const PropertyCallbackInfo<void>& info) {
Isolate* isolate = info.GetIsolate();
isolate->ThrowException(v8_str(isolate, "exception"));
return v8::Intercepted::kYes;
}
v8::Intercepted IndexedGetterThrowsException(
uint32_t index, const PropertyCallbackInfo<Value>& info) {
Isolate* isolate = info.GetIsolate();
isolate->ThrowException(v8_str(isolate, "exception"));
return v8::Intercepted::kYes;
}
v8::Intercepted IndexedSetterThrowsException(
uint32_t index, Local<Value> value,
const PropertyCallbackInfo<void>& info) {
Isolate* isolate = info.GetIsolate();
isolate->ThrowException(v8_str(isolate, "exception"));
return v8::Intercepted::kYes;
}
void GetCrossContextInt(Local<Name> property,
const PropertyCallbackInfo<Value>& info) {
CHECK(!g_expect_interceptor_call);
info.GetReturnValue().Set(g_cross_context_int);
}
void SetCrossContextInt(Local<Name> property, Local<Value> value,
const PropertyCallbackInfo<void>& info) {
CHECK(!g_expect_interceptor_call);
Isolate* isolate = info.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
if (value->IsInt32()) {
g_cross_context_int = value->ToInt32(context).ToLocalChecked()->Value();
}
}
void Return42(Local<String> property, const PropertyCallbackInfo<Value>& info) {
info.GetReturnValue().Set(42);
}
void Ctor(const FunctionCallbackInfo<Value>& info) {
CHECK(info.IsConstructCall());
}
TEST_F(AccessCheckTest, AccessCheckWithInterceptor) {
Local<ObjectTemplate> global_template = ObjectTemplate::New(isolate());
global_template->SetAccessCheckCallbackAndHandler(
AccessCheck,
NamedPropertyHandlerConfiguration(NamedGetter, NamedSetter, NamedQuery,
NamedDeleter, NamedEnumerator),
IndexedPropertyHandlerConfiguration(IndexedGetter, IndexedSetter,
IndexedQuery, IndexedDeleter,
IndexedEnumerator));
global_template->SetNativeDataProperty(
NewString("cross_context_int"), GetCrossContextInt, SetCrossContextInt);
Local<Context> context0 = Context::New(isolate(), nullptr, global_template);
CheckCanRunScriptInContext(context0);
// Create another context.
Local<Context> context1 = Context::New(isolate(), nullptr, global_template);
CheckCrossContextAccess(context1, context0->Global());
}
TEST_F(AccessCheckTest, CallFunctionWithRemoteContextReceiver) {
HandleScope scope(isolate());
Local<FunctionTemplate> global_template = FunctionTemplate::New(isolate());
Local<Signature> signature = Signature::New(isolate(), global_template);
Local<FunctionTemplate> function_template = FunctionTemplate::New(
isolate(), MethodCallback, External::New(isolate(), &function_template),
signature);
global_template->InstanceTemplate()->SetAccessCheckCallbackAndHandler(
AccessCheck,
NamedPropertyHandlerConfiguration(
MethodGetter, nullptr, nullptr, nullptr, nullptr,
External::New(isolate(), &function_template)),
IndexedPropertyHandlerConfiguration());
Local<Object> accessed_object =
Context::NewRemoteContext(isolate(), global_template->InstanceTemplate())
.ToLocalChecked();
Local<Context> accessing_context =
Context::New(isolate(), nullptr, global_template->InstanceTemplate());
HandleScope handle_scope(isolate());
accessing_context->Global()
->Set(accessing_context, NewString("other"), accessed_object)
.FromJust();
Context::Scope context_scope(accessing_context);
{
TryCatch try_catch(isolate());
EXPECT_THAT(RunJS("this.other.method()"), IsInt32(8));
CHECK(!try_catch.HasCaught());
}
}
TEST_F(AccessCheckTest, AccessCheckWithExceptionThrowingInterceptor) {
isolate()->SetFailedAccessCheckCallbackFunction(
[](Local<Object> target, AccessType type, Local<Value> data) {
UNREACHABLE(); // This should never be called.
});
Local<ObjectTemplate> global_template = ObjectTemplate::New(isolate());
global_template->SetAccessCheckCallbackAndHandler(
AccessCheck,
NamedPropertyHandlerConfiguration(NamedGetterThrowsException,
NamedSetterThrowsException),
IndexedPropertyHandlerConfiguration(IndexedGetterThrowsException,
IndexedSetterThrowsException));
// Create two contexts.
Local<Context> context0 = Context::New(isolate(), nullptr, global_template);
Local<Context> context1 = Context::New(isolate(), nullptr, global_template);
CheckCrossContextAccessWithException(context1, context0->Global());
}
TEST_F(AccessCheckTest, NewRemoteContext) {
Local<ObjectTemplate> global_template = ObjectTemplate::New(isolate());
global_template->SetAccessCheckCallbackAndHandler(
AccessCheck,
NamedPropertyHandlerConfiguration(NamedGetter, NamedSetter, NamedQuery,
NamedDeleter, NamedEnumerator),
IndexedPropertyHandlerConfiguration(IndexedGetter, IndexedSetter,
IndexedQuery, IndexedDeleter,
IndexedEnumerator));
global_template->SetNativeDataProperty(
NewString("cross_context_int"), GetCrossContextInt, SetCrossContextInt);
Local<Object> global0 =
Context::NewRemoteContext(isolate(), global_template).ToLocalChecked();
// Create a real context.
{
HandleScope other_scope(isolate());
Local<Context> context1 = Context::New(isolate(), nullptr, global_template);
CheckCrossContextAccess(context1, global0);
}
// Create a context using the detached global.
{
HandleScope other_scope(isolate());
Local<Context> context2 =
Context::New(isolate(), nullptr, global_template, global0);
CheckCanRunScriptInContext(context2);
}
// Turn a regular context into a remote context.
{
HandleScope other_scope(isolate());
Local<Context> context3 = Context::New(isolate(), nullptr, global_template);
CheckCanRunScriptInContext(context3);
// Turn the global object into a remote context, and try to access it.
Local<Object> context3_global = context3->Global();
context3->DetachGlobal();
Local<Object> global3 =
Context::NewRemoteContext(isolate(), global_template, context3_global)
.ToLocalChecked();
Local<Context> context4 = Context::New(isolate(), nullptr, global_template);
CheckCrossContextAccess(context4, global3);
// Turn it back into a regular context.
Local<Context> context5 =
Context::New(isolate(), nullptr, global_template, global3);
CheckCanRunScriptInContext(context5);
}
}
TEST_F(AccessCheckTest, NewRemoteInstance) {
Local<FunctionTemplate> tmpl = FunctionTemplate::New(isolate(), Ctor);
Local<ObjectTemplate> instance = tmpl->InstanceTemplate();
instance->SetAccessCheckCallbackAndHandler(
AccessCheck,
NamedPropertyHandlerConfiguration(NamedGetter, NamedSetter, NamedQuery,
NamedDeleter, NamedEnumerator),
IndexedPropertyHandlerConfiguration(IndexedGetter, IndexedSetter,
IndexedQuery, IndexedDeleter,
IndexedEnumerator));
Local<Object> obj = tmpl->NewRemoteInstance().ToLocalChecked();
Local<Context> context = Context::New(isolate());
CheckCrossContextAccess(context, obj);
}
} // namespace v8

View File

@ -0,0 +1,872 @@
// 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 "include/v8-context.h"
#include "include/v8-function.h"
#include "include/v8-isolate.h"
#include "include/v8-local-handle.h"
#include "include/v8-object.h"
#include "include/v8-template.h"
#include "src/api/api-inl.h"
#include "src/debug/debug.h"
#include "src/execution/isolate.h"
#include "src/objects/objects-inl.h"
#include "test/unittests/test-utils.h"
#include "testing/gmock-support.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace i = v8::internal;
using testing::IsInt32;
using testing::IsString;
using testing::IsUndefined;
using AccessorTest = v8::TestWithContext;
// The goal is to avoid the callback.
static void UnreachableCallback(
const v8::FunctionCallbackInfo<v8::Value>& info) {
UNREACHABLE();
}
TEST_F(AccessorTest, CachedAccessor) {
// TurboFan support for fast accessors is not implemented; turbofanned
// code uses the slow accessor which breaks this test's expectations.
i::v8_flags.always_turbofan = false;
v8::Isolate* isolate = context()->GetIsolate();
v8::HandleScope scope(isolate);
// Create 'foo' class, with a hidden property.
v8::Local<v8::ObjectTemplate> foo = v8::ObjectTemplate::New(isolate);
v8::Local<v8::Private> priv =
v8::Private::ForApi(isolate, NewString("Foo#draft"));
foo->SetAccessorProperty(
NewString("draft"),
v8::FunctionTemplate::NewWithCache(isolate, UnreachableCallback, priv,
v8::Local<v8::Value>()));
// Create 'obj', instance of 'foo'.
v8::Local<v8::Object> obj = foo->NewInstance(context()).ToLocalChecked();
// Install the private property on the instance.
CHECK(obj->SetPrivate(isolate->GetCurrentContext(), priv,
v8::Undefined(isolate))
.FromJust());
CHECK(context()->Global()->Set(context(), NewString("obj"), obj).FromJust());
// Access cached accessor.
EXPECT_THAT(RunJS("obj.draft"), IsUndefined());
// Set hidden property.
CHECK(obj->SetPrivate(isolate->GetCurrentContext(), priv,
NewString("Shhh, I'm private!"))
.FromJust());
EXPECT_THAT(RunJS("obj.draft"), IsString("Shhh, I'm private!"));
// Stress the accessor to use the IC.
EXPECT_THAT(RunJS("var result = '';"
"for (var i = 0; i < 10; ++i) { "
" result = obj.draft; "
"} "
"result; "),
IsString("Shhh, I'm private!"));
}
TEST_F(AccessorTest, CachedAccessorTurboFan) {
i::v8_flags.allow_natives_syntax = true;
// i::v8_flags.always_turbofan = false;
v8::Isolate* isolate = context()->GetIsolate();
v8::HandleScope scope(isolate);
// Create 'foo' class, with a hidden property.
v8::Local<v8::ObjectTemplate> foo = v8::ObjectTemplate::New(isolate);
v8::Local<v8::Private> priv =
v8::Private::ForApi(isolate, NewString("Foo#draft"));
// Install the private property on the template.
// foo->SetPrivate(priv, v8::Undefined(isolate));
foo->SetAccessorProperty(
NewString("draft"),
v8::FunctionTemplate::NewWithCache(isolate, UnreachableCallback, priv,
v8::Local<v8::Value>()));
// Create 'obj', instance of 'foo'.
v8::Local<v8::Object> obj = foo->NewInstance(context()).ToLocalChecked();
// Install the private property on the instance.
CHECK(obj->SetPrivate(isolate->GetCurrentContext(), priv,
v8::Undefined(isolate))
.FromJust());
CHECK(context()->Global()->Set(context(), NewString("obj"), obj).FromJust());
// Access surrogate accessor.
EXPECT_THAT(RunJS("obj.draft"), IsUndefined());
// Set hidden property.
CHECK(obj->SetPrivate(context(), priv, v8::Integer::New(isolate, 123))
.FromJust());
// Test ICs.
RunJS(
"function f() {"
" var x;"
" for (var i = 0; i < 100; i++) {"
" x = obj.draft;"
" }"
" return x;"
"};"
"%PrepareFunctionForOptimization(f);");
EXPECT_THAT(RunJS("f()"), IsInt32(123));
// Reset hidden property.
CHECK(obj->SetPrivate(context(), priv, v8::Integer::New(isolate, 456))
.FromJust());
// Test TurboFan.
RunJS("%OptimizeFunctionOnNextCall(f);");
EXPECT_THAT(RunJS("f()"), IsInt32(456));
CHECK(obj->SetPrivate(context(), priv, v8::Integer::New(isolate, 456))
.FromJust());
// Test non-global ICs.
RunJS(
"function g() {"
" var x = obj;"
" var r = 0;"
" for (var i = 0; i < 100; i++) {"
" r = x.draft;"
" }"
" return r;"
"};"
"%PrepareFunctionForOptimization(g);");
EXPECT_THAT(RunJS("g()"), IsInt32(456));
// Reset hidden property.
CHECK(obj->SetPrivate(context(), priv, v8::Integer::New(isolate, 789))
.FromJust());
// Test non-global access in TurboFan.
RunJS("%OptimizeFunctionOnNextCall(g);");
EXPECT_THAT(RunJS("g()"), IsInt32(789));
}
TEST_F(AccessorTest, CachedAccessorOnGlobalObject) {
i::v8_flags.allow_natives_syntax = true;
v8::HandleScope scope(isolate());
v8::Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(isolate());
v8::Local<v8::ObjectTemplate> object_template = templ->InstanceTemplate();
v8::Local<v8::Private> priv =
v8::Private::ForApi(isolate(), NewString("Foo#draft"));
object_template->SetAccessorProperty(
NewString("draft"),
v8::FunctionTemplate::NewWithCache(isolate(), UnreachableCallback, priv,
v8::Local<v8::Value>()));
v8::Local<v8::Context> ctx =
v8::Context::New(isolate(), nullptr, object_template);
v8::Local<v8::Object> 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.
EXPECT_THAT(RunJS("draft"), IsUndefined());
// Set hidden property.
CHECK(obj->SetPrivate(context(), priv, v8::Integer::New(isolate(), 123))
.FromJust());
// Test ICs.
RunJS(
"function f() {"
" var x;"
" for (var i = 0; i < 100; i++) {"
" x = draft;"
" }"
" return x;"
"}"
"%PrepareFunctionForOptimization(f);");
EXPECT_THAT(RunJS("f()"), IsInt32(123));
// Reset hidden property.
CHECK(obj->SetPrivate(context(), priv, v8::Integer::New(isolate(), 456))
.FromJust());
// Test TurboFan.
RunJS("%OptimizeFunctionOnNextCall(f);");
EXPECT_THAT(RunJS("f()"), IsInt32(456));
CHECK(obj->SetPrivate(context(), priv, v8::Integer::New(isolate(), 456))
.FromJust());
// Test non-global ICs.
RunJS(
"var x = this;"
"function g() {"
" var r = 0;"
" for (var i = 0; i < 100; i++) {"
" r = x.draft;"
" }"
" return r;"
"}"
"%PrepareFunctionForOptimization(g);");
EXPECT_THAT(RunJS("g()"), IsInt32(456));
// Reset hidden property.
CHECK(obj->SetPrivate(context(), priv, v8::Integer::New(isolate(), 789))
.FromJust());
// Test non-global access in TurboFan.
RunJS("%OptimizeFunctionOnNextCall(g);");
EXPECT_THAT(RunJS("g()"), IsInt32(789));
}
}
namespace {
// Getter return value should be non-null to trigger lazy property paths.
void Getter(v8::Local<v8::Name> name,
const v8::PropertyCallbackInfo<v8::Value>& info) {
info.GetReturnValue().Set(
v8::String::NewFromUtf8(info.GetIsolate(), "return value")
.ToLocalChecked());
}
void StringGetter(v8::Local<v8::Name> name,
const v8::PropertyCallbackInfo<v8::Value>& info) {}
int set_accessor_call_count = 0;
void Setter(v8::Local<v8::Name> name, v8::Local<v8::Value> value,
const v8::PropertyCallbackInfo<void>& info) {
set_accessor_call_count++;
}
void EmptyCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {}
} // namespace
// Re-declaration of non-configurable accessors should throw.
TEST_F(AccessorTest, RedeclareAccessor) {
v8::HandleScope scope(isolate());
v8::Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(isolate());
v8::Local<v8::ObjectTemplate> object_template = templ->InstanceTemplate();
object_template->SetNativeDataProperty(NewString("foo"), nullptr, Setter,
v8::Local<v8::Value>(),
v8::PropertyAttribute::DontDelete);
v8::Local<v8::Context> ctx =
v8::Context::New(isolate(), nullptr, object_template);
// Declare function.
v8::Local<v8::String> code = NewString("function foo() {};");
v8::TryCatch try_catch(isolate());
v8::Script::Compile(ctx, code).ToLocalChecked()->Run(ctx).IsEmpty();
CHECK(try_catch.HasCaught());
}
class NoopDelegate : public v8::debug::DebugDelegate {};
static void CheckSideEffectFreeAccesses(v8::Isolate* isolate,
v8::Local<v8::String> call_getter,
v8::Local<v8::String> call_setter) {
const int kIterationsCountForICProgression = 20;
v8::Local<v8::Context> context = isolate->GetCurrentContext();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
NoopDelegate delegate;
i_isolate->debug()->SetDebugDelegate(&delegate);
v8::Local<v8::Script> func =
v8::Script::Compile(context, call_getter).ToLocalChecked();
// Check getter. Run enough number of times to ensure IC creates data handler.
for (int i = 0; i < kIterationsCountForICProgression; i++) {
v8::TryCatch try_catch(isolate);
CHECK(EvaluateGlobal(
isolate, call_getter,
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect,
true)
.IsEmpty());
CHECK(try_catch.HasCaught());
// Ensure that IC state progresses.
CHECK(!func->Run(context).IsEmpty());
}
func = v8::Script::Compile(context, call_setter).ToLocalChecked();
// Check setter. Run enough number of times to ensure IC creates data handler.
for (int i = 0; i < kIterationsCountForICProgression; i++) {
v8::TryCatch try_catch(isolate);
CHECK(EvaluateGlobal(
isolate, call_setter,
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect,
true)
.IsEmpty());
CHECK(try_catch.HasCaught());
// Ensure that IC state progresses.
CHECK(!func->Run(context).IsEmpty());
}
}
TEST_F(AccessorTest, AccessorsWithSideEffects) {
v8::HandleScope scope(isolate());
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate());
NoopDelegate delegate;
i_isolate->debug()->SetDebugDelegate(&delegate);
v8::Local<v8::ObjectTemplate> templ = v8::ObjectTemplate::New(isolate());
v8::Local<v8::Object> obj = templ->NewInstance(context()).ToLocalChecked();
CHECK(context()->Global()->Set(context(), NewString("obj"), obj).FromJust());
v8::Local<v8::FunctionTemplate> templ_with_sideffect =
v8::FunctionTemplate::New(
isolate(), EmptyCallback, v8::Local<v8::Value>(),
v8::Local<v8::Signature>(), 0, v8::ConstructorBehavior::kAllow,
v8::SideEffectType::kHasSideEffect);
v8::Local<v8::FunctionTemplate> templ_no_sideffect =
v8::FunctionTemplate::New(
isolate(), EmptyCallback, v8::Local<v8::Value>(),
v8::Local<v8::Signature>(), 0, v8::ConstructorBehavior::kAllow,
v8::SideEffectType::kHasNoSideEffect);
// Install non-native properties with side effects
obj->SetAccessorProperty(
NewString("get"),
templ_with_sideffect->GetFunction(context()).ToLocalChecked(), {},
v8::PropertyAttribute::None);
obj->SetAccessorProperty(
NewString("set"),
templ_no_sideffect->GetFunction(context()).ToLocalChecked(),
templ_with_sideffect->GetFunction(context()).ToLocalChecked(),
v8::PropertyAttribute::None);
RunJS(
"function callGetter() { obj.get; }"
"function callSetter() { obj.set = 123; }");
CheckSideEffectFreeAccesses(isolate(), NewString("callGetter()"),
NewString("callSetter()"));
}
TEST_F(AccessorTest, TemplateAccessorsWithSideEffects) {
v8::HandleScope scope(isolate());
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate());
NoopDelegate delegate;
i_isolate->debug()->SetDebugDelegate(&delegate);
v8::Local<v8::FunctionTemplate> templ_with_sideffect =
v8::FunctionTemplate::New(
isolate(), EmptyCallback, v8::Local<v8::Value>(),
v8::Local<v8::Signature>(), 0, v8::ConstructorBehavior::kAllow,
v8::SideEffectType::kHasSideEffect);
v8::Local<v8::FunctionTemplate> templ_no_sideffect =
v8::FunctionTemplate::New(
isolate(), EmptyCallback, v8::Local<v8::Value>(),
v8::Local<v8::Signature>(), 0, v8::ConstructorBehavior::kAllow,
v8::SideEffectType::kHasNoSideEffect);
v8::Local<v8::ObjectTemplate> templ = v8::ObjectTemplate::New(isolate());
templ->SetAccessorProperty(NewString("get"), templ_with_sideffect);
templ->SetAccessorProperty(NewString("set"), templ_no_sideffect,
templ_with_sideffect);
v8::Local<v8::Object> obj = templ->NewInstance(context()).ToLocalChecked();
CHECK(context()->Global()->Set(context(), NewString("obj"), obj).FromJust());
RunJS(
"function callGetter() { obj.get; }"
"function callSetter() { obj.set = 123; }");
CheckSideEffectFreeAccesses(isolate(), NewString("callGetter()"),
NewString("callSetter()"));
}
TEST_F(AccessorTest, NativeTemplateAccessorWithSideEffects) {
v8::HandleScope scope(isolate());
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate());
NoopDelegate delegate;
i_isolate->debug()->SetDebugDelegate(&delegate);
v8::Local<v8::ObjectTemplate> templ = v8::ObjectTemplate::New(isolate());
templ->SetNativeDataProperty(
NewString("get"), Getter, nullptr, v8::Local<v8::Value>(),
v8::PropertyAttribute::None, v8::SideEffectType::kHasSideEffect);
templ->SetNativeDataProperty(
NewString("set"), Getter, Setter, v8::Local<v8::Value>(),
v8::PropertyAttribute::None, v8::SideEffectType::kHasNoSideEffect,
v8::SideEffectType::kHasSideEffect);
v8::Local<v8::Object> obj = templ->NewInstance(context()).ToLocalChecked();
CHECK(context()->Global()->Set(context(), NewString("obj"), obj).FromJust());
RunJS(
"function callGetter() { obj.get; }"
"function callSetter() { obj.set = 123; }");
CheckSideEffectFreeAccesses(isolate(), NewString("callGetter()"),
NewString("callSetter()"));
}
TEST_F(AccessorTest, NativeAccessorsWithSideEffects) {
v8::HandleScope scope(isolate());
v8::Local<v8::ObjectTemplate> templ = v8::ObjectTemplate::New(isolate());
v8::Local<v8::Object> obj = templ->NewInstance(context()).ToLocalChecked();
CHECK(context()->Global()->Set(context(), NewString("obj"), obj).FromJust());
// Install native data property with side effects.
obj->SetNativeDataProperty(context(), NewString("get"), Getter, nullptr, {},
v8::PropertyAttribute::None,
v8::SideEffectType::kHasSideEffect)
.ToChecked();
obj->SetNativeDataProperty(context(), NewString("set"), Getter, Setter, {},
v8::PropertyAttribute::None,
v8::SideEffectType::kHasNoSideEffect,
v8::SideEffectType::kHasSideEffect)
.ToChecked();
RunJS(
"function callGetter() { obj.get; }"
"function callSetter() { obj.set = 123; }");
CheckSideEffectFreeAccesses(isolate(), NewString("callGetter()"),
NewString("callSetter()"));
}
// Accessors can be allowlisted as side-effect-free via SetNativeDataProperty.
TEST_F(AccessorTest, AccessorSetHasNoSideEffect) {
v8::HandleScope scope(isolate());
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate());
NoopDelegate delegate;
i_isolate->debug()->SetDebugDelegate(&delegate);
v8::Local<v8::ObjectTemplate> templ = v8::ObjectTemplate::New(isolate());
v8::Local<v8::Object> obj = templ->NewInstance(context()).ToLocalChecked();
CHECK(context()->Global()->Set(context(), NewString("obj"), obj).FromJust());
obj->SetNativeDataProperty(context(), NewString("foo"), Getter).ToChecked();
CHECK(v8::debug::EvaluateGlobal(
isolate(), NewString("obj.foo"),
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
.IsEmpty());
obj->SetNativeDataProperty(context(), NewString("foo"), Getter, nullptr, {},
v8::PropertyAttribute::None,
v8::SideEffectType::kHasNoSideEffect)
.ToChecked();
v8::debug::EvaluateGlobal(
isolate(), NewString("obj.foo"),
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
.ToLocalChecked();
// Check that setter is not allowlisted.
v8::TryCatch try_catch(isolate());
CHECK(v8::debug::EvaluateGlobal(
isolate(), NewString("obj.foo = 1"),
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
.IsEmpty());
CHECK(try_catch.HasCaught());
CHECK_NE(1, v8::debug::EvaluateGlobal(isolate(), NewString("obj.foo"),
v8::debug::EvaluateGlobalMode::kDefault)
.ToLocalChecked()
->Int32Value(context())
.FromJust());
CHECK_EQ(0, set_accessor_call_count);
}
// Set accessors can be allowlisted as side-effect-free via
// SetNativeDataProperty.
TEST_F(AccessorTest, SetNativeDataPropertySetSideEffectReceiverCheck1) {
v8::HandleScope scope(isolate());
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate());
NoopDelegate delegate;
i_isolate->debug()->SetDebugDelegate(&delegate);
v8::Local<v8::ObjectTemplate> templ = v8::ObjectTemplate::New(isolate());
v8::Local<v8::Object> obj = templ->NewInstance(context()).ToLocalChecked();
CHECK(context()->Global()->Set(context(), NewString("obj"), obj).FromJust());
obj->SetNativeDataProperty(context(), NewString("foo"), Getter, Setter, {},
v8::PropertyAttribute::None,
v8::SideEffectType::kHasNoSideEffect,
v8::SideEffectType::kHasSideEffectToReceiver)
.ToChecked();
CHECK(v8::debug::EvaluateGlobal(
isolate(), NewString("obj.foo"),
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
.ToLocalChecked()
->Equals(context(), NewString("return value"))
.FromJust());
v8::TryCatch try_catch(isolate());
CHECK(v8::debug::EvaluateGlobal(
isolate(), NewString("obj.foo = 1"),
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
.IsEmpty());
CHECK(try_catch.HasCaught());
CHECK_EQ(0, set_accessor_call_count);
}
static void ConstructCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
}
TEST_F(AccessorTest, SetNativeDataPropertySetSideEffectReceiverCheck2) {
v8::HandleScope scope(isolate());
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate());
NoopDelegate delegate;
i_isolate->debug()->SetDebugDelegate(&delegate);
v8::Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(
isolate(), ConstructCallback, v8::Local<v8::Value>(),
v8::Local<v8::Signature>(), 0, v8::ConstructorBehavior::kAllow,
v8::SideEffectType::kHasNoSideEffect);
templ->InstanceTemplate()->SetNativeDataProperty(
NewString("bar"), Getter, Setter, v8::Local<v8::Value>(),
v8::PropertyAttribute::None, v8::SideEffectType::kHasSideEffectToReceiver,
v8::SideEffectType::kHasSideEffectToReceiver);
CHECK(context()
->Global()
->Set(context(), NewString("f"),
templ->GetFunction(context()).ToLocalChecked())
.FromJust());
CHECK(v8::debug::EvaluateGlobal(
isolate(), NewString("new f().bar"),
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
.ToLocalChecked()
->Equals(context(), NewString("return value"))
.FromJust());
v8::debug::EvaluateGlobal(
isolate(), NewString("new f().bar = 1"),
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
.ToLocalChecked();
CHECK_EQ(1, set_accessor_call_count);
}
// Accessors can be allowlisted as side-effect-free via SetNativeDataProperty.
TEST_F(AccessorTest, AccessorSetNativeDataPropertyHasNoSideEffect) {
v8::HandleScope scope(isolate());
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate());
NoopDelegate delegate;
i_isolate->debug()->SetDebugDelegate(&delegate);
v8::Local<v8::ObjectTemplate> templ = v8::ObjectTemplate::New(isolate());
v8::Local<v8::Object> obj = templ->NewInstance(context()).ToLocalChecked();
CHECK(context()->Global()->Set(context(), NewString("obj"), obj).FromJust());
obj->SetNativeDataProperty(context(), NewString("foo"), Getter).ToChecked();
CHECK(v8::debug::EvaluateGlobal(
isolate(), NewString("obj.foo"),
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
.IsEmpty());
obj->SetNativeDataProperty(
context(), NewString("foo"), Getter, nullptr, v8::Local<v8::Value>(),
v8::PropertyAttribute::None, v8::SideEffectType::kHasNoSideEffect)
.ToChecked();
v8::debug::EvaluateGlobal(
isolate(), NewString("obj.foo"),
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
.ToLocalChecked();
// Check that setter is not allowlisted.
v8::TryCatch try_catch(isolate());
CHECK(v8::debug::EvaluateGlobal(
isolate(), NewString("obj.foo = 1"),
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
.IsEmpty());
CHECK(try_catch.HasCaught());
CHECK_NE(1, v8::debug::EvaluateGlobal(isolate(), NewString("obj.foo"),
v8::debug::EvaluateGlobalMode::kDefault)
.ToLocalChecked()
->Int32Value(context())
.FromJust());
}
// Accessors can be allowlisted as side-effect-free via SetLazyDataProperty.
TEST_F(AccessorTest, AccessorSetLazyDataPropertyHasNoSideEffect) {
v8::HandleScope scope(isolate());
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate());
NoopDelegate delegate;
i_isolate->debug()->SetDebugDelegate(&delegate);
v8::Local<v8::ObjectTemplate> templ = v8::ObjectTemplate::New(isolate());
v8::Local<v8::Object> obj = templ->NewInstance(context()).ToLocalChecked();
CHECK(context()->Global()->Set(context(), NewString("obj"), obj).FromJust());
obj->SetLazyDataProperty(context(), NewString("foo"), Getter).ToChecked();
CHECK(v8::debug::EvaluateGlobal(
isolate(), NewString("obj.foo"),
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
.IsEmpty());
obj->SetLazyDataProperty(context(), NewString("foo"), Getter,
v8::Local<v8::Value>(), v8::PropertyAttribute::None,
v8::SideEffectType::kHasNoSideEffect)
.ToChecked();
v8::debug::EvaluateGlobal(
isolate(), NewString("obj.foo"),
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
.ToLocalChecked();
// Check that setter is not allowlisted.
v8::TryCatch try_catch(isolate());
CHECK(v8::debug::EvaluateGlobal(
isolate(), NewString("obj.foo = 1"),
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
.IsEmpty());
CHECK(try_catch.HasCaught());
CHECK_NE(1, v8::debug::EvaluateGlobal(isolate(), NewString("obj.foo"),
v8::debug::EvaluateGlobalMode::kDefault)
.ToLocalChecked()
->Int32Value(context())
.FromJust());
}
TEST_F(AccessorTest, ObjectTemplateSetNativeDataPropertyHasNoSideEffect) {
v8::HandleScope scope(isolate());
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate());
NoopDelegate delegate;
i_isolate->debug()->SetDebugDelegate(&delegate);
v8::Local<v8::ObjectTemplate> templ = v8::ObjectTemplate::New(isolate());
templ->SetNativeDataProperty(NewString("foo"), StringGetter);
templ->SetNativeDataProperty(
NewString("foo2"), StringGetter, nullptr, v8::Local<v8::Value>(),
v8::PropertyAttribute::None, v8::SideEffectType::kHasNoSideEffect);
v8::Local<v8::Object> obj = templ->NewInstance(context()).ToLocalChecked();
CHECK(context()->Global()->Set(context(), NewString("obj"), obj).FromJust());
CHECK(v8::debug::EvaluateGlobal(
isolate(), NewString("obj.foo"),
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
.IsEmpty());
v8::debug::EvaluateGlobal(
isolate(), NewString("obj.foo2"),
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
.ToLocalChecked();
// Check that setter is not allowlisted.
v8::TryCatch try_catch(isolate());
CHECK(v8::debug::EvaluateGlobal(
isolate(), NewString("obj.foo2 = 1"),
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
.IsEmpty());
CHECK(try_catch.HasCaught());
CHECK_NE(1, v8::debug::EvaluateGlobal(isolate(), NewString("obj.foo2"),
v8::debug::EvaluateGlobalMode::kDefault)
.ToLocalChecked()
->Int32Value(context())
.FromJust());
}
TEST_F(AccessorTest, ObjectTemplateSetNativePropertyHasNoSideEffect) {
v8::HandleScope scope(isolate());
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate());
NoopDelegate delegate;
i_isolate->debug()->SetDebugDelegate(&delegate);
v8::Local<v8::ObjectTemplate> templ = v8::ObjectTemplate::New(isolate());
templ->SetNativeDataProperty(NewString("foo"), Getter);
templ->SetNativeDataProperty(NewString("foo2"), Getter, nullptr, {},
v8::PropertyAttribute::None,
v8::SideEffectType::kHasNoSideEffect);
v8::Local<v8::Object> obj = templ->NewInstance(context()).ToLocalChecked();
CHECK(context()->Global()->Set(context(), NewString("obj"), obj).FromJust());
CHECK(v8::debug::EvaluateGlobal(
isolate(), NewString("obj.foo"),
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
.IsEmpty());
v8::debug::EvaluateGlobal(
isolate(), NewString("obj.foo2"),
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
.ToLocalChecked();
// Check that setter is not allowlisted.
v8::TryCatch try_catch(isolate());
CHECK(v8::debug::EvaluateGlobal(
isolate(), NewString("obj.foo2 = 1"),
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
.IsEmpty());
CHECK(try_catch.HasCaught());
CHECK_NE(1, v8::debug::EvaluateGlobal(isolate(), NewString("obj.foo2"),
v8::debug::EvaluateGlobalMode::kDefault)
.ToLocalChecked()
->Int32Value(context())
.FromJust());
}
TEST_F(AccessorTest, ObjectTemplateSetLazyPropertyHasNoSideEffect) {
v8::HandleScope scope(isolate());
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate());
NoopDelegate delegate;
i_isolate->debug()->SetDebugDelegate(&delegate);
v8::Local<v8::ObjectTemplate> templ = v8::ObjectTemplate::New(isolate());
templ->SetLazyDataProperty(NewString("foo"), Getter);
templ->SetLazyDataProperty(NewString("foo2"), Getter, v8::Local<v8::Value>(),
v8::PropertyAttribute::None,
v8::SideEffectType::kHasNoSideEffect);
v8::Local<v8::Object> obj = templ->NewInstance(context()).ToLocalChecked();
CHECK(context()->Global()->Set(context(), NewString("obj"), obj).FromJust());
CHECK(v8::debug::EvaluateGlobal(
isolate(), NewString("obj.foo"),
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
.IsEmpty());
v8::debug::EvaluateGlobal(
isolate(), NewString("obj.foo2"),
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
.ToLocalChecked();
// Check that setter is not allowlisted.
v8::TryCatch try_catch(isolate());
CHECK(v8::debug::EvaluateGlobal(
isolate(), NewString("obj.foo2 = 1"),
v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
.IsEmpty());
CHECK(try_catch.HasCaught());
CHECK_NE(1, v8::debug::EvaluateGlobal(isolate(), NewString("obj.foo2"),
v8::debug::EvaluateGlobalMode::kDefault)
.ToLocalChecked()
->Int32Value(context())
.FromJust());
}
namespace {
void FunctionNativeGetter(v8::Local<v8::Name> property,
const v8::PropertyCallbackInfo<v8::Value>& info) {
info.GetIsolate()->ThrowError(
v8::String::NewFromUtf8(info.GetIsolate(), "side effect in getter")
.ToLocalChecked());
}
} // namespace
TEST_F(AccessorTest, BindFunctionTemplateSetNativeDataProperty) {
v8::HandleScope scope(isolate());
// Check that getter is called on Function.prototype.bind.
{
v8::Local<v8::FunctionTemplate> templ =
v8::FunctionTemplate::New(isolate());
templ->SetNativeDataProperty(NewString("name"), FunctionNativeGetter);
v8::Local<v8::Function> func =
templ->GetFunction(context()).ToLocalChecked();
CHECK(context()
->Global()
->Set(context(), NewString("func"), func)
.FromJust());
v8::TryCatch try_catch(isolate());
CHECK(TryRunJS("func.bind()").IsEmpty());
CHECK(try_catch.HasCaught());
}
// Check that getter is called on Function.prototype.bind.
{
v8::Local<v8::FunctionTemplate> templ =
v8::FunctionTemplate::New(isolate());
templ->SetNativeDataProperty(NewString("length"), FunctionNativeGetter);
v8::Local<v8::Function> func =
templ->GetFunction(context()).ToLocalChecked();
CHECK(context()
->Global()
->Set(context(), NewString("func"), func)
.FromJust());
v8::TryCatch try_catch(isolate());
CHECK(TryRunJS("func.bind()").IsEmpty());
CHECK(try_catch.HasCaught());
}
}
namespace {
v8::MaybeLocal<v8::Context> TestHostCreateShadowRealmContextCallback(
v8::Local<v8::Context> initiator_context) {
v8::Isolate* isolate = initiator_context->GetIsolate();
v8::Local<v8::FunctionTemplate> global_constructor =
v8::FunctionTemplate::New(isolate);
v8::Local<v8::ObjectTemplate> global_template =
global_constructor->InstanceTemplate();
// Check that getter is called on Function.prototype.bind.
global_template->SetNativeDataProperty(
v8::String::NewFromUtf8(isolate, "func1").ToLocalChecked(),
[](v8::Local<v8::Name> property,
const v8::PropertyCallbackInfo<v8::Value>& info) {
v8::Isolate* isolate = info.GetIsolate();
v8::Local<v8::FunctionTemplate> templ =
v8::FunctionTemplate::New(isolate);
templ->SetNativeDataProperty(
v8::String::NewFromUtf8(isolate, "name").ToLocalChecked(),
FunctionNativeGetter);
info.GetReturnValue().Set(
templ->GetFunction(isolate->GetCurrentContext()).ToLocalChecked());
});
// Check that getter is called on Function.prototype.bind.
global_template->SetNativeDataProperty(
v8::String::NewFromUtf8(isolate, "func2").ToLocalChecked(),
[](v8::Local<v8::Name> property,
const v8::PropertyCallbackInfo<v8::Value>& info) {
v8::Isolate* isolate = info.GetIsolate();
v8::Local<v8::FunctionTemplate> templ =
v8::FunctionTemplate::New(isolate);
templ->SetNativeDataProperty(
v8::String::NewFromUtf8(isolate, "length").ToLocalChecked(),
FunctionNativeGetter);
info.GetReturnValue().Set(
templ->GetFunction(isolate->GetCurrentContext()).ToLocalChecked());
});
return v8::Context::New(isolate, nullptr, global_template);
}
} // namespace
TEST_F(AccessorTest, WrapFunctionTemplateSetNativeDataProperty) {
i::v8_flags.harmony_shadow_realm = true;
isolate()->SetHostCreateShadowRealmContextCallback(
TestHostCreateShadowRealmContextCallback);
v8::HandleScope scope(isolate());
// Check that getter is called on WrappedFunctionCreate.
{
v8::TryCatch try_catch(isolate());
CHECK(TryRunJS("new ShadowRealm().evaluate('globalThis.func1')").IsEmpty());
CHECK(try_catch.HasCaught());
}
// Check that getter is called on WrappedFunctionCreate.
{
v8::TryCatch try_catch(isolate());
CHECK(TryRunJS("new ShadowRealm().evaluate('globalThis.func2')").IsEmpty());
CHECK(try_catch.HasCaught());
}
}

View File

@ -0,0 +1,56 @@
// Copyright 2020 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.
#ifdef V8_INTL_SUPPORT
#include <stdlib.h>
#include "include/v8-isolate.h"
#include "include/v8-local-handle.h"
#include "src/objects/objects-inl.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "unicode/locid.h"
class ApiIcuTest : public v8::TestWithContext {
public:
void CheckLocaleSpecificValues(const char* locale, const char* date,
const char* number) {
CHECK(NewString(locale)->StrictEquals(
RunJS("Intl.NumberFormat().resolvedOptions().locale")));
CHECK(NewString(date)->StrictEquals(
RunJS("new Date('02/14/2020 13:45').toLocaleString()")));
CHECK(NewString(number)->StrictEquals(
RunJS("Number(10000.3).toLocaleString()")));
}
void SetIcuLocale(const char* locale_name) {
UErrorCode error_code = U_ZERO_ERROR;
icu::Locale locale(locale_name);
icu::Locale::setDefault(locale, error_code);
CHECK(U_SUCCESS(error_code));
}
};
TEST_F(ApiIcuTest, LocaleConfigurationChangeNotification) {
icu::Locale default_locale = icu::Locale::getDefault();
SetIcuLocale("en_US");
isolate()->LocaleConfigurationChangeNotification();
CheckLocaleSpecificValues("en-US", "2/14/2020, 1:45:00 PM", "10,000.3");
SetIcuLocale("ru_RU");
isolate()->LocaleConfigurationChangeNotification();
CheckLocaleSpecificValues("ru-RU", "14.02.2020, 13:45:00", "10 000,3");
SetIcuLocale("zh_CN");
isolate()->LocaleConfigurationChangeNotification();
CheckLocaleSpecificValues("zh-CN", "2020/2/14 13:45:00", "10,000.3");
UErrorCode error_code = U_ZERO_ERROR;
icu::Locale::setDefault(default_locale, error_code);
CHECK(U_SUCCESS(error_code));
}
#endif // V8_INTL_SUPPORT

View File

@ -0,0 +1,305 @@
// Copyright 2019 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 <memory>
#include "include/v8-context.h"
#include "include/v8-function-callback.h"
#include "include/v8-local-handle.h"
#include "include/v8-object.h"
#include "include/v8-persistent-handle.h"
#include "include/v8-promise.h"
#include "include/v8-wasm.h"
#include "src/api/api-inl.h"
#include "src/handles/global-handles.h"
#include "src/wasm/wasm-features.h"
#include "src/wasm/wasm-js.h"
#include "test/common/flag-utils.h"
#include "test/unittests/heap/heap-utils.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
bool wasm_streaming_callback_got_called = false;
bool wasm_streaming_data_got_collected = false;
// The bytes of a minimal WebAssembly module.
const uint8_t kMinimalWasmModuleBytes[]{0x00, 0x61, 0x73, 0x6d,
0x01, 0x00, 0x00, 0x00};
class ApiWasmTest : public TestWithIsolate {
public:
void TestWasmStreaming(WasmStreamingCallback callback,
Promise::PromiseState expected_state) {
isolate()->SetWasmStreamingCallback(callback);
HandleScope scope(isolate());
Local<Context> context = Context::New(isolate());
Context::Scope context_scope(context);
// Call {WebAssembly.compileStreaming} with {null} as parameter. The
// parameter is only really processed by the embedder, so for this test the
// value is irrelevant.
Local<Promise> promise =
Local<Promise>::Cast(RunJS("WebAssembly.compileStreaming(null)"));
EmptyMessageQueues();
CHECK_EQ(expected_state, promise->State());
}
};
void WasmStreamingTestFinalizer(const WeakCallbackInfo<void>& data) {
CHECK(!wasm_streaming_data_got_collected);
wasm_streaming_data_got_collected = true;
i::GlobalHandles::Destroy(reinterpret_cast<i::Address*>(data.GetParameter()));
}
void WasmStreamingCallbackTestCallbackIsCalled(
const FunctionCallbackInfo<Value>& info) {
CHECK(i::ValidateCallbackInfo(info));
CHECK(!wasm_streaming_callback_got_called);
wasm_streaming_callback_got_called = true;
i::Handle<i::Object> global_handle =
reinterpret_cast<i::Isolate*>(info.GetIsolate())
->global_handles()
->Create(*Utils::OpenDirectHandle(*info.Data()));
i::GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(),
WasmStreamingTestFinalizer,
WeakCallbackType::kParameter);
}
void WasmStreamingCallbackTestFinishWithSuccess(
const FunctionCallbackInfo<Value>& info) {
CHECK(i::ValidateCallbackInfo(info));
std::shared_ptr<WasmStreaming> streaming =
WasmStreaming::Unpack(info.GetIsolate(), info.Data());
streaming->OnBytesReceived(kMinimalWasmModuleBytes,
arraysize(kMinimalWasmModuleBytes));
streaming->Finish();
}
void WasmStreamingCallbackTestFinishWithFailure(
const FunctionCallbackInfo<Value>& info) {
CHECK(i::ValidateCallbackInfo(info));
std::shared_ptr<WasmStreaming> streaming =
WasmStreaming::Unpack(info.GetIsolate(), info.Data());
streaming->Finish();
}
void WasmStreamingCallbackTestAbortWithReject(
const FunctionCallbackInfo<Value>& info) {
CHECK(i::ValidateCallbackInfo(info));
std::shared_ptr<WasmStreaming> streaming =
WasmStreaming::Unpack(info.GetIsolate(), info.Data());
streaming->Abort(Object::New(info.GetIsolate()));
}
void WasmStreamingCallbackTestAbortNoReject(
const FunctionCallbackInfo<Value>& info) {
CHECK(i::ValidateCallbackInfo(info));
std::shared_ptr<WasmStreaming> streaming =
WasmStreaming::Unpack(info.GetIsolate(), info.Data());
streaming->Abort({});
}
void WasmStreamingCallbackTestOnBytesReceived(
const FunctionCallbackInfo<Value>& info) {
CHECK(i::ValidateCallbackInfo(info));
std::shared_ptr<WasmStreaming> streaming =
WasmStreaming::Unpack(info.GetIsolate(), info.Data());
// The first bytes of the WebAssembly magic word.
const uint8_t bytes[]{0x00, 0x61, 0x73};
streaming->OnBytesReceived(bytes, arraysize(bytes));
}
void WasmStreamingMoreFunctionsCanBeSerializedCallback(
const FunctionCallbackInfo<Value>& info) {
CHECK(i::ValidateCallbackInfo(info));
std::shared_ptr<WasmStreaming> streaming =
WasmStreaming::Unpack(info.GetIsolate(), info.Data());
streaming->SetMoreFunctionsCanBeSerializedCallback([](CompiledWasmModule) {});
}
TEST_F(ApiWasmTest, WasmStreamingCallback) {
TestWasmStreaming(WasmStreamingCallbackTestCallbackIsCalled,
Promise::kPending);
CHECK(wasm_streaming_callback_got_called);
InvokeMemoryReducingMajorGCs(i_isolate());
CHECK(wasm_streaming_data_got_collected);
}
TEST_F(ApiWasmTest, WasmStreamingOnBytesReceived) {
TestWasmStreaming(WasmStreamingCallbackTestOnBytesReceived,
Promise::kPending);
}
TEST_F(ApiWasmTest, WasmStreamingFinishWithSuccess) {
TestWasmStreaming(WasmStreamingCallbackTestFinishWithSuccess,
Promise::kFulfilled);
}
TEST_F(ApiWasmTest, WasmStreamingFinishWithFailure) {
TestWasmStreaming(WasmStreamingCallbackTestFinishWithFailure,
Promise::kRejected);
}
TEST_F(ApiWasmTest, WasmStreamingAbortWithReject) {
TestWasmStreaming(WasmStreamingCallbackTestAbortWithReject,
Promise::kRejected);
}
TEST_F(ApiWasmTest, WasmStreamingAbortWithoutReject) {
TestWasmStreaming(WasmStreamingCallbackTestAbortNoReject, Promise::kPending);
}
TEST_F(ApiWasmTest, WasmCompileToWasmModuleObject) {
Local<Context> context = Context::New(isolate());
Context::Scope context_scope(context);
auto maybe_module = WasmModuleObject::Compile(
isolate(), {kMinimalWasmModuleBytes, arraysize(kMinimalWasmModuleBytes)});
CHECK(!maybe_module.IsEmpty());
}
TEST_F(ApiWasmTest, WasmStreamingSetCallback) {
TestWasmStreaming(WasmStreamingMoreFunctionsCanBeSerializedCallback,
Promise::kPending);
}
TEST_F(ApiWasmTest, WasmErrorIsSharedCrossOrigin) {
Isolate::Scope iscope(isolate());
HandleScope scope(isolate());
Local<Context> context = Context::New(isolate());
Context::Scope cscope(context);
TryCatch try_catch(isolate());
// A fairly minimal Wasm module that produces an error at runtime:
// it returns {null} from an imported function that's typed to return
// a non-null reference.
const char* expected_message =
"Uncaught TypeError: type incompatibility when transforming from/to JS";
const char* src =
"let raw = new Uint8Array(["
" 0x00, 0x61, 0x73, 0x6d, // wasm magic \n"
" 0x01, 0x00, 0x00, 0x00, // wasm version \n"
" 0x01, 0x06, // Type section, length 6 \n"
" 0x01, 0x60, // 1 type, kind: func \n"
" 0x00, 0x01, 0x64, 0x6f, // 0 params, 1 result: (ref extern) \n"
" 0x02, 0x07, 0x01, // Import section, length 7, 1 import \n"
" 0x01, 0x6d, 0x01, 0x6e, // 'm' 'n' \n"
" 0x00, 0x00, // kind: function $type0 \n"
" 0x03, 0x02, // Function section, length 2 \n"
" 0x01, 0x00, // 1 function, $type0 \n"
" 0x07, 0x05, 0x01, // Export section, length 5, 1 export \n"
" 0x01, 0x66, 0x00, 0x01, // 'f': function #1 \n"
" 0x0a, 0x06, 0x01, // Code section, length 6, 1 function \n"
" 0x04, 0x00, // body size 4, 0 locals \n"
" 0x10, 0x00, 0x0b, // call $m.n; end \n"
"]); \n"
"let mod = new WebAssembly.Module(raw.buffer); \n"
"let instance = new WebAssembly.Instance(mod, {m: {n: () => null}}); \n"
"instance.exports.f();";
TryRunJS(src);
EXPECT_TRUE(try_catch.HasCaught());
Local<Message> message = try_catch.Message();
CHECK_EQ(0, strcmp(*String::Utf8Value(isolate(), message->Get()),
expected_message));
EXPECT_TRUE(message->IsSharedCrossOrigin());
}
TEST_F(ApiWasmTest, WasmEnableDisableImportedStrings) {
Local<Context> context_local = Context::New(isolate());
Context::Scope context_scope(context_local);
i::DirectHandle<i::NativeContext> context =
v8::Utils::OpenDirectHandle(*context_local);
// Test enabling/disabling via flag.
{
i::FlagScope<bool> flag_strings(
&i::v8_flags.experimental_wasm_imported_strings, true);
EXPECT_TRUE(i_isolate()->IsWasmImportedStringsEnabled(context));
// When flag is on, callback return value has no effect.
isolate()->SetWasmImportedStringsEnabledCallback([](auto) { return true; });
EXPECT_TRUE(i_isolate()->IsWasmImportedStringsEnabled(context));
EXPECT_TRUE(i::wasm::WasmEnabledFeatures::FromIsolate(i_isolate())
.has_imported_strings());
isolate()->SetWasmImportedStringsEnabledCallback(
[](auto) { return false; });
EXPECT_TRUE(i_isolate()->IsWasmImportedStringsEnabled(context));
EXPECT_TRUE(i::wasm::WasmEnabledFeatures::FromIsolate(i_isolate())
.has_imported_strings());
}
{
i::FlagScope<bool> flag_strings(
&i::v8_flags.experimental_wasm_imported_strings, false);
EXPECT_FALSE(i_isolate()->IsWasmImportedStringsEnabled(context));
// Test enabling/disabling via callback.
isolate()->SetWasmImportedStringsEnabledCallback([](auto) { return true; });
EXPECT_TRUE(i_isolate()->IsWasmImportedStringsEnabled(context));
EXPECT_TRUE(i::wasm::WasmEnabledFeatures::FromIsolate(i_isolate())
.has_imported_strings());
isolate()->SetWasmImportedStringsEnabledCallback(
[](auto) { return false; });
EXPECT_FALSE(i_isolate()->IsWasmImportedStringsEnabled(context));
EXPECT_FALSE(i::wasm::WasmEnabledFeatures::FromIsolate(i_isolate())
.has_imported_strings());
}
}
TEST_F(ApiWasmTest, WasmEnableDisableJSPI) {
Local<Context> context_local = Context::New(isolate());
Context::Scope context_scope(context_local);
i::DirectHandle<i::NativeContext> context =
v8::Utils::OpenDirectHandle(*context_local);
// Test enabling/disabling via flag.
{
i::FlagScope<bool> flag_strings(&i::v8_flags.experimental_wasm_jspi, true);
EXPECT_TRUE(i_isolate()->IsWasmJSPIRequested(context));
}
{
i::FlagScope<bool> flag_strings(&i::v8_flags.experimental_wasm_jspi, false);
EXPECT_FALSE(i_isolate()->IsWasmJSPIRequested(context));
}
// Test enabling/disabling via callback.
isolate()->SetWasmJSPIEnabledCallback([](auto) { return true; });
EXPECT_TRUE(i_isolate()->IsWasmJSPIRequested(context));
isolate()->SetWasmJSPIEnabledCallback([](auto) { return false; });
EXPECT_FALSE(i_isolate()->IsWasmJSPIRequested(context));
}
TEST_F(ApiWasmTest, WasmInstallJSPI) {
Local<Context> context_local = Context::New(isolate());
Context::Scope context_scope(context_local);
i::DirectHandle<i::NativeContext> context =
v8::Utils::OpenDirectHandle(*context_local);
EXPECT_FALSE(i_isolate()->IsWasmJSPIEnabled(context));
i::wasm::WasmEnabledFeatures features =
i::wasm::WasmEnabledFeatures::FromIsolate(i_isolate());
EXPECT_FALSE(features.has_jspi());
// Test installing JSPI via flag.
isolate()->SetWasmJSPIEnabledCallback([](auto) { return true; });
EXPECT_TRUE(i_isolate()->IsWasmJSPIRequested(context));
EXPECT_FALSE(i_isolate()->IsWasmJSPIEnabled(context));
features = i::wasm::WasmEnabledFeatures::FromIsolate(i_isolate());
EXPECT_FALSE(features.has_jspi());
i::WasmJs::InstallConditionalFeatures(i_isolate(), context);
EXPECT_TRUE(i_isolate()->IsWasmJSPIEnabled(context));
features = i::wasm::WasmEnabledFeatures::FromIsolate(i_isolate());
EXPECT_TRUE(features.has_jspi());
}
} // namespace v8

View File

@ -0,0 +1,96 @@
// 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 "include/libplatform/libplatform.h"
#include "include/v8-context.h"
#include "include/v8-data.h"
#include "include/v8-isolate.h"
#include "include/v8-local-handle.h"
#include "include/v8-value.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
using ContextTest = v8::TestWithIsolate;
TEST_F(ContextTest, HasTemplateLiteralObjectBasic) {
v8::Local<v8::Context> context = v8::Context::New(isolate());
v8::Context::Scope scope(context);
ASSERT_FALSE(
context->HasTemplateLiteralObject(v8::Number::New(isolate(), 1)));
ASSERT_FALSE(context->HasTemplateLiteralObject(v8::String::Empty(isolate())));
ASSERT_FALSE(
context->HasTemplateLiteralObject(v8::Array::New(isolate(), 10)));
}
TEST_F(ContextTest, HasTemplateLiteralObject) {
const char* source = R"(
function ret(literal) {
return literal;
};
ret`one_${'two'}_three`;
)";
const char* otherObject1Source = R"(
Object.freeze(
Object.defineProperty(['one_', '_three'], 'raw', {
value: ['asdf'],
writable: false,
enumerable: false,
configurable: false,
})
);
)";
const char* otherObject2Source = R"(
Object.freeze(
Object.defineProperty(['one_', '_three'], 'raw', {
get() { return ['asdf']; },
enumerable: false,
configurable: false,
})
);
)";
v8::Local<v8::Context> context1 = v8::Context::New(isolate());
v8::Local<v8::Value> templateLiteral1;
v8::Local<v8::Value> templateLiteral1_2;
v8::Local<v8::Value> otherObject1_ctx1;
v8::Local<v8::Value> otherObject2_ctx1;
{
v8::Context::Scope scope(context1);
auto script =
v8::Script::Compile(context1, NewString(source)).ToLocalChecked();
templateLiteral1 = script->Run(context1).ToLocalChecked();
templateLiteral1_2 = script->Run(context1).ToLocalChecked();
otherObject1_ctx1 = RunJS(context1, otherObject1Source);
otherObject2_ctx1 = RunJS(context1, otherObject2Source);
}
v8::Local<v8::Value> templateLiteral2;
v8::Local<v8::Context> context2 = v8::Context::New(isolate());
v8::Local<v8::Value> otherObject1_ctx2;
v8::Local<v8::Value> otherObject2_ctx2;
{
v8::Context::Scope scope(context2);
templateLiteral2 = RunJS(context2, source);
otherObject1_ctx2 = RunJS(context2, otherObject1Source);
otherObject2_ctx2 = RunJS(context1, otherObject2Source);
}
ASSERT_TRUE(context1->HasTemplateLiteralObject(templateLiteral1));
ASSERT_TRUE(context1->HasTemplateLiteralObject(templateLiteral1_2));
ASSERT_FALSE(context1->HasTemplateLiteralObject(templateLiteral2));
ASSERT_FALSE(context2->HasTemplateLiteralObject(templateLiteral1));
ASSERT_FALSE(context2->HasTemplateLiteralObject(templateLiteral1_2));
ASSERT_TRUE(context2->HasTemplateLiteralObject(templateLiteral2));
// Neither otherObject is a template object
ASSERT_FALSE(context1->HasTemplateLiteralObject(otherObject1_ctx1));
ASSERT_FALSE(context1->HasTemplateLiteralObject(otherObject2_ctx1));
ASSERT_FALSE(context1->HasTemplateLiteralObject(otherObject1_ctx2));
ASSERT_FALSE(context1->HasTemplateLiteralObject(otherObject1_ctx1));
ASSERT_FALSE(context2->HasTemplateLiteralObject(otherObject2_ctx1));
ASSERT_FALSE(context2->HasTemplateLiteralObject(otherObject1_ctx2));
ASSERT_FALSE(context2->HasTemplateLiteralObject(otherObject2_ctx2));
ASSERT_FALSE(context2->HasTemplateLiteralObject(otherObject2_ctx2));
}

View File

@ -0,0 +1,986 @@
// Copyright 2021 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-context.h"
#include "include/v8-function.h"
#include "include/v8-isolate.h"
#include "include/v8-local-handle.h"
#include "include/v8-platform.h"
#include "include/v8-primitive.h"
#include "include/v8-script.h"
#include "src/codegen/compilation-cache.h"
#include "test/unittests/heap/heap-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
class DeserializeTest : public TestWithPlatform {
public:
class IsolateAndContextScope {
public:
explicit IsolateAndContextScope(DeserializeTest* test)
: test_(test),
isolate_wrapper_(kNoCounters),
isolate_scope_(isolate_wrapper_.isolate()),
handle_scope_(isolate_wrapper_.isolate()),
context_(Context::New(isolate_wrapper_.isolate())),
context_scope_(context_) {
CHECK_NULL(test->isolate_);
CHECK(test->context_.IsEmpty());
test->isolate_ = isolate_wrapper_.isolate();
test->context_.Reset(test->isolate_, context_);
}
~IsolateAndContextScope() {
test_->isolate_ = nullptr;
test_->context_.Reset();
}
private:
DeserializeTest* test_;
v8::IsolateWrapper isolate_wrapper_;
v8::Isolate::Scope isolate_scope_;
v8::HandleScope handle_scope_;
v8::Local<v8::Context> context_;
v8::Context::Scope context_scope_;
};
Local<String> NewString(const char* val) {
return String::NewFromUtf8(isolate(), val).ToLocalChecked();
}
Local<Value> RunGlobalFunc(const char* name) {
Local<Value> func_val =
context()->Global()->Get(context(), NewString(name)).ToLocalChecked();
CHECK(func_val->IsFunction());
Local<Function> func = Local<Function>::Cast(func_val);
return func->Call(context(), Undefined(isolate()), 0, nullptr)
.ToLocalChecked();
}
Isolate* isolate() { return isolate_; }
v8::Local<v8::Context> context() {
DCHECK(!context_.IsEmpty());
return context_.Get(isolate_);
}
private:
Isolate* isolate_ = nullptr;
v8::Global<v8::Context> context_;
};
// Check that deserialization works.
TEST_F(DeserializeTest, Deserialize) {
std::unique_ptr<v8::ScriptCompiler::CachedData> cached_data;
{
IsolateAndContextScope scope(this);
Local<String> source_code = NewString("function foo() { return 42; }");
Local<Script> script =
Script::Compile(context(), source_code).ToLocalChecked();
CHECK(!script->Run(context()).IsEmpty());
CHECK_EQ(RunGlobalFunc("foo"), Integer::New(isolate(), 42));
cached_data.reset(
ScriptCompiler::CreateCodeCache(script->GetUnboundScript()));
}
{
IsolateAndContextScope scope(this);
Local<String> source_code = NewString("function foo() { return 42; }");
ScriptCompiler::Source source(source_code, cached_data.release());
Local<Script> script =
ScriptCompiler::Compile(context(), &source,
ScriptCompiler::kConsumeCodeCache)
.ToLocalChecked();
CHECK(!source.GetCachedData()->rejected);
CHECK(!script->Run(context()).IsEmpty());
CHECK_EQ(RunGlobalFunc("foo"), v8::Integer::New(isolate(), 42));
}
}
// Check that deserialization with a different script rejects the cache but
// still works via standard compilation.
TEST_F(DeserializeTest, DeserializeRejectsDifferentSource) {
std::unique_ptr<v8::ScriptCompiler::CachedData> cached_data;
{
IsolateAndContextScope scope(this);
Local<String> source_code = NewString("function foo() { return 42; }");
Local<Script> script =
Script::Compile(context(), source_code).ToLocalChecked();
CHECK(!script->Run(context()).IsEmpty());
CHECK_EQ(RunGlobalFunc("foo"), Integer::New(isolate(), 42));
cached_data.reset(
ScriptCompiler::CreateCodeCache(script->GetUnboundScript()));
}
{
IsolateAndContextScope scope(this);
// The source hash is based on the source length, so have to make sure that
// this is different here.
Local<String> source_code = NewString("function bar() { return 142; }");
ScriptCompiler::Source source(source_code, cached_data.release());
Local<Script> script =
ScriptCompiler::Compile(context(), &source,
ScriptCompiler::kConsumeCodeCache)
.ToLocalChecked();
CHECK(source.GetCachedData()->rejected);
CHECK(!script->Run(context()).IsEmpty());
CHECK_EQ(RunGlobalFunc("bar"), v8::Integer::New(isolate(), 142));
}
}
class DeserializeThread : public base::Thread {
public:
explicit DeserializeThread(ScriptCompiler::ConsumeCodeCacheTask* task)
: Thread(base::Thread::Options("DeserializeThread")), task_(task) {}
void Run() override { task_->Run(); }
std::unique_ptr<ScriptCompiler::ConsumeCodeCacheTask> TakeTask() {
return std::move(task_);
}
private:
std::unique_ptr<ScriptCompiler::ConsumeCodeCacheTask> task_;
};
// Check that off-thread deserialization works.
TEST_F(DeserializeTest, OffThreadDeserialize) {
std::unique_ptr<v8::ScriptCompiler::CachedData> cached_data;
{
IsolateAndContextScope scope(this);
Local<String> source_code = NewString("function foo() { return 42; }");
Local<Script> script =
Script::Compile(context(), source_code).ToLocalChecked();
CHECK(!script->Run(context()).IsEmpty());
CHECK_EQ(RunGlobalFunc("foo"), Integer::New(isolate(), 42));
cached_data.reset(
ScriptCompiler::CreateCodeCache(script->GetUnboundScript()));
}
{
IsolateAndContextScope scope(this);
DeserializeThread deserialize_thread(
ScriptCompiler::StartConsumingCodeCache(
isolate(), std::make_unique<ScriptCompiler::CachedData>(
cached_data->data, cached_data->length,
ScriptCompiler::CachedData::BufferNotOwned)));
CHECK(deserialize_thread.Start());
deserialize_thread.Join();
Local<String> source_code = NewString("function foo() { return 42; }");
ScriptCompiler::Source source(source_code, cached_data.release(),
deserialize_thread.TakeTask().release());
Local<Script> script =
ScriptCompiler::Compile(context(), &source,
ScriptCompiler::kConsumeCodeCache)
.ToLocalChecked();
CHECK(!source.GetCachedData()->rejected);
CHECK(!script->Run(context()).IsEmpty());
CHECK_EQ(RunGlobalFunc("foo"), v8::Integer::New(isolate(), 42));
}
}
// Check that off-thread deserialization works.
TEST_F(DeserializeTest, OffThreadDeserializeRejectsDifferentSource) {
std::unique_ptr<v8::ScriptCompiler::CachedData> cached_data;
{
IsolateAndContextScope scope(this);
Local<String> source_code = NewString("function foo() { return 42; }");
Local<Script> script =
Script::Compile(context(), source_code).ToLocalChecked();
CHECK(!script->Run(context()).IsEmpty());
CHECK_EQ(RunGlobalFunc("foo"), Integer::New(isolate(), 42));
cached_data.reset(
ScriptCompiler::CreateCodeCache(script->GetUnboundScript()));
}
{
IsolateAndContextScope scope(this);
DeserializeThread deserialize_thread(
ScriptCompiler::StartConsumingCodeCache(
isolate(), std::make_unique<ScriptCompiler::CachedData>(
cached_data->data, cached_data->length,
ScriptCompiler::CachedData::BufferNotOwned)));
CHECK(deserialize_thread.Start());
deserialize_thread.Join();
Local<String> source_code = NewString("function bar() { return 142; }");
ScriptCompiler::Source source(source_code, cached_data.release(),
deserialize_thread.TakeTask().release());
Local<Script> script =
ScriptCompiler::Compile(context(), &source,
ScriptCompiler::kConsumeCodeCache)
.ToLocalChecked();
CHECK(source.GetCachedData()->rejected);
CHECK(!script->Run(context()).IsEmpty());
CHECK_EQ(RunGlobalFunc("bar"), v8::Integer::New(isolate(), 142));
}
}
class DeserializeStarterThread : public base::Thread {
public:
explicit DeserializeStarterThread(Isolate* isolate,
v8::ScriptCompiler::CachedData* cached_data)
: Thread(base::Thread::Options("DeserializeStarterThread")),
isolate_(isolate),
cached_data_(cached_data) {}
void Run() override {
DeserializeThread deserialize_thread(
ScriptCompiler::StartConsumingCodeCacheOnBackground(
isolate_, std::make_unique<ScriptCompiler::CachedData>(
cached_data_->data, cached_data_->length,
ScriptCompiler::CachedData::BufferNotOwned)));
CHECK(deserialize_thread.Start());
deserialize_thread.Join();
task_ = deserialize_thread.TakeTask();
}
std::unique_ptr<ScriptCompiler::ConsumeCodeCacheTask> TakeTask() {
return std::move(task_);
}
private:
Isolate* isolate_;
v8::ScriptCompiler::CachedData* cached_data_;
std::unique_ptr<ScriptCompiler::ConsumeCodeCacheTask> task_;
};
// Check that off-thread deserialization started from a background thread works.
TEST_F(DeserializeTest, OffThreadDeserializeStartedFromBackgroundThread) {
std::unique_ptr<v8::ScriptCompiler::CachedData> cached_data;
{
IsolateAndContextScope scope(this);
Local<String> source_code = NewString("function foo() { return 42; }");
Local<Script> script =
Script::Compile(context(), source_code).ToLocalChecked();
CHECK(!script->Run(context()).IsEmpty());
CHECK_EQ(RunGlobalFunc("foo"), Integer::New(isolate(), 42));
cached_data.reset(
ScriptCompiler::CreateCodeCache(script->GetUnboundScript()));
}
{
IsolateAndContextScope scope(this);
DeserializeStarterThread deserialize_starter_thread(isolate(),
cached_data.get());
CHECK(deserialize_starter_thread.Start());
{
// Check that code execution works wille the DeserializeStarterThread
// staring a ConsumeCodeCacheTask.
Local<String> other_source_code =
NewString("function bar() { return 21; }");
Local<Script> other_script =
Script::Compile(context(), other_source_code).ToLocalChecked();
CHECK(!other_script->Run(context()).IsEmpty());
CHECK_EQ(RunGlobalFunc("bar"), Integer::New(isolate(), 21));
}
deserialize_starter_thread.Join();
Local<String> source_code = NewString("function foo() { return 42; }");
ScriptCompiler::Source source(
source_code, cached_data.release(),
deserialize_starter_thread.TakeTask().release());
Local<Script> script =
ScriptCompiler::Compile(context(), &source,
ScriptCompiler::kConsumeCodeCache)
.ToLocalChecked();
CHECK(!source.GetCachedData()->rejected);
CHECK(!script->Run(context()).IsEmpty());
CHECK_EQ(RunGlobalFunc("foo"), v8::Integer::New(isolate(), 42));
}
}
class MergeDeserializedCodeTest : public DeserializeTest {
protected:
// The source code used in these tests.
static constexpr char kSourceCode[] = R"(
// Looks like an IIFE but isn't, to get eagerly parsed:
{ let captured = 10;
var eager = (function () {
// Actual IIFE, also eagerly parsed:
return (function iife() {
return captured, 42;
})();
});
// Lazily parsed:
var lazy = function () { return eager(); };
}
)";
// Objects from the Script's object graph whose lifetimes and connectedness
// are useful to track.
enum ScriptObject {
kScript,
kToplevelSfi,
kToplevelFunctionData,
kToplevelFeedbackMetadata,
kEagerSfi,
kEagerFunctionData,
kEagerFeedbackMetadata,
kIifeSfi,
kIifeFunctionData,
kIifeFeedbackMetadata,
kLazySfi,
kScriptObjectsCount
};
enum ScriptObjectFlag {
kNone,
kScriptFlag = 1 << kScript,
kToplevelSfiFlag = 1 << kToplevelSfi,
kToplevelFunctionDataFlag = 1 << kToplevelFunctionData,
kToplevelFeedbackMetadataFlag = 1 << kToplevelFeedbackMetadata,
kEagerSfiFlag = 1 << kEagerSfi,
kEagerFunctionDataFlag = 1 << kEagerFunctionData,
kEagerFeedbackMetadataFlag = 1 << kEagerFeedbackMetadata,
kIifeSfiFlag = 1 << kIifeSfi,
kIifeFunctionDataFlag = 1 << kIifeFunctionData,
kIifeFeedbackMetadataFlag = 1 << kIifeFeedbackMetadata,
kLazySfiFlag = 1 << kLazySfi,
kAllScriptObjects = (1 << kScriptObjectsCount) - 1,
kAllCompiledSfis = kToplevelSfiFlag | kEagerSfiFlag | kIifeSfiFlag,
kAllSfis = kAllCompiledSfis | kLazySfiFlag,
kEagerAndLazy = kLazySfiFlag | kEagerSfiFlag,
kToplevelEagerAndLazy = kToplevelSfiFlag | kEagerAndLazy,
kToplevelAndEager = kToplevelSfiFlag | kEagerSfiFlag,
};
template <typename T>
static i::Tagged<i::SharedFunctionInfo> GetSharedFunctionInfo(
Local<T> function_or_script) {
i::DirectHandle<i::JSFunction> i_function =
i::Cast<i::JSFunction>(Utils::OpenDirectHandle(*function_or_script));
return i_function->shared();
}
static i::Tagged<i::MaybeObject> WeakOrSmi(i::Tagged<i::Object> obj) {
return IsSmi(obj) ? i::Cast<i::Smi>(obj) : i::MakeWeak(obj);
}
static i::Tagged<i::Object> ExtractSharedFunctionInfoData(
i::Tagged<i::SharedFunctionInfo> sfi, i::Isolate* i_isolate) {
i::Tagged<i::Object> data = sfi->GetTrustedData(i_isolate);
// BytecodeArrays live in trusted space and so cannot be referenced through
// tagged/compressed pointers from e.g. a FixedArray. Instead, we need to
// use their in-sandbox wrapper object for that purpose.
if (i::IsBytecodeArray(data)) {
data = i::Cast<i::BytecodeArray>(data)->wrapper();
}
return data;
}
void ValidateStandaloneGraphAndPopulateArray(
i::Tagged<i::SharedFunctionInfo> toplevel_sfi,
i::Tagged<i::WeakFixedArray> array, i::Isolate* i_isolate,
bool lazy_should_be_compiled = false,
bool eager_should_be_compiled = true) {
i::DisallowGarbageCollection no_gc;
CHECK(toplevel_sfi->is_compiled());
array->set(kToplevelSfi, WeakOrSmi(toplevel_sfi));
array->set(kToplevelFunctionData, WeakOrSmi(ExtractSharedFunctionInfoData(
toplevel_sfi, i_isolate)));
array->set(kToplevelFeedbackMetadata,
WeakOrSmi(toplevel_sfi->feedback_metadata()));
i::Tagged<i::Script> script = i::Cast<i::Script>(toplevel_sfi->script());
array->set(kScript, WeakOrSmi(script));
i::Tagged<i::WeakFixedArray> sfis = script->infos();
CHECK_EQ(sfis->length(), 4);
CHECK_EQ(sfis->get(0), WeakOrSmi(toplevel_sfi));
i::Tagged<i::SharedFunctionInfo> eager =
i::Cast<i::SharedFunctionInfo>(sfis->get(1).GetHeapObjectAssumeWeak());
CHECK_EQ(eager->is_compiled(), eager_should_be_compiled);
array->set(kEagerSfi, WeakOrSmi(eager));
if (eager_should_be_compiled) {
array->set(kEagerFunctionData,
WeakOrSmi(ExtractSharedFunctionInfoData(eager, i_isolate)));
array->set(kEagerFeedbackMetadata, WeakOrSmi(eager->feedback_metadata()));
i::Tagged<i::SharedFunctionInfo> iife = i::Cast<i::SharedFunctionInfo>(
sfis->get(2).GetHeapObjectAssumeWeak());
CHECK(iife->is_compiled());
array->set(kIifeSfi, WeakOrSmi(iife));
array->set(kIifeFunctionData,
WeakOrSmi(ExtractSharedFunctionInfoData(iife, i_isolate)));
array->set(kIifeFeedbackMetadata, WeakOrSmi(iife->feedback_metadata()));
}
i::Tagged<i::SharedFunctionInfo> lazy =
i::Cast<i::SharedFunctionInfo>(sfis->get(3).GetHeapObjectAssumeWeak());
CHECK_EQ(lazy->is_compiled(), lazy_should_be_compiled);
array->set(kLazySfi, WeakOrSmi(lazy));
}
void AgeBytecodeAndGC(ScriptObjectFlag sfis_to_age,
i::DirectHandle<i::WeakFixedArray> original_objects,
i::Isolate* i_isolate) {
for (int index = 0; index < kScriptObjectsCount; ++index) {
if ((sfis_to_age & (1 << index)) == (1 << index)) {
i::Tagged<i::SharedFunctionInfo> sfi = i::Cast<i::SharedFunctionInfo>(
original_objects->get(index).GetHeapObjectAssumeWeak());
i::SharedFunctionInfo::EnsureOldForTesting(sfi);
}
}
InvokeMajorGC(i_isolate);
// A second round of GC is necessary in case incremental marking had already
// started before the bytecode was aged.
InvokeMajorGC(i_isolate);
}
class MergeThread : public base::Thread {
public:
explicit MergeThread(ScriptCompiler::ConsumeCodeCacheTask* task)
: Thread(base::Thread::Options("MergeThread")), task_(task) {}
void Run() override { task_->MergeWithExistingScript(); }
private:
ScriptCompiler::ConsumeCodeCacheTask* task_;
};
void RetainObjects(ScriptObjectFlag to_retain,
i::Tagged<i::WeakFixedArray> original_objects,
i::Tagged<i::FixedArray> retained_original_objects,
i::Isolate* i_isolate) {
for (int index = 0; index < kScriptObjectsCount; ++index) {
if ((to_retain & (1 << index)) == (1 << index)) {
i::Tagged<i::MaybeObject> maybe = original_objects->get(index);
if (i::Tagged<i::HeapObject> heap_object;
maybe.GetHeapObjectIfWeak(&heap_object)) {
retained_original_objects->set(index, heap_object);
continue;
}
}
retained_original_objects->set(
index, i::ReadOnlyRoots(i_isolate).undefined_value());
}
}
void TestOffThreadMerge(ScriptObjectFlag retained_before_background_merge,
ScriptObjectFlag aged_before_background_merge,
bool run_code_after_background_merge,
ScriptObjectFlag retained_after_background_merge,
ScriptObjectFlag aged_after_background_merge,
bool lazy_should_be_compiled = false,
bool eager_should_be_compiled = true) {
i::v8_flags.merge_background_deserialized_script_with_compilation_cache =
true;
std::unique_ptr<v8::ScriptCompiler::CachedData> cached_data;
IsolateAndContextScope scope(this);
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate());
ScriptOrigin default_origin(NewString(""));
i::DirectHandle<i::WeakFixedArray> original_objects =
i_isolate->factory()->NewWeakFixedArray(kScriptObjectsCount);
i::DirectHandle<i::FixedArray> retained_original_objects =
i_isolate->factory()->NewFixedArray(kScriptObjectsCount);
i::DirectHandle<i::WeakFixedArray> new_objects =
i_isolate->factory()->NewWeakFixedArray(kScriptObjectsCount);
Local<Script> original_script;
// Compile the script for the first time, to both populate the Isolate
// compilation cache and produce code cache data.
{
v8::EscapableHandleScope handle_scope(isolate());
Local<Script> script =
Script::Compile(context(), NewString(kSourceCode), &default_origin)
.ToLocalChecked();
ValidateStandaloneGraphAndPopulateArray(GetSharedFunctionInfo(script),
*original_objects, i_isolate);
RetainObjects(retained_before_background_merge, *original_objects,
*retained_original_objects, i_isolate);
cached_data.reset(
ScriptCompiler::CreateCodeCache(script->GetUnboundScript()));
if (run_code_after_background_merge) {
// We must retain the v8::Script (a JSFunction) so we can run it later.
original_script = handle_scope.Escape(script);
// It doesn't make any sense to configure a test case which says it
// doesn't want to retain the toplevel SFI but does want to run the
// script later.
CHECK(retained_before_background_merge & kToplevelSfiFlag);
}
}
AgeBytecodeAndGC(aged_before_background_merge, original_objects, i_isolate);
DeserializeThread deserialize_thread(
ScriptCompiler::StartConsumingCodeCache(
isolate(), std::make_unique<ScriptCompiler::CachedData>(
cached_data->data, cached_data->length,
ScriptCompiler::CachedData::BufferNotOwned)));
CHECK(deserialize_thread.Start());
deserialize_thread.Join();
std::unique_ptr<ScriptCompiler::ConsumeCodeCacheTask> task =
deserialize_thread.TakeTask();
task->SourceTextAvailable(isolate(), NewString(kSourceCode),
default_origin);
// If the top-level SFI was retained and not flushed, then no merge is
// necessary because the results from the deserialization will be discarded.
// If nothing at all was retained, then no merge is necessary because the
// original Script is no longer in the compilation cache. Otherwise, a merge
// is necessary.
bool merge_expected =
(retained_before_background_merge != kNone) &&
(!(retained_before_background_merge & kToplevelSfiFlag) ||
(aged_before_background_merge & kToplevelSfiFlag));
CHECK_EQ(merge_expected, task->ShouldMergeWithExistingScript());
if (merge_expected) {
MergeThread merge_thread(task.get());
CHECK(merge_thread.Start());
merge_thread.Join();
}
if (run_code_after_background_merge) {
CHECK(!original_script->Run(context()).IsEmpty());
CHECK_EQ(RunGlobalFunc("lazy"), v8::Integer::New(isolate(), 42));
ValidateStandaloneGraphAndPopulateArray(
GetSharedFunctionInfo(original_script), *original_objects, i_isolate,
true /*lazy_should_be_compiled*/);
}
RetainObjects(retained_after_background_merge, *original_objects,
*retained_original_objects, i_isolate);
AgeBytecodeAndGC(aged_after_background_merge, original_objects, i_isolate);
ScriptCompiler::Source source(NewString(kSourceCode), default_origin,
cached_data.release(), task.release());
Local<Script> script =
ScriptCompiler::Compile(context(), &source,
ScriptCompiler::kConsumeCodeCache)
.ToLocalChecked();
CHECK(!source.GetCachedData()->rejected);
ValidateStandaloneGraphAndPopulateArray(
GetSharedFunctionInfo(script), *new_objects, i_isolate,
lazy_should_be_compiled, eager_should_be_compiled);
// At this point, the original_objects array might still have pointers to
// some old discarded content, such as UncompiledData from flushed
// functions. GC again to clear it all out.
InvokeMajorGC(i_isolate);
// All tracked objects from the original Script should have been reused if
// they're still alive.
for (int index = 0; index < kScriptObjectsCount; ++index) {
if (original_objects->get(index).IsWeak() &&
new_objects->get(index).IsWeak()) {
CHECK_EQ(original_objects->get(index), new_objects->get(index));
}
}
CHECK(!script->Run(context()).IsEmpty());
CHECK_EQ(RunGlobalFunc("lazy"), v8::Integer::New(isolate(), 42));
}
};
TEST_F(MergeDeserializedCodeTest, NoMergeWhenAlreadyCompiled) {
// Retain everything; age nothing.
TestOffThreadMerge(kAllScriptObjects, // retained_before_background_merge
kNone, // aged_before_background_merge
false, // run_code_after_background_merge
kAllScriptObjects, // retained_after_background_merge
kNone); // aged_after_background_merge
}
TEST_F(MergeDeserializedCodeTest, NoMergeWhenOriginalWasDiscarded) {
// Retain nothing.
TestOffThreadMerge(kNone, // retained_before_background_merge
kNone, // aged_before_background_merge
false, // run_code_after_background_merge
kNone, // retained_after_background_merge
kNone); // aged_after_background_merge
}
TEST_F(MergeDeserializedCodeTest, NoMergeWhenOriginalWasDiscardedLate) {
// The original top-level SFI is retained by the background merge task even
// though other retainers are discarded.
TestOffThreadMerge(kAllScriptObjects, // retained_before_background_merge
kNone, // aged_before_background_merge
false, // run_code_after_background_merge
kNone, // retained_after_background_merge
kNone); // aged_after_background_merge
}
TEST_F(MergeDeserializedCodeTest, MergeIntoFlushedSFIs) {
// Retain all SFIs but age them.
TestOffThreadMerge(kAllSfis, // retained_before_background_merge
kAllCompiledSfis, // aged_before_background_merge
false, // run_code_after_background_merge
kAllSfis, // retained_after_background_merge
kNone); // aged_after_background_merge
}
TEST_F(MergeDeserializedCodeTest, MergeBasic) {
// Retain the eager and lazy functions; discard the top-level SFI.
// This is a common scenario which requires a merge.
TestOffThreadMerge(kEagerAndLazy, // retained_before_background_merge
kToplevelSfiFlag, // aged_before_background_merge
false, // run_code_after_background_merge
kNone, // retained_after_background_merge
kNone); // aged_after_background_merge
}
TEST_F(MergeDeserializedCodeTest, MergeBasicWithFlushing) {
// Retain the eager and lazy functions; discard the top-level SFI.
// Also flush the eager function, which discards the IIFE.
// This is a common scenario which requires a merge.
TestOffThreadMerge(kEagerAndLazy, // retained_before_background_merge
kToplevelAndEager, // aged_before_background_merge
false, // run_code_after_background_merge
kNone, // retained_after_background_merge
kNone); // aged_after_background_merge
}
TEST_F(MergeDeserializedCodeTest, MergeBasicWithLateFlushing) {
// Flush the eager function after the background merge has taken place. In
// this case, the data from the background thread points to the eager SFI but
// not its bytecode, so the end result is that the eager SFI is not compiled
// after completion on the main thread.
TestOffThreadMerge(kEagerAndLazy, // retained_before_background_merge
kToplevelSfiFlag, // aged_before_background_merge
false, // run_code_after_background_merge
kNone, // retained_after_background_merge
kEagerSfiFlag, // aged_after_background_merge
false, // lazy_should_be_compiled
false); // eager_should_be_compiled
}
TEST_F(MergeDeserializedCodeTest, RunScriptButNoReMergeNecessary) {
// The original script is run after the background merge, causing the
// top-level SFI and lazy SFI to become compiled. However, no SFIs are
// created when running the script, so the main thread needn't redo the merge.
TestOffThreadMerge(kToplevelEagerAndLazy, // retained_before_background_merge
kToplevelSfiFlag, // aged_before_background_merge
true, // run_code_after_background_merge
kAllScriptObjects, // retained_after_background_merge
kNone, // aged_after_background_merge
true); // lazy_should_be_compiled
}
TEST_F(MergeDeserializedCodeTest, MainThreadReMerge) {
// By flushing the eager SFI early, we cause the IIFE SFI to disappear
// entirely. When the original script runs after the background merge, the
// IIFE SFI is recreated. Thus, the main thread must redo the merge.
TestOffThreadMerge(kToplevelEagerAndLazy, // retained_before_background_merge
kToplevelAndEager, // aged_before_background_merge
true, // run_code_after_background_merge
kAllScriptObjects, // retained_after_background_merge
kToplevelSfiFlag, // aged_after_background_merge
true); // lazy_should_be_compiled
}
TEST_F(MergeDeserializedCodeTest, Regress1360024) {
// This test case triggers a re-merge on the main thread, similar to
// MainThreadReMerge. However, it does not retain the lazy function's SFI at
// any step, which causes the merge to use the SFI from the newly deserialized
// script for that function. This exercises a bug in the original
// implementation where the re-merging on the main thread would crash if the
// merge algorithm had selected any uncompiled SFIs from the new script.
TestOffThreadMerge(kToplevelAndEager, // retained_before_background_merge
kToplevelAndEager, // aged_before_background_merge
true, // run_code_after_background_merge
kToplevelAndEager, // retained_after_background_merge
kToplevelSfiFlag, // aged_after_background_merge
true); // lazy_should_be_compiled
}
TEST_F(MergeDeserializedCodeTest, MergeWithNoFollowUpWork) {
i::v8_flags.merge_background_deserialized_script_with_compilation_cache =
true;
std::unique_ptr<v8::ScriptCompiler::CachedData> cached_data;
IsolateAndContextScope scope(this);
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate());
ScriptOrigin default_origin(NewString(""));
constexpr char kSourceCode[] = "function f() {}";
Local<Script> original_script;
// Compile the script for the first time, to both populate the Isolate
// compilation cache and produce code cache data.
{
v8::EscapableHandleScope handle_scope(isolate());
Local<Script> script =
Script::Compile(context(), NewString(kSourceCode), &default_origin)
.ToLocalChecked();
cached_data.reset(
ScriptCompiler::CreateCodeCache(script->GetUnboundScript()));
// Retain the v8::Script (a JSFunction) so we can run it later.
original_script = handle_scope.Escape(script);
}
// Age the top-level bytecode so that the Isolate compilation cache will
// contain only the Script.
i::SharedFunctionInfo::EnsureOldForTesting(
GetSharedFunctionInfo(original_script));
InvokeMajorGC(i_isolate);
// A second round of GC is necessary in case incremental marking had already
// started before the bytecode was aged.
InvokeMajorGC(i_isolate);
DeserializeThread deserialize_thread(ScriptCompiler::StartConsumingCodeCache(
isolate(), std::make_unique<ScriptCompiler::CachedData>(
cached_data->data, cached_data->length,
ScriptCompiler::CachedData::BufferNotOwned)));
CHECK(deserialize_thread.Start());
deserialize_thread.Join();
std::unique_ptr<ScriptCompiler::ConsumeCodeCacheTask> task =
deserialize_thread.TakeTask();
// At this point, the cached script's top-level SFI is not compiled, so a
// background merge is recommended.
task->SourceTextAvailable(isolate(), NewString(kSourceCode), default_origin);
CHECK(task->ShouldMergeWithExistingScript());
// Run the original script, which will cause its top-level SFI to become
// compiled again, and make the SFI for the nested function exist.
CHECK(!original_script->Run(context()).IsEmpty());
// The background merge does nothing and requests no follow-up work on the
// main thread because the original script has the same SFIs at the same level
// of compiledness.
MergeThread merge_thread(task.get());
CHECK(merge_thread.Start());
merge_thread.Join();
// Complete compilation on the main thread. Even though no follow-up work is
// required, this step should reuse the original script.
ScriptCompiler::Source source(NewString(kSourceCode), default_origin,
cached_data.release(), task.release());
Local<Script> script =
ScriptCompiler::Compile(context(), &source,
ScriptCompiler::kConsumeCodeCache)
.ToLocalChecked();
CHECK_EQ(GetSharedFunctionInfo(script),
GetSharedFunctionInfo(original_script));
}
TEST_F(MergeDeserializedCodeTest, MergeThatCompilesLazyFunction) {
i::v8_flags.merge_background_deserialized_script_with_compilation_cache =
true;
std::unique_ptr<v8::ScriptCompiler::CachedData> cached_data;
IsolateAndContextScope scope(this);
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate());
ScriptOrigin default_origin(NewString(""));
constexpr char kSourceCode[] =
"var f = function () {var s = f.toString(); f = null; return s;};";
constexpr uint8_t kFunctionText[] =
"function () {var s = f.toString(); f = null; return s;}";
// Compile the script for the first time to produce code cache data.
{
v8::HandleScope handle_scope(isolate());
Local<Script> script =
Script::Compile(context(), NewString(kSourceCode), &default_origin)
.ToLocalChecked();
CHECK(!script->Run(context()).IsEmpty());
// Cause the function to become compiled before creating the code cache.
Local<String> expected =
String::NewFromOneByte(isolate(), kFunctionText).ToLocalChecked();
Local<Value> actual = RunGlobalFunc("f");
CHECK(expected->StrictEquals(actual));
cached_data.reset(
ScriptCompiler::CreateCodeCache(script->GetUnboundScript()));
}
i_isolate->compilation_cache()->Clear();
// Compile the script for the second time, but don't run the function 'f'.
{
v8::HandleScope handle_scope(isolate());
Local<Script> script =
Script::Compile(context(), NewString(kSourceCode), &default_origin)
.ToLocalChecked();
CHECK(!script->Run(context()).IsEmpty());
// Age the top-level bytecode so that the Isolate compilation cache will
// contain only the Script.
i::SharedFunctionInfo::EnsureOldForTesting(GetSharedFunctionInfo(script));
}
InvokeMajorGC(i_isolate);
// A second round of GC is necessary in case incremental marking had already
// started before the bytecode was aged.
InvokeMajorGC(i_isolate);
DeserializeThread deserialize_thread(ScriptCompiler::StartConsumingCodeCache(
isolate(), std::make_unique<ScriptCompiler::CachedData>(
cached_data->data, cached_data->length,
ScriptCompiler::CachedData::BufferNotOwned)));
CHECK(deserialize_thread.Start());
deserialize_thread.Join();
std::unique_ptr<ScriptCompiler::ConsumeCodeCacheTask> task =
deserialize_thread.TakeTask();
// At this point, the cached script's function 'f' is not compiled, but the
// matching function in the deserialized graph is compiled, so a background
// merge is recommended.
task->SourceTextAvailable(isolate(), NewString(kSourceCode), default_origin);
CHECK(task->ShouldMergeWithExistingScript());
MergeThread merge_thread(task.get());
CHECK(merge_thread.Start());
merge_thread.Join();
// Complete compilation on the main thread. This step installs compiled data
// for the function 'f'.
ScriptCompiler::Source source(NewString(kSourceCode), default_origin,
cached_data.release(), task.release());
Local<Script> script =
ScriptCompiler::Compile(context(), &source,
ScriptCompiler::kConsumeCodeCache)
.ToLocalChecked();
CHECK(!script->Run(context()).IsEmpty());
// Ensure that we can get the string representation of 'f', which requires the
// ScopeInfo to be set correctly.
Local<String> expected =
String::NewFromOneByte(isolate(), kFunctionText).ToLocalChecked();
Local<Value> actual = RunGlobalFunc("f");
CHECK(expected->StrictEquals(actual));
}
TEST_F(MergeDeserializedCodeTest, MergeThatStartsButDoesNotFinish) {
i::v8_flags.merge_background_deserialized_script_with_compilation_cache =
true;
constexpr int kSimultaneousScripts = 10;
std::vector<std::unique_ptr<v8::ScriptCompiler::CachedData>> cached_data;
IsolateAndContextScope scope(this);
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate());
ScriptOrigin default_origin(NewString(""));
i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(
i_isolate->heap());
// Compile the script for the first time to produce code cache data.
{
v8::HandleScope handle_scope(isolate());
Local<Script> script =
Script::Compile(context(), NewString(kSourceCode), &default_origin)
.ToLocalChecked();
CHECK(!script->Run(context()).IsEmpty());
// Create a bunch of copies of the code cache data.
for (int i = 0; i < kSimultaneousScripts; ++i) {
cached_data.emplace_back(
ScriptCompiler::CreateCodeCache(script->GetUnboundScript()));
}
// Age the top-level bytecode so that the Isolate compilation cache will
// contain only the Script.
i::SharedFunctionInfo::EnsureOldForTesting(GetSharedFunctionInfo(script));
}
InvokeMajorGC(i_isolate);
// A second round of GC is necessary in case incremental marking had already
// started before the bytecode was aged.
InvokeMajorGC(i_isolate);
// Start several background deserializations.
std::vector<std::unique_ptr<DeserializeThread>> deserialize_threads;
for (int i = 0; i < kSimultaneousScripts; ++i) {
deserialize_threads.push_back(std::make_unique<DeserializeThread>(
ScriptCompiler::StartConsumingCodeCache(
isolate(), std::make_unique<ScriptCompiler::CachedData>(
cached_data[i]->data, cached_data[i]->length,
ScriptCompiler::CachedData::BufferNotOwned))));
}
for (int i = 0; i < kSimultaneousScripts; ++i) {
CHECK(deserialize_threads[i]->Start());
}
for (int i = 0; i < kSimultaneousScripts; ++i) {
deserialize_threads[i]->Join();
}
// Start background merges for all of those simultaneous scripts.
std::vector<std::unique_ptr<ScriptCompiler::ConsumeCodeCacheTask>> tasks;
std::vector<std::unique_ptr<MergeThread>> merge_threads;
for (int i = 0; i < kSimultaneousScripts; ++i) {
tasks.push_back(deserialize_threads[i]->TakeTask());
tasks[i]->SourceTextAvailable(isolate(), NewString(kSourceCode),
default_origin);
CHECK(tasks[i]->ShouldMergeWithExistingScript());
merge_threads.push_back(std::make_unique<MergeThread>(tasks[i].get()));
}
for (int i = 0; i < kSimultaneousScripts; ++i) {
CHECK(merge_threads[i]->Start());
}
for (int i = 0; i < kSimultaneousScripts; ++i) {
merge_threads[i]->Join();
}
// Complete compilation of each script on the main thread. The first one will
// actually finish its merge; the others will abandon their in-progress merges
// and instead use the result from the first script since it will be in the
// Isolate compilation cache.
i::IndirectHandle<i::SharedFunctionInfo> first_script_sfi;
for (int i = 0; i < kSimultaneousScripts; ++i) {
ScriptCompiler::Source source(NewString(kSourceCode), default_origin,
cached_data[i].release(), tasks[i].release());
Local<Script> script =
ScriptCompiler::Compile(context(), &source,
ScriptCompiler::kConsumeCodeCache)
.ToLocalChecked();
if (i == 0) {
first_script_sfi = i::handle(GetSharedFunctionInfo(script), i_isolate);
} else {
CHECK_EQ(*first_script_sfi, GetSharedFunctionInfo(script));
}
CHECK(!script->Run(context()).IsEmpty());
}
}
} // namespace v8

View File

@ -0,0 +1,136 @@
// Copyright 2017 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-template.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
using DictionaryTemplateTest = v8::TestWithContext;
namespace v8 {
namespace {
v8::Local<v8::String> v8_str(v8::Isolate* isolate, const char* x) {
return v8::String::NewFromUtf8(isolate, x).ToLocalChecked();
}
v8::Local<v8::Integer> v8_int(v8::Isolate* isolate, int x) {
return v8::Integer::New(isolate, x);
}
} // namespace
TEST_F(DictionaryTemplateTest, SetPropertiesAndInstantiateWithoutValues) {
HandleScope scope(isolate());
constexpr std::string_view property_names[] = {"a", "b"};
Local<DictionaryTemplate> tpl =
DictionaryTemplate::New(isolate(), property_names);
MaybeLocal<Value> values[2];
Local<Object> instance = tpl->NewInstance(context(), values);
EXPECT_FALSE(instance.IsEmpty());
EXPECT_FALSE(
instance->HasOwnProperty(context(), v8_str(isolate(), "a")).ToChecked());
EXPECT_FALSE(
instance->HasOwnProperty(context(), v8_str(isolate(), "b")).ToChecked());
}
TEST_F(DictionaryTemplateTest, SetPropertiesAndInstantiateWithSomeValues) {
HandleScope scope(isolate());
constexpr std::string_view property_names[] = {"a", "b"};
Local<DictionaryTemplate> tpl =
DictionaryTemplate::New(isolate(), property_names);
MaybeLocal<Value> values[2] = {{}, v8_str(isolate(), "b_value")};
Local<Object> instance = tpl->NewInstance(context(), values);
EXPECT_FALSE(instance.IsEmpty());
EXPECT_FALSE(
instance->HasOwnProperty(context(), v8_str(isolate(), "a")).ToChecked());
EXPECT_TRUE(
instance->HasOwnProperty(context(), v8_str(isolate(), "b")).ToChecked());
}
TEST_F(DictionaryTemplateTest, SetPropertiesAndInstantiateWithAllValues) {
HandleScope scope(isolate());
constexpr std::string_view property_names[] = {"a", "b"};
Local<DictionaryTemplate> tpl =
DictionaryTemplate::New(isolate(), property_names);
MaybeLocal<Value> values[2] = {v8_str(isolate(), "a_value"),
v8_str(isolate(), "b_value")};
Local<Object> instance = tpl->NewInstance(context(), values);
EXPECT_FALSE(instance.IsEmpty());
EXPECT_TRUE(
instance->HasOwnProperty(context(), v8_str(isolate(), "a")).ToChecked());
EXPECT_TRUE(
instance->HasOwnProperty(context(), v8_str(isolate(), "b")).ToChecked());
}
TEST_F(DictionaryTemplateTest,
TestPropertyTransitionWithDifferentRepresentation) {
HandleScope scope(isolate());
constexpr std::string_view property_names[] = {"q", "a"};
Local<DictionaryTemplate> tpl =
DictionaryTemplate::New(isolate(), property_names);
constexpr int32_t kBoxedInt = 1 << 31;
MaybeLocal<Value> values[2] = {{}, v8_int(isolate(), kBoxedInt)};
Local<Object> instance1 = tpl->NewInstance(context(), values);
auto value1 =
instance1->Get(context(), v8_str(isolate(), "a")).ToLocalChecked();
EXPECT_EQ(Int32::Cast(*value1)->Value(), kBoxedInt);
// Now transition from a boxed int to a SMI.
constexpr int32_t kSmi = 42;
values[1] = v8_int(isolate(), kSmi);
Local<Object> instance2 = tpl->NewInstance(context(), values);
auto value2 =
instance2->Get(context(), v8_str(isolate(), "a")).ToLocalChecked();
EXPECT_EQ(Int32::Cast(*value2)->Value(), kSmi);
// Now from SMI back to a boxed int again, just in case.
values[1] = v8_int(isolate(), kBoxedInt);
Local<Object> instance3 = tpl->NewInstance(context(), values);
auto value3 =
instance3->Get(context(), v8_str(isolate(), "a")).ToLocalChecked();
EXPECT_EQ(Int32::Cast(*value3)->Value(), kBoxedInt);
}
TEST_F(DictionaryTemplateTest, PrototypeContext) {
HandleScope handle_scope(isolate());
constexpr std::string_view property_names[] = {"a", "b"};
Local<DictionaryTemplate> tpl =
DictionaryTemplate::New(isolate(), property_names);
MaybeLocal<Value> fast_values[2] = {v8_str(isolate(), "a_value"),
v8_str(isolate(), "b_value")};
MaybeLocal<Value> slow_values[2] = {{}, v8_str(isolate(), "b_value")};
Local<Object> instance1 = tpl->NewInstance(context(), fast_values);
Local<Object> object1 = v8::Object::New(isolate());
Local<Object> instance2, instance3;
Local<Object> object2;
{
Local<Context> context2 = Context::New(isolate());
v8::Context::Scope scope(context2);
instance2 = tpl->NewInstance(context2, fast_values);
instance3 = tpl->NewInstance(context2, slow_values);
object2 = v8::Object::New(isolate());
}
EXPECT_TRUE(instance1->GetPrototypeV2() == object1->GetPrototypeV2());
EXPECT_TRUE(instance2->GetPrototypeV2() == object2->GetPrototypeV2());
EXPECT_FALSE(instance1->GetPrototypeV2() == instance2->GetPrototypeV2());
EXPECT_TRUE(instance2->GetPrototypeV2() == instance3->GetPrototypeV2());
}
} // namespace v8

View File

@ -0,0 +1,270 @@
// Copyright 2017 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-context.h"
#include "include/v8-exception.h"
#include "include/v8-isolate.h"
#include "include/v8-local-handle.h"
#include "include/v8-persistent-handle.h"
#include "include/v8-template.h"
#include "src/flags/flags.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
namespace {
class APIExceptionTest : public TestWithIsolate {
public:
static void CEvaluate(const v8::FunctionCallbackInfo<v8::Value>& info) {
CHECK(i::ValidateCallbackInfo(info));
v8::HandleScope scope(info.GetIsolate());
TryRunJS(info.GetIsolate(),
info[0]
->ToString(info.GetIsolate()->GetCurrentContext())
.ToLocalChecked());
}
static void CCatcher(const v8::FunctionCallbackInfo<v8::Value>& info) {
CHECK(i::ValidateCallbackInfo(info));
if (info.Length() < 1) {
info.GetReturnValue().Set(false);
return;
}
v8::HandleScope scope(info.GetIsolate());
v8::TryCatch try_catch(info.GetIsolate());
MaybeLocal<Value> result =
TryRunJS(info.GetIsolate(),
info[0]
->ToString(info.GetIsolate()->GetCurrentContext())
.ToLocalChecked());
CHECK(!try_catch.HasCaught() || result.IsEmpty());
info.GetReturnValue().Set(try_catch.HasCaught());
}
};
class V8_NODISCARD ScopedExposeGc {
public:
ScopedExposeGc() : was_exposed_(i::v8_flags.expose_gc) {
i::v8_flags.expose_gc = true;
}
~ScopedExposeGc() { i::v8_flags.expose_gc = was_exposed_; }
private:
const bool was_exposed_;
};
TEST_F(APIExceptionTest, ExceptionMessageDoesNotKeepContextAlive) {
i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(
i_isolate()->heap());
ScopedExposeGc expose_gc;
Persistent<Context> weak_context;
{
HandleScope handle_scope(isolate());
Local<Context> context = Context::New(isolate());
weak_context.Reset(isolate(), context);
weak_context.SetWeak();
Context::Scope context_scope(context);
TryCatch try_catch(isolate());
isolate()->ThrowException(Undefined(isolate()));
}
isolate()->RequestGarbageCollectionForTesting(
Isolate::kFullGarbageCollection);
EXPECT_TRUE(weak_context.IsEmpty());
}
TEST_F(APIExceptionTest, TryCatchCustomException) {
v8::HandleScope scope(isolate());
v8::Local<Context> context = Context::New(isolate());
v8::Context::Scope context_scope(context);
v8::TryCatch try_catch(isolate());
TryRunJS(
"function CustomError() { this.a = 'b'; }"
"(function f() { throw new CustomError(); })();");
EXPECT_TRUE(try_catch.HasCaught());
EXPECT_TRUE(try_catch.Exception()
->ToObject(context)
.ToLocalChecked()
->Get(context, NewString("a"))
.ToLocalChecked()
->Equals(context, NewString("b"))
.FromJust());
}
class TryCatchNestedTest : public TestWithIsolate {
public:
void TryCatchNested1Helper(int depth) {
if (depth > 0) {
v8::TryCatch try_catch(isolate());
try_catch.SetVerbose(true);
TryCatchNested1Helper(depth - 1);
EXPECT_TRUE(try_catch.HasCaught());
try_catch.ReThrow();
} else {
isolate()->ThrowException(NewString("E1"));
}
}
void TryCatchNested2Helper(int depth) {
if (depth > 0) {
v8::TryCatch try_catch(isolate());
try_catch.SetVerbose(true);
TryCatchNested2Helper(depth - 1);
EXPECT_TRUE(try_catch.HasCaught());
try_catch.ReThrow();
} else {
TryRunJS("throw 'E2';");
}
}
};
TEST_F(TryCatchNestedTest, TryCatchNested) {
v8::HandleScope scope(isolate());
Local<Context> context = Context::New(isolate());
v8::Context::Scope context_scope(context);
{
// Test nested try-catch with a native throw in the end.
v8::TryCatch try_catch(isolate());
TryCatchNested1Helper(5);
EXPECT_TRUE(try_catch.HasCaught());
EXPECT_EQ(
0,
strcmp(*v8::String::Utf8Value(isolate(), try_catch.Exception()), "E1"));
}
{
// Test nested try-catch with a JavaScript throw in the end.
v8::TryCatch try_catch(isolate());
TryCatchNested2Helper(5);
EXPECT_TRUE(try_catch.HasCaught());
EXPECT_EQ(
0,
strcmp(*v8::String::Utf8Value(isolate(), try_catch.Exception()), "E2"));
}
}
TEST_F(APIExceptionTest, TryCatchFinallyUsingTryCatchHandler) {
v8::HandleScope scope(isolate());
Local<Context> context = Context::New(isolate());
v8::Context::Scope context_scope(context);
v8::TryCatch try_catch(isolate());
TryRunJS("try { throw ''; } catch (e) {}");
EXPECT_TRUE(!try_catch.HasCaught());
TryRunJS("try { throw ''; } finally {}");
EXPECT_TRUE(try_catch.HasCaught());
try_catch.Reset();
TryRunJS(
"(function() {"
"try { throw ''; } finally { return; }"
"})()");
EXPECT_TRUE(!try_catch.HasCaught());
TryRunJS(
"(function()"
" { try { throw ''; } finally { throw 0; }"
"})()");
EXPECT_TRUE(try_catch.HasCaught());
}
TEST_F(APIExceptionTest, TryFinallyMessage) {
v8::HandleScope scope(isolate());
v8::Local<Context> context = Context::New(isolate());
v8::Context::Scope context_scope(context);
{
// Test that the original error message is not lost if there is a
// recursive call into Javascript is done in the finally block, e.g. to
// initialize an IC. (crbug.com/129171)
TryCatch try_catch(isolate());
const char* trigger_ic =
"try { \n"
" throw new Error('test'); \n"
"} finally { \n"
" var x = 0; \n"
" x++; \n" // Trigger an IC initialization here.
"} \n";
TryRunJS(trigger_ic);
EXPECT_TRUE(try_catch.HasCaught());
Local<Message> message = try_catch.Message();
EXPECT_TRUE(!message.IsEmpty());
EXPECT_EQ(2, message->GetLineNumber(context).FromJust());
}
{
// Test that the original exception message is indeed overwritten if
// a new error is thrown in the finally block.
TryCatch try_catch(isolate());
const char* throw_again =
"try { \n"
" throw new Error('test'); \n"
"} finally { \n"
" var x = 0; \n"
" x++; \n"
" throw new Error('again'); \n" // This is the new uncaught error.
"} \n";
TryRunJS(throw_again);
EXPECT_TRUE(try_catch.HasCaught());
Local<Message> message = try_catch.Message();
EXPECT_TRUE(!message.IsEmpty());
EXPECT_EQ(6, message->GetLineNumber(context).FromJust());
}
}
// Test that a try-finally block doesn't shadow a try-catch block
// when setting up an external handler.
//
// BUG(271): Some of the exception propagation does not work on the
// ARM simulator because the simulator separates the C++ stack and the
// JS stack. This test therefore fails on the simulator. The test is
// not threaded to allow the threading tests to run on the simulator.
TEST_F(APIExceptionTest, TryCatchInTryFinally) {
v8::HandleScope scope(isolate());
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate());
templ->Set(isolate(), "CCatcher",
v8::FunctionTemplate::New(isolate(), CCatcher));
Local<Context> context = Context::New(isolate(), nullptr, templ);
v8::Context::Scope context_scope(context);
Local<Value> result = RunJS(
"try {"
" try {"
" CCatcher('throw 7;');"
" } finally {"
" }"
"} catch (e) {"
"}");
EXPECT_TRUE(result->IsTrue());
}
TEST_F(APIExceptionTest, TryCatchFinallyStoresMessageUsingTryCatchHandler) {
v8::HandleScope scope(isolate());
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate());
templ->Set(isolate(), "CEvaluate",
v8::FunctionTemplate::New(isolate(), CEvaluate));
Local<Context> context = Context::New(isolate(), nullptr, templ);
v8::Context::Scope context_scope(context);
v8::TryCatch try_catch(isolate());
TryRunJS(
"try {"
" CEvaluate('throw 1;');"
"} finally {"
"}");
EXPECT_TRUE(try_catch.HasCaught());
EXPECT_TRUE(!try_catch.Message().IsEmpty());
String::Utf8Value exception_value(isolate(), try_catch.Exception());
EXPECT_EQ(0, strcmp(*exception_value, "1"));
try_catch.Reset();
TryRunJS(
"try {"
" CEvaluate('throw 1;');"
"} finally {"
" throw 2;"
"}");
EXPECT_TRUE(try_catch.HasCaught());
EXPECT_TRUE(!try_catch.Message().IsEmpty());
String::Utf8Value finally_exception_value(isolate(), try_catch.Exception());
EXPECT_EQ(0, strcmp(*finally_exception_value, "2"));
}
} // namespace
} // namespace v8

View File

@ -0,0 +1,167 @@
// Copyright 2017 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/unittests/heap/heap-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
namespace {
namespace {
class GCCallbacksTest : public internal::TestWithHeapInternalsAndContext {
public:
static void PrologueCallbackAlloc(v8::Isolate* isolate, v8::GCType,
v8::GCCallbackFlags flags) {
v8::HandleScope scope(isolate);
CHECK_EQ(flags, v8::kNoGCCallbackFlags);
CHECK_EQ(current_test_->gc_callbacks_isolate_, isolate);
++current_test_->prologue_call_count_alloc_;
if (!i::v8_flags.single_generation) {
// Simulate full heap to see if we will reenter this callback
current_test_->SimulateFullSpace(current_test_->heap()->new_space());
}
Local<Object> obj = Object::New(isolate);
CHECK(!obj.IsEmpty());
current_test_->InvokeAtomicMajorGC();
}
static void EpilogueCallbackAlloc(v8::Isolate* isolate, v8::GCType,
v8::GCCallbackFlags flags) {
v8::HandleScope scope(isolate);
CHECK_EQ(flags, v8::kNoGCCallbackFlags);
CHECK_EQ(current_test_->gc_callbacks_isolate_, isolate);
++current_test_->epilogue_call_count_alloc_;
if (!i::v8_flags.single_generation) {
// Simulate full heap to see if we will reenter this callback
current_test_->SimulateFullSpace(current_test_->heap()->new_space());
}
Local<Object> obj = Object::New(isolate);
CHECK(!obj.IsEmpty());
current_test_->InvokeAtomicMajorGC();
}
static void PrologueCallback(v8::Isolate* isolate, v8::GCType,
v8::GCCallbackFlags flags) {
CHECK_EQ(flags, v8::kNoGCCallbackFlags);
CHECK_EQ(current_test_->gc_callbacks_isolate_, isolate);
++current_test_->prologue_call_count_;
}
static void EpilogueCallback(v8::Isolate* isolate, v8::GCType,
v8::GCCallbackFlags flags) {
CHECK_EQ(flags, v8::kNoGCCallbackFlags);
CHECK_EQ(current_test_->gc_callbacks_isolate_, isolate);
++current_test_->epilogue_call_count_;
}
static void PrologueCallbackSecond(v8::Isolate* isolate, v8::GCType,
v8::GCCallbackFlags flags) {
CHECK_EQ(flags, v8::kNoGCCallbackFlags);
CHECK_EQ(current_test_->gc_callbacks_isolate_, isolate);
++current_test_->prologue_call_count_second_;
}
static void EpilogueCallbackSecond(v8::Isolate* isolate, v8::GCType,
v8::GCCallbackFlags flags) {
CHECK_EQ(flags, v8::kNoGCCallbackFlags);
CHECK_EQ(current_test_->gc_callbacks_isolate_, isolate);
++current_test_->epilogue_call_count_second_;
}
static void PrologueCallbackNew(v8::Isolate* isolate, v8::GCType,
v8::GCCallbackFlags flags, void* data) {
CHECK_EQ(flags, v8::kNoGCCallbackFlags);
CHECK_EQ(current_test_->gc_callbacks_isolate_, isolate);
++*static_cast<int*>(data);
}
static void EpilogueCallbackNew(v8::Isolate* isolate, v8::GCType,
v8::GCCallbackFlags flags, void* data) {
CHECK_EQ(flags, v8::kNoGCCallbackFlags);
CHECK_EQ(current_test_->gc_callbacks_isolate_, isolate);
++*static_cast<int*>(data);
}
protected:
void SetUp() override {
internal::TestWithHeapInternalsAndContext::SetUp();
DCHECK_NULL(current_test_);
current_test_ = this;
}
void TearDown() override {
DCHECK_NOT_NULL(current_test_);
current_test_ = nullptr;
internal::TestWithHeapInternalsAndContext::TearDown();
}
static GCCallbacksTest* current_test_;
v8::Isolate* gc_callbacks_isolate_ = nullptr;
int prologue_call_count_ = 0;
int epilogue_call_count_ = 0;
int prologue_call_count_second_ = 0;
int epilogue_call_count_second_ = 0;
int prologue_call_count_alloc_ = 0;
int epilogue_call_count_alloc_ = 0;
};
GCCallbacksTest* GCCallbacksTest::current_test_ = nullptr;
} // namespace
TEST_F(GCCallbacksTest, GCCallbacks) {
// For SimulateFullSpace in PrologueCallbackAlloc and EpilogueCallbackAlloc.
i::v8_flags.stress_concurrent_allocation = false;
v8::Isolate* isolate = context()->GetIsolate();
gc_callbacks_isolate_ = isolate;
isolate->AddGCPrologueCallback(PrologueCallback);
isolate->AddGCEpilogueCallback(EpilogueCallback);
CHECK_EQ(0, prologue_call_count_);
CHECK_EQ(0, epilogue_call_count_);
InvokeMajorGC();
CHECK_EQ(1, prologue_call_count_);
CHECK_EQ(1, epilogue_call_count_);
isolate->AddGCPrologueCallback(PrologueCallbackSecond);
isolate->AddGCEpilogueCallback(EpilogueCallbackSecond);
InvokeMajorGC();
CHECK_EQ(2, prologue_call_count_);
CHECK_EQ(2, epilogue_call_count_);
CHECK_EQ(1, prologue_call_count_second_);
CHECK_EQ(1, epilogue_call_count_second_);
isolate->RemoveGCPrologueCallback(PrologueCallback);
isolate->RemoveGCEpilogueCallback(EpilogueCallback);
InvokeMajorGC();
CHECK_EQ(2, prologue_call_count_);
CHECK_EQ(2, epilogue_call_count_);
CHECK_EQ(2, prologue_call_count_second_);
CHECK_EQ(2, epilogue_call_count_second_);
isolate->RemoveGCPrologueCallback(PrologueCallbackSecond);
isolate->RemoveGCEpilogueCallback(EpilogueCallbackSecond);
InvokeMajorGC();
CHECK_EQ(2, prologue_call_count_);
CHECK_EQ(2, epilogue_call_count_);
CHECK_EQ(2, prologue_call_count_second_);
CHECK_EQ(2, epilogue_call_count_second_);
CHECK_EQ(0, prologue_call_count_alloc_);
CHECK_EQ(0, epilogue_call_count_alloc_);
isolate->AddGCPrologueCallback(PrologueCallbackAlloc);
isolate->AddGCEpilogueCallback(EpilogueCallbackAlloc);
InvokeAtomicMajorGC();
CHECK_EQ(1, prologue_call_count_alloc_);
CHECK_EQ(1, epilogue_call_count_alloc_);
isolate->RemoveGCPrologueCallback(PrologueCallbackAlloc);
isolate->RemoveGCEpilogueCallback(EpilogueCallbackAlloc);
}
} // namespace
} // namespace v8

View File

@ -0,0 +1,229 @@
// Copyright 2017 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-exception.h"
#include "include/v8-function.h"
#include "include/v8-local-handle.h"
#include "include/v8-object.h"
#include "include/v8-template.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
namespace {
using InterceptorTest = TestWithContext;
v8::Intercepted NamedGetter(Local<Name> property,
const PropertyCallbackInfo<Value>& info) {
return v8::Intercepted::kNo;
}
TEST_F(InterceptorTest, FreezeApiObjectWithInterceptor) {
TryCatch try_catch(isolate());
Local<FunctionTemplate> tmpl = FunctionTemplate::New(isolate());
tmpl->InstanceTemplate()->SetHandler(
NamedPropertyHandlerConfiguration(NamedGetter));
Local<Function> ctor = tmpl->GetFunction(context()).ToLocalChecked();
Local<Object> obj = ctor->NewInstance(context()).ToLocalChecked();
ASSERT_TRUE(
obj->SetIntegrityLevel(context(), IntegrityLevel::kFrozen).IsNothing());
ASSERT_TRUE(try_catch.HasCaught());
}
} // namespace
namespace internal {
namespace {
class InterceptorLoggingTest : public TestWithNativeContext {
public:
InterceptorLoggingTest() = default;
static const int kTestIndex = 0;
static v8::Intercepted NamedPropertyGetter(
Local<v8::Name> name, const v8::PropertyCallbackInfo<Value>& info) {
LogCallback(info, "named getter");
return v8::Intercepted::kNo;
}
static v8::Intercepted NamedPropertySetter(
Local<v8::Name> name, Local<v8::Value> value,
const v8::PropertyCallbackInfo<void>& info) {
LogCallback(info, "named setter");
return v8::Intercepted::kNo;
}
static v8::Intercepted NamedPropertyQuery(
Local<v8::Name> name, const v8::PropertyCallbackInfo<v8::Integer>& info) {
LogCallback(info, "named query");
return v8::Intercepted::kNo;
}
static v8::Intercepted NamedPropertyDeleter(
Local<v8::Name> name, const v8::PropertyCallbackInfo<v8::Boolean>& info) {
LogCallback(info, "named deleter");
return v8::Intercepted::kNo;
}
static void NamedPropertyEnumerator(
const v8::PropertyCallbackInfo<Array>& info) {
LogCallback(info, "named enumerator");
}
static v8::Intercepted NamedPropertyDefiner(
Local<v8::Name> name, const v8::PropertyDescriptor& desc,
const v8::PropertyCallbackInfo<void>& info) {
LogCallback(info, "named definer");
return v8::Intercepted::kNo;
}
static v8::Intercepted NamedPropertyDescriptor(
Local<v8::Name> name, const v8::PropertyCallbackInfo<Value>& info) {
LogCallback(info, "named descriptor");
return v8::Intercepted::kNo;
}
static v8::Intercepted IndexedPropertyGetter(
uint32_t index, const v8::PropertyCallbackInfo<Value>& info) {
LogCallback(info, "indexed getter");
return v8::Intercepted::kNo;
}
static v8::Intercepted IndexedPropertySetter(
uint32_t index, Local<v8::Value> value,
const v8::PropertyCallbackInfo<void>& info) {
LogCallback(info, "indexed setter");
return v8::Intercepted::kNo;
}
static v8::Intercepted IndexedPropertyQuery(
uint32_t index, const v8::PropertyCallbackInfo<v8::Integer>& info) {
LogCallback(info, "indexed query");
return v8::Intercepted::kNo;
}
static v8::Intercepted IndexedPropertyDeleter(
uint32_t index, const v8::PropertyCallbackInfo<v8::Boolean>& info) {
LogCallback(info, "indexed deleter");
return v8::Intercepted::kNo;
}
static void IndexedPropertyEnumerator(
const v8::PropertyCallbackInfo<Array>& info) {
LogCallback(info, "indexed enumerator");
}
static v8::Intercepted IndexedPropertyDefiner(
uint32_t index, const v8::PropertyDescriptor& desc,
const v8::PropertyCallbackInfo<void>& info) {
LogCallback(info, "indexed definer");
return v8::Intercepted::kNo;
}
static v8::Intercepted IndexedPropertyDescriptor(
uint32_t index, const v8::PropertyCallbackInfo<Value>& info) {
LogCallback(info, "indexed descriptor");
return v8::Intercepted::kNo;
}
template <class T>
static void LogCallback(const v8::PropertyCallbackInfo<T>& info,
const char* callback_name) {
InterceptorLoggingTest* test = reinterpret_cast<InterceptorLoggingTest*>(
info.This()->GetAlignedPointerFromInternalField(kTestIndex));
test->Log(callback_name);
}
void Log(const char* callback_name) {
if (log_is_empty_) {
log_is_empty_ = false;
} else {
log_ << ", ";
}
log_ << callback_name;
}
protected:
void SetUp() override {
// Set up the object that supports full interceptors.
v8::Local<v8::ObjectTemplate> templ = v8::ObjectTemplate::New(v8_isolate());
templ->SetInternalFieldCount(1);
templ->SetHandler(v8::NamedPropertyHandlerConfiguration(
NamedPropertyGetter, NamedPropertySetter, NamedPropertyQuery,
NamedPropertyDeleter, NamedPropertyEnumerator, NamedPropertyDefiner,
NamedPropertyDescriptor));
templ->SetHandler(v8::IndexedPropertyHandlerConfiguration(
IndexedPropertyGetter, IndexedPropertySetter, IndexedPropertyQuery,
IndexedPropertyDeleter, IndexedPropertyEnumerator,
IndexedPropertyDefiner, IndexedPropertyDescriptor));
v8::Local<v8::Object> instance =
templ->NewInstance(context()).ToLocalChecked();
instance->SetAlignedPointerInInternalField(kTestIndex, this);
SetGlobalProperty("obj", instance);
}
std::string Run(const char* script) {
log_is_empty_ = true;
log_.str(std::string());
log_.clear();
RunJS(script);
return log_.str();
}
private:
bool log_is_empty_ = false;
std::stringstream log_;
};
TEST_F(InterceptorLoggingTest, DispatchTest) {
EXPECT_EQ(Run("for (var p in obj) {}"),
"indexed enumerator, named enumerator");
EXPECT_EQ(Run("Object.keys(obj)"), "indexed enumerator, named enumerator");
EXPECT_EQ(Run("obj.foo"), "named getter");
EXPECT_EQ(Run("obj[42]"), "indexed getter");
EXPECT_EQ(Run("obj.foo = null"),
"named setter, named descriptor, named query");
EXPECT_EQ(Run("obj[42] = null"),
"indexed setter, indexed descriptor, indexed query");
EXPECT_EQ(Run("Object.getOwnPropertyDescriptor(obj, 'foo')"),
"named descriptor");
EXPECT_EQ(Run("Object.getOwnPropertyDescriptor(obj, 42)"),
"indexed descriptor");
EXPECT_EQ(Run("Object.defineProperty(obj, 'foo', {value: 42})"),
"named descriptor, named definer, named setter");
EXPECT_EQ(Run("Object.defineProperty(obj, 'foo', {get(){} })"),
"named descriptor, named definer");
EXPECT_EQ(Run("Object.defineProperty(obj, 'foo', {set(value){}})"),
"named descriptor, named definer");
EXPECT_EQ(Run("Object.defineProperty(obj, 'foo', {get(){}, set(value){}})"),
"named descriptor, named definer");
EXPECT_EQ(Run("Object.defineProperty(obj, 42, {value: 'foo'})"),
"indexed descriptor, "
// then attempt definer first and fallback to setter.
"indexed definer, indexed setter");
EXPECT_EQ(Run("Object.prototype.propertyIsEnumerable.call(obj, 'a')"),
"named query");
EXPECT_EQ(Run("Object.prototype.propertyIsEnumerable.call(obj, 42)"),
"indexed query");
EXPECT_EQ(Run("Object.prototype.hasOwnProperty.call(obj, 'a')"),
"named query");
// TODO(cbruni): Fix once hasOnwProperty is fixed (https://crbug.com/872628)
EXPECT_EQ(Run("Object.prototype.hasOwnProperty.call(obj, '42')"), "");
}
} // namespace
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,168 @@
// Copyright 2017 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/execution/isolate.h"
#include "include/libplatform/libplatform.h"
#include "include/v8-platform.h"
#include "include/v8-template.h"
#include "src/base/platform/semaphore.h"
#include "src/init/v8.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
using IsolateTest = TestWithIsolate;
namespace {
class MemoryPressureTask : public v8::Task {
public:
MemoryPressureTask(Isolate* isolate, base::Semaphore* semaphore)
: isolate_(isolate), semaphore_(semaphore) {}
~MemoryPressureTask() override = default;
MemoryPressureTask(const MemoryPressureTask&) = delete;
MemoryPressureTask& operator=(const MemoryPressureTask&) = delete;
// v8::Task implementation.
void Run() override {
isolate_->MemoryPressureNotification(MemoryPressureLevel::kCritical);
semaphore_->Signal();
}
private:
Isolate* isolate_;
base::Semaphore* semaphore_;
};
} // namespace
// Check that triggering a memory pressure notification on the isolate thread
// doesn't request a GC interrupt.
TEST_F(IsolateTest, MemoryPressureNotificationForeground) {
internal::Isolate* i_isolate =
reinterpret_cast<internal::Isolate*>(isolate());
ASSERT_FALSE(i_isolate->stack_guard()->CheckGC());
isolate()->MemoryPressureNotification(MemoryPressureLevel::kCritical);
ASSERT_FALSE(i_isolate->stack_guard()->CheckGC());
}
// Check that triggering a memory pressure notification on an background thread
// requests a GC interrupt.
TEST_F(IsolateTest, MemoryPressureNotificationBackground) {
internal::Isolate* i_isolate =
reinterpret_cast<internal::Isolate*>(isolate());
base::Semaphore semaphore(0);
internal::V8::GetCurrentPlatform()->PostTaskOnWorkerThread(
TaskPriority::kUserVisible,
std::make_unique<MemoryPressureTask>(isolate(), &semaphore));
semaphore.Wait();
ASSERT_TRUE(i_isolate->stack_guard()->CheckGC());
v8::platform::PumpMessageLoop(internal::V8::GetCurrentPlatform(), isolate());
}
using IncumbentContextTest = TestWithIsolate;
// Check that Isolate::GetIncumbentContext() returns the correct one in basic
// scenarios.
TEST_F(IncumbentContextTest, Basic) {
auto Str = [&](const char* s) {
return String::NewFromUtf8(isolate(), s).ToLocalChecked();
};
auto Run = [&](Local<Context> context, const char* script) {
Context::Scope scope(context);
return Script::Compile(context, Str(script))
.ToLocalChecked()
->Run(context)
.ToLocalChecked();
};
// Set up the test environment; three contexts with getIncumbentGlobal()
// function.
Local<FunctionTemplate> get_incumbent_global = FunctionTemplate::New(
isolate(), [](const FunctionCallbackInfo<Value>& info) {
Local<Context> incumbent_context =
info.GetIsolate()->GetIncumbentContext();
info.GetReturnValue().Set(incumbent_context->Global());
});
Local<ObjectTemplate> global_template = ObjectTemplate::New(isolate());
global_template->Set(isolate(), "getIncumbentGlobal", get_incumbent_global);
Local<Context> context_a = Context::New(isolate(), nullptr, global_template);
Local<Context> context_b = Context::New(isolate(), nullptr, global_template);
Local<Context> context_c = Context::New(isolate(), nullptr, global_template);
Local<Object> global_a = context_a->Global();
Local<Object> global_b = context_b->Global();
Local<Object> global_c = context_c->Global();
Local<String> security_token = Str("security_token");
context_a->SetSecurityToken(security_token);
context_b->SetSecurityToken(security_token);
context_c->SetSecurityToken(security_token);
global_a->Set(context_a, Str("b"), global_b).ToChecked();
global_b->Set(context_b, Str("c"), global_c).ToChecked();
// Test scenario 2: A -> B -> C, then the incumbent is C.
Run(context_a, "funcA = function() { return b.funcB(); }");
Run(context_b, "funcB = function() { return c.getIncumbentGlobal(); }");
// Without BackupIncumbentScope.
EXPECT_EQ(global_b, Run(context_a, "funcA()"));
{
// With BackupIncumbentScope.
Context::BackupIncumbentScope backup_incumbent(context_a);
EXPECT_EQ(global_b, Run(context_a, "funcA()"));
}
// Test scenario 2: A -> B -> C -> C, then the incumbent is C.
Run(context_a, "funcA = function() { return b.funcB(); }");
Run(context_b, "funcB = function() { return c.funcC(); }");
Run(context_c, "funcC = function() { return getIncumbentGlobal(); }");
// Without BackupIncumbentScope.
EXPECT_EQ(global_c, Run(context_a, "funcA()"));
{
// With BackupIncumbentScope.
Context::BackupIncumbentScope backup_incumbent(context_a);
EXPECT_EQ(global_c, Run(context_a, "funcA()"));
}
}
namespace {
thread_local std::multimap<v8::CrashKeyId, std::string> crash_keys;
void CrashKeyCallback(v8::CrashKeyId id, const std::string& value) {
crash_keys.insert({id, value});
}
} // namespace
TEST_F(IsolateTest, SetAddCrashKeyCallback) {
isolate()->SetAddCrashKeyCallback(CrashKeyCallback);
i::Isolate* i_isolate = reinterpret_cast<internal::Isolate*>(isolate());
i::Heap* heap = i_isolate->heap();
size_t expected_keys_count = 5;
EXPECT_EQ(crash_keys.count(v8::CrashKeyId::kIsolateAddress), 1u);
EXPECT_EQ(crash_keys.count(v8::CrashKeyId::kReadonlySpaceFirstPageAddress),
1u);
EXPECT_EQ(crash_keys.count(v8::CrashKeyId::kOldSpaceFirstPageAddress), 1u);
EXPECT_EQ(crash_keys.count(v8::CrashKeyId::kSnapshotChecksumCalculated), 1u);
EXPECT_EQ(crash_keys.count(v8::CrashKeyId::kSnapshotChecksumExpected), 1u);
if (heap->code_range_base()) {
++expected_keys_count;
EXPECT_EQ(crash_keys.count(v8::CrashKeyId::kCodeRangeBaseAddress), 1u);
}
if (heap->code_space()->first_page()) {
++expected_keys_count;
EXPECT_EQ(crash_keys.count(v8::CrashKeyId::kCodeSpaceFirstPageAddress), 1u);
}
EXPECT_EQ(crash_keys.size(), expected_keys_count);
}
} // namespace v8

View File

@ -0,0 +1,105 @@
// Copyright 2017 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-context.h"
#include "include/v8-local-handle.h"
#include "include/v8-primitive.h"
#include "include/v8-template.h"
#include "src/api/api-inl.h"
#include "src/objects/objects-inl.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
namespace remote_object_unittest {
using RemoteObjectTest = TestWithIsolate;
namespace {
bool AccessCheck(Local<Context> accessing_context,
Local<Object> accessed_object, Local<Value> data) {
return false;
}
v8::Intercepted NamedGetter(Local<Name> property,
const PropertyCallbackInfo<Value>& info) {
return v8::Intercepted::kNo;
}
void Constructor(const FunctionCallbackInfo<Value>& info) {
ASSERT_TRUE(info.IsConstructCall());
}
} // namespace
TEST_F(RemoteObjectTest, CreationContextOfRemoteContext) {
Local<ObjectTemplate> global_template = ObjectTemplate::New(isolate());
global_template->SetAccessCheckCallbackAndHandler(
AccessCheck, NamedPropertyHandlerConfiguration(NamedGetter),
IndexedPropertyHandlerConfiguration());
Local<Object> remote_context =
Context::NewRemoteContext(isolate(), global_template).ToLocalChecked();
EXPECT_TRUE(remote_context->GetCreationContext(isolate()).IsEmpty());
}
TEST_F(RemoteObjectTest, CreationContextOfRemoteObject) {
Local<FunctionTemplate> constructor_template =
FunctionTemplate::New(isolate(), Constructor);
constructor_template->InstanceTemplate()->SetAccessCheckCallbackAndHandler(
AccessCheck, NamedPropertyHandlerConfiguration(NamedGetter),
IndexedPropertyHandlerConfiguration());
Local<Object> remote_object =
constructor_template->NewRemoteInstance().ToLocalChecked();
EXPECT_TRUE(remote_object->GetCreationContext(isolate()).IsEmpty());
}
TEST_F(RemoteObjectTest, RemoteContextInstanceChecks) {
Local<FunctionTemplate> parent_template =
FunctionTemplate::New(isolate(), Constructor);
Local<FunctionTemplate> constructor_template =
FunctionTemplate::New(isolate(), Constructor);
constructor_template->InstanceTemplate()->SetAccessCheckCallbackAndHandler(
AccessCheck, NamedPropertyHandlerConfiguration(NamedGetter),
IndexedPropertyHandlerConfiguration());
constructor_template->Inherit(parent_template);
Local<Object> remote_context =
Context::NewRemoteContext(isolate(),
constructor_template->InstanceTemplate())
.ToLocalChecked();
EXPECT_TRUE(parent_template->HasInstance(remote_context));
EXPECT_TRUE(constructor_template->HasInstance(remote_context));
}
TEST_F(RemoteObjectTest, TypeOfRemoteContext) {
Local<ObjectTemplate> global_template = ObjectTemplate::New(isolate());
global_template->SetAccessCheckCallbackAndHandler(
AccessCheck, NamedPropertyHandlerConfiguration(NamedGetter),
IndexedPropertyHandlerConfiguration());
Local<Object> remote_context =
Context::NewRemoteContext(isolate(), global_template).ToLocalChecked();
String::Utf8Value result(isolate(), remote_context->TypeOf(isolate()));
EXPECT_STREQ("object", *result);
}
TEST_F(RemoteObjectTest, TypeOfRemoteObject) {
Local<FunctionTemplate> constructor_template =
FunctionTemplate::New(isolate(), Constructor);
constructor_template->InstanceTemplate()->SetAccessCheckCallbackAndHandler(
AccessCheck, NamedPropertyHandlerConfiguration(NamedGetter),
IndexedPropertyHandlerConfiguration());
Local<Object> remote_object =
constructor_template->NewRemoteInstance().ToLocalChecked();
String::Utf8Value result(isolate(), remote_object->TypeOf(isolate()));
EXPECT_STREQ("object", *result);
}
} // namespace remote_object_unittest
} // namespace v8

View File

@ -0,0 +1,64 @@
// Copyright 2017 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-isolate.h"
#include "src/flags/flags.h"
#include "src/heap/heap-controller.h"
#include "src/heap/heap.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
TEST(ResourceConstraints, ConfigureDefaultsFromHeapSizeSmall) {
const size_t MB = static_cast<size_t>(i::MB);
v8::ResourceConstraints constraints;
constraints.ConfigureDefaultsFromHeapSize(1 * MB, 1 * MB);
ASSERT_EQ(i::Heap::MinOldGenerationSize(),
constraints.max_old_generation_size_in_bytes());
ASSERT_EQ(i::Heap::MinYoungGenerationSize(),
constraints.max_young_generation_size_in_bytes());
ASSERT_EQ(0u, constraints.initial_old_generation_size_in_bytes());
ASSERT_EQ(0u, constraints.initial_young_generation_size_in_bytes());
}
TEST(ResourceConstraints, ConfigureDefaultsFromHeapSizeLarge) {
const size_t KB = static_cast<size_t>(i::KB);
const size_t MB = static_cast<size_t>(i::MB);
const size_t pm = i::Heap::kPointerMultiplier;
v8::ResourceConstraints constraints;
const size_t expected_young_gen_max_size =
i::Heap::DefaultMaxSemiSpaceSize() *
(internal::v8_flags.minor_ms ? 2 : 3);
constraints.ConfigureDefaultsFromHeapSize(
50u * MB, internal::V8HeapTrait::kMaxSize + expected_young_gen_max_size);
// Check that for large heap sizes max semi space size is set to the maximum
// supported capacity (i.e. 8MB with pointer compression and 16MB without;
// MinorMS supports double capacity).
ASSERT_EQ(expected_young_gen_max_size,
constraints.max_young_generation_size_in_bytes());
ASSERT_EQ(internal::V8HeapTrait::kMaxSize,
constraints.max_old_generation_size_in_bytes());
// Check that for small initial heap sizes initial semi space size is set to
// the minimum supported capacity (i.e. 1MB with pointer compression and 512KB
// without).
ASSERT_EQ((internal::v8_flags.minor_ms ? 2 : 3) * 512 * pm * KB,
constraints.initial_young_generation_size_in_bytes());
ASSERT_EQ(50u * MB - (internal::v8_flags.minor_ms ? 2 : 3) * 512 * pm * KB,
constraints.initial_old_generation_size_in_bytes());
}
TEST(ResourceConstraints, ConfigureDefaults) {
const size_t GB = static_cast<size_t>(i::GB);
v8::ResourceConstraints constraints;
constraints.ConfigureDefaults(2u * GB, 0u);
ASSERT_EQ(i::V8HeapTrait::kMaxSize / 2,
constraints.max_old_generation_size_in_bytes());
ASSERT_EQ(0u, constraints.initial_old_generation_size_in_bytes());
ASSERT_EQ(i::Heap::DefaultMaxSemiSpaceSize() / 2 *
(internal::v8_flags.minor_ms ? (2 * 2) : 3),
constraints.max_young_generation_size_in_bytes());
ASSERT_EQ(0u, constraints.initial_young_generation_size_in_bytes());
}
} // namespace v8

View File

@ -0,0 +1,79 @@
// Copyright 2024 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 <limits>
#include "include/v8-internal.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8::internal {
// Small Smis are 31 bit in size and used in compression scenarios or when
// explicitly enabled otherwise.
using SmallSmi = SmiTagging<4>;
// Large Smis are 32-bit in size and are used in uncompressed 64-bit builds by
// default when not explicitly opting for 31-bit Smis.
using LargeSmi = SmiTagging<8>;
constexpr int32_t kInt31Max = std::numeric_limits<int32_t>::max() / 2;
constexpr int32_t kInt31Min = std::numeric_limits<int32_t>::min() / 2;
constexpr uint32_t kInt31MaxAsUint = std::numeric_limits<int32_t>::max() / 2;
TEST(SmiTaggingTest, AssertCornerCases) {
static_assert(SmallSmi::IsValidSmi(0));
// int8_t, uint8_t
static_assert(SmallSmi::IsValidSmi(std::numeric_limits<int8_t>::max()));
static_assert(SmallSmi::IsValidSmi(std::numeric_limits<int8_t>::min()));
static_assert(SmallSmi::IsValidSmi(std::numeric_limits<uint8_t>::max()));
static_assert(SmallSmi::IsValidSmi(std::numeric_limits<uint8_t>::min()));
// int16_t, uint16_t
static_assert(SmallSmi::IsValidSmi(std::numeric_limits<int16_t>::max()));
static_assert(SmallSmi::IsValidSmi(std::numeric_limits<int16_t>::min()));
static_assert(SmallSmi::IsValidSmi(std::numeric_limits<uint16_t>::max()));
static_assert(SmallSmi::IsValidSmi(std::numeric_limits<uint16_t>::min()));
// int31_t, uint31_t
static_assert(SmallSmi::IsValidSmi(kInt31Max));
static_assert(SmallSmi::IsValidSmi(kInt31Min));
static_assert(SmallSmi::IsValidSmi(kInt31MaxAsUint));
static_assert(!SmallSmi::IsValidSmi(kInt31Max + 1));
static_assert(!SmallSmi::IsValidSmi(kInt31Min - 1));
static_assert(!SmallSmi::IsValidSmi(kInt31MaxAsUint + 1));
// int32_t, uint32_t
static_assert(!SmallSmi::IsValidSmi(std::numeric_limits<int32_t>::max()));
static_assert(!SmallSmi::IsValidSmi(std::numeric_limits<int32_t>::min()));
static_assert(!SmallSmi::IsValidSmi(std::numeric_limits<uint32_t>::max()));
// int64_t, uint64_t
static_assert(!SmallSmi::IsValidSmi(std::numeric_limits<int64_t>::max()));
static_assert(!SmallSmi::IsValidSmi(std::numeric_limits<int64_t>::min()));
static_assert(!SmallSmi::IsValidSmi(std::numeric_limits<uint64_t>::max()));
static_assert(LargeSmi::IsValidSmi(0));
// int8_t, uint8_t
static_assert(LargeSmi::IsValidSmi(std::numeric_limits<int8_t>::max()));
static_assert(LargeSmi::IsValidSmi(std::numeric_limits<int8_t>::min()));
static_assert(LargeSmi::IsValidSmi(std::numeric_limits<uint8_t>::max()));
static_assert(LargeSmi::IsValidSmi(std::numeric_limits<uint8_t>::min()));
// int16_t, uint16_t
static_assert(LargeSmi::IsValidSmi(std::numeric_limits<int16_t>::max()));
static_assert(LargeSmi::IsValidSmi(std::numeric_limits<int16_t>::min()));
static_assert(LargeSmi::IsValidSmi(std::numeric_limits<uint16_t>::max()));
static_assert(LargeSmi::IsValidSmi(std::numeric_limits<uint16_t>::min()));
// int31_t, uint31_t
static_assert(LargeSmi::IsValidSmi(kInt31Max));
static_assert(LargeSmi::IsValidSmi(kInt31Min));
static_assert(LargeSmi::IsValidSmi(kInt31MaxAsUint));
static_assert(LargeSmi::IsValidSmi(kInt31Max + 1));
static_assert(LargeSmi::IsValidSmi(kInt31Min - 1));
static_assert(LargeSmi::IsValidSmi(kInt31MaxAsUint + 1));
// int32_t, uint32_t
static_assert(LargeSmi::IsValidSmi(std::numeric_limits<int32_t>::max()));
static_assert(LargeSmi::IsValidSmi(std::numeric_limits<int32_t>::min()));
static_assert(!LargeSmi::IsValidSmi(std::numeric_limits<uint32_t>::max()));
// int64_t, uint64_t
static_assert(!LargeSmi::IsValidSmi(std::numeric_limits<int64_t>::max()));
static_assert(!LargeSmi::IsValidSmi(std::numeric_limits<int64_t>::min()));
static_assert(!LargeSmi::IsValidSmi(std::numeric_limits<uint64_t>::max()));
}
} // namespace v8::internal

View File

@ -0,0 +1,165 @@
// Copyright 2023 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-container.h"
#include "include/v8-primitive.h"
#include "include/v8-value.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
namespace {
using ArrayTest = TestWithContext;
using CbResult = v8::Array::CallbackResult;
TEST_F(ArrayTest, IterateEmpty) {
HandleScope scope(isolate());
Local<Array> array = Array::New(isolate());
Array::IterationCallback unreachable_callback =
[](uint32_t index, Local<Value> element, void* data) -> CbResult {
UNREACHABLE();
};
CHECK(array->Iterate(context(), unreachable_callback, nullptr).IsJust());
}
TEST_F(ArrayTest, IterateOneElement) {
HandleScope scope(isolate());
Local<Array> smi_array = Array::New(isolate());
Local<Array> double_array = Array::New(isolate());
Local<Array> object_array = Array::New(isolate());
Local<Array> dictionary_array = Array::New(isolate());
struct Data {
int sentinel;
Local<Context> context;
Isolate* isolate;
int invocation_count = 0;
};
Data data{42, context(), isolate()};
const Local<Value> kSmi = Number::New(isolate(), 333);
const uint32_t kIndex = 3;
CHECK(smi_array->Set(context(), kIndex, kSmi).FromJust());
Array::IterationCallback smi_callback =
[](uint32_t index, Local<Value> element, void* data) -> CbResult {
Data* d = reinterpret_cast<Data*>(data);
CHECK_EQ(42, d->sentinel);
++d->invocation_count;
if (index != kIndex) {
CHECK(element->IsUndefined());
return CbResult::kContinue;
}
CHECK_EQ(333, element->NumberValue(d->context).FromJust());
return CbResult::kContinue;
};
CHECK(smi_array->Iterate(context(), smi_callback, &data).IsJust());
CHECK_EQ(kIndex + 1, data.invocation_count);
const Local<Value> kDouble = Number::New(isolate(), 1.5);
CHECK(double_array->Set(context(), kIndex, kDouble).FromJust());
Array::IterationCallback double_callback =
[](uint32_t index, Local<Value> element, void* data) -> CbResult {
Data* d = reinterpret_cast<Data*>(data);
CHECK_EQ(42, d->sentinel);
if (index != kIndex) {
CHECK(element->IsUndefined());
return CbResult::kContinue;
}
CHECK_EQ(1.5, element->NumberValue(d->context).FromJust());
return CbResult::kContinue;
};
CHECK(double_array->Iterate(context(), double_callback, &data).IsJust());
// An "object" in the ElementsKind sense.
const Local<Value> kObject = String::NewFromUtf8Literal(isolate(), "foo");
CHECK(object_array->Set(context(), kIndex, kObject).FromJust());
Array::IterationCallback object_callback =
[](uint32_t index, Local<Value> element, void* data) -> CbResult {
Data* d = reinterpret_cast<Data*>(data);
CHECK_EQ(42, d->sentinel);
if (index != kIndex) {
CHECK(element->IsUndefined());
return CbResult::kContinue;
}
CHECK_EQ(kIndex, index);
Local<String> str = element->ToString(d->context).ToLocalChecked();
CHECK_EQ(0, strcmp("foo", *String::Utf8Value(d->isolate, str)));
return CbResult::kContinue;
};
CHECK(object_array->Iterate(context(), object_callback, &data).IsJust());
Local<String> zero = String::NewFromUtf8Literal(isolate(), "0");
CHECK(dictionary_array->DefineOwnProperty(context(), zero, kSmi, v8::ReadOnly)
.FromJust());
Array::IterationCallback dictionary_callback =
[](uint32_t index, Local<Value> element, void* data) -> CbResult {
Data* d = reinterpret_cast<Data*>(data);
CHECK_EQ(42, d->sentinel);
CHECK_EQ(0, index);
CHECK_EQ(333, element->NumberValue(d->context).FromJust());
return CbResult::kContinue;
};
CHECK(dictionary_array->Iterate(context(), dictionary_callback, &data)
.IsJust());
}
static void GetElement(Local<Name> name,
const v8::PropertyCallbackInfo<v8::Value>& info) {
CHECK(i::ValidateCallbackInfo(info));
Isolate* isolate = info.GetIsolate();
Local<String> zero_str = String::NewFromUtf8Literal(isolate, "0");
Local<Value> zero_num = Number::New(isolate, 123);
CHECK(name->Equals(isolate->GetCurrentContext(), zero_str).FromJust());
info.GetReturnValue().Set(zero_num);
}
TEST_F(ArrayTest, IterateAccessorElements) {
HandleScope scope(isolate());
// {SetAccessor} doesn't automatically set the length.
Local<Array> array = Array::New(isolate(), 1);
struct Data {
int sentinel;
Local<Context> context;
Isolate* isolate;
};
Data data{42, context(), isolate()};
Local<String> zero = String::NewFromUtf8Literal(isolate(), "0");
CHECK(array->SetNativeDataProperty(context(), zero, GetElement).FromJust());
Array::IterationCallback callback = [](uint32_t index, Local<Value> element,
void* data) -> CbResult {
Data* d = reinterpret_cast<Data*>(data);
CHECK_EQ(0, index);
CHECK_EQ(123, element->NumberValue(d->context).FromJust());
d->sentinel = 234;
return CbResult::kContinue;
};
CHECK(array->Iterate(context(), callback, &data).IsJust());
CHECK_EQ(234, data.sentinel); // Callback has been called at least once.
}
TEST_F(ArrayTest, IterateEarlyTermination) {
HandleScope scope(isolate());
Local<Array> array = Array::New(isolate());
const Local<Value> kValue = Number::New(isolate(), 333);
CHECK(array->Set(context(), 0, kValue).FromJust());
CHECK(array->Set(context(), 1, kValue).FromJust());
CHECK(array->Set(context(), 2, kValue).FromJust());
Array::IterationCallback exception_callback =
[](uint32_t index, Local<Value> element, void* data) -> CbResult {
CHECK_EQ(0, index);
return CbResult::kException;
};
CHECK(array->Iterate(context(), exception_callback, nullptr).IsNothing());
Array::IterationCallback break_callback =
[](uint32_t index, Local<Value> element, void* data) -> CbResult {
CHECK_EQ(0, index);
return CbResult::kBreak;
};
CHECK(array->Iterate(context(), break_callback, nullptr).IsJust());
}
} // namespace
} // namespace v8

View File

@ -0,0 +1,42 @@
// 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 "include/v8-maybe.h"
#include "src/base/compiler-specific.h"
#include "src/base/macros.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
namespace internal {
namespace {
struct Movable {
Movable() = default;
Movable(const Movable&) = delete;
Movable& operator=(const Movable&) = delete;
Movable(Movable&&) V8_NOEXCEPT = default;
Movable& operator=(Movable&&) V8_NOEXCEPT = default;
};
} // namespace
TEST(MaybeTest, AllowMovableTypes) {
Maybe<Movable> m1 = Just(Movable{});
EXPECT_TRUE(m1.IsJust());
Maybe<Movable> m2 = Just<Movable>({});
EXPECT_TRUE(m2.IsJust());
Maybe<Movable> m3 = Nothing<Movable>();
EXPECT_TRUE(m3.IsNothing());
Maybe<Movable> m4 = Just(Movable{});
Movable mm = std::move(m4).FromJust();
USE(mm);
}
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,217 @@
// Copyright 2017 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-context.h"
#include "include/v8-function.h"
#include "include/v8-isolate.h"
#include "include/v8-local-handle.h"
#include "include/v8-primitive.h"
#include "include/v8-template.h"
#include "src/objects/objects-inl.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
namespace {
using ObjectTest = TestWithContext;
void accessor_name_getter_callback(Local<Name>,
const PropertyCallbackInfo<Value>&) {}
TEST_F(ObjectTest, SetAccessorWhenUnconfigurablePropAlreadyDefined) {
TryCatch try_catch(isolate());
Local<Object> global = context()->Global();
Local<String> property_name = String::NewFromUtf8Literal(isolate(), "foo");
PropertyDescriptor prop_desc;
prop_desc.set_configurable(false);
global->DefineProperty(context(), property_name, prop_desc).ToChecked();
Maybe<bool> result = global->SetNativeDataProperty(
context(), property_name, accessor_name_getter_callback);
ASSERT_TRUE(result.IsJust());
ASSERT_FALSE(result.FromJust());
ASSERT_FALSE(try_catch.HasCaught());
}
using LapContextTest = TestWithIsolate;
TEST_F(LapContextTest, CurrentContextInLazyAccessorOnPrototype) {
// The receiver object is created in |receiver_context|, but its prototype
// object is created in |prototype_context|, and the property is accessed
// from |caller_context|.
Local<Context> receiver_context = Context::New(isolate());
Local<Context> prototype_context = Context::New(isolate());
Local<Context> caller_context = Context::New(isolate());
static int call_count; // The number of calls of the accessor callback.
call_count = 0;
Local<FunctionTemplate> function_template = FunctionTemplate::New(isolate());
Local<Signature> signature = Signature::New(isolate(), function_template);
Local<String> property_key =
String::NewFromUtf8Literal(isolate(), "property");
Local<FunctionTemplate> get_or_set = FunctionTemplate::New(
isolate(),
[](const FunctionCallbackInfo<Value>& info) {
++call_count;
Local<Context> prototype_context = *reinterpret_cast<Local<Context>*>(
info.Data().As<External>()->Value());
EXPECT_EQ(prototype_context, info.GetIsolate()->GetCurrentContext());
},
External::New(isolate(), &prototype_context), signature);
function_template->PrototypeTemplate()->SetAccessorProperty(
property_key, get_or_set, get_or_set);
// |object| is created in |receiver_context|, and |prototype| is created
// in |prototype_context|. And then, object.__proto__ = prototype.
Local<Function> interface_for_receiver =
function_template->GetFunction(receiver_context).ToLocalChecked();
Local<Function> interface_for_prototype =
function_template->GetFunction(prototype_context).ToLocalChecked();
Local<String> prototype_key =
String::NewFromUtf8Literal(isolate(), "prototype");
Local<Object> prototype =
interface_for_prototype->Get(caller_context, prototype_key)
.ToLocalChecked()
.As<Object>();
Local<Object> object =
interface_for_receiver->NewInstance(receiver_context).ToLocalChecked();
object->SetPrototypeV2(caller_context, prototype).ToChecked();
EXPECT_EQ(receiver_context,
object->GetCreationContext(isolate()).ToLocalChecked());
EXPECT_EQ(prototype_context,
prototype->GetCreationContext(isolate()).ToLocalChecked());
EXPECT_EQ(0, call_count);
object->Get(caller_context, property_key).ToLocalChecked();
EXPECT_EQ(1, call_count);
object->Set(caller_context, property_key, Null(isolate())).ToChecked();
EXPECT_EQ(2, call_count);
// Test with a compiled version.
Local<String> object_key = String::NewFromUtf8Literal(isolate(), "object");
caller_context->Global()->Set(caller_context, object_key, object).ToChecked();
const char script[] =
"function f() { object.property; object.property = 0; } "
"%PrepareFunctionForOptimization(f); "
"f(); f(); "
"%OptimizeFunctionOnNextCall(f); "
"f();";
Context::Scope scope(caller_context);
internal::v8_flags.allow_natives_syntax = true;
Script::Compile(caller_context, String::NewFromUtf8Literal(isolate(), script))
.ToLocalChecked()
->Run(caller_context)
.ToLocalChecked();
EXPECT_EQ(8, call_count);
}
TEST_F(LapContextTest, CurrentContextInLazyAccessorOnPlatformObject) {
Local<Context> receiver_context = Context::New(isolate());
Local<Context> caller_context = Context::New(isolate());
static int call_count; // The number of calls of the accessor callback.
call_count = 0;
Local<FunctionTemplate> function_template = FunctionTemplate::New(isolate());
Local<Signature> signature = Signature::New(isolate(), function_template);
Local<String> property_key =
String::NewFromUtf8Literal(isolate(), "property");
Local<FunctionTemplate> get_or_set = FunctionTemplate::New(
isolate(),
[](const FunctionCallbackInfo<Value>& info) {
++call_count;
Local<Context> receiver_context = *reinterpret_cast<Local<Context>*>(
info.Data().As<External>()->Value());
EXPECT_EQ(receiver_context, info.GetIsolate()->GetCurrentContext());
},
External::New(isolate(), &receiver_context), signature);
function_template->InstanceTemplate()->SetAccessorProperty(
property_key, get_or_set, get_or_set);
Local<Function> interface =
function_template->GetFunction(receiver_context).ToLocalChecked();
Local<Object> object =
interface->NewInstance(receiver_context).ToLocalChecked();
EXPECT_EQ(0, call_count);
object->Get(caller_context, property_key).ToLocalChecked();
EXPECT_EQ(1, call_count);
object->Set(caller_context, property_key, Null(isolate())).ToChecked();
EXPECT_EQ(2, call_count);
// Test with a compiled version.
Local<String> object_key = String::NewFromUtf8Literal(isolate(), "object");
caller_context->Global()->Set(caller_context, object_key, object).ToChecked();
const char script[] =
"function f() { object.property; object.property = 0; } "
"%PrepareFunctionForOptimization(f);"
"f(); f(); "
"%OptimizeFunctionOnNextCall(f); "
"f();";
Context::Scope scope(caller_context);
internal::v8_flags.allow_natives_syntax = true;
Script::Compile(caller_context, String::NewFromUtf8Literal(isolate(), script))
.ToLocalChecked()
->Run(caller_context)
.ToLocalChecked();
EXPECT_EQ(8, call_count);
}
TEST_F(LapContextTest, CurrentContextInLazyAccessorOnInterface) {
Local<Context> interface_context = Context::New(isolate());
Local<Context> caller_context = Context::New(isolate());
static int call_count; // The number of calls of the accessor callback.
call_count = 0;
Local<FunctionTemplate> function_template = FunctionTemplate::New(isolate());
Local<String> property_key =
String::NewFromUtf8Literal(isolate(), "property");
Local<FunctionTemplate> get_or_set = FunctionTemplate::New(
isolate(),
[](const FunctionCallbackInfo<Value>& info) {
++call_count;
Local<Context> interface_context = *reinterpret_cast<Local<Context>*>(
info.Data().As<External>()->Value());
EXPECT_EQ(interface_context, info.GetIsolate()->GetCurrentContext());
},
External::New(isolate(), &interface_context), Local<Signature>());
function_template->SetAccessorProperty(property_key, get_or_set, get_or_set);
Local<Function> interface =
function_template->GetFunction(interface_context).ToLocalChecked();
EXPECT_EQ(0, call_count);
interface->Get(caller_context, property_key).ToLocalChecked();
EXPECT_EQ(1, call_count);
interface->Set(caller_context, property_key, Null(isolate())).ToChecked();
EXPECT_EQ(2, call_count);
// Test with a compiled version.
Local<String> interface_key =
String::NewFromUtf8Literal(isolate(), "Interface");
caller_context->Global()
->Set(caller_context, interface_key, interface)
.ToChecked();
const char script[] =
"function f() { Interface.property; Interface.property = 0; } "
"%PrepareFunctionForOptimization(f);"
"f(); f(); "
"%OptimizeFunctionOnNextCall(f); "
"f();";
Context::Scope scope(caller_context);
internal::v8_flags.allow_natives_syntax = true;
Script::Compile(caller_context, String::NewFromUtf8Literal(isolate(), script))
.ToLocalChecked()
->Run(caller_context)
.ToLocalChecked();
EXPECT_EQ(8, call_count);
}
} // namespace
} // namespace v8

View File

@ -0,0 +1,898 @@
// Copyright 2017 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-script.h"
#include <algorithm>
#include "include/v8-context.h"
#include "include/v8-isolate.h"
#include "include/v8-local-handle.h"
#include "include/v8-primitive.h"
#include "include/v8-template.h"
#include "src/objects/objects-inl.h"
#include "test/common/flag-utils.h"
#include "test/common/streaming-helper.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
namespace {
namespace {
bool ValueEqualsString(v8::Isolate* isolate, Local<Value> lhs,
const char* rhs) {
CHECK(!lhs.IsEmpty());
CHECK(lhs->IsString());
String::Utf8Value utf8_lhs(isolate, lhs);
return strcmp(rhs, *utf8_lhs) == 0;
}
std::string from_v8_string(Isolate* isolate, Local<String> str) {
String::Utf8Value utf8(isolate, str);
return *utf8;
}
v8::MaybeLocal<Module> ResolveToTopLevelAwait(Local<Context> context,
Local<String> specifier,
Local<FixedArray> assertions,
Local<Module> referrer) {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
v8::ScriptOrigin origin(specifier, 0, 0, false, -1, Local<Value>(), false,
false, true);
String::Utf8Value specifier_string(isolate, specifier);
std::string source_string =
"const promise = new Promise((resolve, reject) => {\n"
" if (should_resolve) {\n"
" resolve();\n"
" }\n"
"});\n"
"await promise;\n";
if (strncmp(*specifier_string, "stall", strlen("stall")) == 0) {
source_string = "const should_resolve = false;\n" + source_string;
} else if (strncmp(*specifier_string, "resolve", strlen("resolve")) == 0) {
source_string = "const should_resolve = true;\n" + source_string;
} else {
UNREACHABLE();
}
v8::ScriptCompiler::Source source(
v8::String::NewFromUtf8(isolate, source_string.c_str()).ToLocalChecked(),
origin);
auto res = v8::ScriptCompiler::CompileModule(isolate, &source);
return res;
}
class ScriptTest : public TestWithContext {
protected:
void TestGetStalledTopLevelAwaitMessage(
const char* source_str, std::vector<std::string> expected_stalled) {
v8::Isolate::Scope iscope(isolate());
v8::HandleScope scope(isolate());
v8::Local<v8::Context> context = v8::Context::New(isolate());
v8::Context::Scope cscope(context);
v8::ScriptOrigin origin(NewString("root.mjs"), 0, 0, false, -1,
Local<Value>(), false, false, true);
v8::ScriptCompiler::Source source(NewString(source_str), origin);
Local<Module> root =
v8::ScriptCompiler::CompileModule(isolate(), &source).ToLocalChecked();
CHECK(root->InstantiateModule(context, ResolveToTopLevelAwait)
.FromMaybe(false));
Local<v8::Promise> promise =
root->Evaluate(context).ToLocalChecked().As<v8::Promise>();
isolate()->PerformMicrotaskCheckpoint();
CHECK_EQ(expected_stalled.size() > 0
? v8::Promise::PromiseState::kPending
: v8::Promise::PromiseState::kFulfilled,
promise->State());
auto [stalled_modules, stalled_messages] =
root->GetStalledTopLevelAwaitMessages(isolate());
CHECK_EQ(expected_stalled.size(), stalled_modules.size());
CHECK_EQ(expected_stalled.size(), stalled_messages.size());
for (size_t i = 0; i < expected_stalled.size(); ++i) {
Local<Message> message = stalled_messages[i];
CHECK_EQ("Top-level await promise never resolved",
from_v8_string(isolate(), message->Get()));
CHECK_EQ(expected_stalled[i],
from_v8_string(isolate(),
message->GetScriptResourceName().As<String>()));
CHECK_EQ(
"await promise;",
from_v8_string(isolate(),
message->GetSourceLine(context).ToLocalChecked()));
CHECK_EQ(7, message->GetLineNumber(context).ToChecked());
CHECK_EQ(0, message->GetStartColumn(context).ToChecked());
CHECK_EQ(1, message->GetEndColumn(context).ToChecked());
}
}
};
class CompileHintsTest : public ScriptTest {
protected:
std::vector<int> ProduceCompileHintsHelper(
std::initializer_list<const char*> sources) {
const char* url = "http://www.foo.com/foo.js";
v8::ScriptOrigin origin(NewString(url), 13, 0);
Local<Script> top_level_script;
bool first = true;
for (auto source : sources) {
v8::ScriptCompiler::Source script_source(NewString(source), origin);
Local<Script> script =
v8::ScriptCompiler::Compile(
v8_context(), &script_source,
v8::ScriptCompiler::CompileOptions::kProduceCompileHints)
.ToLocalChecked();
if (first) {
top_level_script = script;
first = false;
}
v8::MaybeLocal<v8::Value> result = script->Run(v8_context());
EXPECT_FALSE(result.IsEmpty());
}
return top_level_script->GetCompileHintsCollector()->GetCompileHints(
v8_isolate());
}
bool FunctionIsCompiled(const char* name) {
const char* url = "http://www.foo.com/foo.js";
v8::ScriptOrigin origin(NewString(url), 13, 0);
v8::ScriptCompiler::Source script_source(NewString(name), origin);
Local<Script> script =
v8::ScriptCompiler::Compile(v8_context(), &script_source)
.ToLocalChecked();
v8::MaybeLocal<v8::Value> result = script->Run(v8_context());
auto function =
i::Cast<i::JSFunction>(Utils::OpenHandle(*result.ToLocalChecked()));
i::Builtin builtin = function->code(i_isolate())->builtin_id();
return builtin != i::Builtin::kCompileLazy;
}
};
} // namespace
TEST_F(ScriptTest, UnboundScriptPosition) {
const char* url = "http://www.foo.com/foo.js";
v8::ScriptOrigin origin(NewString(url), 13, 0);
v8::ScriptCompiler::Source script_source(NewString("var foo;"), origin);
Local<Script> script =
v8::ScriptCompiler::Compile(v8_context(), &script_source)
.ToLocalChecked();
EXPECT_TRUE(ValueEqualsString(
isolate(), script->GetUnboundScript()->GetScriptName(), url));
Local<UnboundScript> unbound_script = script->GetUnboundScript();
int line_number = unbound_script->GetLineNumber();
EXPECT_EQ(13, line_number);
int column_number = unbound_script->GetColumnNumber();
EXPECT_EQ(0, column_number);
}
TEST_F(ScriptTest, GetSourceMappingUrlFromComment) {
const char* url = "http://www.foo.com/foo.js";
v8::ScriptOrigin origin(NewString(url));
v8::ScriptCompiler::Source script_source(
NewString("var foo;\n//# sourceMappingURL=foo.js.map"), origin);
Local<Script> script =
v8::ScriptCompiler::Compile(v8_context(), &script_source)
.ToLocalChecked();
EXPECT_EQ(
"foo.js.map",
from_v8_string(
isolate(),
script->GetUnboundScript()->GetSourceMappingURL().As<String>()));
}
TEST_F(ScriptTest, OriginSourceMapOverridesSourceMappingUrlComment) {
const char* url = "http://www.foo.com/foo.js";
const char* api_source_map = "http://override/foo.js.map";
v8::ScriptOrigin origin(NewString(url), 13, 0, false, -1,
NewString(api_source_map));
v8::ScriptCompiler::Source script_source(
NewString("var foo;\n//# sourceMappingURL=foo.js.map"), origin);
Local<Script> script =
v8::ScriptCompiler::Compile(v8_context(), &script_source)
.ToLocalChecked();
EXPECT_EQ(
api_source_map,
from_v8_string(
isolate(),
script->GetUnboundScript()->GetSourceMappingURL().As<String>()));
}
TEST_F(ScriptTest, IgnoreOriginSourceMapEmptyString) {
const char* url = "http://www.foo.com/foo.js";
const char* api_source_map = "";
v8::ScriptOrigin origin(NewString(url), 13, 0, false, -1,
NewString(api_source_map));
v8::ScriptCompiler::Source script_source(
NewString("var foo;\n//# sourceMappingURL=foo.js.map"), origin);
Local<Script> script =
v8::ScriptCompiler::Compile(v8_context(), &script_source)
.ToLocalChecked();
EXPECT_EQ(
"foo.js.map",
from_v8_string(
isolate(),
script->GetUnboundScript()->GetSourceMappingURL().As<String>()));
}
TEST_F(ScriptTest, GetSingleStalledTopLevelAwaitMessage) {
TestGetStalledTopLevelAwaitMessage("import 'stall.mjs';", {"stall.mjs"});
}
TEST_F(ScriptTest, GetMultipleStalledTopLevelAwaitMessage) {
TestGetStalledTopLevelAwaitMessage(
"import 'stall.mjs';\n"
"import 'stall_2.mjs';\n"
"import 'stall_3.mjs';\n"
"import 'stall_4.mjs';\n",
{"stall.mjs", "stall_2.mjs", "stall_3.mjs", "stall_4.mjs"});
}
TEST_F(ScriptTest, GetMixedStalledTopLevelAwaitMessage) {
TestGetStalledTopLevelAwaitMessage(
"import 'stall.mjs';\n"
"import 'resolve.mjs';\n"
"import 'stall_2.mjs';\n"
"import 'resolve.mjs';\n",
{"stall.mjs", "stall_2.mjs"});
}
TEST_F(ScriptTest, GetEmptyStalledTopLevelAwaitMessage) {
TestGetStalledTopLevelAwaitMessage(
"import 'resolve.mjs';\n"
"import 'resolve_2.mjs';\n"
"import 'resolve_3.mjs';\n",
{});
}
TEST_F(ScriptTest, ProduceCompileHints) {
const char* url = "http://www.foo.com/foo.js";
v8::ScriptOrigin origin(NewString(url), 13, 0);
const char* code = "function lazy1() {} function lazy2() {} lazy1();";
v8::ScriptCompiler::Source script_source(NewString(code), origin);
// Test producing compile hints.
{
Local<Script> script =
v8::ScriptCompiler::Compile(
v8_context(), &script_source,
v8::ScriptCompiler::CompileOptions::kProduceCompileHints)
.ToLocalChecked();
{
auto compile_hints =
script->GetCompileHintsCollector()->GetCompileHints(v8_isolate());
EXPECT_EQ(0u, compile_hints.size());
}
v8::Local<v8::Context> context = v8::Context::New(isolate());
v8::MaybeLocal<v8::Value> result = script->Run(context);
EXPECT_FALSE(result.IsEmpty());
{
auto compile_hints =
script->GetCompileHintsCollector()->GetCompileHints(v8_isolate());
EXPECT_EQ(1u, compile_hints.size());
EXPECT_EQ(14, compile_hints[0]);
}
// The previous data is still there if we retrieve compile hints again.
{
auto compile_hints =
script->GetCompileHintsCollector()->GetCompileHints(v8_isolate());
EXPECT_EQ(1u, compile_hints.size());
EXPECT_EQ(14, compile_hints[0]);
}
// Call the other lazy function and retrieve compile hints again.
const char* code2 = "lazy2();";
v8::ScriptCompiler::Source script_source2(NewString(code2), origin);
Local<Script> script2 =
v8::ScriptCompiler::Compile(v8_context(), &script_source2)
.ToLocalChecked();
v8::MaybeLocal<v8::Value> result2 = script2->Run(context);
EXPECT_FALSE(result2.IsEmpty());
{
auto compile_hints =
script->GetCompileHintsCollector()->GetCompileHints(v8_isolate());
EXPECT_EQ(2u, compile_hints.size());
EXPECT_EQ(14, compile_hints[0]);
EXPECT_EQ(34, compile_hints[1]);
}
}
// Test that compile hints are not produced unless the relevant compile option
// is set.
{
const char* nohints_code =
"function nohints_lazy1() {} function nohints_lazy2() {} "
"nohints_lazy1();";
v8::ScriptCompiler::Source nohints_script_source(NewString(nohints_code),
origin);
Local<Script> script =
v8::ScriptCompiler::Compile(v8_context(), &nohints_script_source)
.ToLocalChecked();
{
auto compile_hints =
script->GetCompileHintsCollector()->GetCompileHints(v8_isolate());
EXPECT_EQ(0u, compile_hints.size());
}
v8::Local<v8::Context> context = v8::Context::New(isolate());
v8::MaybeLocal<v8::Value> result = script->Run(context);
EXPECT_FALSE(result.IsEmpty());
{
auto compile_hints =
script->GetCompileHintsCollector()->GetCompileHints(v8_isolate());
EXPECT_EQ(0u, compile_hints.size());
}
}
}
TEST_F(ScriptTest, ProduceCompileHintsForArrowFunctions) {
const char* url = "http://www.foo.com/foo.js";
v8::ScriptOrigin origin(NewString(url), 13, 0);
const char* code = "lazy1 = () => {}; (() => { lazy2 = () => {} })()";
v8::ScriptCompiler::Source script_source(NewString(code), origin);
// Test producing compile hints.
{
Local<Script> script =
v8::ScriptCompiler::Compile(
v8_context(), &script_source,
v8::ScriptCompiler::CompileOptions::kProduceCompileHints)
.ToLocalChecked();
{
auto compile_hints =
script->GetCompileHintsCollector()->GetCompileHints(v8_isolate());
EXPECT_EQ(0u, compile_hints.size());
}
v8::Local<v8::Context> context = v8::Context::New(isolate());
v8::MaybeLocal<v8::Value> result = script->Run(context);
EXPECT_FALSE(result.IsEmpty());
{
auto compile_hints =
script->GetCompileHintsCollector()->GetCompileHints(v8_isolate());
EXPECT_EQ(0u, compile_hints.size());
}
// Call one of the lazy functions and retrieve compile hints again.
const char* code2 = "lazy1();";
v8::ScriptCompiler::Source script_source2(NewString(code2), origin);
Local<Script> script2 =
v8::ScriptCompiler::Compile(v8_context(), &script_source2)
.ToLocalChecked();
v8::MaybeLocal<v8::Value> result2 = script2->Run(context);
EXPECT_FALSE(result2.IsEmpty());
{
auto compile_hints =
script->GetCompileHintsCollector()->GetCompileHints(v8_isolate());
EXPECT_EQ(1u, compile_hints.size());
EXPECT_EQ(8, compile_hints[0]);
}
// Call the other lazy function and retrieve the compile hints again.
const char* code3 = "lazy2();";
v8::ScriptCompiler::Source script_source3(NewString(code3), origin);
Local<Script> script3 =
v8::ScriptCompiler::Compile(v8_context(), &script_source3)
.ToLocalChecked();
v8::MaybeLocal<v8::Value> result3 = script3->Run(context);
EXPECT_FALSE(result3.IsEmpty());
{
auto compile_hints =
script->GetCompileHintsCollector()->GetCompileHints(v8_isolate());
EXPECT_EQ(2u, compile_hints.size());
EXPECT_EQ(8, compile_hints[0]);
EXPECT_EQ(35, compile_hints[1]);
}
}
}
namespace {
bool CompileHintsCallback(int position, void* data) {
std::vector<int>* hints = reinterpret_cast<std::vector<int>*>(data);
return std::find(hints->begin(), hints->end(), position) != hints->end();
}
} // namespace
TEST_F(CompileHintsTest, ConsumeCompileHints) {
const char* url = "http://www.foo.com/foo.js";
v8::ScriptOrigin origin(NewString(url), 13, 0);
v8::Local<v8::Context> context = v8::Context::New(isolate());
// Produce compile hints which we'll use as data later. The function positions
// must match the script we're compiling later, but we'll change the script
// source code to make sure that 1) the compile result is not coming from a
// cache 2) we're querying the correct functions.
std::vector<int> compile_hints = ProduceCompileHintsHelper(
{"function lazy1() {} function lazy2() {}", "lazy1()"});
{
const char* code = "function func1() {} function func2() {}";
v8::ScriptCompiler::Source script_source(
NewString(code), origin, CompileHintsCallback,
reinterpret_cast<void*>(&compile_hints));
Local<Script> script =
v8::ScriptCompiler::Compile(
v8_context(), &script_source,
v8::ScriptCompiler::CompileOptions::kConsumeCompileHints)
.ToLocalChecked();
v8::MaybeLocal<v8::Value> result = script->Run(context);
EXPECT_FALSE(result.IsEmpty());
}
EXPECT_TRUE(FunctionIsCompiled("func1"));
EXPECT_FALSE(FunctionIsCompiled("func2"));
}
TEST_F(CompileHintsTest, ConsumeCompileHintsForArrowFunctions) {
const char* url = "http://www.foo.com/foo.js";
v8::ScriptOrigin origin(NewString(url), 13, 0);
v8::Local<v8::Context> context = v8::Context::New(isolate());
// Produce compile hints which we'll use as data later. The function positions
// must match the script we're compiling later, but we'll change the script
// source code to make sure that 1) the compile result is not coming from a
// cache 2) we're querying the correct functions.
std::vector<int> compile_hints = ProduceCompileHintsHelper(
{"lazy1 = (a, b, c) => {}; lazy2 = () => {}", "lazy1()"});
{
const char* code = "func1 = (a, b, c) => {}; func2 = () => {}";
v8::ScriptCompiler::Source script_source(
NewString(code), origin, CompileHintsCallback,
reinterpret_cast<void*>(&compile_hints));
Local<Script> script =
v8::ScriptCompiler::Compile(
v8_context(), &script_source,
v8::ScriptCompiler::CompileOptions::kConsumeCompileHints)
.ToLocalChecked();
v8::MaybeLocal<v8::Value> result = script->Run(context);
EXPECT_FALSE(result.IsEmpty());
}
EXPECT_TRUE(FunctionIsCompiled("func1"));
EXPECT_FALSE(FunctionIsCompiled("func2"));
}
TEST_F(CompileHintsTest, StreamingCompileHints) {
const char* url = "http://www.foo.com/foo.js";
v8::ScriptOrigin origin(NewString(url), 13, 0);
// Produce compile hints which we'll use as data later. The function positions
// must match the script we're compiling later, but we'll change the script
// source code to make sure that 1) the compile result is not coming from a
// cache 2) we're querying the correct functions.
std::vector<int> compile_hints = ProduceCompileHintsHelper(
{"function lazy1() {} function lazy2() {}", "lazy1()"});
// Consume compile hints.
const char* chunks[] = {
"function func1() {} function fu"
"nc2() {}",
nullptr};
v8::ScriptCompiler::StreamedSource source(
std::make_unique<i::TestSourceStream>(chunks),
v8::ScriptCompiler::StreamedSource::ONE_BYTE);
std::unique_ptr<v8::ScriptCompiler::ScriptStreamingTask> task(
v8::ScriptCompiler::StartStreaming(isolate(), &source,
v8::ScriptType::kClassic,
ScriptCompiler::kConsumeCompileHints,
CompileHintsCallback, &compile_hints));
// TestSourceStream::GetMoreData won't block, so it's OK to just join the
// background task.
StreamerThread::StartThreadForTaskAndJoin(task.get());
task.reset();
std::unique_ptr<char[]> full_source(
i::TestSourceStream::FullSourceString(chunks));
v8::Local<Script> script =
v8::ScriptCompiler::Compile(v8_context(), &source,
NewString(full_source.get()), origin)
.ToLocalChecked();
v8::MaybeLocal<v8::Value> result = script->Run(v8_context());
EXPECT_FALSE(result.IsEmpty());
EXPECT_TRUE(FunctionIsCompiled("func1"));
EXPECT_FALSE(FunctionIsCompiled("func2"));
}
TEST_F(CompileHintsTest, CompileHintsMagicCommentBasic) {
const char* url = "http://www.foo.com/foo.js";
v8::ScriptOrigin origin(NewString(url), 13, 0);
v8::Local<v8::Context> context = v8::Context::New(isolate());
// Run the top level code.
const char* code =
"//# allFunctionsCalledOnLoad\n"
"function f1() {}\n"
"let f2 = function() { }";
v8::ScriptCompiler::Source script_source(NewString(code), origin);
Local<Script> script =
v8::ScriptCompiler::Compile(
v8_context(), &script_source,
v8::ScriptCompiler::CompileOptions(
v8::ScriptCompiler::CompileOptions::kProduceCompileHints |
v8::ScriptCompiler::CompileOptions::
kFollowCompileHintsMagicComment))
.ToLocalChecked();
v8::MaybeLocal<v8::Value> result = script->Run(context);
EXPECT_FALSE(result.IsEmpty());
EXPECT_TRUE(FunctionIsCompiled("f1"));
EXPECT_TRUE(FunctionIsCompiled("f2"));
}
TEST_F(CompileHintsTest, CompileHintsMagicCommentDifferentFunctionTypes) {
const char* url = "http://www.foo.com/foo.js";
v8::ScriptOrigin origin(NewString(url), 13, 0);
v8::Local<v8::Context> context = v8::Context::New(isolate());
// Run the top level code.
const char* code =
"//# allFunctionsCalledOnLoad\n"
"f1 = () => {};\n"
"class C { f2() { } set f3(x) { } }\n"
"o = { get f4() { } };\n";
v8::ScriptCompiler::Source script_source(NewString(code), origin);
Local<Script> script =
v8::ScriptCompiler::Compile(
v8_context(), &script_source,
v8::ScriptCompiler::CompileOptions(
v8::ScriptCompiler::CompileOptions::kProduceCompileHints |
v8::ScriptCompiler::CompileOptions::
kFollowCompileHintsMagicComment))
.ToLocalChecked();
v8::MaybeLocal<v8::Value> result = script->Run(context);
EXPECT_FALSE(result.IsEmpty());
EXPECT_TRUE(FunctionIsCompiled("f1"));
EXPECT_TRUE(FunctionIsCompiled("C.prototype.f2"));
EXPECT_TRUE(FunctionIsCompiled(
"Object.getOwnPropertyDescriptor(C.prototype, 'f3').set"));
EXPECT_TRUE(
FunctionIsCompiled("Object.getOwnPropertyDescriptor(o, 'f4').get"));
}
TEST_F(CompileHintsTest, CompileHintsMagicCommentBetweenFunctions) {
const char* url = "http://www.foo.com/foo.js";
v8::ScriptOrigin origin(NewString(url), 13, 0);
v8::Local<v8::Context> context = v8::Context::New(isolate());
// Run the top level code.
const char* code =
"function f1() {}\n"
"//# allFunctionsCalledOnLoad\n"
"function f2() {}";
v8::ScriptCompiler::Source script_source(NewString(code), origin);
Local<Script> script =
v8::ScriptCompiler::Compile(
v8_context(), &script_source,
v8::ScriptCompiler::CompileOptions(
v8::ScriptCompiler::CompileOptions::kProduceCompileHints |
v8::ScriptCompiler::CompileOptions::
kFollowCompileHintsMagicComment))
.ToLocalChecked();
v8::MaybeLocal<v8::Value> result = script->Run(context);
EXPECT_FALSE(result.IsEmpty());
EXPECT_FALSE(FunctionIsCompiled("f1"));
// Compile hint between functions is not picked up.
EXPECT_FALSE(FunctionIsCompiled("f2"));
}
TEST_F(CompileHintsTest, CompileHintsMagicCommentInvalid) {
const char* url = "http://www.foo.com/foo.js";
v8::ScriptOrigin origin(NewString(url), 13, 0);
v8::Local<v8::Context> context = v8::Context::New(isolate());
// Run the top level code.
const char* code =
"//# allFunctionsCalledOnLoadAndSomeStuffAfter\n" // Not a valid compile
// hint.
"//@ allFunctionsCalledOnLoad\n" // Not a valid compile hint.
"function f1() {}";
v8::ScriptCompiler::Source script_source(NewString(code), origin);
Local<Script> script =
v8::ScriptCompiler::Compile(
v8_context(), &script_source,
v8::ScriptCompiler::CompileOptions(
v8::ScriptCompiler::CompileOptions::kProduceCompileHints |
v8::ScriptCompiler::CompileOptions::
kFollowCompileHintsMagicComment))
.ToLocalChecked();
v8::MaybeLocal<v8::Value> result = script->Run(context);
EXPECT_FALSE(result.IsEmpty());
// Retrieve the function object for f1.
EXPECT_FALSE(FunctionIsCompiled("f1"));
}
// Regression test for https://issues.chromium.org/issues/351876778 , repurposed
// for the per-function version, since the per-file version no longer has the
// "value" field which could be two byte.
TEST_F(ScriptTest, CompileHintsMagicCommentInvalid2) {
const char* url = "http://www.foo.com/foo.js";
v8::ScriptOrigin origin(NewString(url), 13, 0);
v8::Local<v8::Context> context = v8::Context::New(isolate());
const char* code =
"//# functionsCalledOnLoad=\xCF\x80\n" // Two byte character
"function f1() {}";
v8::ScriptCompiler::Source script_source(NewString(code), origin);
Local<Script> script =
v8::ScriptCompiler::Compile(
v8_context(), &script_source,
v8::ScriptCompiler::CompileOptions(
v8::ScriptCompiler::CompileOptions::kProduceCompileHints |
v8::ScriptCompiler::CompileOptions::
kFollowCompileHintsMagicComment))
.ToLocalChecked();
v8::MaybeLocal<v8::Value> result = script->Run(context);
EXPECT_FALSE(result.IsEmpty());
}
TEST_F(CompileHintsTest, CompileHintsMagicCommentNotEnabledByCompileOptions) {
const char* url = "http://www.foo.com/foo.js";
v8::ScriptOrigin origin(NewString(url), 13, 0);
v8::Local<v8::Context> context = v8::Context::New(isolate());
// Run the top level code.
const char* code =
"//# allFunctionsCalledOnLoad\n"
"function f1() {}";
v8::ScriptCompiler::Source script_source(NewString(code), origin);
Local<Script> script =
v8::ScriptCompiler::Compile(
v8_context(), &script_source,
// Not enabling the magic comment with compile options!
v8::ScriptCompiler::CompileOptions::kProduceCompileHints)
.ToLocalChecked();
v8::MaybeLocal<v8::Value> result = script->Run(context);
EXPECT_FALSE(result.IsEmpty());
EXPECT_FALSE(FunctionIsCompiled("f1"));
}
TEST_F(CompileHintsTest, StreamingCompileHintsMagic) {
const char* url = "http://www.foo.com/foo.js";
v8::ScriptOrigin origin(NewString(url), 13, 0);
// Consume compile hints.
const char* chunks[] = {"//# allFunctionsCalled", "OnLoad\n",
"function func1() {} function fu", "nc2() {}",
nullptr};
v8::ScriptCompiler::StreamedSource source(
std::make_unique<i::TestSourceStream>(chunks),
v8::ScriptCompiler::StreamedSource::ONE_BYTE);
std::unique_ptr<v8::ScriptCompiler::ScriptStreamingTask> task(
v8::ScriptCompiler::StartStreaming(
isolate(), &source, v8::ScriptType::kClassic,
v8::ScriptCompiler::CompileOptions(
v8::ScriptCompiler::kProduceCompileHints |
v8::ScriptCompiler::kFollowCompileHintsMagicComment)));
// TestSourceStream::GetMoreData won't block, so it's OK to just join the
// background task.
StreamerThread::StartThreadForTaskAndJoin(task.get());
task.reset();
std::unique_ptr<char[]> full_source(
i::TestSourceStream::FullSourceString(chunks));
v8::Local<Script> script =
v8::ScriptCompiler::Compile(v8_context(), &source,
NewString(full_source.get()), origin)
.ToLocalChecked();
v8::MaybeLocal<v8::Value> result = script->Run(v8_context());
EXPECT_FALSE(result.IsEmpty());
EXPECT_TRUE(FunctionIsCompiled("func1"));
EXPECT_TRUE(FunctionIsCompiled("func2"));
}
TEST_F(CompileHintsTest, CompileHintsMagicCommentAfterComments) {
const char* url = "http://www.foo.com/foo.js";
v8::ScriptOrigin origin(NewString(url), 13, 0);
v8::Local<v8::Context> context = v8::Context::New(isolate());
// Run the top level code.
const char* code =
"// a single-line comment\n"
"/* a multi-\n"
"line comment */\n"
"// another single-line comment\n"
"//# allFunctionsCalledOnLoad\n"
"function f1() {}";
v8::ScriptCompiler::Source script_source(NewString(code), origin);
Local<Script> script =
v8::ScriptCompiler::Compile(
v8_context(), &script_source,
v8::ScriptCompiler::CompileOptions(
v8::ScriptCompiler::CompileOptions::kProduceCompileHints |
v8::ScriptCompiler::CompileOptions::
kFollowCompileHintsMagicComment))
.ToLocalChecked();
v8::MaybeLocal<v8::Value> result = script->Run(context);
EXPECT_FALSE(result.IsEmpty());
EXPECT_TRUE(FunctionIsCompiled("f1"));
}
TEST_F(CompileHintsTest, CompileHintsPerFunctionMagicComment) {
const char* url = "http://www.foo.com/foo.js";
v8::ScriptOrigin origin(NewString(url), 13, 0);
v8::Local<v8::Context> context = v8::Context::New(isolate());
// Run the top level code.
const char* code =
"// a comment here tests that the positions are handled correctly as"
"being relative to the compile hints comment end position\n"
"//# functionsCalledOnLoad=wBuC\n" // Encodes positions 24 and 63
"function test_function_1(test_param1) {}\n"
// ^ function position
"let test_function_2 = (test_param2) => {}";
// ^ function position
v8::ScriptCompiler::Source script_source(NewString(code), origin);
Local<Script> script =
v8::ScriptCompiler::Compile(
v8_context(), &script_source,
v8::ScriptCompiler::CompileOptions(
v8::ScriptCompiler::CompileOptions::
kFollowCompileHintsPerFunctionMagicComment))
.ToLocalChecked();
v8::MaybeLocal<v8::Value> result = script->Run(context);
EXPECT_FALSE(result.IsEmpty());
EXPECT_TRUE(FunctionIsCompiled("test_function_1"));
EXPECT_TRUE(FunctionIsCompiled("test_function_2"));
}
TEST_F(CompileHintsTest, CompileHintsPerFunctionMagicCommentSome) {
const char* url = "http://www.foo.com/foo.js";
v8::ScriptOrigin origin(NewString(url), 13, 0);
v8::Local<v8::Context> context = v8::Context::New(isolate());
// Run the top level code.
const char* code =
"// a comment here tests that the positions are handled correctly as"
"being relative to the compile hints comment end position\n"
"//# functionsCalledOnLoad=0GkF\n"
"function test_function_1(test_param1) {}\n"
"function test_function_2(test_param1) {}\n"
"function test_function_3(test_param1) {}\n"
"function test_function_4(test_param1) {}\n"
"function test_function_5(test_param1) {}\n"
"function test_function_6(test_param1) {}\n"
"function test_function_7(test_param1) {}\n";
v8::ScriptCompiler::Source script_source(NewString(code), origin);
Local<Script> script =
v8::ScriptCompiler::Compile(
v8_context(), &script_source,
v8::ScriptCompiler::CompileOptions(
v8::ScriptCompiler::CompileOptions::
kFollowCompileHintsPerFunctionMagicComment))
.ToLocalChecked();
v8::MaybeLocal<v8::Value> result = script->Run(context);
EXPECT_FALSE(result.IsEmpty());
EXPECT_TRUE(FunctionIsCompiled("test_function_3"));
EXPECT_TRUE(FunctionIsCompiled("test_function_5"));
EXPECT_FALSE(FunctionIsCompiled("test_function_1"));
EXPECT_FALSE(FunctionIsCompiled("test_function_2"));
EXPECT_FALSE(FunctionIsCompiled("test_function_4"));
EXPECT_FALSE(FunctionIsCompiled("test_function_6"));
EXPECT_FALSE(FunctionIsCompiled("test_function_7"));
}
TEST_F(CompileHintsTest, CompileHintsPerFunctionMagicCommentContinued) {
const char* url = "http://www.foo.com/foo.js";
v8::ScriptOrigin origin(NewString(url), 13, 0);
v8::Local<v8::Context> context = v8::Context::New(isolate());
// Run the top level code.
const char* code =
"// a comment here tests that the positions are handled correctly as"
"being relative to the compile hints comment end position\n"
"//# functionsCalledOnLoad=wBuC\n" // Encodes positions 24 and 63
"function test_function_1(test_param1) {}\n"
// ^ function position
"let test_function_2 = (test_param2) => {}"
// ^ function position
"//# functionsCalledOnLoad=wBuC\n" // Encodes positions 24 and 63
"function test_function_3(test_param3) {}\n"
// ^ function position
"let test_function_4 = (test_param4) => {}";
// ^ function position
v8::ScriptCompiler::Source script_source(NewString(code), origin);
Local<Script> script =
v8::ScriptCompiler::Compile(
v8_context(), &script_source,
v8::ScriptCompiler::CompileOptions(
v8::ScriptCompiler::CompileOptions::
kFollowCompileHintsPerFunctionMagicComment))
.ToLocalChecked();
v8::MaybeLocal<v8::Value> result = script->Run(context);
EXPECT_FALSE(result.IsEmpty());
EXPECT_TRUE(FunctionIsCompiled("test_function_1"));
EXPECT_TRUE(FunctionIsCompiled("test_function_2"));
EXPECT_TRUE(FunctionIsCompiled("test_function_3"));
EXPECT_TRUE(FunctionIsCompiled("test_function_4"));
}
TEST_F(CompileHintsTest,
CompileHintsPerFunctionMagicCommentNotEnabledByCompileOptions) {
const char* url = "http://www.foo.com/foo.js";
v8::ScriptOrigin origin(NewString(url), 13, 0);
v8::Local<v8::Context> context = v8::Context::New(isolate());
// Run the top level code.
const char* code =
"// a comment here tests that the positions are handled correctly as"
"being relative to the compile hints comment end position\n"
"//# functionsCalledOnLoad=wBuC\n" // Encodes positions 24 and 63
"function test_function_1(test_param1) {}\n"
// ^ function position
"let test_function_2 = (test_param2) => {}";
// ^ function position
v8::ScriptCompiler::Source script_source(NewString(code), origin);
Local<Script> script =
v8::ScriptCompiler::Compile(
v8_context(), &script_source,
v8::ScriptCompiler::CompileOptions(
v8::ScriptCompiler::CompileOptions::kNoCompileOptions))
.ToLocalChecked();
v8::MaybeLocal<v8::Value> result = script->Run(context);
EXPECT_FALSE(result.IsEmpty());
EXPECT_FALSE(FunctionIsCompiled("test_function_1"));
EXPECT_FALSE(FunctionIsCompiled("test_function_2"));
}
} // namespace
} // namespace v8

View File

@ -0,0 +1,32 @@
// Copyright 2023 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-value.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
namespace {
using ValueTest = TestWithContext;
TEST_F(ValueTest, TypecheckWitness) {
Local<Object> global = context()->Global();
Local<String> foo = String::NewFromUtf8Literal(isolate(), "foo");
Local<String> bar = String::NewFromUtf8Literal(isolate(), "bar");
v8::TypecheckWitness witness(isolate());
EXPECT_FALSE(witness.Matches(global));
EXPECT_FALSE(witness.Matches(foo));
witness.Update(global);
EXPECT_TRUE(witness.Matches(global));
EXPECT_FALSE(witness.Matches(foo));
witness.Update(foo);
EXPECT_FALSE(witness.Matches(global));
EXPECT_TRUE(witness.Matches(foo));
EXPECT_TRUE(witness.Matches(bar));
}
} // namespace
} // namespace v8