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:
+125
-85
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user