in Python/Product/PythonTools/ptvsd/debugger.py [0:0]
def handle_line(self, frame, arg):
self.is_importing_stdlib = False
if not DETACHED:
# resolve whether step_complete and/or handling_breakpoints
step_complete = False
handle_breakpoints = True
stepping = self.stepping
if stepping is not STEPPING_NONE: # check for the common case of no stepping first...
if ((stepping == STEPPING_OVER or stepping == STEPPING_INTO) and frame.f_lineno != self.stopped_on_line):
if self.should_block_on_frame(frame): # don't step complete in our own debugger / non-user code
step_complete = True
elif stepping == STEPPING_LAUNCH_BREAK or stepping == STEPPING_ATTACH_BREAK:
# If launching rather than attaching, don't break into initial Python code needed to set things up
if stepping == STEPPING_LAUNCH_BREAK and (not MODULES or not self.should_block_on_frame(frame)):
handle_breakpoints = False
else:
step_complete = True
# handle breakpoints
hit_bp_id = None
if BREAKPOINTS and handle_breakpoints:
bp = BREAKPOINTS.get(frame.f_lineno)
if bp is not None:
for (filename, bp_id), bp in bp.items():
if filename != frame.f_code.co_filename:
# When the breakpoint is bound, the filename is updated to match co_filename of
# the module to which it was bound, so only exact matches are considered hits.
if bp.is_bound:
continue
# Otherwise, use relaxed path check that tries to handle differences between
# local and remote filesystems for remote scenarios:
if not breakpoint_path_match(filename, frame.f_code.co_filename):
continue
# If we got here, filename and line number both match.
# Check condition to see if we actually hit this breakpoint.
if bp.condition_kind != BREAKPOINT_CONDITION_ALWAYS:
try:
res = eval(bp.condition, frame.f_globals, frame.f_locals)
if bp.condition_kind == BREAKPOINT_CONDITION_WHEN_CHANGED:
last_val = bp.last_condition_value
bp.last_condition_value = res
if last_val == res:
# Condition didn't change, breakpoint not hit.
continue
else:
if not res:
# Condition isn't true, breakpoint not hit.
continue
except:
# If anything goes wrong while evaluating condition, breakpoint is hit.
pass
# If we got here, then condition matched, and we need to update the hit count
# (even if we don't end up signaling the breakpoint because of pass count).
bp.hit_count += 1
# Check the new hit count against pass count.
if bp.pass_count_kind != BREAKPOINT_PASS_COUNT_ALWAYS:
pass_count_kind = bp.pass_count_kind
pass_count = bp.pass_count
hit_count = bp.hit_count
if pass_count_kind == BREAKPOINT_PASS_COUNT_EVERY:
if (hit_count % pass_count) != 0:
continue
elif pass_count_kind == BREAKPOINT_PASS_COUNT_WHEN_EQUAL:
if hit_count != pass_count:
continue
elif pass_count_kind == BREAKPOINT_PASS_COUNT_WHEN_EQUAL_OR_GREATER:
if hit_count < pass_count:
continue
# If we got here, then condition and pass count both match, so we should notify VS.
hit_bp_id = bp_id
# There may be other breakpoints for the same file/line, and we need to update
# their hit counts, too, so keep looping. If more than one is hit, it's fine,
# we will just signal the last one.
if hit_bp_id is not None:
# handle case where both hitting a breakpoint and step complete by reporting the breakpoint
# if the reported breakpoint is a tracepoint, report the step complete if/when the tracepoint is auto-resumed
probe_stack()
update_all_thread_stacks(self)
self.block(lambda: (report_breakpoint_hit(hit_bp_id, self.id), mark_all_threads_for_break(skip_thread = self)), step_complete)
elif step_complete:
self.block_maybe_attach()
# forward call to previous trace function, if any, updating trace function appropriately
old_trace_func = self.prev_trace_func
if old_trace_func is not None:
self.prev_trace_func = None # clear first incase old_trace_func stack overflows
self.prev_trace_func = old_trace_func(frame, 'line', arg)
return self.trace_func