| Index: runtime/vm/debugger.cc
|
| ===================================================================
|
| --- runtime/vm/debugger.cc (revision 31370)
|
| +++ runtime/vm/debugger.cc (working copy)
|
| @@ -50,17 +50,19 @@
|
|
|
|
|
| SourceBreakpoint::SourceBreakpoint(intptr_t id,
|
| - const Function& func,
|
| + const Script& script,
|
| intptr_t token_pos)
|
| : id_(id),
|
| - function_(func.raw()),
|
| + script_(script.raw()),
|
| token_pos_(token_pos),
|
| - line_number_(-1),
|
| + is_resolved_(false),
|
| is_enabled_(false),
|
| - next_(NULL) {
|
| - ASSERT(!func.IsNull());
|
| - ASSERT((func.token_pos() <= token_pos_) &&
|
| - (token_pos_ <= func.end_token_pos()));
|
| + next_(NULL),
|
| + function_(Function::null()),
|
| + line_number_(-1) {
|
| + ASSERT(id_ > 0);
|
| + ASSERT(!script.IsNull());
|
| + ASSERT(token_pos_ >= 0);
|
| }
|
|
|
|
|
| @@ -76,51 +78,59 @@
|
| }
|
|
|
|
|
| -RawScript* SourceBreakpoint::SourceCode() {
|
| - const Function& func = Function::Handle(function_);
|
| - return func.script();
|
| +void SourceBreakpoint::SetResolved(const Function& func, intptr_t token_pos) {
|
| + ASSERT(func.script() == script_);
|
| + ASSERT((func.token_pos() <= token_pos) &&
|
| + (token_pos <= func.end_token_pos()));
|
| + function_ = func.raw();
|
| + token_pos_ = token_pos;
|
| + line_number_ = -1; // Recalcualte lazily.
|
| + is_resolved_ = true;
|
| }
|
|
|
|
|
| +// TODO(hausner): Get rid of library parameter. A source breakpoint location
|
| +// does not imply a library, since the same source code can be included
|
| +// in more than one library, e.g. the text location of mixin functions.
|
| void SourceBreakpoint::GetCodeLocation(
|
| Library* lib,
|
| Script* script,
|
| - intptr_t* pos) const {
|
| - const Function& func = Function::Handle(function_);
|
| - const Class& cls = Class::Handle(func.origin());
|
| - *lib = cls.library();
|
| - *script = func.script();
|
| - *pos = token_pos();
|
| + 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 {
|
| + *lib = Library::null();
|
| + }
|
| }
|
|
|
|
|
| RawString* SourceBreakpoint::SourceUrl() {
|
| - const Script& script = Script::Handle(SourceCode());
|
| - return script.url();
|
| + return Script::Handle(script()).url();
|
| }
|
|
|
|
|
| intptr_t SourceBreakpoint::LineNumber() {
|
| // Compute line number lazily since it causes scanning of the script.
|
| if (line_number_ < 0) {
|
| - const Script& script = Script::Handle(SourceCode());
|
| + const Script& script = Script::Handle(this->script());
|
| script.GetTokenLocation(token_pos_, &line_number_, NULL);
|
| }
|
| return line_number_;
|
| }
|
|
|
|
|
| -void SourceBreakpoint::set_function(const Function& func) {
|
| - function_ = func.raw();
|
| -}
|
| -
|
| -
|
| void SourceBreakpoint::VisitObjectPointers(ObjectPointerVisitor* visitor) {
|
| + visitor->VisitPointer(reinterpret_cast<RawObject**>(&script_));
|
| visitor->VisitPointer(reinterpret_cast<RawObject**>(&function_));
|
| }
|
|
|
|
|
| -void SourceBreakpoint::PrintToJSONStream(JSONStream* stream) const {
|
| +void SourceBreakpoint::PrintToJSONStream(JSONStream* stream) {
|
| Isolate* isolate = Isolate::Current();
|
|
|
| JSONObject jsobj(stream);
|
| @@ -128,10 +138,8 @@
|
|
|
| jsobj.AddProperty("id", id());
|
| jsobj.AddProperty("enabled", IsEnabled());
|
| + jsobj.AddProperty("resolved", IsResolved());
|
|
|
| - const Function& func = Function::Handle(function());
|
| - jsobj.AddProperty("resolved", func.HasCode());
|
| -
|
| Library& library = Library::Handle(isolate);
|
| Script& script = Script::Handle(isolate);
|
| intptr_t token_pos;
|
| @@ -139,7 +147,6 @@
|
| {
|
| JSONObject location(&jsobj, "location");
|
| location.AddProperty("type", "Location");
|
| - location.AddProperty("libId", library.index());
|
|
|
| const String& url = String::Handle(script.url());
|
| location.AddProperty("script", url.ToCString());
|
| @@ -221,13 +228,30 @@
|
| }
|
|
|
|
|
| +// Returns true if function contains the token position in the given script.
|
| +static bool FunctionContains(const Function& func,
|
| + const Script& script,
|
| + intptr_t token_pos) {
|
| + if ((func.token_pos() <= token_pos) && (token_pos <= func.end_token_pos())) {
|
| + // Check script equality second because it allocates
|
| + // handles as a side effect.
|
| + return func.script() == script.raw();
|
| + }
|
| + return false;
|
| +}
|
| +
|
| +
|
| bool Debugger::HasBreakpoint(const Function& func) {
|
| if (!func.HasCode()) {
|
| // If the function is not compiled yet, just check whether there
|
| - // is a user-defined latent breakpoint.
|
| + // is a user-defined breakpoint that falls into the token
|
| + // range of the function. This may be a false positive: the breakpoint
|
| + // might be inside a local closure.
|
| + Script& script = Script::Handle(isolate_);
|
| SourceBreakpoint* sbpt = src_breakpoints_;
|
| while (sbpt != NULL) {
|
| - if (func.raw() == sbpt->function()) {
|
| + script = sbpt->script();
|
| + if (FunctionContains(func, script, sbpt->token_pos())) {
|
| return true;
|
| }
|
| sbpt = sbpt->next_;
|
| @@ -1285,54 +1309,47 @@
|
| }
|
|
|
|
|
| -// Given a function and a token position range, return the best fit
|
| -// token position to set a breakpoint.
|
| -// If multiple possible breakpoint positions are within the given range,
|
| -// the one with the lowest machine code address is picked.
|
| -// If no possible breakpoint location exists in the given range, the closest
|
| -// token position after the range is returned.
|
| +// Given a function and a token position, return the best fit
|
| +// token position to set a breakpoint. The best fit is the safe point
|
| +// with the lowest compiled code address that follows the requsted
|
| +// token position.
|
| intptr_t Debugger::ResolveBreakpointPos(const Function& func,
|
| - intptr_t first_token_pos,
|
| - intptr_t last_token_pos) {
|
| + intptr_t requested_token_pos) {
|
| ASSERT(func.HasCode());
|
| ASSERT(!func.HasOptimizedCode());
|
| Code& code = Code::Handle(func.unoptimized_code());
|
| ASSERT(!code.IsNull());
|
| PcDescriptors& desc = PcDescriptors::Handle(code.pc_descriptors());
|
| intptr_t best_fit_index = -1;
|
| - intptr_t best_fit = INT_MAX;
|
| + intptr_t best_fit_pos = INT_MAX;
|
| uword lowest_pc = kUwordMax;
|
| intptr_t lowest_pc_index = -1;
|
| for (intptr_t i = 0; i < desc.Length(); i++) {
|
| intptr_t desc_token_pos = desc.TokenPos(i);
|
| ASSERT(desc_token_pos >= 0);
|
| - if (desc_token_pos < first_token_pos) {
|
| - // This descriptor is before the given range.
|
| + if (desc_token_pos < requested_token_pos) {
|
| + // This descriptor is before the first acceptable token position.
|
| continue;
|
| }
|
| if (IsSafePoint(desc.DescriptorKind(i))) {
|
| - if ((desc_token_pos - first_token_pos) < best_fit) {
|
| - // So far, this descriptor has the closest token position to the
|
| - // beginning of the range.
|
| - best_fit = desc_token_pos - first_token_pos;
|
| - ASSERT(best_fit >= 0);
|
| + if (desc_token_pos < best_fit_pos) {
|
| + // So far, this descriptor has the lowest token position after
|
| + // the first acceptable token position.
|
| + best_fit_pos = desc_token_pos;
|
| best_fit_index = i;
|
| }
|
| - if ((first_token_pos <= desc_token_pos) &&
|
| - (desc_token_pos <= last_token_pos) &&
|
| - (desc.PC(i) < lowest_pc)) {
|
| - // This descriptor is within the token position range and so
|
| - // far has the lowest code address.
|
| + if (desc.PC(i) < lowest_pc) {
|
| + // This descriptor so far has the lowest code address.
|
| lowest_pc = desc.PC(i);
|
| lowest_pc_index = i;
|
| }
|
| }
|
| }
|
| if (lowest_pc_index >= 0) {
|
| - // We found the the pc descriptor within the given token range that
|
| - // has the lowest execution address. This is the first possible
|
| - // breakpoint on the line. We use this instead of the nearest
|
| - // PC descriptor measured in token index distance.
|
| + // We found the pc descriptor that has the lowest execution address.
|
| + // This is the first possible breakpoint after the requested token
|
| + // position. We use this instead of the nearest PC descriptor
|
| + // measured in token index distance.
|
| best_fit_index = lowest_pc_index;
|
| }
|
| if (best_fit_index >= 0) {
|
| @@ -1343,15 +1360,16 @@
|
|
|
|
|
| void Debugger::MakeCodeBreakpointsAt(const Function& func,
|
| - intptr_t token_pos,
|
| SourceBreakpoint* bpt) {
|
| + ASSERT((bpt != NULL) && bpt->IsResolved());
|
| ASSERT(!func.HasOptimizedCode());
|
| Code& code = Code::Handle(func.unoptimized_code());
|
| ASSERT(!code.IsNull());
|
| PcDescriptors& desc = PcDescriptors::Handle(code.pc_descriptors());
|
| for (intptr_t i = 0; i < desc.Length(); i++) {
|
| intptr_t desc_token_pos = desc.TokenPos(i);
|
| - if ((desc_token_pos == token_pos) && IsSafePoint(desc.DescriptorKind(i))) {
|
| + if ((desc_token_pos == bpt->token_pos_) &&
|
| + IsSafePoint(desc.DescriptorKind(i))) {
|
| CodeBreakpoint* code_bpt = GetCodeBreakpoint(desc.PC(i));
|
| if (code_bpt == NULL) {
|
| // No code breakpoint for this code exists; create one.
|
| @@ -1359,83 +1377,207 @@
|
| RegisterCodeBreakpoint(code_bpt);
|
| }
|
| code_bpt->set_src_bpt(bpt);
|
| + if (bpt->IsEnabled()) {
|
| + code_bpt->Enable();
|
| + }
|
| }
|
| }
|
| }
|
|
|
|
|
| -SourceBreakpoint* Debugger::SetBreakpoint(const Function& target_function,
|
| - intptr_t first_token_pos,
|
| - intptr_t last_token_pos) {
|
| - if ((last_token_pos < target_function.token_pos()) ||
|
| - (target_function.end_token_pos() < first_token_pos)) {
|
| - // The given token position is not within the target function.
|
| - return NULL;
|
| +void Debugger::FindEquivalentFunctions(const Script& script,
|
| + intptr_t start_pos,
|
| + intptr_t end_pos,
|
| + GrowableObjectArray* function_list) {
|
| + Class& cls = Class::Handle(isolate_);
|
| + Array& functions = Array::Handle(isolate_);
|
| + GrowableObjectArray& closures = GrowableObjectArray::Handle(isolate_);
|
| + Function& function = Function::Handle(isolate_);
|
| +
|
| + 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);
|
| + // Note: we need to check the functions of this class even if
|
| + // the class is defined in a differenct 'script'. There could
|
| + // be mixin functions from the given script in this class.
|
| + 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());
|
| + // Check token position first to avoid unnecessary calls
|
| + // to script() which allocates handles.
|
| + if ((function.token_pos() == start_pos)
|
| + && (function.end_token_pos() == end_pos)
|
| + && (function.script() == script.raw())) {
|
| + function_list->Add(function);
|
| + if (function.HasImplicitClosureFunction()) {
|
| + function = function.ImplicitClosureFunction();
|
| + function_list->Add(function);
|
| + }
|
| + }
|
| + }
|
| + }
|
| + closures = cls.closures();
|
| + if (!closures.IsNull()) {
|
| + const intptr_t num_closures = closures.Length();
|
| + for (intptr_t pos = 0; pos < num_closures; pos++) {
|
| + function ^= closures.At(pos);
|
| + ASSERT(!function.IsNull());
|
| + if ((function.token_pos() == start_pos)
|
| + && (function.end_token_pos() == end_pos)
|
| + && (function.script() == script.raw())) {
|
| + function_list->Add(function);
|
| + if (function.HasImplicitClosureFunction()) {
|
| + function = function.ImplicitClosureFunction();
|
| + function_list->Add(function);
|
| + }
|
| + }
|
| + }
|
| + }
|
| + }
|
| }
|
| - intptr_t breakpoint_pos = -1;
|
| - Function& closure = Function::Handle(isolate_);
|
| - if (target_function.HasImplicitClosureFunction()) {
|
| - // There is a closurized version of this function.
|
| - closure = target_function.ImplicitClosureFunction();
|
| +}
|
| +
|
| +
|
| +static void SelectBestFit(Function* best_fit, Function* func) {
|
| + if (best_fit->IsNull()) {
|
| + *best_fit = func->raw();
|
| }
|
| - // Determine actual breakpoint location if the function or an
|
| - // implicit closure of the function has been compiled already.
|
| - if (target_function.HasCode()) {
|
| - DeoptimizeWorld();
|
| - ASSERT(!target_function.HasOptimizedCode());
|
| - breakpoint_pos =
|
| - ResolveBreakpointPos(target_function, first_token_pos, last_token_pos);
|
| - } else if (!closure.IsNull() && closure.HasCode()) {
|
| - DeoptimizeWorld();
|
| - ASSERT(!closure.HasOptimizedCode());
|
| - breakpoint_pos =
|
| - ResolveBreakpointPos(closure, first_token_pos, last_token_pos);
|
| - } else {
|
| - // This function has not been compiled yet. Set a pending
|
| - // breakpoint to be resolved later.
|
| - SourceBreakpoint* source_bpt =
|
| - GetSourceBreakpoint(target_function, first_token_pos);
|
| - if (source_bpt != NULL) {
|
| - // A pending source breakpoint for this uncompiled location
|
| - // already exists.
|
| - if (FLAG_verbose_debug) {
|
| - OS::Print("Pending breakpoint for uncompiled function"
|
| - " '%s' at line %" Pd " already exists\n",
|
| - target_function.ToFullyQualifiedCString(),
|
| - source_bpt->LineNumber());
|
| + if (func->token_pos() > best_fit->token_pos()) {
|
| + if (func->end_token_pos() <= best_fit->end_token_pos()) {
|
| + // func is contained within best_fit. Select it even if it
|
| + // has not been compiled yet.
|
| + *best_fit = func->raw();
|
| + if (func->HasImplicitClosureFunction()) {
|
| + *func = func->ImplicitClosureFunction();
|
| + if (func->HasCode()) {
|
| + *best_fit = func->raw();
|
| + }
|
| }
|
| - return source_bpt;
|
| }
|
| - source_bpt =
|
| - new SourceBreakpoint(nextId(), target_function, first_token_pos);
|
| - RegisterSourceBreakpoint(source_bpt);
|
| - if (FLAG_verbose_debug) {
|
| - OS::Print("Registering pending breakpoint for "
|
| - "uncompiled function '%s' at line %" Pd "\n",
|
| - target_function.ToFullyQualifiedCString(),
|
| - source_bpt->LineNumber());
|
| + } else if ((func->token_pos() == best_fit->token_pos())
|
| + && (func->end_token_pos() == best_fit->end_token_pos())
|
| + && func->HasCode()) {
|
| + // If func covers the same range, it is considered a better fit if
|
| + // it has been compiled.
|
| + *best_fit = func->raw();
|
| + }
|
| +}
|
| +
|
| +
|
| +RawFunction* Debugger::FindBestFit(const Script& script,
|
| + intptr_t token_pos) {
|
| + Class& cls = Class::Handle(isolate_);
|
| + Array& functions = Array::Handle(isolate_);
|
| + GrowableObjectArray& closures = GrowableObjectArray::Handle(isolate_);
|
| + Function& function = Function::Handle(isolate_);
|
| + Function& best_fit = Function::Handle(isolate_);
|
| +
|
| + 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);
|
| + // Note: we need to check the functions of this class even if
|
| + // the class is defined in a differenct 'script'. There could
|
| + // be mixin functions from the given script in this class.
|
| + 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 (FunctionContains(function, script, token_pos)) {
|
| + SelectBestFit(&best_fit, &function);
|
| + }
|
| + }
|
| + }
|
| +
|
| + closures = cls.closures();
|
| + if (!closures.IsNull()) {
|
| + const intptr_t num_closures = closures.Length();
|
| + for (intptr_t pos = 0; pos < num_closures; pos++) {
|
| + function ^= closures.At(pos);
|
| + ASSERT(!function.IsNull());
|
| + if (FunctionContains(function, script, token_pos)) {
|
| + SelectBestFit(&best_fit, &function);
|
| + }
|
| + }
|
| + }
|
| }
|
| - source_bpt->Enable();
|
| - return source_bpt;
|
| }
|
| - ASSERT(breakpoint_pos != -1);
|
| - SourceBreakpoint* source_bpt =
|
| - GetSourceBreakpoint(target_function, breakpoint_pos);
|
| - if (source_bpt != NULL) {
|
| - // A source breakpoint for this location already exists.
|
| - return source_bpt;
|
| + return best_fit.raw();
|
| +}
|
| +
|
| +
|
| +SourceBreakpoint* Debugger::SetBreakpoint(const Script& script,
|
| + intptr_t token_pos) {
|
| + Function& func = Function::Handle(isolate_);
|
| + func = FindBestFit(script, token_pos);
|
| + if (func.IsNull()) {
|
| + return NULL;
|
| }
|
| - source_bpt = new SourceBreakpoint(nextId(), target_function, breakpoint_pos);
|
| - RegisterSourceBreakpoint(source_bpt);
|
| - if (target_function.HasCode()) {
|
| - MakeCodeBreakpointsAt(target_function, breakpoint_pos, source_bpt);
|
| + if (!func.IsNull() && func.HasCode()) {
|
| + // A function containing this breakpoint location has already
|
| + // been compiled. We can resolve the breakpoint now.
|
| + DeoptimizeWorld();
|
| + intptr_t breakpoint_pos = ResolveBreakpointPos(func, token_pos);
|
| + if (breakpoint_pos >= 0) {
|
| + SourceBreakpoint* bpt = GetSourceBreakpoint(script, breakpoint_pos);
|
| + if (bpt != NULL) {
|
| + // A source breakpoint for this location already exists.
|
| + return bpt;
|
| + }
|
| + bpt = new SourceBreakpoint(nextId(), script, token_pos);
|
| + bpt->SetResolved(func, breakpoint_pos);
|
| + RegisterSourceBreakpoint(bpt);
|
| + // 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 functions whose
|
| + // source code range matches exactly the best fit function we
|
| + // found.
|
| + GrowableObjectArray& functions =
|
| + GrowableObjectArray::Handle(GrowableObjectArray::New());
|
| + FindEquivalentFunctions(script,
|
| + func.token_pos(),
|
| + func.end_token_pos(),
|
| + &functions);
|
| + const intptr_t num_functions = functions.Length();
|
| + // We must have found at least one function: func.
|
| + ASSERT(num_functions > 0);
|
| + // Create code breakpoints for all compiled functions we found.
|
| + for (intptr_t i = 0; i < num_functions; i++) {
|
| + func ^= functions.At(i);
|
| + if (func.HasCode()) {
|
| + MakeCodeBreakpointsAt(func, bpt);
|
| + }
|
| + }
|
| + bpt->Enable();
|
| + SignalBpResolved(bpt);
|
| + return bpt;
|
| + }
|
| }
|
| - if (!closure.IsNull() && closure.HasCode()) {
|
| - MakeCodeBreakpointsAt(closure, breakpoint_pos, source_bpt);
|
| + // There is no compiled function at this token position.
|
| + // Register an unresolved breakpoint.
|
| + if (FLAG_verbose_debug && !func.IsNull()) {
|
| + intptr_t line_number;
|
| + script.GetTokenLocation(token_pos, &line_number, NULL);
|
| + OS::Print("Registering pending breakpoint for "
|
| + "uncompiled function '%s' at line %" Pd "\n",
|
| + func.ToFullyQualifiedCString(),
|
| + line_number);
|
| }
|
| - source_bpt->Enable();
|
| - SignalBpResolved(source_bpt);
|
| - return source_bpt;
|
| + SourceBreakpoint* bpt = GetSourceBreakpoint(script, token_pos);
|
| + if (bpt == NULL) {
|
| + bpt = new SourceBreakpoint(nextId(), script, token_pos);
|
| + }
|
| + RegisterSourceBreakpoint(bpt);
|
| + bpt->Enable();
|
| + return bpt;
|
| }
|
|
|
|
|
| @@ -1469,9 +1611,8 @@
|
| SourceBreakpoint* Debugger::SetBreakpointAtEntry(
|
| const Function& target_function) {
|
| ASSERT(!target_function.IsNull());
|
| - return SetBreakpoint(target_function,
|
| - target_function.token_pos(),
|
| - target_function.end_token_pos());
|
| + const Script& script = Script::Handle(target_function.script());
|
| + return SetBreakpoint(script, target_function.token_pos());
|
| }
|
|
|
|
|
| @@ -1505,33 +1646,25 @@
|
| }
|
| return NULL;
|
| } else if (last_token_idx < 0) {
|
| - // Line does not contain any tokens. first_token_index is the first
|
| - // token after the given line. We check whether that token is
|
| - // part of a function.
|
| - last_token_idx = first_token_idx;
|
| - }
|
| -
|
| - Function& func = Function::Handle(isolate_);
|
| - while (first_token_idx <= last_token_idx) {
|
| - func = lib.LookupFunctionInScript(script, first_token_idx);
|
| - if (!func.IsNull()) {
|
| - break;
|
| - }
|
| - first_token_idx++;
|
| - }
|
| - if (func.IsNull()) {
|
| + // Line does not contain any tokens.
|
| if (FLAG_verbose_debug) {
|
| OS::Print("No executable code at line %" Pd " in '%s'\n",
|
| line_number, script_url.ToCString());
|
| }
|
| return NULL;
|
| }
|
| - if (last_token_idx < 0) {
|
| - // The token at first_token_index is past the requested source line.
|
| - // Set the breakpoint at the closest position after that line.
|
| - last_token_idx = func.end_token_pos();
|
| +
|
| + SourceBreakpoint* bpt = NULL;
|
| + ASSERT(first_token_idx <= last_token_idx);
|
| + while ((bpt == NULL) && (first_token_idx <= last_token_idx)) {
|
| + bpt = SetBreakpoint(script, first_token_idx);
|
| + first_token_idx++;
|
| }
|
| - return SetBreakpoint(func, first_token_idx, last_token_idx);
|
| + if ((bpt == NULL) && FLAG_verbose_debug) {
|
| + OS::Print("No executable code at line %" Pd " in '%s'\n",
|
| + line_number, script_url.ToCString());
|
| + }
|
| + return bpt;
|
| }
|
|
|
|
|
| @@ -1923,37 +2056,35 @@
|
| }
|
|
|
|
|
| -static RawFunction* GetOriginalFunction(const Function& func) {
|
| - if (func.IsClosureFunction()) {
|
| - // Local functions (closures) in mixin functions do not have
|
| - // an original function they were cloned from.
|
| - // Note: this is a problem when a breakpoint is set in a mixed-in
|
| - // closure. The breakpoint is linked to the closure attached to the
|
| - // mixin application class, not the mixin class. When the same
|
| - // closure is compiled for another mixin application class, we
|
| - // don't find the breakpoint since we'll be looking in the
|
| - // mixin class.
|
| - return func.raw();
|
| +// Return innermost closure contained in 'function' that contains
|
| +// the given token position.
|
| +RawFunction* Debugger::FindInnermostClosure(const Function& function,
|
| + intptr_t token_pos) {
|
| + const Class& owner = Class::Handle(isolate_, function.Owner());
|
| + if (owner.closures() == GrowableObjectArray::null()) {
|
| + return Function::null();
|
| }
|
| - const Class& origin_class = Class::Handle(func.origin());
|
| - if (origin_class.is_patch()) {
|
| - // Patched functions from patch classes are removed from the
|
| - // function array of the patch class, so we will not find an
|
| - // original function object.
|
| - return func.raw();
|
| - }
|
| - const Array& functions = Array::Handle(origin_class.functions());
|
| - Object& orig_func = Object::Handle();
|
| - for (intptr_t i = 0; i < functions.Length(); i++) {
|
| - orig_func = functions.At(i);
|
| - // Function names are symbols, so we can compare the raw pointers.
|
| - if (func.name() == Function::Cast(orig_func).name()) {
|
| - return Function::Cast(orig_func).raw();
|
| + // Note that we need to check that the closure is in the same
|
| + // script as the outer function. We could have closures originating
|
| + // in mixin classes whose source code is contained in a different
|
| + // script.
|
| + const Script& outer_origin = Script::Handle(isolate_, function.script());
|
| + const GrowableObjectArray& closures =
|
| + GrowableObjectArray::Handle(isolate_, owner.closures());
|
| + const intptr_t num_closures = closures.Length();
|
| + Function& closure = Function::Handle(isolate_);
|
| + Function& best_fit = Function::Handle(isolate_);
|
| + for (intptr_t i = 0; i < num_closures; i++) {
|
| + closure ^= closures.At(i);
|
| + if ((function.token_pos() < closure.token_pos()) &&
|
| + (closure.end_token_pos() < function.end_token_pos()) &&
|
| + (closure.token_pos() <= token_pos) &&
|
| + (token_pos <= closure.end_token_pos()) &&
|
| + (closure.script() == outer_origin.raw())) {
|
| + SelectBestFit(&best_fit, &closure);
|
| }
|
| }
|
| - // Uncommon case: not a mixin function.
|
| - ASSERT(!Class::Handle(func.Owner()).IsMixinApplication());
|
| - return func.raw();
|
| + return best_fit.raw();
|
| }
|
|
|
|
|
| @@ -1962,56 +2093,67 @@
|
| // Return with minimal overhead if there are no breakpoints.
|
| return;
|
| }
|
| - Function& lookup_function = Function::Handle(func.raw());
|
| - if (func.IsImplicitClosureFunction()) {
|
| - // If the newly compiled function is a an implicit closure (a closure that
|
| - // was formed by assigning a static or instance method to a function
|
| - // object), we need to use the closure's parent function to see whether
|
| - // there are any breakpoints. The parent function is the actual method on
|
| - // which the user sets breakpoints.
|
| - lookup_function = func.parent_function();
|
| - ASSERT(!lookup_function.IsNull());
|
| - }
|
| - if (lookup_function.Owner() != lookup_function.origin()) {
|
| - // This is a cloned function from a mixin class. If a breakpoint
|
| - // was set in this function, it is registered using the function
|
| - // of the origin class.
|
| - lookup_function = GetOriginalFunction(lookup_function);
|
| - }
|
| - SourceBreakpoint* bpt = src_breakpoints_;
|
| - while (bpt != NULL) {
|
| - if (lookup_function.raw() == bpt->function()) {
|
| - // Check if the breakpoint is inside a closure or local function
|
| - // within the newly compiled function. Note that we must look
|
| - // in the owner class of the function that just got compiled
|
| - // (i.e. func), not the owner class of the function we use to
|
| - // record the breakpoint (lookup_function).
|
| - Class& owner = Class::Handle(func.Owner());
|
| - Function& closure =
|
| - Function::Handle(owner.LookupClosureFunction(bpt->token_pos()));
|
| - if (!closure.IsNull() && (closure.raw() != lookup_function.raw())) {
|
| + // Iterate over all source breakpoints to check whether breakpoints
|
| + // need to be set in the newly compiled function.
|
| + Script& script = Script::Handle(isolate_);
|
| + for (SourceBreakpoint* bpt = src_breakpoints_;
|
| + bpt != NULL;
|
| + bpt = bpt->next()) {
|
| + script = bpt->script();
|
| + if (FunctionContains(func, script, bpt->token_pos())) {
|
| + Function& inner_function = Function::Handle(isolate_);
|
| + inner_function = FindInnermostClosure(func, bpt->token_pos());
|
| + if (!inner_function.IsNull()) {
|
| + // The local function of a function we just compiled cannot
|
| + // be compiled already.
|
| + ASSERT(!inner_function.HasCode());
|
| if (FLAG_verbose_debug) {
|
| - OS::Print("Resetting pending breakpoint to function %s\n",
|
| - closure.ToFullyQualifiedCString());
|
| + OS::Print("Pending BP remains unresolved in inner function '%s'\n",
|
| + inner_function.ToFullyQualifiedCString());
|
| }
|
| - bpt->set_function(closure);
|
| - } else {
|
| + continue;
|
| + }
|
| +
|
| + // TODO(hausner): What should we do if function is optimized?
|
| + // Can we deoptimize the function?
|
| + ASSERT(!func.HasOptimizedCode());
|
| +
|
| + // There is no local function within func that contains the
|
| + // breakpoint token position. Resolve the breakpoint if necessary
|
| + // and set the code breakpoints.
|
| + if (!bpt->IsResolved()) {
|
| + // Resolve source breakpoint in the newly compiled function.
|
| + intptr_t bp_pos = ResolveBreakpointPos(func, bpt->token_pos());
|
| + if (bp_pos < 0) {
|
| + if (FLAG_verbose_debug) {
|
| + OS::Print("Failed resolving breakpoint for function '%s'\n",
|
| + String::Handle(func.name()).ToCString());
|
| + }
|
| + continue;
|
| + }
|
| + intptr_t requested_pos = bpt->token_pos();
|
| + bpt->SetResolved(func, bp_pos);
|
| if (FLAG_verbose_debug) {
|
| - OS::Print("Enable pending breakpoint for function '%s'\n",
|
| - String::Handle(lookup_function.name()).ToCString());
|
| + OS::Print("Resolved BP %" Pd " to pos %" Pd ", line %" Pd ", "
|
| + "function '%s' (requested pos %" Pd ")\n",
|
| + bpt->id(),
|
| + bpt->token_pos(),
|
| + bpt->LineNumber(),
|
| + func.ToFullyQualifiedCString(),
|
| + requested_pos);
|
| }
|
| - const Script& script= Script::Handle(func.script());
|
| - intptr_t first_pos, last_pos;
|
| - script.TokenRangeAtLine(bpt->LineNumber(), &first_pos, &last_pos);
|
| - intptr_t bp_pos =
|
| - ResolveBreakpointPos(func, bpt->token_pos(), last_pos);
|
| - bpt->set_token_pos(bp_pos);
|
| - MakeCodeBreakpointsAt(func, bp_pos, bpt);
|
| SignalBpResolved(bpt);
|
| }
|
| - bpt->Enable(); // Enables the code breakpoint as well.
|
| + ASSERT(bpt->IsResolved());
|
| + if (FLAG_verbose_debug) {
|
| + OS::Print("Setting breakpoint %" Pd " at line %" Pd " for %s '%s'\n",
|
| + bpt->id(),
|
| + bpt->LineNumber(),
|
| + func.IsClosureFunction() ? "closure" : "function",
|
| + String::Handle(func.name()).ToCString());
|
| + }
|
| + MakeCodeBreakpointsAt(func, bpt);
|
| }
|
| - bpt = bpt->next();
|
| }
|
| }
|
|
|
| @@ -2105,12 +2247,11 @@
|
| }
|
|
|
|
|
| -SourceBreakpoint* Debugger::GetSourceBreakpoint(const Function& func,
|
| +SourceBreakpoint* Debugger::GetSourceBreakpoint(const Script& script,
|
| intptr_t token_pos) {
|
| SourceBreakpoint* bpt = src_breakpoints_;
|
| while (bpt != NULL) {
|
| - if ((bpt->function() == func.raw()) &&
|
| - (bpt->token_pos() == token_pos)) {
|
| + if ((bpt->script_ == script.raw()) && (bpt->token_pos_ == token_pos)) {
|
| return bpt;
|
| }
|
| bpt = bpt->next();
|
|
|