v8/test/unittests/api/access-check-unittest.cc
Feng Yu a217f66b5e [test] Replace v8_str with NewString in unittests/
This pr refactor all usage of v8_str in unittests/ into NewString
method in test-utils.h

Change-Id: I8ffc456851488e8c050d7a38f5459aebfa3f314f
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3813064
Reviewed-by: Leszek Swirski <leszeks@chromium.org>
Commit-Queue: 王澳 <wangao.james@bytedance.com>
Cr-Commit-Position: refs/heads/main@{#82314}
2022-08-09 17:22:58 +00:00

618 lines
23 KiB
C++

// 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;");
EXPECT_THAT(RunJS("this.all_can_read"), IsInt32(42));
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());
}
// AllCanRead properties are also inaccessible.
{
TryCatch try_catch(isolate());
CHECK(TryRunJS("this.other.all_can_read").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 {
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 =
String::NewFromUtf8(isolate, source).ToLocalChecked();
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<Value> result =
CompileRun(isolate(),
"Object.getOwnPropertyDescriptor(this, 'property')"
" .get.call(other);")
.ToLocalChecked();
EXPECT_TRUE(result->IsUndefined());
CompileRun(isolate(),
"Object.getOwnPropertyDescriptor(this, 'property')"
" .set.call(other, 42);");
}
class AccessRegressionTest : public AccessCheckTest {
protected:
i::Handle<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::Handle<i::JSReceiver> r =
Utils::OpenHandle(*Local<Function>::Cast(getter));
EXPECT_TRUE(r->IsJSFunction());
return i::Handle<i::JSFunction>::cast(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::Handle<i::JSFunction> getter = RetrieveFunctionFrom(
context2,
"Object.getOwnPropertyDescriptor(object_from_context1, 'property').get");
ASSERT_EQ(getter->native_context(), *Utils::OpenHandle(*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 instantation 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::Handle<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::Handle<i::JSFunction> getter_c1 = RetrieveFunctionFrom(
context1, "Object.getOwnPropertyDescriptor(object, 'property').get");
i::Handle<i::JSFunction> getter_c2 = RetrieveFunctionFrom(
context2, "Object.getOwnPropertyDescriptor(object, 'property').get");
ASSERT_EQ(getter_c1->native_context(), *Utils::OpenHandle(*context1));
ASSERT_EQ(getter_c2->native_context(), *Utils::OpenHandle(*context2));
}
void 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, String::NewFromUtf8(isolate, "cross_context_int")
.ToLocalChecked())
.FromJust())
info.GetReturnValue().Set(g_cross_context_int);
}
void NamedSetter(Local<Name> property, Local<Value> value,
const PropertyCallbackInfo<Value>& info) {
CHECK(g_expect_interceptor_call);
Isolate* isolate = info.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
if (!property
->Equals(context, String::NewFromUtf8(isolate, "cross_context_int")
.ToLocalChecked())
.FromJust())
return;
if (value->IsInt32()) {
g_cross_context_int = value->ToInt32(context).ToLocalChecked()->Value();
}
info.GetReturnValue().Set(value);
}
void 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, String::NewFromUtf8(isolate, "cross_context_int")
.ToLocalChecked())
.FromJust())
return;
info.GetReturnValue().Set(DontDelete);
}
void 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, String::NewFromUtf8(isolate, "cross_context_int")
.ToLocalChecked())
.FromJust())
return;
info.GetReturnValue().Set(false);
}
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,
String::NewFromUtf8(isolate, "cross_context_int").ToLocalChecked())
.FromJust();
info.GetReturnValue().Set(names);
}
void IndexedGetter(uint32_t index, const PropertyCallbackInfo<Value>& info) {
CHECK(g_expect_interceptor_call);
if (index == 7) info.GetReturnValue().Set(g_cross_context_int);
}
void IndexedSetter(uint32_t index, Local<Value> value,
const PropertyCallbackInfo<Value>& info) {
CHECK(g_expect_interceptor_call);
Isolate* isolate = info.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
if (index != 7) return;
if (value->IsInt32()) {
g_cross_context_int = value->ToInt32(context).ToLocalChecked()->Value();
}
info.GetReturnValue().Set(value);
}
void IndexedQuery(uint32_t index, const PropertyCallbackInfo<Integer>& info) {
CHECK(g_expect_interceptor_call);
if (index == 7) info.GetReturnValue().Set(DontDelete);
}
void IndexedDeleter(uint32_t index, const PropertyCallbackInfo<Boolean>& info) {
CHECK(g_expect_interceptor_call);
if (index == 7) info.GetReturnValue().Set(false);
}
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, String::NewFromUtf8(isolate, "7").ToLocalChecked())
.FromJust();
info.GetReturnValue().Set(names);
}
void 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());
}
void MethodCallback(const FunctionCallbackInfo<Value>& info) {
info.GetReturnValue().Set(8);
}
void NamedGetterThrowsException(Local<Name> property,
const PropertyCallbackInfo<Value>& info) {
info.GetIsolate()->ThrowException(
String::NewFromUtf8(info.GetIsolate(), "exception").ToLocalChecked());
}
void NamedSetterThrowsException(Local<Name> property, Local<Value> value,
const PropertyCallbackInfo<Value>& info) {
info.GetIsolate()->ThrowException(
String::NewFromUtf8(info.GetIsolate(), "exception").ToLocalChecked());
}
void IndexedGetterThrowsException(uint32_t index,
const PropertyCallbackInfo<Value>& info) {
info.GetIsolate()->ThrowException(
String::NewFromUtf8(info.GetIsolate(), "exception").ToLocalChecked());
}
void IndexedSetterThrowsException(uint32_t index, Local<Value> value,
const PropertyCallbackInfo<Value>& info) {
info.GetIsolate()->ThrowException(
String::NewFromUtf8(info.GetIsolate(), "exception").ToLocalChecked());
}
void GetCrossContextInt(Local<String> property,
const PropertyCallbackInfo<Value>& info) {
CHECK(!g_expect_interceptor_call);
info.GetReturnValue().Set(g_cross_context_int);
}
void SetCrossContextInt(Local<String> 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);
global_template->SetNativeDataProperty(NewString("all_can_read"), Return42,
nullptr, Local<Value>(), None,
ALL_CAN_READ);
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);
global_template->SetNativeDataProperty(NewString("all_can_read"), Return42,
nullptr, Local<Value>(), None,
ALL_CAN_READ);
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));
tmpl->SetNativeDataProperty(NewString("all_can_read"), Return42, nullptr,
Local<Value>(), None, ALL_CAN_READ);
Local<Object> obj = tmpl->NewRemoteInstance().ToLocalChecked();
Local<Context> context = Context::New(isolate());
CheckCrossContextAccess(context, obj);
}
} // namespace v8