diff --git a/include/v8.h b/include/v8.h index 224c16f9e8..6a16d5ae38 100644 --- a/include/v8.h +++ b/include/v8.h @@ -4200,6 +4200,16 @@ class V8_EXPORT Object : public Value { V8_INLINE static Object* Cast(Value* obj); + /** + * Support for TC39 "dynamic code brand checks" proposal. + * + * This API allows to query whether an object was constructed from a + * "code kind" ObjectTemplate. + * + * See also: v8::ObjectTemplate::SetCodeKind + */ + bool IsCodeKind(Isolate* isolate); + private: Object(); static void CheckCast(Value* obj); @@ -6981,6 +6991,18 @@ class V8_EXPORT ObjectTemplate : public Template { */ void SetImmutableProto(); + /** + * Support for TC39 "dynamic code brand checks" proposal. + * + * This API allows to mark (& query) objects as "code kind", which causes + * them to be treated as code-like (i.e. like Strings) in the context of + * eval and function constructor. + * + * Reference: https://github.com/tc39/proposal-dynamic-code-brand-checks + */ + void SetCodeKind(); + bool IsCodeKind(); + V8_INLINE static ObjectTemplate* Cast(Data* data); private: @@ -7555,11 +7577,15 @@ struct ModifyCodeGenerationFromStringsResult { /** * Callback to check if codegen is allowed from a source object, and convert - * the source to string if necessary.See ModifyCodeGenerationFromStrings. + * the source to string if necessary. See: ModifyCodeGenerationFromStrings. */ typedef ModifyCodeGenerationFromStringsResult ( *ModifyCodeGenerationFromStringsCallback)(Local context, Local source); +typedef ModifyCodeGenerationFromStringsResult ( + *ModifyCodeGenerationFromStringsCallback2)(Local context, + Local source, + bool is_code_kind); // --- WebAssembly compilation callbacks --- typedef bool (*ExtensionCallback)(const FunctionCallbackInfo&); @@ -9443,8 +9469,15 @@ class V8_EXPORT Isolate { "See http://crbug.com/v8/10096.") void SetAllowCodeGenerationFromStringsCallback( AllowCodeGenerationFromStringsCallback callback); + V8_DEPRECATE_SOON( + "Use Isolate::SetModifyCodeGenerationFromStringsCallback with " + "ModifyCodeGenerationFromStringsCallback2 instead. See " + "http://crbug.com/1096017 and TC39 Dynamic Code Brand Checks proposal " + "at https://github.com/tc39/proposal-dynamic-code-brand-checks.") void SetModifyCodeGenerationFromStringsCallback( ModifyCodeGenerationFromStringsCallback callback); + void SetModifyCodeGenerationFromStringsCallback( + ModifyCodeGenerationFromStringsCallback2 callback); /** * Set the callback to invoke to check if wasm code generation should diff --git a/src/api/api.cc b/src/api/api.cc index 177896b3fa..6376e9a5d7 100644 --- a/src/api/api.cc +++ b/src/api/api.cc @@ -2004,6 +2004,17 @@ void ObjectTemplate::SetImmutableProto() { self->set_immutable_proto(true); } +bool ObjectTemplate::IsCodeKind() { + return Utils::OpenHandle(this)->code_kind(); +} + +void ObjectTemplate::SetCodeKind() { + auto self = Utils::OpenHandle(this); + i::Isolate* isolate = self->GetIsolate(); + ENTER_V8_NO_SCRIPT_NO_EXCEPTION(isolate); + self->set_code_kind(true); +} + // --- S c r i p t s --- // Internally, UnboundScript is a SharedFunctionInfo, and Script is a @@ -9042,6 +9053,9 @@ CALLBACK_SETTER(AllowCodeGenerationFromStringsCallback, CALLBACK_SETTER(ModifyCodeGenerationFromStringsCallback, ModifyCodeGenerationFromStringsCallback, modify_code_gen_callback) +CALLBACK_SETTER(ModifyCodeGenerationFromStringsCallback, + ModifyCodeGenerationFromStringsCallback2, + modify_code_gen_callback2) CALLBACK_SETTER(AllowWasmCodeGenerationCallback, AllowWasmCodeGenerationCallback, allow_wasm_code_gen_callback) @@ -9191,6 +9205,14 @@ void v8::Isolate::LocaleConfigurationChangeNotification() { #endif // V8_INTL_SUPPORT } +bool v8::Object::IsCodeKind(v8::Isolate* isolate) { + i::Isolate* i_isolate = reinterpret_cast(isolate); + LOG_API(i_isolate, Object, IsCodeKind); + ENTER_V8_NO_SCRIPT_NO_EXCEPTION(i_isolate); + i::HandleScope scope(i_isolate); + return Utils::OpenHandle(this)->IsCodeKind(i_isolate); +} + // static std::unique_ptr MicrotaskQueue::New(Isolate* isolate, MicrotasksPolicy policy) { diff --git a/src/builtins/builtins-function.cc b/src/builtins/builtins-function.cc index b3fbc4fd94..2915fdf8de 100644 --- a/src/builtins/builtins-function.cc +++ b/src/builtins/builtins-function.cc @@ -80,6 +80,14 @@ MaybeHandle CreateDynamicFunction(Isolate* isolate, } } + bool is_code_kind = true; + for (int i = 0; i < argc; ++i) { + if (!args.at(i + 1)->IsCodeKind(isolate)) { + is_code_kind = false; + break; + } + } + // Compile the string in the constructor and not a helper so that errors to // come from here. Handle function; @@ -88,7 +96,7 @@ MaybeHandle CreateDynamicFunction(Isolate* isolate, isolate, function, Compiler::GetFunctionFromString( handle(target->native_context(), isolate), source, - ONLY_SINGLE_FUNCTION_LITERAL, parameters_end_pos), + ONLY_SINGLE_FUNCTION_LITERAL, parameters_end_pos, is_code_kind), Object); Handle result; ASSIGN_RETURN_ON_EXCEPTION( diff --git a/src/codegen/compiler.cc b/src/codegen/compiler.cc index 15e46a91a5..879bc0285d 100644 --- a/src/codegen/compiler.cc +++ b/src/codegen/compiler.cc @@ -2072,21 +2072,27 @@ bool CodeGenerationFromStringsAllowed(Isolate* isolate, Handle context, } // Check whether embedder allows code generation in this context. -// (via v8::Isolate::SetModifyCodeGenerationFromStringsCallback) +// (via v8::Isolate::SetModifyCodeGenerationFromStringsCallback +// or v8::Isolate::SetModifyCodeGenerationFromStringsCallback2) bool ModifyCodeGenerationFromStrings(Isolate* isolate, Handle context, - Handle* source) { - DCHECK(isolate->modify_code_gen_callback()); + Handle* source, + bool is_code_kind) { + DCHECK(isolate->modify_code_gen_callback() || + isolate->modify_code_gen_callback2()); DCHECK(source); // Callback set. Run it, and use the return value as source, or block // execution if it's not set. VMState state(isolate); - ModifyCodeGenerationFromStringsCallback modify_callback = - isolate->modify_code_gen_callback(); RuntimeCallTimerScope timer( isolate, RuntimeCallCounterId::kCodeGenerationFromStringsCallbacks); ModifyCodeGenerationFromStringsResult result = - modify_callback(v8::Utils::ToLocal(context), v8::Utils::ToLocal(*source)); + isolate->modify_code_gen_callback() + ? isolate->modify_code_gen_callback()(v8::Utils::ToLocal(context), + v8::Utils::ToLocal(*source)) + : isolate->modify_code_gen_callback2()(v8::Utils::ToLocal(context), + v8::Utils::ToLocal(*source), + is_code_kind); if (result.codegen_allowed && !result.modified_source.IsEmpty()) { // Use the new source (which might be the same as the old source). *source = @@ -2112,7 +2118,7 @@ bool ModifyCodeGenerationFromStrings(Isolate* isolate, Handle context, // static std::pair, bool> Compiler::ValidateDynamicCompilationSource( Isolate* isolate, Handle context, - Handle original_source) { + Handle original_source, bool is_code_kind) { // Check if the context unconditionally allows code gen from strings. // allow_code_gen_from_strings can be many things, so we'll always check // against the 'false' literal, so that e.g. undefined and 'true' are treated @@ -2126,6 +2132,11 @@ std::pair, bool> Compiler::ValidateDynamicCompilationSource( // allow_code_gen_callback only allows proper strings. // (I.e., let allow_code_gen_callback decide, if it has been set.) if (isolate->allow_code_gen_callback()) { + // If we run into this condition, the embedder has marked some object + // templates as "code kind", but has given us a callback that only accepts + // strings. That makes no sense. + DCHECK(!original_source->IsCodeKind(isolate)); + if (!original_source->IsString()) { return {MaybeHandle(), true}; } @@ -2139,9 +2150,11 @@ std::pair, bool> Compiler::ValidateDynamicCompilationSource( // Check if the context wants to block or modify this source object. // Double-check that we really have a string now. // (Let modify_code_gen_callback decide, if it's been set.) - if (isolate->modify_code_gen_callback()) { + if (isolate->modify_code_gen_callback() || + isolate->modify_code_gen_callback2()) { Handle modified_source = original_source; - if (!ModifyCodeGenerationFromStrings(isolate, context, &modified_source)) { + if (!ModifyCodeGenerationFromStrings(isolate, context, &modified_source, + is_code_kind)) { return {MaybeHandle(), false}; } if (!modified_source->IsString()) { @@ -2150,6 +2163,15 @@ std::pair, bool> Compiler::ValidateDynamicCompilationSource( return {Handle::cast(modified_source), false}; } + if (!context->allow_code_gen_from_strings().IsFalse(isolate) && + original_source->IsCodeKind(isolate)) { + // Codegen is unconditionally allowed, and we're been given a CodeKind + // object. Stringify. + MaybeHandle stringified_source = + Object::ToString(isolate, original_source); + return {stringified_source, stringified_source.is_null()}; + } + // If unconditional codegen was disabled, and no callback defined, we block // strings and allow all other objects. return {MaybeHandle(), !original_source->IsString()}; @@ -2186,12 +2208,13 @@ MaybeHandle Compiler::GetFunctionFromValidatedString( // static MaybeHandle Compiler::GetFunctionFromString( Handle context, Handle source, - ParseRestriction restriction, int parameters_end_pos) { + ParseRestriction restriction, int parameters_end_pos, bool is_code_kind) { Isolate* const isolate = context->GetIsolate(); - Handle native_context(context->native_context(), isolate); - return GetFunctionFromValidatedString( - context, ValidateDynamicCompilationSource(isolate, context, source).first, - restriction, parameters_end_pos); + MaybeHandle validated_source = + ValidateDynamicCompilationSource(isolate, context, source, is_code_kind) + .first; + return GetFunctionFromValidatedString(context, validated_source, restriction, + parameters_end_pos); } namespace { diff --git a/src/codegen/compiler.h b/src/codegen/compiler.h index a7bcf093cf..817a136792 100644 --- a/src/codegen/compiler.h +++ b/src/codegen/compiler.h @@ -142,13 +142,14 @@ class V8_EXPORT_PRIVATE Compiler : public AllStatic { // Create a (bound) function for a String source within a context for eval. V8_WARN_UNUSED_RESULT static MaybeHandle GetFunctionFromString( Handle context, Handle source, - ParseRestriction restriction, int parameters_end_pos); + ParseRestriction restriction, int parameters_end_pos, bool is_code_kind); // Decompose GetFunctionFromString into two functions, to allow callers to // deal seperately with a case of object not handled by the embedder. V8_WARN_UNUSED_RESULT static std::pair, bool> ValidateDynamicCompilationSource(Isolate* isolate, Handle context, - Handle source_object); + Handle source_object, + bool is_code_kind = false); V8_WARN_UNUSED_RESULT static MaybeHandle GetFunctionFromValidatedString(Handle context, MaybeHandle source, diff --git a/src/execution/isolate.h b/src/execution/isolate.h index 72322b3ad1..42d4ae6816 100644 --- a/src/execution/isolate.h +++ b/src/execution/isolate.h @@ -423,6 +423,8 @@ using DebugObjectCache = std::vector>; V(AllowCodeGenerationFromStringsCallback, allow_code_gen_callback, nullptr) \ V(ModifyCodeGenerationFromStringsCallback, modify_code_gen_callback, \ nullptr) \ + V(ModifyCodeGenerationFromStringsCallback2, modify_code_gen_callback2, \ + nullptr) \ V(AllowWasmCodeGenerationCallback, allow_wasm_code_gen_callback, nullptr) \ V(ExtensionCallback, wasm_module_callback, &NoExtension) \ V(ExtensionCallback, wasm_instance_callback, &NoExtension) \ diff --git a/src/logging/counters.h b/src/logging/counters.h index a2ed8fac56..98816157ff 100644 --- a/src/logging/counters.h +++ b/src/logging/counters.h @@ -790,6 +790,7 @@ class RuntimeCallTimer final { V(Object_HasRealIndexedProperty) \ V(Object_HasRealNamedCallbackProperty) \ V(Object_HasRealNamedProperty) \ + V(Object_IsCodeKind) \ V(Object_New) \ V(Object_ObjectProtoToString) \ V(Object_Set) \ diff --git a/src/objects/js-objects.cc b/src/objects/js-objects.cc index 935fe4fdcb..af76057f9b 100644 --- a/src/objects/js-objects.cc +++ b/src/objects/js-objects.cc @@ -2043,6 +2043,21 @@ bool JSReceiver::HasProxyInPrototype(Isolate* isolate) { return false; } +bool JSReceiver::IsCodeKind(Isolate* isolate) const { + DisallowGarbageCollection no_gc; + Object maybe_constructor = map().GetConstructor(); + if (!maybe_constructor.IsJSFunction()) return false; + if (!JSFunction::cast(maybe_constructor).shared().IsApiFunction()) { + return false; + } + Object instance_template = JSFunction::cast(maybe_constructor) + .shared() + .get_api_func_data() + .GetInstanceTemplate(); + if (instance_template.IsUndefined(isolate)) return false; + return ObjectTemplateInfo::cast(instance_template).code_kind(); +} + // static MaybeHandle JSObject::New(Handle constructor, Handle new_target, diff --git a/src/objects/js-objects.h b/src/objects/js-objects.h index 3f4d5b9bf6..03861a3b04 100644 --- a/src/objects/js-objects.h +++ b/src/objects/js-objects.h @@ -284,6 +284,9 @@ class JSReceiver : public HeapObject { TORQUE_GENERATED_JS_RECEIVER_FIELDS) bool HasProxyInPrototype(Isolate* isolate); + // TC39 "Dynamic Code Brand Checks" + bool IsCodeKind(Isolate* isolate) const; + OBJECT_CONSTRUCTORS(JSReceiver, HeapObject); }; diff --git a/src/objects/objects.cc b/src/objects/objects.cc index 8a66529df3..049e32721b 100644 --- a/src/objects/objects.cc +++ b/src/objects/objects.cc @@ -1812,6 +1812,11 @@ bool Object::IterationHasObservableEffects() { return true; } +bool Object::IsCodeKind(Isolate* isolate) const { + DisallowGarbageCollection no_gc; + return IsJSReceiver() && JSReceiver::cast(*this).IsCodeKind(isolate); +} + void Object::ShortPrint(FILE* out) const { OFStream os(out); os << Brief(*this); diff --git a/src/objects/objects.h b/src/objects/objects.h index 70efa01360..240f7f4de8 100644 --- a/src/objects/objects.h +++ b/src/objects/objects.h @@ -586,6 +586,9 @@ class Object : public TaggedImpl { // and length. bool IterationHasObservableEffects(); + // TC39 "Dynamic Code Brand Checks" + bool IsCodeKind(Isolate* isolate) const; + EXPORT_DECL_VERIFIER(Object) #ifdef VERIFY_HEAP diff --git a/src/objects/template.tq b/src/objects/template.tq index 1336fb19ba..564d3569dc 100644 --- a/src/objects/template.tq +++ b/src/objects/template.tq @@ -65,7 +65,8 @@ extern class FunctionTemplateInfo extends TemplateInfo { bitfield struct ObjectTemplateInfoFlags extends uint31 { is_immutable_prototype: bool: 1 bit; - embedder_field_count: int32: 29 bit; + is_code_kind: bool: 1 bit; + embedder_field_count: int32: 28 bit; } @generateCppClass diff --git a/src/objects/templates-inl.h b/src/objects/templates-inl.h index 8c0ab15407..6edb752955 100644 --- a/src/objects/templates-inl.h +++ b/src/objects/templates-inl.h @@ -132,6 +132,14 @@ void ObjectTemplateInfo::set_immutable_proto(bool immutable) { return set_data(IsImmutablePrototypeBit::update(data(), immutable)); } +bool ObjectTemplateInfo::code_kind() const { + return IsCodeKindBit::decode(data()); +} + +void ObjectTemplateInfo::set_code_kind(bool is_code_kind) { + return set_data(IsCodeKindBit::update(data(), is_code_kind)); +} + bool FunctionTemplateInfo::IsTemplateFor(JSObject object) { return IsTemplateFor(object.map()); } diff --git a/src/objects/templates.h b/src/objects/templates.h index 2e8caa14bd..736d8d81e6 100644 --- a/src/objects/templates.h +++ b/src/objects/templates.h @@ -158,6 +158,7 @@ class ObjectTemplateInfo public: DECL_INT_ACCESSORS(embedder_field_count) DECL_BOOLEAN_ACCESSORS(immutable_proto) + DECL_BOOLEAN_ACCESSORS(code_kind) // Dispatched behavior. DECL_PRINTER(ObjectTemplateInfo) diff --git a/src/runtime/runtime-test.cc b/src/runtime/runtime-test.cc index 24ea2b9933..16023fbbd9 100644 --- a/src/runtime/runtime-test.cc +++ b/src/runtime/runtime-test.cc @@ -1135,7 +1135,8 @@ RUNTIME_FUNCTION(Runtime_IsAsmWasmCode) { namespace { v8::ModifyCodeGenerationFromStringsResult DisallowCodegenFromStringsCallback( - v8::Local context, v8::Local source) { + v8::Local context, v8::Local source, + bool is_code_kind) { return {false, {}}; } diff --git a/test/cctest/test-api.cc b/test/cctest/test-api.cc index 888423316f..4119044167 100644 --- a/test/cctest/test-api.cc +++ b/test/cctest/test-api.cc @@ -19427,7 +19427,7 @@ void CheckCodeGenerationDisallowed() { char first_fourty_bytes[41]; v8::ModifyCodeGenerationFromStringsResult CodeGenerationAllowed( - Local context, Local source) { + Local context, Local source, bool is_code_kind) { String::Utf8Value str(CcTest::isolate(), source); size_t len = std::min(sizeof(first_fourty_bytes) - 1, static_cast(str.length())); @@ -19438,13 +19438,13 @@ v8::ModifyCodeGenerationFromStringsResult CodeGenerationAllowed( } v8::ModifyCodeGenerationFromStringsResult CodeGenerationDisallowed( - Local context, Local source) { + Local context, Local source, bool is_code_kind) { ApiTestFuzzer::Fuzz(); return {false, {}}; } v8::ModifyCodeGenerationFromStringsResult ModifyCodeGeneration( - Local context, Local source) { + Local context, Local source, bool is_code_kind) { // Allow (passthrough, unmodified) all objects that are not strings. if (!source->IsString()) { return {/* codegen_allowed= */ true, v8::MaybeLocal()}; @@ -19540,7 +19540,7 @@ TEST(ModifyCodeGenFromStrings) { } v8::ModifyCodeGenerationFromStringsResult RejectStringsIncrementNumbers( - Local context, Local source) { + Local context, Local source, bool is_code_kind) { if (source->IsString()) { return {false, v8::MaybeLocal()}; } @@ -28629,3 +28629,137 @@ TEST(TriggerThreadSafeMetricsEvent) { CHECK_EQ(recorder->count_, 1); // Increased. CHECK_EQ(recorder->module_count_, 42); } + +void SetupCodeKind(LocalContext* env, const char* name, + v8::Local to_string, + bool is_code_kind) { + // Setup a JS constructor + object template for testing IsCodeKind. + v8::Local constructor = + v8::FunctionTemplate::New((*env)->GetIsolate()); + constructor->SetClassName(v8_str(name)); + constructor->InstanceTemplate()->Set((*env)->GetIsolate(), "toString", + to_string); + if (is_code_kind) { + constructor->InstanceTemplate()->SetCodeKind(); + } + CHECK_EQ(is_code_kind, constructor->InstanceTemplate()->IsCodeKind()); + CHECK((*env) + ->Global() + ->Set(env->local(), v8_str(name), + constructor->GetFunction(env->local()).ToLocalChecked()) + .FromJust()); +} + +TEST(CodeKindEval) { + LocalContext env; + v8::Isolate* isolate = env->GetIsolate(); + v8::HandleScope scope(isolate); + + // Setup two object templates with an eval-able string representation. + // One code kind, one not, and otherwise identical. + auto string_fn = v8::FunctionTemplate::New( + isolate, [](const v8::FunctionCallbackInfo& info) { + info.GetReturnValue().Set(v8_str("2+2")); + }); + SetupCodeKind(&env, "CodeKind", string_fn, true); + SetupCodeKind(&env, "OtherKind", string_fn, false); + + // Check v8::Object::IsCodeKind. + CHECK(CompileRun("new CodeKind()").As()->IsCodeKind(isolate)); + CHECK(!CompileRun("new OtherKind()").As()->IsCodeKind(isolate)); + + // Expected behaviour for normal objects: + // - eval returns them as-is + // - when pre-stringified, the string gets evaluated (of course) + ExpectString("eval(new OtherKind()) + \"\"", "2+2"); + ExpectInt32("eval(\"\" + new OtherKind())", 4); + + // Expected behaviour for 'code kind': Is always evaluated. + ExpectInt32("eval(new CodeKind())", 4); + ExpectInt32("eval(\"\" + new CodeKind())", 4); + + // Modify callback will always returns a replacement string: + // Expected behaviour: Always execute the replacement string. + isolate->SetModifyCodeGenerationFromStringsCallback( + [](v8::Local context, v8::Local source, + bool is_code_kind) -> v8::ModifyCodeGenerationFromStringsResult { + return {true, v8_str("3+3")}; + }); + ExpectInt32("eval(new OtherKind())", 6); + ExpectInt32("eval(new CodeKind())", 6); + + // Modify callback always disallows: + // Expected behaviour: Always fail to execute. + isolate->SetModifyCodeGenerationFromStringsCallback( + [](v8::Local context, v8::Local source, + bool is_code_kind) -> v8::ModifyCodeGenerationFromStringsResult { + return {false, v8::Local()}; + }); + CHECK(CompileRun("eval(new OtherKind())").IsEmpty()); + CHECK(CompileRun("eval(new CodeKind())").IsEmpty()); + + // Modify callback allows only "code kind": + // Expected behaviour: Only code_kind executed, with replacement string. + isolate->SetModifyCodeGenerationFromStringsCallback( + [](v8::Local context, v8::Local source, + bool is_code_kind) -> v8::ModifyCodeGenerationFromStringsResult { + bool ok = is_code_kind || + (source->IsObject() && + source.As()->IsCodeKind(context->GetIsolate())); + return {ok, v8_str("5+7")}; + }); + CHECK(CompileRun("eval(new OtherKind())").IsEmpty()); + ExpectInt32("eval(new CodeKind())", 12); +} + +TEST(CodeKindFunction) { + LocalContext env; + v8::Isolate* isolate = env->GetIsolate(); + v8::HandleScope scope(isolate); + + // These follow the pattern of the CodeKindEval test above, but with + // "new Function" instead of eval. + + // Setup two object templates with an eval-able string representation. + // One code kind, one not, and otherwise identical. + auto string_fn = v8::FunctionTemplate::New( + isolate, [](const v8::FunctionCallbackInfo& info) { + info.GetReturnValue().Set(v8_str("return 2+2")); + }); + SetupCodeKind(&env, "CodeKind", string_fn, true); + SetupCodeKind(&env, "OtherKind", string_fn, false); + + ExpectInt32("new Function(new OtherKind())()", 4); + ExpectInt32("new Function(new CodeKind())()", 4); + + // Modify callback will always return a replacement string: + env.local()->AllowCodeGenerationFromStrings(false); + isolate->SetModifyCodeGenerationFromStringsCallback( + [](v8::Local context, v8::Local source, + bool is_code_kind) -> v8::ModifyCodeGenerationFromStringsResult { + return {true, v8_str("(function anonymous(\n) {\nreturn 7;\n})\n")}; + }); + ExpectInt32("new Function(new OtherKind())()", 7); + ExpectInt32("new Function(new CodeKind())()", 7); + + // Modify callback always disallows: + isolate->SetModifyCodeGenerationFromStringsCallback( + [](v8::Local context, v8::Local source, + bool is_code_kind) -> v8::ModifyCodeGenerationFromStringsResult { + return {false, v8::Local()}; + }); + CHECK(CompileRun("new Function(new OtherKind())()").IsEmpty()); + CHECK(CompileRun("new Function(new CodeKind())()").IsEmpty()); + + // Modify callback allows only "code kind": + isolate->SetModifyCodeGenerationFromStringsCallback( + [](v8::Local context, v8::Local source, + bool is_code_kind) -> v8::ModifyCodeGenerationFromStringsResult { + bool ok = is_code_kind || + (source->IsObject() && + source.As()->IsCodeKind(context->GetIsolate())); + return {ok, v8_str("(function anonymous(\n) {\nreturn 7;\n})\n")}; + }); + CHECK(CompileRun("new Function(new OtherKind())()").IsEmpty()); + ExpectInt32("new Function(new CodeKind())()", 7); +}