[api] TC39 Dynamic Code Brand checks
https://github.com/tc39/proposal-dynamic-code-brand-checks An experimental implementation of the TC39 "Dynamic Code Brand Checks". This implementation sticks an API-only symbol on each "code kind" object, which is more flexible, but costs memory for each instance. Bug: chromium:1096017 Change-Id: Idfeca035c61204ca0cea8ec735fdfa40a49d85e4 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2339618 Commit-Queue: Daniel Vogelheim <vogelheim@chromium.org> Reviewed-by: Leszek Swirski <leszeks@chromium.org> Reviewed-by: Toon Verwaest <verwaest@chromium.org> Reviewed-by: Camillo Bruni <cbruni@chromium.org> Cr-Commit-Position: refs/heads/master@{#70842}
This commit is contained in:
parent
e68285e21d
commit
aabe6406c4
35
include/v8.h
35
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> context,
|
||||
Local<Value> source);
|
||||
typedef ModifyCodeGenerationFromStringsResult (
|
||||
*ModifyCodeGenerationFromStringsCallback2)(Local<Context> context,
|
||||
Local<Value> source,
|
||||
bool is_code_kind);
|
||||
|
||||
// --- WebAssembly compilation callbacks ---
|
||||
typedef bool (*ExtensionCallback)(const FunctionCallbackInfo<Value>&);
|
||||
@ -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
|
||||
|
@ -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<i::Isolate*>(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> MicrotaskQueue::New(Isolate* isolate,
|
||||
MicrotasksPolicy policy) {
|
||||
|
@ -80,6 +80,14 @@ MaybeHandle<Object> 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<JSFunction> function;
|
||||
@ -88,7 +96,7 @@ MaybeHandle<Object> 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<Object> result;
|
||||
ASSIGN_RETURN_ON_EXCEPTION(
|
||||
|
@ -2072,21 +2072,27 @@ bool CodeGenerationFromStringsAllowed(Isolate* isolate, Handle<Context> 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> context,
|
||||
Handle<i::Object>* source) {
|
||||
DCHECK(isolate->modify_code_gen_callback());
|
||||
Handle<i::Object>* 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<EXTERNAL> 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> context,
|
||||
// static
|
||||
std::pair<MaybeHandle<String>, bool> Compiler::ValidateDynamicCompilationSource(
|
||||
Isolate* isolate, Handle<Context> context,
|
||||
Handle<i::Object> original_source) {
|
||||
Handle<i::Object> 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<MaybeHandle<String>, 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<String>(), true};
|
||||
}
|
||||
@ -2139,9 +2150,11 @@ std::pair<MaybeHandle<String>, 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<i::Object> modified_source = original_source;
|
||||
if (!ModifyCodeGenerationFromStrings(isolate, context, &modified_source)) {
|
||||
if (!ModifyCodeGenerationFromStrings(isolate, context, &modified_source,
|
||||
is_code_kind)) {
|
||||
return {MaybeHandle<String>(), false};
|
||||
}
|
||||
if (!modified_source->IsString()) {
|
||||
@ -2150,6 +2163,15 @@ std::pair<MaybeHandle<String>, bool> Compiler::ValidateDynamicCompilationSource(
|
||||
return {Handle<String>::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<String> 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<String>(), !original_source->IsString()};
|
||||
@ -2186,12 +2208,13 @@ MaybeHandle<JSFunction> Compiler::GetFunctionFromValidatedString(
|
||||
// static
|
||||
MaybeHandle<JSFunction> Compiler::GetFunctionFromString(
|
||||
Handle<Context> context, Handle<Object> source,
|
||||
ParseRestriction restriction, int parameters_end_pos) {
|
||||
ParseRestriction restriction, int parameters_end_pos, bool is_code_kind) {
|
||||
Isolate* const isolate = context->GetIsolate();
|
||||
Handle<Context> native_context(context->native_context(), isolate);
|
||||
return GetFunctionFromValidatedString(
|
||||
context, ValidateDynamicCompilationSource(isolate, context, source).first,
|
||||
restriction, parameters_end_pos);
|
||||
MaybeHandle<String> validated_source =
|
||||
ValidateDynamicCompilationSource(isolate, context, source, is_code_kind)
|
||||
.first;
|
||||
return GetFunctionFromValidatedString(context, validated_source, restriction,
|
||||
parameters_end_pos);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
@ -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<JSFunction> GetFunctionFromString(
|
||||
Handle<Context> context, Handle<i::Object> 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<MaybeHandle<String>, bool>
|
||||
ValidateDynamicCompilationSource(Isolate* isolate, Handle<Context> context,
|
||||
Handle<i::Object> source_object);
|
||||
Handle<i::Object> source_object,
|
||||
bool is_code_kind = false);
|
||||
V8_WARN_UNUSED_RESULT static MaybeHandle<JSFunction>
|
||||
GetFunctionFromValidatedString(Handle<Context> context,
|
||||
MaybeHandle<String> source,
|
||||
|
@ -423,6 +423,8 @@ using DebugObjectCache = std::vector<Handle<HeapObject>>;
|
||||
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) \
|
||||
|
@ -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) \
|
||||
|
@ -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> JSObject::New(Handle<JSFunction> constructor,
|
||||
Handle<JSReceiver> new_target,
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
|
@ -586,6 +586,9 @@ class Object : public TaggedImpl<HeapObjectReferenceType::STRONG, Address> {
|
||||
// and length.
|
||||
bool IterationHasObservableEffects();
|
||||
|
||||
// TC39 "Dynamic Code Brand Checks"
|
||||
bool IsCodeKind(Isolate* isolate) const;
|
||||
|
||||
EXPORT_DECL_VERIFIER(Object)
|
||||
|
||||
#ifdef VERIFY_HEAP
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -1135,7 +1135,8 @@ RUNTIME_FUNCTION(Runtime_IsAsmWasmCode) {
|
||||
namespace {
|
||||
|
||||
v8::ModifyCodeGenerationFromStringsResult DisallowCodegenFromStringsCallback(
|
||||
v8::Local<v8::Context> context, v8::Local<v8::Value> source) {
|
||||
v8::Local<v8::Context> context, v8::Local<v8::Value> source,
|
||||
bool is_code_kind) {
|
||||
return {false, {}};
|
||||
}
|
||||
|
||||
|
@ -19427,7 +19427,7 @@ void CheckCodeGenerationDisallowed() {
|
||||
char first_fourty_bytes[41];
|
||||
|
||||
v8::ModifyCodeGenerationFromStringsResult CodeGenerationAllowed(
|
||||
Local<Context> context, Local<Value> source) {
|
||||
Local<Context> context, Local<Value> source, bool is_code_kind) {
|
||||
String::Utf8Value str(CcTest::isolate(), source);
|
||||
size_t len = std::min(sizeof(first_fourty_bytes) - 1,
|
||||
static_cast<size_t>(str.length()));
|
||||
@ -19438,13 +19438,13 @@ v8::ModifyCodeGenerationFromStringsResult CodeGenerationAllowed(
|
||||
}
|
||||
|
||||
v8::ModifyCodeGenerationFromStringsResult CodeGenerationDisallowed(
|
||||
Local<Context> context, Local<Value> source) {
|
||||
Local<Context> context, Local<Value> source, bool is_code_kind) {
|
||||
ApiTestFuzzer::Fuzz();
|
||||
return {false, {}};
|
||||
}
|
||||
|
||||
v8::ModifyCodeGenerationFromStringsResult ModifyCodeGeneration(
|
||||
Local<Context> context, Local<Value> source) {
|
||||
Local<Context> context, Local<Value> source, bool is_code_kind) {
|
||||
// Allow (passthrough, unmodified) all objects that are not strings.
|
||||
if (!source->IsString()) {
|
||||
return {/* codegen_allowed= */ true, v8::MaybeLocal<String>()};
|
||||
@ -19540,7 +19540,7 @@ TEST(ModifyCodeGenFromStrings) {
|
||||
}
|
||||
|
||||
v8::ModifyCodeGenerationFromStringsResult RejectStringsIncrementNumbers(
|
||||
Local<Context> context, Local<Value> source) {
|
||||
Local<Context> context, Local<Value> source, bool is_code_kind) {
|
||||
if (source->IsString()) {
|
||||
return {false, v8::MaybeLocal<String>()};
|
||||
}
|
||||
@ -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<v8::FunctionTemplate> to_string,
|
||||
bool is_code_kind) {
|
||||
// Setup a JS constructor + object template for testing IsCodeKind.
|
||||
v8::Local<FunctionTemplate> 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<v8::Value>& 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<v8::Object>()->IsCodeKind(isolate));
|
||||
CHECK(!CompileRun("new OtherKind()").As<v8::Object>()->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<v8::Context> context, v8::Local<v8::Value> 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<v8::Context> context, v8::Local<v8::Value> source,
|
||||
bool is_code_kind) -> v8::ModifyCodeGenerationFromStringsResult {
|
||||
return {false, v8::Local<v8::String>()};
|
||||
});
|
||||
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<v8::Context> context, v8::Local<v8::Value> source,
|
||||
bool is_code_kind) -> v8::ModifyCodeGenerationFromStringsResult {
|
||||
bool ok = is_code_kind ||
|
||||
(source->IsObject() &&
|
||||
source.As<v8::Object>()->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<v8::Value>& 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<v8::Context> context, v8::Local<v8::Value> 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<v8::Context> context, v8::Local<v8::Value> source,
|
||||
bool is_code_kind) -> v8::ModifyCodeGenerationFromStringsResult {
|
||||
return {false, v8::Local<v8::String>()};
|
||||
});
|
||||
CHECK(CompileRun("new Function(new OtherKind())()").IsEmpty());
|
||||
CHECK(CompileRun("new Function(new CodeKind())()").IsEmpty());
|
||||
|
||||
// Modify callback allows only "code kind":
|
||||
isolate->SetModifyCodeGenerationFromStringsCallback(
|
||||
[](v8::Local<v8::Context> context, v8::Local<v8::Value> source,
|
||||
bool is_code_kind) -> v8::ModifyCodeGenerationFromStringsResult {
|
||||
bool ok = is_code_kind ||
|
||||
(source->IsObject() &&
|
||||
source.As<v8::Object>()->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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user