diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc index d03fc573e7b..b327668c8c3 100644 --- a/runtime/vm/object.cc +++ b/runtime/vm/object.cc @@ -2125,6 +2125,19 @@ void Class::AddFunction(const Function& function) const { } +void Class::RemoveFunction(const Function& function) const { + const Array& arr = Array::Handle(functions()); + StorePointer(&raw_ptr()->functions_, Object::empty_array().raw()); + Function& entry = Function::Handle(); + for (intptr_t i = 0; i < arr.Length(); i++) { + entry ^= arr.At(i); + if (function.raw() != entry.raw()) { + AddFunction(entry); + } + } +} + + intptr_t Class::FindFunctionIndex(const Function& needle) const { Isolate* isolate = Isolate::Current(); if (EnsureIsFinalized(isolate) != Error::null()) { diff --git a/runtime/vm/object.h b/runtime/vm/object.h index 915b853962f..cdf9ddb515c 100644 --- a/runtime/vm/object.h +++ b/runtime/vm/object.h @@ -1107,6 +1107,7 @@ class Class : public Object { RawArray* functions() const { return raw_ptr()->functions_; } void SetFunctions(const Array& value) const; void AddFunction(const Function& function) const; + void RemoveFunction(const Function& function) const; intptr_t FindFunctionIndex(const Function& function) const; RawFunction* FunctionFromIndex(intptr_t idx) const; intptr_t FindImplicitClosureFunctionIndex(const Function& needle) const; @@ -2274,6 +2275,7 @@ FOR_EACH_FUNCTION_KIND_BIT(DEFINE_BIT) FINAL_HEAP_OBJECT_IMPLEMENTATION(Function, Object); friend class Class; + friend class Parser; // For set_eval_script. // RawFunction::VisitFunctionPointers accesses the private constructor of // Function. friend class RawFunction; diff --git a/runtime/vm/parser.cc b/runtime/vm/parser.cc index 97dfe20468a..f28497a206f 100644 --- a/runtime/vm/parser.cc +++ b/runtime/vm/parser.cc @@ -1106,6 +1106,42 @@ ParsedFunction* Parser::ParseStaticFieldInitializer(const Field& field) { } +RawObject* Parser::ParseFunctionFromSource(const Class& owning_class, + const String& source) { + Isolate* isolate = Isolate::Current(); + StackZone zone(isolate); + LongJumpScope jump; + if (setjmp(*jump.Set()) == 0) { + const String& uri = String::Handle(Symbols::New("dynamically-added")); + const Script& script = Script::Handle( + Script::New(uri, source, RawScript::kSourceTag)); + const Library& owning_library = Library::Handle(owning_class.library()); + const String& private_key = String::Handle(owning_library.private_key()); + script.Tokenize(private_key); + const intptr_t token_pos = 0; + Parser parser(script, owning_library, token_pos); + parser.is_top_level_ = true; + parser.set_current_class(owning_class); + const String& class_name = String::Handle(owning_class.Name()); + ClassDesc members(owning_class, class_name, false, token_pos); + const intptr_t metadata_pos = parser.SkipMetadata(); + parser.ParseClassMemberDefinition(&members, metadata_pos); + ASSERT(members.functions().Length() == 1); + const Function& func = + Function::ZoneHandle(Function::RawCast(members.functions().At(0))); + func.set_eval_script(script); + ParsedFunction* parsed_function = new ParsedFunction(isolate, func); + Parser::ParseFunction(parsed_function); + return func.raw(); + } else { + const Error& error = Error::Handle(isolate->object_store()->sticky_error()); + isolate->object_store()->clear_sticky_error(); + return error.raw(); + } + UNREACHABLE(); +} + + SequenceNode* Parser::ParseStaticFinalGetter(const Function& func) { TRACE_PARSER("ParseStaticFinalGetter"); ParamList params; @@ -3301,7 +3337,7 @@ void Parser::ParseMethodOrConstructor(ClassDesc* members, MemberDesc* method) { TRACE_PARSER("ParseMethodOrConstructor"); ASSERT(CurrentToken() == Token::kLPAREN || method->IsGetter()); ASSERT(method->type != NULL); - ASSERT(method->name_pos > 0); + ASSERT(is_top_level_ || method->name_pos > 0); ASSERT(current_member_ == method); if (method->has_var) { diff --git a/runtime/vm/parser.h b/runtime/vm/parser.h index 1fb7f89e56a..e9510466839 100644 --- a/runtime/vm/parser.h +++ b/runtime/vm/parser.h @@ -221,6 +221,10 @@ class Parser : public ValueObject { // given static field. static ParsedFunction* ParseStaticFieldInitializer(const Field& field); + // Returns a RawFunction or RawError. + static RawObject* ParseFunctionFromSource(const Class& owning_class, + const String& source); + // Parse a function to retrieve parameter information that is not retained in // the dart::Function object. Returns either an error if the parse fails // (which could be the case for local functions), or a flat array of entries diff --git a/runtime/vm/service.cc b/runtime/vm/service.cc index 5404a3e1749..5707da206d9 100644 --- a/runtime/vm/service.cc +++ b/runtime/vm/service.cc @@ -22,6 +22,7 @@ #include "vm/object_graph.h" #include "vm/object_id_ring.h" #include "vm/object_store.h" +#include "vm/parser.h" #include "vm/port.h" #include "vm/profiler.h" #include "vm/reusable_handles.h" @@ -1276,6 +1277,36 @@ static bool HandleClassesFunctionsCoverage( } +static bool HandleFunctionSetSource( + Isolate* isolate, const Class& cls, const Function& func, JSONStream* js) { + if (js->LookupOption("source") == NULL) { + PrintError(js, "set_source expects a 'source' option\n"); + return true; + } + const String& source = + String::Handle(String::New(js->LookupOption("source"))); + const Object& result = Object::Handle( + Parser::ParseFunctionFromSource(cls, source)); + if (result.IsError()) { + Error::Cast(result).PrintJSON(js, false); + return true; + } + if (!result.IsFunction()) { + PrintError(js, "source did not compile to a function.\n"); + return true; + } + + // Replace function. + cls.RemoveFunction(func); + cls.AddFunction(Function::Cast(result)); + + JSONObject jsobj(js); + jsobj.AddProperty("type", "Success"); + jsobj.AddProperty("id", ""); + return true; +} + + static bool HandleClassesFunctions(Isolate* isolate, const Class& cls, JSONStream* js) { if (js->num_arguments() != 4 && js->num_arguments() != 5) { @@ -1298,11 +1329,13 @@ static bool HandleClassesFunctions(Isolate* isolate, const Class& cls, func.PrintJSON(js, false); return true; } else { - const char* subcollection = js->GetArgument(4); - if (strcmp(subcollection, "coverage") == 0) { + const char* subcommand = js->GetArgument(4); + if (strcmp(subcommand, "coverage") == 0) { return HandleClassesFunctionsCoverage(isolate, func, js); + } else if (strcmp(subcommand, "set_source") == 0) { + return HandleFunctionSetSource(isolate, cls, func, js); } else { - PrintError(js, "Invalid sub collection %s", subcollection); + PrintError(js, "Invalid sub command %s", subcommand); return true; } } diff --git a/runtime/vm/service_test.cc b/runtime/vm/service_test.cc index 20863b07525..c585ca821e4 100644 --- a/runtime/vm/service_test.cc +++ b/runtime/vm/service_test.cc @@ -1074,7 +1074,7 @@ TEST_CASE(Service_Classes) { Service::HandleIsolateMessage(isolate, service_msg); handler.HandleNextMessage(); ExpectSubstringF(handler.msg(), - "{\"type\":\"Error\",\"id\":\"\",\"message\":\"Invalid sub collection x\"," + "{\"type\":\"Error\",\"id\":\"\",\"message\":\"Invalid sub command x\"," "\"request\":" "{\"arguments\":[\"classes\",\"%" Pd "\",\"functions\",\"b\",\"x\"]," "\"option_keys\":[],\"option_values\":[]}}", cid); @@ -1120,6 +1120,127 @@ TEST_CASE(Service_Classes) { } +TEST_CASE(Service_SetSource) { + const char* kScript = + "var port;\n" // Set to our mock port by C++. + "\n" + "class A {\n" + " a() { return 1; }\n" + " b() { return 0; }\n" + " c(String f) { return f.length; }\n" + "}\n" + "main() {\n" + " var z = new A();\n" + " return z.a();\n" + "}\n" + "runB() {\n" + " var z = new A();\n" + " return z.b();\n" + "}\n" + "runC() {\n" + " var z = new A();\n" + " return z.c();\n" + "}\n"; + + Isolate* isolate = Isolate::Current(); + Dart_Handle lib = TestCase::LoadTestScript(kScript, NULL); + EXPECT_VALID(lib); + Library& vmlib = Library::Handle(); + vmlib ^= Api::UnwrapHandle(lib); + EXPECT(!vmlib.IsNull()); + Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, NULL); + EXPECT_VALID(result); + const Class& class_a = Class::Handle(GetClass(vmlib, "A")); + EXPECT(!class_a.IsNull()); + intptr_t cid = class_a.id(); + + // Build a mock message handler and wrap it in a dart port. + ServiceTestMessageHandler handler; + Dart_Port port_id = PortMap::CreatePort(&handler); + Dart_Handle port = Api::NewHandle(isolate, SendPort::New(port_id)); + EXPECT_VALID(port); + EXPECT_VALID(Dart_SetField(lib, NewString("port"), port)); + + Array& service_msg = Array::Handle(); + + // Request the class A over the service. + service_msg = EvalF(lib, "[0, port, ['classes', '%" Pd "'], [], []]", cid); + Service::HandleIsolateMessage(isolate, service_msg); + handler.HandleNextMessage(); + EXPECT_SUBSTRING("\"type\":\"Class\"", handler.msg()); + ExpectSubstringF(handler.msg(), + "\"id\":\"classes\\/%" Pd "\",\"name\":\"A\",", cid); + ExpectSubstringF(handler.msg(), "\"allocationStats\":"); + ExpectSubstringF(handler.msg(), "\"tokenPos\":"); + ExpectSubstringF(handler.msg(), "\"endTokenPos\":"); + + // Request function 'b' from class A. + service_msg = EvalF(lib, + "[0, port, ['classes', '%" Pd "', 'functions', 'b']," + "[], []]", cid); + Service::HandleIsolateMessage(isolate, service_msg); + handler.HandleNextMessage(); + EXPECT_SUBSTRING("\"type\":\"Function\"", handler.msg()); + ExpectSubstringF(handler.msg(), + "\"id\":\"classes\\/%" Pd "\\/functions\\/b\"," + "\"name\":\"b\",", cid); + + // Invalid set source of function 'b' from class A. + service_msg = EvalF( + lib, + "[0, port, ['classes', '%" Pd "', 'functions', 'b', 'set_source']," + "[], []]", cid); + Service::HandleIsolateMessage(isolate, service_msg); + handler.HandleNextMessage(); + EXPECT_SUBSTRING("\"type\":\"Error\"", handler.msg()); + EXPECT_SUBSTRING("set_source expects a 'source' option", handler.msg()); + + // Set source (with syntax error) of function 'b' from class A. + service_msg = EvalF( + lib, + "[0, port, ['classes', '%" Pd "', 'functions', 'b', 'set_source']," + "['source'], ['b() { return 4 }']]", cid); + Service::HandleIsolateMessage(isolate, service_msg); + handler.HandleNextMessage(); + EXPECT_SUBSTRING("\"type\":\"Error\"", handler.msg()); + + // Set source of function 'b' from class A. + service_msg = EvalF( + lib, + "[0, port, ['classes', '%" Pd "', 'functions', 'b', 'set_source']," + "['source'], ['b() { return 4; }']]", cid); + Service::HandleIsolateMessage(isolate, service_msg); + handler.HandleNextMessage(); + EXPECT_SUBSTRING("Success", handler.msg()); + + // Run function 'b' see that it is executing replaced code. + result = Dart_Invoke(lib, NewString("runB"), 0, NULL); + EXPECT_VALID(result); + ASSERT(Dart_IsInteger(result)); + int64_t r; + result = Dart_IntegerToInt64(result, &r); + EXPECT_VALID(result); + EXPECT_EQ(4, r); + + // Set source of function 'c' from class A, changing its signature. + service_msg = EvalF( + lib, + "[0, port, ['classes', '%" Pd "', 'functions', 'c', 'set_source']," + "['source'], ['c() { return 99; }']]", cid); + Service::HandleIsolateMessage(isolate, service_msg); + handler.HandleNextMessage(); + EXPECT_SUBSTRING("Success", handler.msg()); + + // Run function 'c' see that it is executing replaced code. + result = Dart_Invoke(lib, NewString("runC"), 0, NULL); + EXPECT_VALID(result); + ASSERT(Dart_IsInteger(result)); + result = Dart_IntegerToInt64(result, &r); + EXPECT_VALID(result); + EXPECT_EQ(99, r); +} + + TEST_CASE(Service_Types) { const char* kScript = "var port;\n" // Set to our mock port by C++.