| Tor Norbye | 3a2425a | 2013-11-04 10:16:08 -0800 | [diff] [blame^] | 1 | import inspect |
| 2 | import trace |
| 3 | import os |
| 4 | |
| 5 | trace._warn = lambda *args: None # workaround for http://bugs.python.org/issue17143 (PY-8706) |
| 6 | import gc |
| 7 | from pydevd_comm import CMD_SIGNATURE_CALL_TRACE, NetCommand |
| 8 | import pydevd_vars |
| 9 | |
| 10 | class Signature(object): |
| 11 | def __init__(self, file, name): |
| 12 | self.file = file |
| 13 | self.name = name |
| 14 | self.args = [] |
| 15 | self.args_str = [] |
| 16 | |
| 17 | def add_arg(self, name, type): |
| 18 | self.args.append((name, type)) |
| 19 | self.args_str.append("%s:%s"%(name, type)) |
| 20 | |
| 21 | def __str__(self): |
| 22 | return "%s %s(%s)"%(self.file, self.name, ", ".join(self.args_str)) |
| 23 | |
| 24 | |
| 25 | class SignatureFactory(object): |
| 26 | def __init__(self): |
| 27 | self._caller_cache = {} |
| 28 | self.project_roots = os.getenv('PYCHARM_PROJECT_ROOTS', '').split(os.pathsep) |
| 29 | |
| 30 | def is_in_scope(self, filename): |
| 31 | filename = os.path.normcase(filename) |
| 32 | for root in self.project_roots: |
| 33 | root = os.path.normcase(root) |
| 34 | if filename.startswith(root): |
| 35 | return True |
| 36 | return False |
| 37 | |
| 38 | |
| 39 | |
| 40 | def create_signature(self, frame): |
| 41 | try: |
| 42 | code = frame.f_code |
| 43 | locals = frame.f_locals |
| 44 | filename, modulename, funcname = self.file_module_function_of(frame) |
| 45 | res = Signature(filename, funcname) |
| 46 | for i in range(0, code.co_argcount): |
| 47 | name = code.co_varnames[i] |
| 48 | tp = type(locals[name]) |
| 49 | class_name = tp.__name__ |
| 50 | if class_name == 'instance': |
| 51 | tp = locals[name].__class__ |
| 52 | class_name = tp.__name__ |
| 53 | |
| 54 | if tp.__module__ and tp.__module__ != '__main__': |
| 55 | class_name = "%s.%s"%(tp.__module__, class_name) |
| 56 | |
| 57 | res.add_arg(name, class_name) |
| 58 | return res |
| 59 | except: |
| 60 | import traceback |
| 61 | traceback.print_exc() |
| 62 | |
| 63 | |
| 64 | def file_module_function_of(self, frame): #this code is take from trace module and fixed to work with new-style classes |
| 65 | code = frame.f_code |
| 66 | filename = code.co_filename |
| 67 | if filename: |
| 68 | modulename = trace.modname(filename) |
| 69 | else: |
| 70 | modulename = None |
| 71 | |
| 72 | funcname = code.co_name |
| 73 | clsname = None |
| 74 | if code in self._caller_cache: |
| 75 | if self._caller_cache[code] is not None: |
| 76 | clsname = self._caller_cache[code] |
| 77 | else: |
| 78 | self._caller_cache[code] = None |
| 79 | ## use of gc.get_referrers() was suggested by Michael Hudson |
| 80 | # all functions which refer to this code object |
| 81 | funcs = [f for f in gc.get_referrers(code) |
| 82 | if inspect.isfunction(f)] |
| 83 | # require len(func) == 1 to avoid ambiguity caused by calls to |
| 84 | # new.function(): "In the face of ambiguity, refuse the |
| 85 | # temptation to guess." |
| 86 | if len(funcs) == 1: |
| 87 | dicts = [d for d in gc.get_referrers(funcs[0]) |
| 88 | if isinstance(d, dict)] |
| 89 | if len(dicts) == 1: |
| 90 | classes = [c for c in gc.get_referrers(dicts[0]) |
| 91 | if hasattr(c, "__bases__") or inspect.isclass(c)] |
| 92 | elif len(dicts) > 1: #new-style classes |
| 93 | classes = [c for c in gc.get_referrers(dicts[1]) |
| 94 | if hasattr(c, "__bases__") or inspect.isclass(c)] |
| 95 | else: |
| 96 | classes = [] |
| 97 | |
| 98 | if len(classes) == 1: |
| 99 | # ditto for new.classobj() |
| 100 | clsname = classes[0].__name__ |
| 101 | # cache the result - assumption is that new.* is |
| 102 | # not called later to disturb this relationship |
| 103 | # _caller_cache could be flushed if functions in |
| 104 | # the new module get called. |
| 105 | self._caller_cache[code] = clsname |
| 106 | |
| 107 | |
| 108 | if clsname is not None: |
| 109 | funcname = "%s.%s" % (clsname, funcname) |
| 110 | |
| 111 | return filename, modulename, funcname |
| 112 | |
| 113 | def create_signature_message(signature): |
| 114 | cmdTextList = ["<xml>"] |
| 115 | |
| 116 | cmdTextList.append('<call_signature file="%s" name="%s">' % (pydevd_vars.makeValidXmlValue(signature.file), pydevd_vars.makeValidXmlValue(signature.name))) |
| 117 | |
| 118 | for arg in signature.args: |
| 119 | cmdTextList.append('<arg name="%s" type="%s"></arg>' % (pydevd_vars.makeValidXmlValue(arg[0]), pydevd_vars.makeValidXmlValue(arg[1]))) |
| 120 | |
| 121 | cmdTextList.append("</call_signature></xml>") |
| 122 | cmdText = ''.join(cmdTextList) |
| 123 | return NetCommand(CMD_SIGNATURE_CALL_TRACE, 0, cmdText) |
| 124 | |
| 125 | def sendSignatureCallTrace(dbg, frame, filename): |
| 126 | if dbg.signature_factory: |
| 127 | if dbg.signature_factory.is_in_scope(filename): |
| 128 | dbg.writer.addCommand(create_signature_message(dbg.signature_factory.create_signature(frame))) |
| 129 | |
| 130 | |
| 131 | |