Introduce Function::FunctionProtoToString()

Add a new function on the public API to allow serializing a function to
a string using the built-in toString() implementation, allowing
serialization without worrying about untrusted author script overriding
the toString() implementation. This is similar in nature to
Object::ObjectProtoToString() (but that only returns "[object Function]"
for any passed function).

Add tests for the same.

Bug: chromium:1144841
Change-Id: Ie4c29b870034c0817c23bf91f9424f956098823d
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2514768
Reviewed-by: Toon Verwaest <verwaest@chromium.org>
Commit-Queue: Devlin <rdevlin.cronin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#70976}
This commit is contained in:
Devlin Cronin 2020-11-02 14:01:19 -08:00 committed by Commit Bot
parent a3e67fea74
commit 2ccd4dc564
6 changed files with 54 additions and 2 deletions

View File

@ -4621,6 +4621,15 @@ class V8_EXPORT Function : public Object {
*/
Local<Value> GetBoundFunction() const;
/**
* Calls builtin Function.prototype.toString on this function.
* This is different from Value::ToString() that may call a user-defined
* toString() function, and different than Object::ObjectProtoToString() which
* always serializes "[object Function]".
*/
V8_WARN_UNUSED_RESULT MaybeLocal<String> FunctionProtoToString(
Local<Context> context);
ScriptOrigin GetScriptOrigin() const;
V8_INLINE static Function* Cast(Value* obj);
static const int kLineOffsetNotFound;

View File

@ -5126,6 +5126,18 @@ Local<v8::Value> Function::GetBoundFunction() const {
return v8::Undefined(reinterpret_cast<v8::Isolate*>(self->GetIsolate()));
}
MaybeLocal<String> v8::Function::FunctionProtoToString(Local<Context> context) {
PREPARE_FOR_EXECUTION(context, Function, FunctionProtoToString, String);
auto self = Utils::OpenHandle(this);
Local<Value> result;
has_pending_exception = !ToLocal<Value>(
i::Execution::CallBuiltin(isolate, isolate->function_to_string(), self, 0,
nullptr),
&result);
RETURN_ON_FAILED_EXECUTION(String);
RETURN_ESCAPED(Local<String>::Cast(result));
}
int Name::GetIdentityHash() {
auto self = Utils::OpenHandle(this);
return static_cast<int>(self->Hash());

View File

@ -1571,8 +1571,10 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
Builtins::kFastFunctionPrototypeBind, 1, false);
SimpleInstallFunction(isolate_, prototype, "call",
Builtins::kFunctionPrototypeCall, 1, false);
SimpleInstallFunction(isolate_, prototype, "toString",
Builtins::kFunctionPrototypeToString, 0, false);
Handle<JSFunction> function_to_string =
SimpleInstallFunction(isolate_, prototype, "toString",
Builtins::kFunctionPrototypeToString, 0, false);
native_context()->set_function_to_string(*function_to_string);
// Install the @@hasInstance function.
Handle<JSFunction> has_instance = InstallFunctionAtSymbol(

View File

@ -739,6 +739,7 @@ class RuntimeCallTimer final {
V(Float64Array_New) \
V(Function_Call) \
V(Function_New) \
V(Function_FunctionProtoToString) \
V(Function_NewInstance) \
V(FunctionTemplate_GetFunction) \
V(FunctionTemplate_New) \

View File

@ -311,6 +311,7 @@ enum ContextLookupFlags {
V(FINALIZATION_REGISTRY_CLEANUP_SOME, JSFunction, \
finalization_registry_cleanup_some) \
V(FUNCTION_HAS_INSTANCE_INDEX, JSFunction, function_has_instance) \
V(FUNCTION_TO_STRING_INDEX, JSFunction, function_to_string) \
V(OBJECT_TO_STRING, JSFunction, object_to_string) \
V(OBJECT_VALUE_OF_FUNCTION_INDEX, JSFunction, object_value_of_function) \
V(PROMISE_ALL_INDEX, JSFunction, promise_all) \

View File

@ -17566,6 +17566,33 @@ THREADED_TEST(FunctionGetBoundFunction) {
original_function->GetScriptColumnNumber());
}
THREADED_TEST(FunctionProtoToString) {
LocalContext context;
v8::Isolate* isolate = CcTest::isolate();
v8::HandleScope scope(isolate);
// Replace Function.prototype.toString.
CompileRun(R"(
Function.prototype.toString = function() {
return 'customized toString';
})");
constexpr char kTestFunction[] = "function testFunction() { return 7; }";
std::string wrapped_function("(");
wrapped_function.append(kTestFunction).append(")");
Local<Function> function =
CompileRun(wrapped_function.c_str()).As<Function>();
Local<String> value = function->ToString(context.local()).ToLocalChecked();
CHECK(value->IsString());
CHECK(
value->Equals(context.local(), v8_str("customized toString")).FromJust());
// FunctionProtoToString() should not call the replaced toString function.
value = function->FunctionProtoToString(context.local()).ToLocalChecked();
CHECK(value->IsString());
CHECK(value->Equals(context.local(), v8_str(kTestFunction)).FromJust());
}
static void GetterWhichReturns42(
Local<String> name,