Index: runtime/vm/debugger.cc |
=================================================================== |
--- runtime/vm/debugger.cc (revision 42490) |
+++ runtime/vm/debugger.cc (working copy) |
@@ -54,12 +54,14 @@ |
}; |
+// Create an unresolved breakpoint in given token range and script. |
SourceBreakpoint::SourceBreakpoint(intptr_t id, |
const Script& script, |
intptr_t token_pos, |
intptr_t end_token_pos) |
: id_(id), |
script_(script.raw()), |
+ url_(script.url()), |
token_pos_(token_pos), |
end_token_pos_(end_token_pos), |
is_resolved_(false), |
@@ -73,6 +75,24 @@ |
ASSERT(token_pos_ >= 0); |
} |
+// Create a latent breakpoint at given url and line number. |
+SourceBreakpoint::SourceBreakpoint(intptr_t id, |
+ const String& url, |
+ intptr_t line_number) |
+ : id_(id), |
+ script_(Script::null()), |
+ url_(url.raw()), |
+ token_pos_(-1), |
+ end_token_pos_(-1), |
+ is_resolved_(false), |
+ is_enabled_(false), |
+ is_one_shot_(false), |
+ next_(NULL), |
+ function_(Function::null()), |
+ line_number_(line_number) { |
+ ASSERT(id >= 0); |
+ ASSERT(line_number_ >= 0); |
+} |
void SourceBreakpoint::Enable() { |
is_enabled_ = true; |
@@ -87,6 +107,7 @@ |
void SourceBreakpoint::SetResolved(const Function& func, intptr_t token_pos) { |
+ ASSERT(!IsLatent()); |
ASSERT(func.script() == script_); |
ASSERT((func.token_pos() <= token_pos) && |
(token_pos <= func.end_token_pos())); |
@@ -104,25 +125,28 @@ |
void SourceBreakpoint::GetCodeLocation(Library* lib, |
Script* script, |
intptr_t* pos) { |
- *script = this->script(); |
- *pos = token_pos_; |
- if (IsResolved()) { |
- const Function& func = Function::Handle(function_); |
- ASSERT(!func.IsNull()); |
- const Class& cls = Class::Handle(func.origin()); |
- *lib = cls.library(); |
+ if (IsLatent()) { |
+ *lib = Library::null(); |
+ *script = Script::null(); |
+ *pos = -1; |
} else { |
- *lib = Library::null(); |
+ *script = this->script(); |
+ *pos = token_pos_; |
+ if (IsResolved()) { |
+ const Function& func = Function::Handle(function_); |
+ ASSERT(!func.IsNull()); |
+ const Class& cls = Class::Handle(func.origin()); |
+ *lib = cls.library(); |
+ } else { |
+ *lib = Library::null(); |
+ } |
} |
} |
-RawString* SourceBreakpoint::SourceUrl() { |
- return Script::Handle(script()).url(); |
-} |
- |
- |
intptr_t SourceBreakpoint::LineNumber() { |
+ // Latent breakpoints must have a requested line number >= 0. |
+ ASSERT(!IsLatent() || line_number_ >= 0); |
// Compute line number lazily since it causes scanning of the script. |
if (line_number_ < 0) { |
const Script& script = Script::Handle(this->script()); |
@@ -134,6 +158,7 @@ |
void SourceBreakpoint::VisitObjectPointers(ObjectPointerVisitor* visitor) { |
visitor->VisitPointer(reinterpret_cast<RawObject**>(&script_)); |
+ visitor->VisitPointer(reinterpret_cast<RawObject**>(&url_)); |
visitor->VisitPointer(reinterpret_cast<RawObject**>(&function_)); |
} |
@@ -997,6 +1022,7 @@ |
isolate_id_(ILLEGAL_ISOLATE_ID), |
initialized_(false), |
next_id_(1), |
+ latent_breakpoints_(NULL), |
src_breakpoints_(NULL), |
code_breakpoints_(NULL), |
resume_action_(kContinue), |
@@ -1012,6 +1038,7 @@ |
Debugger::~Debugger() { |
isolate_id_ = ILLEGAL_ISOLATE_ID; |
ASSERT(!IsPaused()); |
+ ASSERT(latent_breakpoints_ == NULL); |
ASSERT(src_breakpoints_ == NULL); |
ASSERT(code_breakpoints_ == NULL); |
ASSERT(stack_trace_ == NULL); |
@@ -1025,6 +1052,11 @@ |
src_breakpoints_ = src_breakpoints_->next(); |
delete bpt; |
} |
+ while (latent_breakpoints_ != NULL) { |
+ SourceBreakpoint* bpt = latent_breakpoints_; |
+ latent_breakpoints_ = latent_breakpoints_->next(); |
+ delete bpt; |
+ } |
while (code_breakpoints_ != NULL) { |
CodeBreakpoint* bpt = code_breakpoints_; |
code_breakpoints_ = code_breakpoints_->next(); |
@@ -1514,8 +1546,8 @@ |
cls = class_table.At(i); |
// If the class is not finalized, e.g. if it hasn't been parsed |
// yet entirely, we can ignore it. If it contains a function with |
- // a latent breakpoint, we will detect it if and when the function |
- // gets compiled. |
+ // an unresolved breakpoint, we will detect it if and when the |
+ // function gets compiled. |
if (!cls.is_finalized()) { |
continue; |
} |
@@ -1783,11 +1815,15 @@ |
} |
} |
if (scripts.Length() == 0) { |
+ // No script found with given url. Create a latent breakpoint which |
+ // will be set if the url is loaded later. |
+ SourceBreakpoint* latent_bpt = GetLatentBreakpoint(script_url, line_number); |
if (FLAG_verbose_debug) { |
- OS::Print("Failed to find script with url '%s'\n", |
- script_url.ToCString()); |
+ OS::Print("Set latent breakpoint in url '%s' at line %" Pd "\n", |
+ script_url.ToCString(), |
+ line_number); |
} |
- return NULL; |
+ return latent_bpt; |
} |
if (scripts.Length() > 1) { |
if (FLAG_verbose_debug) { |
@@ -2033,6 +2069,11 @@ |
bpt->VisitObjectPointers(visitor); |
bpt = bpt->next(); |
} |
+ bpt = latent_breakpoints_; |
+ while (bpt != NULL) { |
+ bpt->VisitObjectPointers(visitor); |
+ bpt = bpt->next(); |
+ } |
CodeBreakpoint* cbpt = code_breakpoints_; |
while (cbpt != NULL) { |
cbpt->VisitObjectPointers(visitor); |
@@ -2314,15 +2355,17 @@ |
continue; |
} |
intptr_t requested_pos = bpt->token_pos(); |
+ intptr_t requested_end_pos = bpt->end_token_pos(); |
bpt->SetResolved(func, bp_pos); |
if (FLAG_verbose_debug) { |
OS::Print("Resolved BP %" Pd " to pos %" Pd ", line %" Pd ", " |
- "function '%s' (requested pos %" Pd ")\n", |
+ "function '%s' (requested range %" Pd "-%" Pd ")\n", |
bpt->id(), |
bpt->token_pos(), |
bpt->LineNumber(), |
func.ToFullyQualifiedCString(), |
- requested_pos); |
+ requested_pos, |
+ requested_end_pos); |
} |
SignalBpResolved(bpt); |
} |
@@ -2340,6 +2383,94 @@ |
} |
+void Debugger::NotifyDoneLoading() { |
+ if (latent_breakpoints_ == NULL) { |
+ // Common, fast path. |
+ return; |
+ } |
+ Library& lib = Library::Handle(isolate_); |
+ Script& script = Script::Handle(isolate_); |
+ String& url = String::Handle(isolate_); |
+ SourceBreakpoint* bpt = latent_breakpoints_; |
+ SourceBreakpoint* prev_bpt = NULL; |
+ const GrowableObjectArray& libs = |
+ GrowableObjectArray::Handle(isolate_->object_store()->libraries()); |
+ while (bpt != NULL) { |
+ url = bpt->url(); |
+ for (intptr_t i = 0; i < libs.Length(); i++) { |
+ lib ^= libs.At(i); |
+ script = lib.LookupScript(url); |
+ if (!script.IsNull()) { |
+ // Found a script with matching url for this latent breakpoint. |
+ // Unlink the latent breakpoint from the list. |
+ SourceBreakpoint* matched_bpt = bpt; |
+ bpt = bpt->next(); |
+ if (prev_bpt == NULL) { |
+ latent_breakpoints_ = bpt; |
+ } else { |
+ prev_bpt->set_next(bpt); |
+ } |
+ // Now find the token range at the requested line and make a |
+ // new unresolved source breakpoint. |
+ intptr_t line_number = matched_bpt->LineNumber(); |
+ ASSERT(line_number >= 0); |
+ intptr_t first_token_pos, last_token_pos; |
+ script.TokenRangeAtLine(line_number, &first_token_pos, &last_token_pos); |
+ if ((first_token_pos < 0) || |
+ (last_token_pos < 0)) { |
+ // Script does not contain the given line number or there are no |
+ // tokens on the line. Drop the breakpoint silently. |
+ if (FLAG_verbose_debug) { |
+ OS::Print("No code found at line %" Pd ": " |
+ "dropping latent breakpoint %" Pd " in '%s'\n", |
+ line_number, |
+ matched_bpt->id(), |
+ url.ToCString()); |
+ } |
+ delete matched_bpt; |
+ } else { |
+ // We don't expect to already have a breakpoint for this location. |
+ // If there is one, assert in debug build but silently drop |
+ // the latent breakpoint in release build. |
+ SourceBreakpoint* existing_bpt = |
+ GetSourceBreakpoint(script, first_token_pos); |
+ ASSERT(existing_bpt == NULL); |
+ if (existing_bpt == NULL) { |
+ // Create and register a new source breakpoint for the |
+ // latent breakpoint. |
+ SourceBreakpoint* unresolved_bpt = |
+ new SourceBreakpoint(matched_bpt->id(), |
+ script, |
+ first_token_pos, |
+ last_token_pos); |
+ RegisterSourceBreakpoint(unresolved_bpt); |
+ unresolved_bpt->Enable(); |
+ if (FLAG_verbose_debug) { |
+ OS::Print("Converted latent breakpoint " |
+ "%" Pd " in '%s' at line %" Pd "\n", |
+ matched_bpt->id(), |
+ url.ToCString(), |
+ line_number); |
+ } |
+ } |
+ delete matched_bpt; |
+ // Break out of the iteration over loaded libraries. If the |
+ // same url has been loaded into more than one library, we |
+ // only set a breakpoint in the first one. |
+ // TODO(hausner): There is one possible pitfall here. |
+ // If the user sets a latent breakpoint using a partial url that |
+ // ends up matching more than one script, the breakpoint might |
+ // get set in the wrong script. |
+ // It would be better if we could warn the user if multiple |
+ // scripts are matching. |
+ break; |
+ } |
+ } |
+ } |
+ } |
+} |
+ |
+ |
// TODO(hausner): Could potentially make this faster by checking |
// whether the call target at pc is a debugger stub. |
bool Debugger::HasActiveBreakpoint(uword pc) { |
@@ -2470,6 +2601,25 @@ |
} |
+SourceBreakpoint* Debugger::GetLatentBreakpoint(const String& url, |
+ intptr_t line) { |
+ SourceBreakpoint* bpt = latent_breakpoints_; |
+ String& bpt_url = String::Handle(isolate_); |
+ while (bpt != NULL) { |
+ bpt_url = bpt->url(); |
+ if (bpt_url.Equals(url) && (bpt->LineNumber() == line)) { |
+ return bpt; |
+ } |
+ bpt = bpt->next(); |
+ } |
+ // No breakpint for this url and line requested. Allocate new one. |
+ bpt = new SourceBreakpoint(nextId(), url, line); |
+ bpt->set_next(latent_breakpoints_); |
+ latent_breakpoints_ = bpt; |
+ return bpt; |
+} |
+ |
+ |
void Debugger::RegisterSourceBreakpoint(SourceBreakpoint* bpt) { |
ASSERT(bpt->next() == NULL); |
bpt->set_next(src_breakpoints_); |