Support breakpoints in deferred libraries

This CL introduces latent breakpoints, which are bp in urls that are
not yet loaded. When the VM loads additional scripts through deferred
libraries, latent breakpoints get set.

R=iposva@google.com

Review URL: https://codereview.chromium.org//808643004

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@42510 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
hausner@google.com 2014-12-19 16:35:37 +00:00
parent 86dfd8470f
commit f96ae9969e
7 changed files with 264 additions and 28 deletions

View file

@ -14,6 +14,7 @@
#include "vm/dart_api_message.h"
#include "vm/dart_api_state.h"
#include "vm/dart_entry.h"
#include "vm/debugger.h"
#include "vm/debuginfo.h"
#include "vm/exceptions.h"
#include "vm/flags.h"
@ -5310,6 +5311,13 @@ DART_EXPORT Dart_Handle Dart_FinalizeLoading(bool complete_futures) {
return state;
}
// Now that the newly loaded classes are finalized, notify the debugger
// that new code has been loaded. If there are latent breakpoints in
// the new code, the debugger convert them to unresolved source breakpoints.
// The code that completes the futures (invoked below) may call into the
// newly loaded code and trigger one of these breakpoints.
isolate->debugger()->NotifyDoneLoading();
if (complete_futures) {
const Library& corelib = Library::Handle(isolate, Library::CoreLibrary());
const String& function_name =

View file

@ -54,12 +54,14 @@ class RemoteObjectCache : public ZoneAllocated {
};
// 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 @@ SourceBreakpoint::SourceBreakpoint(intptr_t id,
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::Disable() {
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::SetResolved(const Function& func, intptr_t token_pos) {
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();
} else {
if (IsLatent()) {
*lib = Library::null();
*script = Script::null();
*pos = -1;
} else {
*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 @@ intptr_t SourceBreakpoint::LineNumber() {
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 @@ Debugger::Debugger()
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()
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 @@ void Debugger::Shutdown() {
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 @@ void Debugger::FindCompiledFunctions(const Script& script,
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 @@ SourceBreakpoint* Debugger::SetBreakpointAtLine(const String& script_url,
}
}
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 @@ void Debugger::VisitObjectPointers(ObjectPointerVisitor* visitor) {
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 @@ void Debugger::NotifyCompilation(const Function& func) {
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::NotifyCompilation(const Function& func) {
}
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::GetBreakpointById(intptr_t id) {
}
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_);

View file

@ -25,12 +25,25 @@ class StackFrame;
// SourceBreakpoint represents a user-specified breakpoint location in
// Dart source. There may be more than one CodeBreakpoint object per
// SourceBreakpoint.
// An unresolved breakpoint is one where the underlying code has not
// been compiled yet. Since the code has not been compiled, we don't know
// the definitive source location yet. The requested source location may
// change when the underlying code gets compiled.
// A latent breakpoint represents a breakpoint location in Dart source
// that is not loaded in the VM when the breakpoint is requested.
// When a script with matching url is loaded, a latent breakpoint
// becomes an unresolved breakpoint.
class SourceBreakpoint {
public:
// Create a new unresolved breakpoint.
SourceBreakpoint(intptr_t id,
const Script& script,
intptr_t token_pos,
intptr_t end_token_pos);
// Create a new latent breakpoint.
SourceBreakpoint(intptr_t id,
const String& url,
intptr_t line_number);
RawFunction* function() const { return function_; }
intptr_t token_pos() const { return token_pos_; }
@ -38,7 +51,7 @@ class SourceBreakpoint {
intptr_t id() const { return id_; }
RawScript* script() const { return script_; }
RawString* SourceUrl();
RawString* url() const { return url_; }
intptr_t LineNumber();
void GetCodeLocation(Library* lib, Script* script, intptr_t* token_pos);
@ -47,6 +60,7 @@ class SourceBreakpoint {
void Disable();
bool IsEnabled() const { return is_enabled_; }
bool IsResolved() const { return is_resolved_; }
bool IsLatent() const { return token_pos_ < 0; }
bool IsOneShot() const { return is_one_shot_; }
void SetIsOneShot() { is_one_shot_ = true; }
@ -62,6 +76,7 @@ class SourceBreakpoint {
const intptr_t id_;
RawScript* script_;
RawString* url_;
intptr_t token_pos_;
intptr_t end_token_pos_;
bool is_resolved_;
@ -342,6 +357,7 @@ class Debugger {
void Shutdown();
void NotifyCompilation(const Function& func);
void NotifyDoneLoading();
RawFunction* ResolveFunction(const Library& library,
const String& class_name,
@ -462,6 +478,7 @@ class Debugger {
intptr_t last_token_pos);
void RemoveInternalBreakpoints();
void UnlinkCodeBreakpoints(SourceBreakpoint* src_bpt);
SourceBreakpoint* GetLatentBreakpoint(const String& url, intptr_t line);
void RegisterSourceBreakpoint(SourceBreakpoint* bpt);
void RegisterCodeBreakpoint(CodeBreakpoint* bpt);
SourceBreakpoint* GetSourceBreakpoint(const Script& script,
@ -511,7 +528,7 @@ class Debugger {
// ID number generator.
intptr_t next_id_;
SourceBreakpoint* latent_breakpoints_;
SourceBreakpoint* src_breakpoints_;
CodeBreakpoint* code_breakpoints_;

View file

@ -374,7 +374,7 @@ DART_EXPORT Dart_Handle Dart_GetBreakpointURL(intptr_t bp_id) {
return Api::NewError("%s: breakpoint with id %" Pd " does not exist",
CURRENT_FUNC, bp_id);
}
return Api::NewHandle(isolate, bpt->SourceUrl());
return Api::NewHandle(isolate, bpt->url());
}
@ -646,7 +646,7 @@ DART_EXPORT Dart_Handle Dart_GetSupertype(Dart_Handle type_in) {
return Api::NewError("%s: type in 'type_in' is not an instantiated type",
CURRENT_FUNC);
}
const Class& cls= Class::Handle(type.type_class());
const Class& cls = Class::Handle(type.type_class());
if (cls.NumTypeParameters() == 0) {
// The super type has no type parameters or it is already instantiated
// just return it.

View file

@ -397,7 +397,8 @@ StepOut() => new RunCommand.stepOut();
class SetBreakpointCommand extends Command {
int line;
SetBreakpointCommand(int this.line) {
String url;
SetBreakpointCommand(this.line, this.url) {
template = {"id": 0,
"command": "setBreakpoint",
"params": { "isolateId": 0,
@ -407,18 +408,21 @@ class SetBreakpointCommand extends Command {
void send(Debugger debugger) {
assert(debugger.scriptUrl != null);
template["params"]["url"] = debugger.scriptUrl;
if (url == null) {
url = debugger.scriptUrl;
}
template["params"]["url"] = url;
template["params"]["line"] = line;
debugger.sendMessage(template);
}
void matchResponse(Debugger debugger) {
super.matchResponse(debugger);
print("Set breakpoint at line $line");
print("Set breakpoint at line $line in $url");
}
}
SetBreakpoint(int line) => new SetBreakpointCommand(line);
SetBreakpoint(int line, {String url}) => new SetBreakpointCommand(line, url);
class Event {
String name;

View file

@ -0,0 +1,11 @@
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library lazyCode;
void stopTheBuck() {
// Line number of print call must match breakpoint request
// in deferred_code_test.dart.
print("The debugger stops here.");
}

View file

@ -0,0 +1,46 @@
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// Test to verify that a breakpoint can be set in code that is not yet
// loaded, i.e. in a deferred library.
//
// This test forks a second vm process that runs this dart script as
// a debug target.
// Run this test with option --wire to see the json messages sent
// between the processes.
// Run this test with option --verbose to see the stdout and stderr output
// of the debug target process.
import "debug_lib.dart";
import "deferred_code_lib.dart" deferred as D;
main(List<String> arguments) {
if (RunScript(testScript, arguments)) return;
var loaded = false;
print("Hello from debuggee");
D.loadLibrary().then((_) {
loaded = true;
print("Done loading deferred library");
D.stopTheBuck();
});
print("main terminates");
}
// Expected debugger events and commands.
var testScript = [
MatchFrame(0, "main"), // Top frame in trace is function "main".
SetBreakpoint(22), // Breakpoint just before call to loadLibrary().
Resume(),
// Set BP in deferred library code before the loadLibrary() call is executed.
MatchFrame(0, "main"),
MatchLine(22),
MatchLocals({"loaded": "false"}),
SetBreakpoint(10, url: "deferred_code_lib.dart"),
Resume(),
MatchFrame(0, "stopTheBuck"), // Expect to be stopped in deferred library code.
// MatchLine(10), // Line matching only works for the main script.
Resume(),
];