Allow setting breakpoints in literal function initializers of fields.

Fixes #29581 when the VM parser is used.

R=asiva@google.com

Review-Url: https://codereview.chromium.org/2904793002 .
This commit is contained in:
Siva Chandra
2017-05-26 13:43:30 -07:00
parent 9c2dff8ebc
commit 3b9cf7351b
5 changed files with 260 additions and 87 deletions
+125 -85
View File
@@ -20,6 +20,7 @@
#include "vm/object.h"
#include "vm/object_store.h"
#include "vm/os.h"
#include "vm/parser.h"
#include "vm/port.h"
#include "vm/runtime_entry.h"
#include "vm/service.h"
@@ -2654,15 +2655,22 @@ static bool IsTokenPosWithinFunction(const Function& func, TokenPosition pos) {
}
RawFunction* Debugger::FindBestFit(const Script& script,
TokenPosition token_pos) {
// Returns true if a best fit is found. A best fit can either be a function
// or a field. If it is a function, then the best fit function is returned
// in |best_fit|. If a best fit is a field, it means that a latent
// breakpoint can be set in the range |token_pos| to |last_token_pos|.
bool Debugger::FindBestFit(const Script& script,
TokenPosition token_pos,
TokenPosition last_token_pos,
Function* best_fit) {
Zone* zone = Thread::Current()->zone();
Class& cls = Class::Handle(zone);
Array& functions = Array::Handle(zone);
const GrowableObjectArray& closures = GrowableObjectArray::Handle(
zone, isolate_->object_store()->closure_functions());
Array& functions = Array::Handle(zone);
Function& function = Function::Handle(zone);
Function& best_fit = Function::Handle(zone);
Array& fields = Array::Handle(zone);
Field& field = Field::Handle(zone);
Error& error = Error::Handle(zone);
const intptr_t num_closures = closures.Length();
@@ -2673,51 +2681,74 @@ RawFunction* Debugger::FindBestFit(const Script& script,
}
if (IsTokenPosWithinFunction(function, token_pos)) {
// Select the inner most closure.
SelectBestFit(&best_fit, &function);
SelectBestFit(best_fit, &function);
}
}
if (!best_fit.IsNull()) {
if (!best_fit->IsNull()) {
// The inner most closure found will be the best fit. Going
// over class functions below will not help in any further
// narrowing.
return best_fit.raw();
return true;
}
const ClassTable& class_table = *isolate_->class_table();
const intptr_t num_classes = class_table.NumCids();
for (intptr_t i = 1; i < num_classes; i++) {
if (class_table.HasValidClassAt(i)) {
cls = class_table.At(i);
if (cls.script() != script.raw()) {
continue;
if (!class_table.HasValidClassAt(i)) {
continue;
}
cls = class_table.At(i);
if (cls.script() != script.raw()) {
continue;
}
// Parse class definition if not done yet.
error = cls.EnsureIsFinalized(Thread::Current());
if (!error.IsNull()) {
// Ignore functions in this class.
// TODO(hausner): Should we propagate this error? How?
// EnsureIsFinalized only returns an error object if there
// is no longjump base on the stack.
continue;
}
functions = cls.functions();
if (!functions.IsNull()) {
const intptr_t num_functions = functions.Length();
for (intptr_t pos = 0; pos < num_functions; pos++) {
function ^= functions.At(pos);
ASSERT(!function.IsNull());
if (IsTokenPosWithinFunction(function, token_pos)) {
// Closures and inner functions within a class method are not
// present in the functions of a class. Hence, we can return
// right away as looking through other functions of a class
// will not narrow down to any inner function/closure.
*best_fit = function.raw();
return true;
}
}
// Parse class definition if not done yet.
error = cls.EnsureIsFinalized(Thread::Current());
if (!error.IsNull()) {
// Ignore functions in this class.
// TODO(hausner): Should we propagate this error? How?
// EnsureIsFinalized only returns an error object if there
// is no longjump base on the stack.
continue;
}
functions = cls.functions();
if (!functions.IsNull()) {
const intptr_t num_functions = functions.Length();
for (intptr_t pos = 0; pos < num_functions; pos++) {
function ^= functions.At(pos);
ASSERT(!function.IsNull());
if (IsTokenPosWithinFunction(function, token_pos)) {
// Closures and inner functions within a class method are not
// present in the functions of a class. Hence, we can return
// right away as looking through other functions of a class
// will not narrow down to any inner function/closure.
return function.raw();
}
// If none of the functions in the class contain token_pos, then we
// check if it falls within a function literal initializer of a field
// that has not been initialized yet. If the field (and hence the
// function literal initializer) has already been initialized, then
// it would have been found above in the object store as a closure.
fields = cls.fields();
if (!fields.IsNull()) {
const intptr_t num_fields = fields.Length();
for (intptr_t pos = 0; pos < num_fields; pos++) {
TokenPosition start;
TokenPosition end;
field ^= fields.At(pos);
ASSERT(!field.IsNull());
if (Parser::FieldHasFunctionLiteralInitializer(field, &start, &end)) {
if ((start <= token_pos && token_pos <= end) ||
(token_pos <= start && start <= last_token_pos)) {
return true;
}
}
}
}
}
return Function::null();
return false;
}
@@ -2727,68 +2758,77 @@ BreakpointLocation* Debugger::SetBreakpoint(const Script& script,
intptr_t requested_line,
intptr_t requested_column) {
Function& func = Function::Handle();
func = FindBestFit(script, token_pos);
if (func.IsNull()) {
if (!FindBestFit(script, token_pos, last_token_pos, &func)) {
return NULL;
}
// There may be more than one function object for a given function
// in source code. There may be implicit closure functions, and
// there may be copies of mixin functions. Collect all compiled
// functions whose source code range matches exactly the best fit
// function we found.
GrowableObjectArray& functions =
GrowableObjectArray::Handle(GrowableObjectArray::New());
FindCompiledFunctions(script, func.token_pos(), func.end_token_pos(),
&functions);
if (!func.IsNull()) {
// There may be more than one function object for a given function
// in source code. There may be implicit closure functions, and
// there may be copies of mixin functions. Collect all compiled
// functions whose source code range matches exactly the best fit
// function we found.
GrowableObjectArray& functions =
GrowableObjectArray::Handle(GrowableObjectArray::New());
FindCompiledFunctions(script, func.token_pos(), func.end_token_pos(),
&functions);
if (functions.Length() > 0) {
// One or more function object containing this breakpoint location
// have already been compiled. We can resolve the breakpoint now.
DeoptimizeWorld();
func ^= functions.At(0);
TokenPosition breakpoint_pos =
ResolveBreakpointPos(func, token_pos, last_token_pos, requested_column);
if (breakpoint_pos.IsReal()) {
BreakpointLocation* bpt =
GetBreakpointLocation(script, breakpoint_pos, requested_column);
if (bpt != NULL) {
// A source breakpoint for this location already exists.
if (functions.Length() > 0) {
// One or more function object containing this breakpoint location
// have already been compiled. We can resolve the breakpoint now.
DeoptimizeWorld();
func ^= functions.At(0);
TokenPosition breakpoint_pos = ResolveBreakpointPos(
func, token_pos, last_token_pos, requested_column);
if (breakpoint_pos.IsReal()) {
BreakpointLocation* bpt =
GetBreakpointLocation(script, breakpoint_pos, requested_column);
if (bpt != NULL) {
// A source breakpoint for this location already exists.
return bpt;
}
bpt = new BreakpointLocation(script, token_pos, last_token_pos,
requested_line, requested_column);
bpt->SetResolved(func, breakpoint_pos);
RegisterBreakpointLocation(bpt);
// Create code breakpoints for all compiled functions we found.
const intptr_t num_functions = functions.Length();
for (intptr_t i = 0; i < num_functions; i++) {
func ^= functions.At(i);
ASSERT(func.HasCode());
MakeCodeBreakpointAt(func, bpt);
}
if (FLAG_verbose_debug) {
intptr_t line_number;
intptr_t column_number;
script.GetTokenLocation(breakpoint_pos, &line_number, &column_number);
OS::Print(
"Resolved BP for "
"function '%s' at line %" Pd " col %" Pd "\n",
func.ToFullyQualifiedCString(), line_number, column_number);
}
return bpt;
}
bpt = new BreakpointLocation(script, token_pos, last_token_pos,
requested_line, requested_column);
bpt->SetResolved(func, breakpoint_pos);
RegisterBreakpointLocation(bpt);
// Create code breakpoints for all compiled functions we found.
const intptr_t num_functions = functions.Length();
for (intptr_t i = 0; i < num_functions; i++) {
func ^= functions.At(i);
ASSERT(func.HasCode());
MakeCodeBreakpointAt(func, bpt);
}
if (FLAG_verbose_debug) {
intptr_t line_number;
intptr_t column_number;
script.GetTokenLocation(breakpoint_pos, &line_number, &column_number);
OS::Print(
"Resolved BP for "
"function '%s' at line %" Pd " col %" Pd "\n",
func.ToFullyQualifiedCString(), line_number, column_number);
}
return bpt;
}
}
// There is no compiled function at this token position.
// Register an unresolved breakpoint.
if (FLAG_verbose_debug && !func.IsNull()) {
// There is either an uncompiled function, or an uncompiled function literal
// initializer of a field at |token_pos|. Hence, Register an unresolved
// breakpoint.
if (FLAG_verbose_debug) {
intptr_t line_number;
intptr_t column_number;
script.GetTokenLocation(token_pos, &line_number, &column_number);
OS::Print(
"Registering pending breakpoint for "
"uncompiled function '%s' at line %" Pd " col %" Pd "\n",
func.ToFullyQualifiedCString(), line_number, column_number);
if (func.IsNull()) {
OS::Print(
"Registering pending breakpoint for "
"an uncompiled function literal at line %" Pd " col %" Pd "\n",
line_number, column_number);
} else {
OS::Print(
"Registering pending breakpoint for "
"uncompiled function '%s' at line %" Pd " col %" Pd "\n",
func.ToFullyQualifiedCString(), line_number, column_number);
}
}
BreakpointLocation* bpt =
GetBreakpointLocation(script, token_pos, requested_column);
+4 -1
View File
@@ -631,7 +631,10 @@ class Debugger {
TokenPosition start_pos,
TokenPosition end_pos,
GrowableObjectArray* function_list);
RawFunction* FindBestFit(const Script& script, TokenPosition token_pos);
bool FindBestFit(const Script& script,
TokenPosition token_pos,
TokenPosition last_token_pos,
Function* best_fit);
RawFunction* FindInnermostClosure(const Function& function,
TokenPosition token_pos);
TokenPosition ResolveBreakpointPos(const Function& func,
+76
View File
@@ -71,6 +71,82 @@ TEST_CASE(Debugger_GetBreakpointsById) {
EXPECT(debugger->GetBreakpointById(bp_id2) != NULL);
}
static int closure_hit_count = 0;
int64_t closure_bp_id[4];
static void PausedInClosuresHandler(Dart_IsolateId isolate_id,
intptr_t bp_id,
const Dart_CodeLocation& location) {
EXPECT(bp_id == closure_bp_id[closure_hit_count]);
closure_hit_count++;
}
TEST_CASE(Debugger_SetBreakpointInFunctionLiteralFieldInitializers) {
const char* kScriptChars =
"main() {\n"
" var c = new MyClass();\n"
" c.closure(1, 2);\n"
" MyClass.staticClosure(7, 8);\n"
" closure(3, 4);\n"
" closureSingleLine(5, 6);\n"
"}\n"
"class MyClass {\n"
" var closure = (int a, int b) {\n"
" return a + b;\n"
" };\n"
" static var staticClosure = (int a, int b) {\n"
" return a * b;\n"
" };\n"
"}\n"
"var closure = (int a, int b) {\n"
" return a + b;\n"
"};\n"
"var closureSingleLine = (int a, int b) => a * b;\n"
"int v = 10;\n";
SetFlagScope<bool> sfs(&FLAG_remove_script_timestamps_for_test, true);
Dart_Handle lib = TestCase::LoadTestScript(kScriptChars, NULL);
EXPECT_VALID(lib);
Isolate* isolate = Isolate::Current();
Debugger* debugger = isolate->debugger();
Dart_Handle url = NewString(TestCase::url());
Dart_Handle result = Dart_SetBreakpoint(url, 10);
EXPECT_VALID(result);
EXPECT(Dart_IsInteger(result));
EXPECT_VALID(Dart_IntegerToInt64(result, &closure_bp_id[0]));
result = Dart_SetBreakpoint(url, 13);
EXPECT_VALID(result);
EXPECT(Dart_IsInteger(result));
EXPECT_VALID(Dart_IntegerToInt64(result, &closure_bp_id[1]));
result = Dart_SetBreakpoint(url, 17);
EXPECT_VALID(result);
EXPECT(Dart_IsInteger(result));
EXPECT_VALID(Dart_IntegerToInt64(result, &closure_bp_id[2]));
result = Dart_SetBreakpoint(url, 19);
EXPECT_VALID(result);
EXPECT(Dart_IsInteger(result));
EXPECT_VALID(Dart_IntegerToInt64(result, &closure_bp_id[3]));
result = Dart_SetBreakpoint(url, 20);
EXPECT_ERROR(result, "could not set breakpoint at line 20");
EXPECT(debugger->GetBreakpointById(closure_bp_id[0]) != NULL);
EXPECT(debugger->GetBreakpointById(closure_bp_id[1]) != NULL);
EXPECT(debugger->GetBreakpointById(closure_bp_id[2]) != NULL);
EXPECT(debugger->GetBreakpointById(closure_bp_id[3]) != NULL);
Dart_SetPausedEventHandler(PausedInClosuresHandler);
result = Dart_Invoke(lib, NewString("main"), 0, NULL);
EXPECT_VALID(result);
// TODO(sivachandra): Understand why a breakpoint on a single line
// functional literal is not being hit. When that issue is resolved,
// adjust this test to check hitting the breakpoint at line 19 also.
EXPECT(closure_hit_count == 3);
}
TEST_CASE(Debugger_RemoveBreakpoint) {
const char* kScriptChars =
"main() {\n"
+45 -1
View File
@@ -983,6 +983,43 @@ void Parser::ParseClass(const Class& cls) {
}
bool Parser::FieldHasFunctionLiteralInitializer(const Field& field,
TokenPosition* start,
TokenPosition* end) {
if (!field.has_initializer()) {
return false;
}
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
const Class& cls = Class::Handle(zone, field.Owner());
const Script& script = Script::Handle(zone, cls.script());
const Library& lib = Library::Handle(zone, cls.library());
Parser parser(script, lib, field.token_pos());
return parser.GetFunctionLiteralInitializerRange(field, start, end);
}
bool Parser::GetFunctionLiteralInitializerRange(const Field& field,
TokenPosition* start,
TokenPosition* end) {
ASSERT(field.has_initializer());
// Since |field| has an initializer, skip until '='.
while (CurrentToken() != Token::kASSIGN) {
ConsumeToken();
}
// Skip past the '=' as well.
ConsumeToken();
*start = TokenPos();
if (IsFunctionLiteral()) {
SkipExpr();
*end = PrevTokenPos();
return true;
}
return false;
}
RawObject* Parser::ParseFunctionParameters(const Function& func) {
ASSERT(!func.IsNull());
LongJumpScope jump;
@@ -8197,7 +8234,6 @@ AstNode* Parser::ParseFunctionStatement(bool is_literal) {
ASSERT(innermost_function_.raw() == function.raw());
innermost_function_ = function.parent_function();
return is_literal ? closure : new (Z) StoreLocalNode(
function_pos, function_variable, closure);
}
@@ -15259,6 +15295,14 @@ ArgumentListNode* Parser::BuildNoSuchMethodArguments(
return NULL;
}
bool Parser::FieldHasFunctionLiteralInitializer(const Field& field,
TokenPosition* start,
TokenPosition* end) {
UNREACHABLE();
return false;
}
} // namespace dart
#endif // DART_PRECOMPILED_RUNTIME
+10
View File
@@ -274,6 +274,13 @@ class Parser : public ValueObject {
static void ParseFunction(ParsedFunction* parsed_function);
// Return true if |field| has a function literal initializer.
// When true is returned, |start| and |end| will hold the token
// range of the function literal.
static bool FieldHasFunctionLiteralInitializer(const Field& field,
TokenPosition* start,
TokenPosition* end);
// Parse and evaluate the metadata expressions at token_pos in the
// class namespace of class cls (which can be the implicit toplevel
// class if the metadata is at the top-level).
@@ -858,6 +865,9 @@ class Parser : public ValueObject {
void CheckInstanceFieldAccess(TokenPosition field_pos,
const String& field_name);
bool ParsingStaticMember() const;
bool GetFunctionLiteralInitializerRange(const Field& field,
TokenPosition* start,
TokenPosition* end);
const AbstractType* ReceiverType(const Class& cls);
bool IsInstantiatorRequired() const;
bool InGenericFunctionScope() const;