--- a/DebugClients/Python/DebugBase.py Sat Sep 03 18:01:19 2016 +0200 +++ b/DebugClients/Python/DebugBase.py Sat Sep 03 18:02:37 2016 +0200 @@ -14,9 +14,7 @@ import atexit import inspect import ctypes - -from DebugProtocol import ResponseClearWatch, ResponseClearBreak, \ - ResponseLine, ResponseSyntax, ResponseException, CallTrace +from inspect import CO_GENERATOR gRecursionLimit = 64 @@ -57,7 +55,7 @@ bdb.Bdb.__init__(self) self._dbgClient = dbgClient - self._mainThread = 1 + self._mainThread = True self.breaks = self._dbgClient.breakpoints @@ -200,6 +198,7 @@ if not self.__skip_it(fromFrame) and not self.__skip_it(toFrame): if event in ["call", "return"]: fr = fromFrame + # TODO: change from and to info to a dictionary fromStr = "%s:%s:%s" % ( self._dbgClient.absPath(self.fix_frame_filename(fr)), fr.f_lineno, @@ -209,8 +208,7 @@ self._dbgClient.absPath(self.fix_frame_filename(fr)), fr.f_lineno, fr.f_code.co_name) - self._dbgClient.write("%s%s@@%s@@%s\n" % ( - CallTrace, event[0], fromStr, toStr)) + self._dbgClient.sendCallTrace(event, fromStr, toStr) def trace_dispatch(self, frame, event, arg): """ @@ -279,6 +277,9 @@ @exception bdb.BdbQuit raised to indicate the end of the debug session """ if self.stop_here(frame) or frame == self.returnframe: + # Ignore return events in generator except when stepping. + if self.stopframe and frame.f_code.co_flags & CO_GENERATOR: + return self.trace_dispatch self.user_return(frame, arg) if self.quitting and not self._dbgClient.passive: raise bdb.BdbQuit @@ -294,9 +295,27 @@ @exception bdb.BdbQuit raised to indicate the end of the debug session """ if not self.__skip_it(frame): + # When stepping with next/until/return in a generator frame, + # skip the internal StopIteration exception (with no traceback) + # triggered by a subiterator run with the 'yield from' + # statement. + if not (frame.f_code.co_flags & CO_GENERATOR and + arg[0] is StopIteration and arg[2] is None): + self.user_exception(frame, arg) + if self.quitting: + raise bdb.BdbQuit + + # Stop at the StopIteration or GeneratorExit exception when the user + # has set stopframe in a generator by issuing a return command, or a + # next/until command at the last statement in the generator before the + # exception. + elif (self.stopframe and frame is not self.stopframe and + self.stopframe.f_code.co_flags & CO_GENERATOR and + arg[0] in (StopIteration, GeneratorExit)): self.user_exception(frame, arg) if self.quitting: raise bdb.BdbQuit + return self.trace_dispatch def set_trace(self, frame=None): @@ -318,9 +337,9 @@ # Modified version of the one found in bdb.py # Here we only set a new stop frame if it is a normal continue. if not special: - self.stopframe = self.botframe - self.returnframe = None - self.quitting = 0 + self._set_stopinfo(self.botframe, None) + else: + self._set_stopinfo(self.stopframe, None) def set_quit(self): """ @@ -355,10 +374,14 @@ frame.f_globals['__file__'] and \ frame.f_globals['__file__'] == frame.f_code.co_filename: root, ext = os.path.splitext(frame.f_globals['__file__']) - if ext == '.pyc' or ext == '.py' or ext == '.pyo': + if ext in ['.pyc', '.py', '.py2', '.pyo']: fixedName = root + '.py' if os.path.exists(fixedName): return fixedName + + fixedName = root + '.py2' + if os.path.exists(fixedName): + return fixedName return frame.f_code.co_filename @@ -420,7 +443,7 @@ @param cond expression of the watch expression to be cleared (string) """ self.clear_watch(cond) - self._dbgClient.write('%s%s\n' % (ResponseClearWatch, cond)) + self._dbgClient.sendClearTemporaryWatch(cond) def __effective(self, frame): """ @@ -446,7 +469,7 @@ if b.values[frame][0] == 0: b.values[frame][0] = 1 b.values[frame][1] = val - return (b, 1) + return (b, True) else: continue b.values[frame][0] = 1 @@ -457,7 +480,7 @@ b.values[frame][2] -= 1 continue else: - return (b, 1) + return (b, True) else: continue continue @@ -474,7 +497,7 @@ except KeyError: b.values[frame] = [0, None, b.ignore] continue - return (None, None) + return (None, False) def break_here(self, frame): """ @@ -488,29 +511,34 @@ """ filename = self.canonic(self.fix_frame_filename(frame)) if filename not in self.breaks and "Watch" not in self.breaks: - return 0 + return False if filename in self.breaks: lineno = frame.f_lineno + if lineno not in self.breaks[filename]: + # The line itself has no breakpoint, but maybe the line is the + # first line of a function with breakpoint set by function + # name. + lineno = frame.f_code.co_firstlineno if lineno in self.breaks[filename]: - # flag says ok to delete temp. bp + # flag says ok to delete temp. breakpoint (bp, flag) = bdb.effective(filename, lineno, frame) if bp: self.currentbp = bp.number if (flag and bp.temporary): self.__do_clear(filename, lineno) - return 1 + return True if "Watch" in self.breaks: - # flag says ok to delete temp. bp + # flag says ok to delete temp. watch (bp, flag) = self.__effective(frame) if bp: self.currentbp = bp.number if (flag and bp.temporary): self.__do_clearWatch(bp.cond) - return 1 + return True - return 0 + return False def break_anywhere(self, frame): """ @@ -551,8 +579,7 @@ @param lineno linenumber of the bp """ self.clear_break(filename, lineno) - self._dbgClient.write('%s%s,%d\n' % (ResponseClearBreak, filename, - lineno)) + self._dbgClient.sendClearTemporaryBreakpoint(filename, lineno) def getStack(self): """ @@ -612,7 +639,9 @@ return fr = frame while (fr is not None and - fr.f_code != self._dbgClient.handleLine.func_code): + fr.f_code not in [ + self._dbgClient.handleLine.func_code, + self._dbgClient.handleJsonCommand.func_code]): self._dbgClient.mainFrame = fr fr = fr.f_back @@ -654,7 +683,7 @@ self.__isBroken = True - self._dbgClient.write('%s%s\n' % (ResponseLine, unicode(stack))) + self._dbgClient.sendResponseLine(stack) self._dbgClient.eventLoop() def user_exception(self, frame, (exctype, excval, exctb), unhandled=0): @@ -674,29 +703,44 @@ if exctype in [SystemExit, bdb.BdbQuit]: atexit._run_exitfuncs() if excval is None: - excval = 0 + exitcode = 0 + message = "" elif isinstance(excval, (unicode, str)): - self._dbgClient.write(excval) - excval = 1 - if isinstance(excval, int): - self._dbgClient.progTerminated(excval) + exitcode = 1 + message = excval + elif isinstance(excval, int): + exitcode = excval + message = "" + elif isinstance(excval, SystemExit): + code = excval.code + if isinstance(code, (unicode, str)): + exitcode = 1 + message = code + elif isinstance(code, int): + exitcode = code + message = "" + else: + exitcode = 1 + message = str(code) else: - self._dbgClient.progTerminated(excval.code) + exitcode = 1 + message = str(excval) + self._dbgClient.progTerminated(exitcode, message) return if exctype in [SyntaxError, IndentationError]: try: - message, (filename, linenr, charnr, text) = excval + message, (filename, lineno, charno, text) = excval except ValueError: - exclist = [] + message = "" + filename = "" + lineno = 0 + charno = 0 realSyntaxError = True - else: - exclist = [message, [filename, linenr, charnr]] - realSyntaxError = os.path.exists(filename) if realSyntaxError: - self._dbgClient.write("%s%s\n" % (ResponseSyntax, - unicode(exclist))) + self._dbgClient.sendSyntaxError( + message, filename, lineno, charno) self._dbgClient.eventLoop() return @@ -712,11 +756,11 @@ else: exctypetxt = unicode(exctype) try: - exclist = [exctypetxt, - unicode(excval).encode(self._dbgClient.getCoding())] + excvaltxt = unicode(excval).encode(self._dbgClient.getCoding()) except TypeError: - exclist = [exctypetxt, str(excval)] + excvaltxt = str(excval) + stack = [] if exctb: frlist = self.__extract_stack(exctb) frlist.reverse() @@ -746,9 +790,9 @@ else: fargs = "" - exclist.append([filename, linenr, ffunc, fargs]) + stack.append([filename, linenr, ffunc, fargs]) - self._dbgClient.write("%s%s\n" % (ResponseException, unicode(exclist))) + self._dbgClient.sendException(exctypetxt, excvaltxt, stack) if exctb is None: return @@ -798,7 +842,7 @@ @return flag indicating whether the debugger should stop here """ if self.__skip_it(frame): - return 0 + return False return bdb.Bdb.stop_here(self, frame) def __skip_it(self, frame): @@ -812,32 +856,33 @@ @return flag indicating whether the debugger should skip this frame """ if frame is None: - return 1 + return True fn = self.fix_frame_filename(frame) # Eliminate things like <string> and <stdin>. if fn[0] == '<': - return 1 + return True #XXX - think of a better way to do this. It's only a convenience for #debugging the debugger - when the debugger code is in the current #directory. if os.path.basename(fn) in [ - 'AsyncFile.py', 'AsyncIO.py', - 'DebugConfig.py', 'DCTestResult.py', - 'DebugBase.py', 'DebugClientBase.py', - 'DebugClientCapabilities.py', 'DebugClient.py', - 'DebugClientThreads.py', 'DebugProtocol.py', - 'DebugThread.py', 'FlexCompleter.py', + 'AsyncFile.py', 'DCTestResult.py', + 'DebugBase.py', 'DebugClient.py', + 'DebugClientBase.py', + 'DebugClientCapabilities.py', + 'DebugClientThreads.py', + 'DebugConfig.py', 'DebugThread.py', + 'DebugUtilities.py', 'FlexCompleter.py', 'PyProfile.py'] or \ os.path.dirname(fn).endswith("coverage"): - return 1 + return True if self._dbgClient.shouldSkip(fn): - return 1 + return True - return 0 + return False def isBroken(self): """