diff --git a/Lib/bdb.py b/Lib/bdb.py --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -3,6 +3,8 @@ import fnmatch import sys import os +import pyclbr +import __main__ __all__ = ["BdbQuit", "Bdb", "Breakpoint"] @@ -37,9 +39,64 @@ def reset(self): import linecache linecache.checkcache() + self.modulecache = {} self.botframe = None self._set_stopinfo(None, None) + def _descriptor(self, func, module=''): + """Return the descriptor of function or method 'func' in 'module'.""" + if not module: + module = __main__.__file__ + if module.lower().endswith(".py"): + module = module[:-3] + + if module not in self.modulecache: + self.modulecache[module] = {} + try: + desc = pyclbr.readmodule_ex(module) + except (ImportError, KeyError): + return None + mod_dict = self.modulecache[module] + for obj in desc.values(): + if isinstance(obj, pyclbr.Class): + for name, lineno in obj.methods.items(): + method = obj.name + '.' + name + mod_dict[method] = (method, lineno, obj.file) + elif isinstance(obj, pyclbr.Function): + mod_dict[obj.name] = (obj.name, obj.lineno, obj.file) + + try: + return self.modulecache[module][func] + except KeyError: + return None + + def function_descriptor(self, name): + """Return the descriptor of function or method 'name'.""" + i = name.rfind('.') + if i >= 0: + func = name[i+1:] + module = name[:i] + j = module.rfind('.') + # try first the top level module + if module and j < 0: + desc = self._descriptor(name) + # a class method + if desc: + return desc + desc = self._descriptor(func, module) + if desc: + # a function in module + return desc + if j <= 0: + return None + method = module[j+1:] + '.' + func + module = module[:j] + # a method in module + return self._descriptor(method, module) + else: + # a function in top level module + return self._descriptor(name) + def trace_dispatch(self, frame, event, arg): if self.quitting: return # None diff --git a/Lib/pdb.py b/Lib/pdb.py --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -88,26 +88,6 @@ __all__ = ["run", "pm", "Pdb", "runeval", "runctx", "runcall", "set_trace", "post_mortem", "help"] -def find_function(funcname, filename): - cre = re.compile(r'def\s+%s\s*[(]' % re.escape(funcname)) - try: - fp = open(filename) - except IOError: - return None - # consumer of this info expects the first line to be 1 - lineno = 1 - answer = None - while True: - line = fp.readline() - if line == '': - break - if cre.match(line): - answer = funcname, filename, lineno - break - lineno += 1 - fp.close() - return answer - def getsourcelines(obj): lines, lineno = inspect.findsource(obj) if inspect.isframe(obj) and obj.f_globals is obj.f_locals: @@ -656,14 +636,12 @@ lineno = code.co_firstlineno filename = code.co_filename except: - # last thing to try - (ok, filename, ln) = self.lineinfo(arg) - if not ok: + desc = self.function_descriptor(arg) + if not desc: self.error('The specified object %r is not a function ' 'or was not found along sys.path.' % arg) return - funcname = ok # ok contains a function name - lineno = int(ln) + funcname, lineno, filename = desc if not filename: filename = self.defaultFile() # Check for reasonable breakpoint @@ -700,39 +678,6 @@ complete_tbreak = _complete_location - def lineinfo(self, identifier): - failed = (None, None, None) - # Input is identifier, may be in single quotes - idstring = identifier.split("'") - if len(idstring) == 1: - # not in single quotes - id = idstring[0].strip() - elif len(idstring) == 3: - # quoted - id = idstring[1].strip() - else: - return failed - if id == '': return failed - parts = id.split('.') - # Protection for derived debuggers - if parts[0] == 'self': - del parts[0] - if len(parts) == 0: - return failed - # Best first guess at file to look at - fname = self.defaultFile() - if len(parts) == 1: - item = parts[0] - else: - # More than one part. - # First is module, second is method/class - f = self.lookupmodule(parts[0]) - if f: - fname = f - item = parts[1] - answer = find_function(item, fname) - return answer or failed - def checkline(self, filename, lineno): """Check whether specified line seems to be executable. diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -667,6 +667,30 @@ any('main.py(5)foo()->None' in l for l in stdout.splitlines()), 'Fail to step into the caller after a return') + def test_issueXXX(self): + script = """ + class C: + def foo(self): + pass + """ + commands = """ + break C.foo + break bar.bar + break + continue + """ + bar = """ + def bar(): + pass + """ + with open('bar.py', 'w') as f: + f.write(textwrap.dedent(bar)) + self.addCleanup(support.unlink, 'bar.py') + stdout, stderr = self.run_pdb(script, commands) + self.assertTrue( + any('2 breakpoint keep yes' in l for l in stdout.splitlines()), + 'Fail to set a breakpoint in a module not imported') + def tearDown(self): support.unlink(support.TESTFN)