Commit 8437ed16 authored by Igor Sheludko's avatar Igor Sheludko Committed by V8 LUCI CQ

[runtime] Add interceptors side effects detector

This CL introduces SideEffectDetectorScope which requires explicit
allowlisting of cases when side effects are allowed after calling
interceptor callbacks.
Side effects are not allowed when the callback does not intercept
the request.
The side effects detector is not enabled yet, it will be enabled in
a follow-up CL.

Bug: chromium:1310062
Change-Id: I805764920ed016cb37390aef7bb02cbdf5f72846
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3641172Reviewed-by: 's avatarToon Verwaest <verwaest@chromium.org>
Commit-Queue: Igor Sheludko <ishell@chromium.org>
Cr-Commit-Position: refs/heads/main@{#80484}
parent ef77fe0f
......@@ -148,6 +148,17 @@ Handle<Object> FunctionCallbackArguments::Call(CallHandlerInfo handler) {
return GetReturnValue<Object>(isolate);
}
PropertyCallbackArguments::~PropertyCallbackArguments(){
#ifdef DEBUG
// TODO(chromium:1310062): enable this check.
// if (javascript_execution_counter_) {
// CHECK_WITH_MSG(javascript_execution_counter_ ==
// isolate()->javascript_execution_counter(),
// "Unexpected side effect detected");
// }
#endif // DEBUG
}
Handle<JSObject> PropertyCallbackArguments::CallNamedEnumerator(
Handle<InterceptorInfo> interceptor) {
DCHECK(interceptor->is_named());
......@@ -296,6 +307,10 @@ Handle<Object> PropertyCallbackArguments::CallAccessorGetter(
Handle<AccessorInfo> info, Handle<Name> name) {
Isolate* isolate = this->isolate();
RCS_SCOPE(isolate, RuntimeCallCounterId::kAccessorGetterCallback);
// Unlike interceptor callbacks we know that the property exists, so
// the callback is allowed to have side effects.
AcceptSideEffects();
AccessorNameGetterCallback f =
ToCData<AccessorNameGetterCallback>(info->getter());
return BasicCallNamedGetterCallback(f, name, info,
......@@ -307,6 +322,10 @@ Handle<Object> PropertyCallbackArguments::CallAccessorSetter(
Handle<Object> value) {
Isolate* isolate = this->isolate();
RCS_SCOPE(isolate, RuntimeCallCounterId::kAccessorSetterCallback);
// Unlike interceptor callbacks we know that the property exists, so
// the callback is allowed to have side effects.
AcceptSideEffects();
AccessorNameSetterCallback f =
ToCData<AccessorNameSetterCallback>(accessor_info->setter());
PREPARE_CALLBACK_INFO(isolate, f, Handle<Object>, void, accessor_info,
......
......@@ -12,7 +12,12 @@ namespace internal {
PropertyCallbackArguments::PropertyCallbackArguments(
Isolate* isolate, Object data, Object self, JSObject holder,
Maybe<ShouldThrow> should_throw)
: Super(isolate) {
: Super(isolate)
#ifdef DEBUG
,
javascript_execution_counter_(isolate->javascript_execution_counter())
#endif // DEBUG
{
slot_at(T::kThisIndex).store(self);
slot_at(T::kHolderIndex).store(holder);
slot_at(T::kDataIndex).store(data);
......
......@@ -58,7 +58,15 @@ class CustomArguments : public CustomArgumentsBase {
// Note: Calling args.Call() sets the return value on args. For multiple
// Call()'s, a new args should be used every time.
class PropertyCallbackArguments
// This class also serves as a side effects detection scope (JavaScript code
// execution). It is used for ensuring correctness of the interceptor callback
// implementations. The idea is that the interceptor callback that does not
// intercept an operation must not produce side effects. If the callback
// signals that it has handled the operation (by either returning a respective
// result or by throwing an exception) then the AcceptSideEffects() method
// must be called to "accept" the side effects that have happened during the
// lifetime of the PropertyCallbackArguments object.
class PropertyCallbackArguments final
: public CustomArguments<PropertyCallbackInfo<Value> > {
public:
using T = PropertyCallbackInfo<Value>;
......@@ -74,6 +82,7 @@ class PropertyCallbackArguments
PropertyCallbackArguments(Isolate* isolate, Object data, Object self,
JSObject holder, Maybe<ShouldThrow> should_throw);
inline ~PropertyCallbackArguments();
// Don't copy PropertyCallbackArguments, because they would both have the
// same prev_ pointer.
......@@ -128,6 +137,14 @@ class PropertyCallbackArguments
inline Handle<JSObject> CallIndexedEnumerator(
Handle<InterceptorInfo> interceptor);
// Accept potential JavaScript side effects that might occurr during life
// time of this object.
inline void AcceptSideEffects() {
#ifdef DEBUG
javascript_execution_counter_ = 0;
#endif // DEBUG
}
private:
/*
* The following Call functions wrap the calling of all callbacks to handle
......@@ -148,6 +165,13 @@ class PropertyCallbackArguments
inline JSObject holder();
inline Object receiver();
#ifdef DEBUG
// This stores current value of Isolate::javascript_execution_counter().
// It's used for detecting whether JavaScript code was executed between
// PropertyCallbackArguments's constructior and destructor.
uint32_t javascript_execution_counter_;
#endif // DEBUG
};
class FunctionCallbackArguments
......
......@@ -377,6 +377,7 @@ V8_WARN_UNUSED_RESULT MaybeHandle<Object> Invoke(Isolate* isolate,
V8::GetCurrentPlatform()->DumpWithoutCrashing();
return isolate->factory()->undefined_value();
}
isolate->IncrementJavascriptExecutionCounter();
if (params.execution_target == Execution::Target::kCallable) {
Handle<Context> context = isolate->native_context();
......
......@@ -180,6 +180,16 @@ class StackMemory;
} \
} while (false)
#define RETURN_FAILURE_IF_SCHEDULED_EXCEPTION_DETECTOR(isolate, detector) \
do { \
Isolate* __isolate__ = (isolate); \
DCHECK(!__isolate__->has_pending_exception()); \
if (__isolate__->has_scheduled_exception()) { \
detector.AcceptSideEffects(); \
return __isolate__->PromoteScheduledException(); \
} \
} while (false)
// Macros for MaybeHandle.
#define RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate, value) \
......@@ -192,6 +202,10 @@ class StackMemory;
} \
} while (false)
#define RETURN_VALUE_IF_SCHEDULED_EXCEPTION_DETECTOR(isolate, detector, value) \
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate, \
(detector.AcceptSideEffects(), value))
#define RETURN_EXCEPTION_IF_SCHEDULED_EXCEPTION(isolate, T) \
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate, MaybeHandle<T>())
......@@ -518,6 +532,7 @@ using DebugObjectCache = std::vector<Handle<HeapObject>>;
V(bool, javascript_execution_assert, true) \
V(bool, javascript_execution_throws, true) \
V(bool, javascript_execution_dump, true) \
V(uint32_t, javascript_execution_counter, 0) \
V(bool, deoptimization_assert, true) \
V(bool, compilation_assert, true) \
V(bool, no_exception_assert, true)
......@@ -1655,6 +1670,10 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {
return reinterpret_cast<Address>(&javascript_execution_assert_);
}
void IncrementJavascriptExecutionCounter() {
javascript_execution_counter_++;
}
Address handle_scope_implementer_address() {
return reinterpret_cast<Address>(&handle_scope_implementer_);
}
......
......@@ -3332,14 +3332,22 @@ RUNTIME_FUNCTION(Runtime_LoadPropertyWithInterceptor) {
isolate, receiver, Object::ConvertReceiver(isolate, receiver));
}
Handle<InterceptorInfo> interceptor(holder->GetNamedInterceptor(), isolate);
PropertyCallbackArguments arguments(isolate, interceptor->data(), *receiver,
*holder, Just(kDontThrow));
Handle<Object> result = arguments.CallNamedGetter(interceptor, name);
{
Handle<InterceptorInfo> interceptor(holder->GetNamedInterceptor(), isolate);
PropertyCallbackArguments arguments(isolate, interceptor->data(), *receiver,
*holder, Just(kDontThrow));
RETURN_FAILURE_IF_SCHEDULED_EXCEPTION(isolate);
Handle<Object> result = arguments.CallNamedGetter(interceptor, name);
RETURN_FAILURE_IF_SCHEDULED_EXCEPTION_DETECTOR(isolate, arguments);
if (!result.is_null()) return *result;
if (!result.is_null()) {
arguments.AcceptSideEffects();
return *result;
}
// If the interceptor didn't handle the request, then there must be no
// side effects.
}
LookupIterator it(isolate, receiver, name, holder);
// Skip any lookup work until we hit the (possibly non-masking) interceptor.
......@@ -3350,6 +3358,7 @@ RUNTIME_FUNCTION(Runtime_LoadPropertyWithInterceptor) {
}
// Skip past the interceptor.
it.Next();
Handle<Object> result;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, result, Object::GetProperty(&it));
if (it.IsFound()) return *result;
......@@ -3387,16 +3396,20 @@ RUNTIME_FUNCTION(Runtime_StorePropertyWithInterceptor) {
handle(JSObject::cast(receiver->map().prototype()), isolate);
}
DCHECK(interceptor_holder->HasNamedInterceptor());
Handle<InterceptorInfo> interceptor(interceptor_holder->GetNamedInterceptor(),
isolate);
{
Handle<InterceptorInfo> interceptor(
interceptor_holder->GetNamedInterceptor(), isolate);
DCHECK(!interceptor->non_masking());
PropertyCallbackArguments arguments(isolate, interceptor->data(), *receiver,
*receiver, Just(kDontThrow));
DCHECK(!interceptor->non_masking());
PropertyCallbackArguments arguments(isolate, interceptor->data(), *receiver,
*receiver, Just(kDontThrow));
Handle<Object> result = arguments.CallNamedSetter(interceptor, name, value);
RETURN_FAILURE_IF_SCHEDULED_EXCEPTION(isolate);
if (!result.is_null()) return *value;
Handle<Object> result = arguments.CallNamedSetter(interceptor, name, value);
RETURN_FAILURE_IF_SCHEDULED_EXCEPTION_DETECTOR(isolate, arguments);
if (!result.is_null()) return *value;
// If the interceptor didn't handle the request, then there must be no
// side effects.
}
LookupIterator it(isolate, receiver, name, receiver);
// Skip past any access check on the receiver.
......@@ -3426,7 +3439,7 @@ RUNTIME_FUNCTION(Runtime_LoadElementWithInterceptor) {
*receiver, Just(kDontThrow));
Handle<Object> result = arguments.CallIndexedGetter(interceptor, index);
RETURN_FAILURE_IF_SCHEDULED_EXCEPTION(isolate);
RETURN_FAILURE_IF_SCHEDULED_EXCEPTION_DETECTOR(isolate, arguments);
if (result.is_null()) {
LookupIterator it(isolate, receiver, index, receiver);
......@@ -3465,24 +3478,30 @@ RUNTIME_FUNCTION(Runtime_HasElementWithInterceptor) {
DCHECK_GE(args.smi_value_at(1), 0);
uint32_t index = args.smi_value_at(1);
Handle<InterceptorInfo> interceptor(receiver->GetIndexedInterceptor(),
isolate);
PropertyCallbackArguments arguments(isolate, interceptor->data(), *receiver,
*receiver, Just(kDontThrow));
if (!interceptor->query().IsUndefined(isolate)) {
Handle<Object> result = arguments.CallIndexedQuery(interceptor, index);
if (!result.is_null()) {
int32_t value;
CHECK(result->ToInt32(&value));
return value == ABSENT ? ReadOnlyRoots(isolate).false_value()
: ReadOnlyRoots(isolate).true_value();
}
} else if (!interceptor->getter().IsUndefined(isolate)) {
Handle<Object> result = arguments.CallIndexedGetter(interceptor, index);
if (!result.is_null()) {
return ReadOnlyRoots(isolate).true_value();
{
Handle<InterceptorInfo> interceptor(receiver->GetIndexedInterceptor(),
isolate);
PropertyCallbackArguments arguments(isolate, interceptor->data(), *receiver,
*receiver, Just(kDontThrow));
if (!interceptor->query().IsUndefined(isolate)) {
Handle<Object> result = arguments.CallIndexedQuery(interceptor, index);
if (!result.is_null()) {
int32_t value;
CHECK(result->ToInt32(&value));
if (value == ABSENT) return ReadOnlyRoots(isolate).false_value();
arguments.AcceptSideEffects();
return ReadOnlyRoots(isolate).true_value();
}
} else if (!interceptor->getter().IsUndefined(isolate)) {
Handle<Object> result = arguments.CallIndexedGetter(interceptor, index);
if (!result.is_null()) {
arguments.AcceptSideEffects();
return ReadOnlyRoots(isolate).true_value();
}
}
// If the interceptor didn't handle the request, then there must be no
// side effects.
}
LookupIterator it(isolate, receiver, index, receiver);
......
......@@ -1167,9 +1167,11 @@ MaybeHandle<Object> GetPropertyWithInterceptorInternal(
result = args.CallNamedGetter(interceptor, it->name());
}
RETURN_EXCEPTION_IF_SCHEDULED_EXCEPTION(isolate, Object);
RETURN_VALUE_IF_SCHEDULED_EXCEPTION_DETECTOR(isolate, args,
MaybeHandle<Object>());
if (result.is_null()) return isolate->factory()->undefined_value();
*done = true;
args.AcceptSideEffects();
// Rebox handle before return
return handle(*result, isolate);
}
......@@ -1205,6 +1207,10 @@ Maybe<PropertyAttributes> GetPropertyAttributesWithInterceptorInternal(
CHECK(result->ToInt32(&value));
DCHECK_IMPLIES((value & ~PropertyAttributes::ALL_ATTRIBUTES_MASK) != 0,
value == PropertyAttributes::ABSENT);
// In case of absent property side effects are not allowed.
if (value != PropertyAttributes::ABSENT) {
args.AcceptSideEffects();
}
return Just(static_cast<PropertyAttributes>(value));
}
} else if (!interceptor->getter().IsUndefined(isolate)) {
......@@ -1215,10 +1221,14 @@ Maybe<PropertyAttributes> GetPropertyAttributesWithInterceptorInternal(
} else {
result = args.CallNamedGetter(interceptor, it->name());
}
if (!result.is_null()) return Just(DONT_ENUM);
if (!result.is_null()) {
args.AcceptSideEffects();
return Just(DONT_ENUM);
}
}
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate, Nothing<PropertyAttributes>());
RETURN_VALUE_IF_SCHEDULED_EXCEPTION_DETECTOR(isolate, args,
Nothing<PropertyAttributes>());
return Just(ABSENT);
}
......@@ -1252,7 +1262,9 @@ Maybe<bool> SetPropertyWithInterceptorInternal(
result = !args.CallNamedSetter(interceptor, it->name(), value).is_null();
}
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(it->isolate(), Nothing<bool>());
RETURN_VALUE_IF_SCHEDULED_EXCEPTION_DETECTOR(it->isolate(), args,
Nothing<bool>());
if (result) args.AcceptSideEffects();
return Just(result);
}
......@@ -1274,8 +1286,6 @@ Maybe<bool> DefinePropertyWithInterceptorInternal(
Object::ConvertReceiver(isolate, receiver),
Nothing<bool>());
}
PropertyCallbackArguments args(isolate, interceptor->data(), *receiver,
*holder, should_throw);
std::unique_ptr<v8::PropertyDescriptor> descriptor(
new v8::PropertyDescriptor());
......@@ -1298,6 +1308,8 @@ Maybe<bool> DefinePropertyWithInterceptorInternal(
descriptor->set_configurable(desc->configurable());
}
PropertyCallbackArguments args(isolate, interceptor->data(), *receiver,
*holder, should_throw);
if (it->IsElement(*holder)) {
result =
!args.CallIndexedDefiner(interceptor, it->array_index(), *descriptor)
......@@ -1307,7 +1319,9 @@ Maybe<bool> DefinePropertyWithInterceptorInternal(
!args.CallNamedDefiner(interceptor, it->name(), *descriptor).is_null();
}
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(it->isolate(), Nothing<bool>());
RETURN_VALUE_IF_SCHEDULED_EXCEPTION_DETECTOR(it->isolate(), args,
Nothing<bool>());
if (result) args.AcceptSideEffects();
return Just(result);
}
......@@ -1754,10 +1768,11 @@ Maybe<bool> GetPropertyDescriptorWithInterceptor(LookupIterator* it,
result = args.CallNamedDescriptor(interceptor, it->name());
}
// An exception was thrown in the interceptor. Propagate.
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate, Nothing<bool>());
RETURN_VALUE_IF_SCHEDULED_EXCEPTION_DETECTOR(isolate, args, Nothing<bool>());
if (!result.is_null()) {
// Request successfully intercepted, try to set the property
// Request was successfully intercepted, try to set the property
// descriptor.
args.AcceptSideEffects();
Utils::ApiCheck(
PropertyDescriptor::ToPropertyDescriptor(isolate, result, desc),
it->IsElement(*holder) ? "v8::IndexedPropertyDescriptorCallback"
......@@ -3918,10 +3933,11 @@ Maybe<bool> JSObject::DeletePropertyWithInterceptor(LookupIterator* it,
result = args.CallNamedDeleter(interceptor, it->name());
}
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate, Nothing<bool>());
RETURN_VALUE_IF_SCHEDULED_EXCEPTION_DETECTOR(isolate, args, Nothing<bool>());
if (result.is_null()) return Nothing<bool>();
DCHECK(result->IsBoolean());
args.AcceptSideEffects();
// Rebox CustomArguments::kReturnValueOffset before returning.
return Just(result->IsTrue(isolate));
}
......
......@@ -660,6 +660,7 @@ KeyAccumulator::FilterForEnumerableProperties(
if (!accessor->HasEntry(*result, entry)) continue;
// args are invalid after args.Call(), create a new one in every iteration.
// Query callbacks are not expected to have side effects.
PropertyCallbackArguments args(isolate_, interceptor->data(), *receiver,
*object, Just(kDontThrow));
......@@ -702,9 +703,14 @@ Maybe<bool> KeyAccumulator::CollectInterceptorKeysInternal(
result = enum_args.CallNamedEnumerator(interceptor);
}
}
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate_, Nothing<bool>());
RETURN_VALUE_IF_SCHEDULED_EXCEPTION_DETECTOR(isolate_, enum_args,
Nothing<bool>());
if (result.is_null()) return Just(true);
// Request was successfully intercepted, so accept potential side effects
// happened up to this point.
enum_args.AcceptSideEffects();
if ((filter_ & ONLY_ENUMERABLE) &&
!interceptor->query().IsUndefined(isolate_)) {
RETURN_NOTHING_IF_NOT_SUCCESSFUL(FilterForEnumerableProperties(
......
......@@ -40,6 +40,9 @@
# These tests are expected to hit a CHECK (i.e. a FAIL result actually means
# the test passed).
'test-verifiers/Fail*': [FAIL, CRASH],
# BUG(chromium:1310062): these tests must crash once the side effect check
# is enabled.
#'test-api-interceptors/Crash*': [FAIL, CRASH],
# This test always fails. It tests that LiveEdit causes abort when turned off.
'test-debug/LiveEditDisabled': [FAIL],
......
......@@ -701,7 +701,7 @@ THREADED_TEST(GlobalObjectAccessor) {
static void EmptyGetter(Local<Name> name,
const v8::PropertyCallbackInfo<v8::Value>& info) {
ApiTestFuzzer::Fuzz();
// The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
}
......
......@@ -224,7 +224,7 @@ v8::Local<v8::Object> bottom;
void CheckThisIndexedPropertyHandler(
uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info) {
CheckReturnValue(info, FUNCTION_ADDR(CheckThisIndexedPropertyHandler));
ApiTestFuzzer::Fuzz();
// The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
CHECK(info.This()
->Equals(info.GetIsolate()->GetCurrentContext(), bottom)
.FromJust());
......@@ -233,7 +233,7 @@ void CheckThisIndexedPropertyHandler(
void CheckThisNamedPropertyHandler(
Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
CheckReturnValue(info, FUNCTION_ADDR(CheckThisNamedPropertyHandler));
ApiTestFuzzer::Fuzz();
// The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
CHECK(info.This()
->Equals(info.GetIsolate()->GetCurrentContext(), bottom)
.FromJust());
......@@ -243,7 +243,7 @@ void CheckThisIndexedPropertyDefiner(
uint32_t index, const v8::PropertyDescriptor& desc,
const v8::PropertyCallbackInfo<v8::Value>& info) {
CheckReturnValue(info, FUNCTION_ADDR(CheckThisIndexedPropertyDefiner));
ApiTestFuzzer::Fuzz();
// The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
CHECK(info.This()
->Equals(info.GetIsolate()->GetCurrentContext(), bottom)
.FromJust());
......@@ -253,7 +253,7 @@ void CheckThisNamedPropertyDefiner(
Local<Name> property, const v8::PropertyDescriptor& desc,
const v8::PropertyCallbackInfo<v8::Value>& info) {
CheckReturnValue(info, FUNCTION_ADDR(CheckThisNamedPropertyDefiner));
ApiTestFuzzer::Fuzz();
// The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
CHECK(info.This()
->Equals(info.GetIsolate()->GetCurrentContext(), bottom)
.FromJust());
......@@ -263,7 +263,7 @@ void CheckThisIndexedPropertySetter(
uint32_t index, Local<Value> value,
const v8::PropertyCallbackInfo<v8::Value>& info) {
CheckReturnValue(info, FUNCTION_ADDR(CheckThisIndexedPropertySetter));
ApiTestFuzzer::Fuzz();
// The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
CHECK(info.This()
->Equals(info.GetIsolate()->GetCurrentContext(), bottom)
.FromJust());
......@@ -272,7 +272,7 @@ void CheckThisIndexedPropertySetter(
void CheckThisIndexedPropertyDescriptor(
uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info) {
CheckReturnValue(info, FUNCTION_ADDR(CheckThisIndexedPropertyDescriptor));
ApiTestFuzzer::Fuzz();
// The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
CHECK(info.This()
->Equals(info.GetIsolate()->GetCurrentContext(), bottom)
.FromJust());
......@@ -281,7 +281,7 @@ void CheckThisIndexedPropertyDescriptor(
void CheckThisNamedPropertyDescriptor(
Local<Name> property, const v8::PropertyCallbackInfo<v8::Value>& info) {
CheckReturnValue(info, FUNCTION_ADDR(CheckThisNamedPropertyDescriptor));
ApiTestFuzzer::Fuzz();
// The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
CHECK(info.This()
->Equals(info.GetIsolate()->GetCurrentContext(), bottom)
.FromJust());
......@@ -291,7 +291,7 @@ void CheckThisNamedPropertySetter(
Local<Name> property, Local<Value> value,
const v8::PropertyCallbackInfo<v8::Value>& info) {
CheckReturnValue(info, FUNCTION_ADDR(CheckThisNamedPropertySetter));
ApiTestFuzzer::Fuzz();
// The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
CHECK(info.This()
->Equals(info.GetIsolate()->GetCurrentContext(), bottom)
.FromJust());
......@@ -300,7 +300,7 @@ void CheckThisNamedPropertySetter(
void CheckThisIndexedPropertyQuery(
uint32_t index, const v8::PropertyCallbackInfo<v8::Integer>& info) {
CheckReturnValue(info, FUNCTION_ADDR(CheckThisIndexedPropertyQuery));
ApiTestFuzzer::Fuzz();
// The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
CHECK(info.This()
->Equals(info.GetIsolate()->GetCurrentContext(), bottom)
.FromJust());
......@@ -310,7 +310,7 @@ void CheckThisIndexedPropertyQuery(
void CheckThisNamedPropertyQuery(
Local<Name> property, const v8::PropertyCallbackInfo<v8::Integer>& info) {
CheckReturnValue(info, FUNCTION_ADDR(CheckThisNamedPropertyQuery));
ApiTestFuzzer::Fuzz();
// The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
CHECK(info.This()
->Equals(info.GetIsolate()->GetCurrentContext(), bottom)
.FromJust());
......@@ -320,7 +320,7 @@ void CheckThisNamedPropertyQuery(
void CheckThisIndexedPropertyDeleter(
uint32_t index, const v8::PropertyCallbackInfo<v8::Boolean>& info) {
CheckReturnValue(info, FUNCTION_ADDR(CheckThisIndexedPropertyDeleter));
ApiTestFuzzer::Fuzz();
// The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
CHECK(info.This()
->Equals(info.GetIsolate()->GetCurrentContext(), bottom)
.FromJust());
......@@ -330,7 +330,7 @@ void CheckThisIndexedPropertyDeleter(
void CheckThisNamedPropertyDeleter(
Local<Name> property, const v8::PropertyCallbackInfo<v8::Boolean>& info) {
CheckReturnValue(info, FUNCTION_ADDR(CheckThisNamedPropertyDeleter));
ApiTestFuzzer::Fuzz();
// The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
CHECK(info.This()
->Equals(info.GetIsolate()->GetCurrentContext(), bottom)
.FromJust());
......@@ -340,7 +340,7 @@ void CheckThisNamedPropertyDeleter(
void CheckThisIndexedPropertyEnumerator(
const v8::PropertyCallbackInfo<v8::Array>& info) {
CheckReturnValue(info, FUNCTION_ADDR(CheckThisIndexedPropertyEnumerator));
ApiTestFuzzer::Fuzz();
// The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
CHECK(info.This()
->Equals(info.GetIsolate()->GetCurrentContext(), bottom)
.FromJust());
......@@ -350,7 +350,7 @@ void CheckThisIndexedPropertyEnumerator(
void CheckThisNamedPropertyEnumerator(
const v8::PropertyCallbackInfo<v8::Array>& info) {
CheckReturnValue(info, FUNCTION_ADDR(CheckThisNamedPropertyEnumerator));
ApiTestFuzzer::Fuzz();
// The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
CHECK(info.This()
->Equals(info.GetIsolate()->GetCurrentContext(), bottom)
.FromJust());
......@@ -372,12 +372,12 @@ void EchoNamedProperty(Local<Name> name,
void InterceptorHasOwnPropertyGetter(
Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
ApiTestFuzzer::Fuzz();
// The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
}
void InterceptorHasOwnPropertyGetterGC(
Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
ApiTestFuzzer::Fuzz();
// The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
CcTest::CollectAllGarbage();
}
......@@ -883,7 +883,7 @@ void CheckInterceptorIC(v8::GenericNamedPropertyGetterCallback getter,
v8::GenericNamedPropertyQueryCallback query,
v8::GenericNamedPropertyDefinerCallback definer,
v8::PropertyHandlerFlags flags, const char* source,
int expected) {
std::optional<int> expected) {
v8::Isolate* isolate = CcTest::isolate();
v8::HandleScope scope(isolate);
v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
......@@ -896,12 +896,16 @@ void CheckInterceptorIC(v8::GenericNamedPropertyGetterCallback getter,
templ->NewInstance(context.local()).ToLocalChecked())
.FromJust();
v8::Local<Value> value = CompileRun(source);
CHECK_EQ(expected, value->Int32Value(context.local()).FromJust());
if (expected) {
CHECK_EQ(*expected, value->Int32Value(context.local()).FromJust());
} else {
CHECK(value.IsEmpty());
}
}
void CheckInterceptorIC(v8::GenericNamedPropertyGetterCallback getter,
v8::GenericNamedPropertyQueryCallback query,
const char* source, int expected) {
const char* source, std::optional<int> expected) {
CheckInterceptorIC(getter, nullptr, query, nullptr,
v8::PropertyHandlerFlags::kNone, source, expected);
}
......@@ -944,13 +948,14 @@ namespace {
void InterceptorLoadXICGetter(Local<Name> name,
const v8::PropertyCallbackInfo<v8::Value>& info) {
ApiTestFuzzer::Fuzz();
info.GetReturnValue().Set(
v8_str("x")
->Equals(info.GetIsolate()->GetCurrentContext(), name)
.FromJust()
? v8::Local<v8::Value>(v8::Integer::New(info.GetIsolate(), 42))
: v8::Local<v8::Value>());
if (v8_str("x")
->Equals(info.GetIsolate()->GetCurrentContext(), name)
.FromJust()) {
// Side effects are allowed only when the property is present or throws.
ApiTestFuzzer::Fuzz();
info.GetReturnValue().Set(
v8::Local<v8::Value>(v8::Integer::New(info.GetIsolate(), 42)));
}
}
void InterceptorLoadXICGetterWithSideEffects(
......@@ -1472,7 +1477,10 @@ namespace {
template <typename TKey, v8::internal::PropertyAttributes attribute>
void HasICQuery(TKey name, const v8::PropertyCallbackInfo<v8::Integer>& info) {
ApiTestFuzzer::Fuzz();
if (attribute != v8::internal::ABSENT) {
// Side effects are allowed only when the property is present or throws.
ApiTestFuzzer::Fuzz();
}
v8::Isolate* isolate = CcTest::isolate();
CHECK_EQ(isolate, info.GetIsolate());
info.GetReturnValue().Set(v8::Integer::New(isolate, attribute));
......@@ -1481,19 +1489,25 @@ void HasICQuery(TKey name, const v8::PropertyCallbackInfo<v8::Integer>& info) {
template <typename TKey>
void HasICQueryToggle(TKey name,
const v8::PropertyCallbackInfo<v8::Integer>& info) {
ApiTestFuzzer::Fuzz();
static bool toggle = false;
toggle = !toggle;
static bool is_absent = false;
is_absent = !is_absent;
if (!is_absent) {
// Side effects are allowed only when the property is present or throws.
ApiTestFuzzer::Fuzz();
}
v8::Isolate* isolate = CcTest::isolate();
CHECK_EQ(isolate, info.GetIsolate());
info.GetReturnValue().Set(v8::Integer::New(
isolate, toggle ? v8::internal::ABSENT : v8::internal::NONE));
isolate, is_absent ? v8::internal::ABSENT : v8::internal::NONE));
}
template <typename TKey, v8::internal::PropertyAttributes attribute>
void HasICQuerySideEffect(TKey name,
const v8::PropertyCallbackInfo<v8::Integer>& info) {
ApiTestFuzzer::Fuzz();
if (attribute != v8::internal::ABSENT) {
// Side effects are allowed only when the property is present or throws.
ApiTestFuzzer::Fuzz();
}
v8::Isolate* isolate = CcTest::isolate();
CHECK_EQ(isolate, info.GetIsolate());
CompileRun("interceptor_query_side_effect()");
......@@ -1567,7 +1581,26 @@ THREADED_TEST(InterceptorHasICQueryToggle) {
500);
}
THREADED_TEST(InterceptorStoreICWithSideEffectfulCallbacks) {
THREADED_TEST(InterceptorStoreICWithSideEffectfulCallbacks1) {
CheckInterceptorIC(EmptyInterceptorGetter,
HasICQuerySideEffect<Local<Name>, v8::internal::NONE>,
"let r;"
"let inside_side_effect = false;"
"let interceptor_query_side_effect = function() {"
" if (!inside_side_effect) {"
" inside_side_effect = true;"
" r.x = 153;"
" inside_side_effect = false;"
" }"
"};"
"for (var i = 0; i < 20; i++) {"
" r = { __proto__: o };"
" r.x = i;"
"}",
19);
}
TEST(Crash_InterceptorStoreICWithSideEffectfulCallbacks1) {
CheckInterceptorIC(EmptyInterceptorGetter,
HasICQuerySideEffect<Local<Name>, v8::internal::ABSENT>,
"let r;"
......@@ -1584,7 +1617,9 @@ THREADED_TEST(InterceptorStoreICWithSideEffectfulCallbacks) {
" r.x = i;"
"}",
19);
}
TEST(Crash_InterceptorStoreICWithSideEffectfulCallbacks2) {
CheckInterceptorIC(InterceptorLoadXICGetterWithSideEffects,
nullptr, // query callback is not provided
"let r;"
......@@ -1615,7 +1650,39 @@ THREADED_TEST(InterceptorDefineICWithSideEffectfulCallbacks) {
" o.y = 153;"
" inside_side_effect = false;"
" }"
" return null;"
" return true;" // Accept the request.
"};"
"class Base {"
" constructor(arg) {"
" return arg;"
" }"
"}"
"class ClassWithField extends Base {"
" y = (() => {"
" return 42;"
" })();"
" constructor(arg) {"
" super(arg);"
" }"
"}"
"new ClassWithField(o);"
"o.y",
153);
}
TEST(Crash_InterceptorDefineICWithSideEffectfulCallbacks) {
CheckInterceptorIC(EmptyInterceptorGetter, EmptyInterceptorSetter,
EmptyInterceptorQuery,
EmptyInterceptorDefinerWithSideEffect,
v8::PropertyHandlerFlags::kNonMasking,
"let inside_side_effect = false;"
"let interceptor_definer_side_effect = function() {"
" if (!inside_side_effect) {"
" inside_side_effect = true;"
" o.y = 153;"
" inside_side_effect = false;"
" }"
" return null;" // Decline the request.
"};"
"class Base {"
" constructor(arg) {"
......@@ -2741,16 +2808,16 @@ TEST(PropertyHandlerInPrototypeWithDefine) {
bool is_bootstrapping = false;
static void PrePropertyHandlerGet(
Local<Name> key, const v8::PropertyCallbackInfo<v8::Value>& info) {
ApiTestFuzzer::Fuzz();
if (!is_bootstrapping &&
v8_str("pre")
->Equals(info.GetIsolate()->GetCurrentContext(), key)
.FromJust()) {
// Side effects are allowed only when the property is present or throws.
ApiTestFuzzer::Fuzz();
info.GetReturnValue().Set(v8_str("PrePropertyHandler: pre"));
}
}
static void PrePropertyHandlerQuery(
Local<Name> key, const v8::PropertyCallbackInfo<v8::Integer>& info) {
if (!is_bootstrapping &&
......@@ -3318,26 +3385,25 @@ THREADED_TEST(NamedInterceptorMapTransitionRead) {
CHECK_EQ(23, result->Int32Value(context.local()).FromJust());
}
static void IndexedPropertyGetter(
uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info) {
ApiTestFuzzer::Fuzz();
if (index == 37) {
// Side effects are allowed only when the property is present or throws.
ApiTestFuzzer::Fuzz();
info.GetReturnValue().Set(v8_num(625));
}
}
static void IndexedPropertySetter(
uint32_t index, Local<Value> value,
const v8::PropertyCallbackInfo<v8::Value>& info) {
ApiTestFuzzer::Fuzz();
if (index == 39) {
// Side effects are allowed only when the property is present or throws.
ApiTestFuzzer::Fuzz();
info.GetReturnValue().Set(value);
}
}
THREADED_TEST(IndexedInterceptorWithIndexedAccessor) {
v8::Isolate* isolate = CcTest::isolate();
v8::HandleScope scope(isolate);
......@@ -3370,26 +3436,25 @@ THREADED_TEST(IndexedInterceptorWithIndexedAccessor) {
CHECK(v8_num(625)->Equals(context.local(), result).FromJust());
}
static void UnboxedDoubleIndexedPropertyGetter(
uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info) {
ApiTestFuzzer::Fuzz();
if (index < 25) {
// Side effects are allowed only when the property is present or throws.
ApiTestFuzzer::Fuzz();
info.GetReturnValue().Set(v8_num(index));
}
}
static void UnboxedDoubleIndexedPropertySetter(
uint32_t index, Local<Value> value,
const v8::PropertyCallbackInfo<v8::Value>& info) {
ApiTestFuzzer::Fuzz();
if (index < 25) {
// Side effects are allowed only when the property is present or throws.
ApiTestFuzzer::Fuzz();
info.GetReturnValue().Set(v8_num(index));
}
}
void UnboxedDoubleIndexedPropertyEnumerator(
const v8::PropertyCallbackInfo<v8::Array>& info) {
// Force the list of returned keys to be stored in a FastDoubleArray.
......@@ -3797,7 +3862,7 @@ void IndexedQueryCallback(uint32_t index,
void IndexHasICQueryAbsent(uint32_t index,
const v8::PropertyCallbackInfo<v8::Integer>& info) {
ApiTestFuzzer::Fuzz();
// The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
v8::Isolate* isolate = CcTest::isolate();
CHECK_EQ(isolate, info.GetIsolate());
info.GetReturnValue().Set(v8::Integer::New(isolate, v8::internal::ABSENT));
......@@ -3950,23 +4015,25 @@ THREADED_TEST(Deleter) {
v8_compile("k[4]")->Run(context.local()).ToLocalChecked()->IsUndefined());
}
static void GetK(Local<Name> name,
const v8::PropertyCallbackInfo<v8::Value>& info) {
ApiTestFuzzer::Fuzz();
v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
if (name->Equals(context, v8_str("foo")).FromJust() ||
name->Equals(context, v8_str("bar")).FromJust() ||
name->Equals(context, v8_str("baz")).FromJust()) {
// Side effects are allowed only when the property is present or throws.
ApiTestFuzzer::Fuzz();
info.GetReturnValue().SetUndefined();
}
}
static void IndexedGetK(uint32_t index,
const v8::PropertyCallbackInfo<v8::Value>& info) {
ApiTestFuzzer::Fuzz();
if (index == 0 || index == 1) info.GetReturnValue().SetUndefined();
if (index == 0 || index == 1) {
// Side effects are allowed only when the property is present or throws.
ApiTestFuzzer::Fuzz();
info.GetReturnValue().SetUndefined();
}
}
......@@ -4691,17 +4758,17 @@ static int interceptor_call_count = 0;
static void InterceptorICRefErrorGetter(
Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
ApiTestFuzzer::Fuzz();
if (!is_bootstrapping &&
v8_str("x")
->Equals(info.GetIsolate()->GetCurrentContext(), name)
.FromJust() &&
interceptor_call_count++ < 20) {
// Side effects are allowed only when the property is present or throws.
ApiTestFuzzer::Fuzz();
info.GetReturnValue().Set(call_ic_function2);
}
}
// This test should hit load and call ICs for the interceptor case.
// Once in a while, the interceptor will reply that a property was not
// found in which case we should get a reference error.
......@@ -4743,21 +4810,23 @@ static int interceptor_ic_exception_get_count = 0;
static void InterceptorICExceptionGetter(
Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
ApiTestFuzzer::Fuzz();
if (is_bootstrapping) return;
if (v8_str("x")
->Equals(info.GetIsolate()->GetCurrentContext(), name)
.FromJust() &&
++interceptor_ic_exception_get_count < 20) {
// Side effects are allowed only when the property is present or throws.
ApiTestFuzzer::Fuzz();
info.GetReturnValue().Set(call_ic_function3);
}
if (interceptor_ic_exception_get_count == 20) {
// Side effects are allowed only when the property is present or throws.
ApiTestFuzzer::Fuzz();
info.GetIsolate()->ThrowException(v8_num(42));
return;
}
}
// Test interceptor load/call IC where the interceptor throws an
// exception once in a while.
THREADED_TEST(InterceptorICGetterExceptions) {
......@@ -4800,13 +4869,13 @@ static int interceptor_ic_exception_set_count = 0;
static void InterceptorICExceptionSetter(
Local<Name> key, Local<Value> value,
const v8::PropertyCallbackInfo<v8::Value>& info) {
ApiTestFuzzer::Fuzz();
if (++interceptor_ic_exception_set_count > 20) {
// Side effects are allowed only when the property is present or throws.
ApiTestFuzzer::Fuzz();
info.GetIsolate()->ThrowException(v8_num(42));
}
}
// Test interceptor store IC where the interceptor throws an exception
// once in a while.
THREADED_TEST(InterceptorICSetterExceptions) {
......@@ -5287,23 +5356,23 @@ struct ShouldInterceptData {
bool should_intercept;
};
void ShouldNamedInterceptor(Local<Name> name,
const v8::PropertyCallbackInfo<Value>& info) {
ApiTestFuzzer::Fuzz();
CheckReturnValue(info, FUNCTION_ADDR(ShouldNamedInterceptor));
auto data = GetWrappedObject<ShouldInterceptData>(info.Data());
if (!data->should_intercept) return;
// Side effects are allowed only when the property is present or throws.
ApiTestFuzzer::Fuzz();
info.GetReturnValue().Set(v8_num(data->value));
}
void ShouldIndexedInterceptor(uint32_t,
const v8::PropertyCallbackInfo<Value>& info) {
ApiTestFuzzer::Fuzz();
CheckReturnValue(info, FUNCTION_ADDR(ShouldIndexedInterceptor));
auto data = GetWrappedObject<ShouldInterceptData>(info.Data());
if (!data->should_intercept) return;
// Side effects are allowed only when the property is present or throws.
ApiTestFuzzer::Fuzz();
info.GetReturnValue().Set(v8_num(data->value));
}
......@@ -5885,22 +5954,25 @@ namespace {
void DatabaseGetter(Local<Name> name,
const v8::PropertyCallbackInfo<Value>& info) {
ApiTestFuzzer::Fuzz();
auto context = info.GetIsolate()->GetCurrentContext();
v8::MaybeLocal<Value> maybe_db =
info.Holder()->GetRealNamedProperty(context, v8_str("db"));
if (maybe_db.IsEmpty()) return;
Local<v8::Object> db = maybe_db.ToLocalChecked().As<v8::Object>();
if (!db->Has(context, name).FromJust()) return;
// Side effects are allowed only when the property is present or throws.
ApiTestFuzzer::Fuzz();
info.GetReturnValue().Set(db->Get(context, name).ToLocalChecked());
}
void DatabaseSetter(Local<Name> name, Local<Value> value,
const v8::PropertyCallbackInfo<Value>& info) {
ApiTestFuzzer::Fuzz();
auto context = info.GetIsolate()->GetCurrentContext();
if (name->Equals(context, v8_str("db")).FromJust()) return;
// Side effects are allowed only when the property is present or throws.
ApiTestFuzzer::Fuzz();
Local<v8::Object> db = info.Holder()
->GetRealNamedProperty(context, v8_str("db"))
.ToLocalChecked()
......
......@@ -7998,7 +7998,8 @@ static void PGetter2(Local<Name> name,
const v8::PropertyCallbackInfo<v8::Value>& info) {
ApiTestFuzzer::Fuzz();
p_getter_count2++;
v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
v8::Isolate* isolate = info.GetIsolate();
v8::Local<v8::Context> context = isolate->GetCurrentContext();
v8::Local<v8::Object> global = context->Global();
CHECK(
info.Holder()
......@@ -8025,6 +8026,8 @@ static void PGetter2(Local<Name> name,
global->Get(context, v8_str("o4")).ToLocalChecked())
.FromJust());
}
// Return something to indicate that the operation was intercepted.
info.GetReturnValue().Set(True(isolate));
}
......@@ -10226,7 +10229,7 @@ THREADED_TEST(InstanceProperties) {
static void GlobalObjectInstancePropertiesGet(
Local<Name> key, const v8::PropertyCallbackInfo<v8::Value>&) {
ApiTestFuzzer::Fuzz();
// The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
}
static int script_execution_count = 0;
......@@ -11584,7 +11587,7 @@ THREADED_TEST(HandleIteration) {
static void InterceptorCallICFastApi(
Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
ApiTestFuzzer::Fuzz();
// The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
CheckReturnValue(info, FUNCTION_ADDR(InterceptorCallICFastApi));
int* call_count =
reinterpret_cast<int*>(v8::External::Cast(*info.Data())->Value());
......@@ -12156,6 +12159,8 @@ static void ShouldThrowOnErrorSetter(Local<Name> name, Local<v8::Value> value,
->Set(isolate->GetCurrentContext(), v8_str("should_throw_setter"),
should_throw_on_error_value)
.FromJust());
// Return a boolean to indicate that the operation was intercepted.
info.GetReturnValue().Set(True(isolate));
}
......@@ -12224,6 +12229,8 @@ static void ShouldThrowOnErrorDeleter(
->Set(isolate->GetCurrentContext(), v8_str("should_throw_deleter"),
should_throw_on_error_value)
.FromJust());
// Return a boolean to indicate that the operation was intercepted.
info.GetReturnValue().Set(True(isolate));
}
......@@ -12986,6 +12993,11 @@ int ApiTestFuzzer::current_;
// We are in a callback and want to switch to another thread (if we
// are currently running the thread fuzzing test).
void ApiTestFuzzer::Fuzz() {
// Emulate context switch which might cause side effects as well.
// This is mostly to ensure that the callbacks in the tests do not cause
// side effects when they don't intercept the operation.
CcTest::i_isolate()->IncrementJavascriptExecutionCounter();
if (!fuzzing_) return;
ApiTestFuzzer* test = RegisterThreadedTest::nth(current_)->fuzzer_;
test->ContextSwitch();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment