From 76ddc0d37b90f0213a14ab97497b708d4b6cc004 Mon Sep 17 00:00:00 2001 From: Elizaveta Shashkova Date: Wed, 29 Jun 2016 19:28:00 +0300 Subject: [PATCH] Move the common function for getting class name for code object (PY-19650) --- .../pydev/_pydevd_bundle/pydevd_constants.py | 2 + .../pydev/_pydevd_bundle/pydevd_frame.py | 35 +++-------- .../pydev/_pydevd_bundle/pydevd_signature.py | 38 +++--------- .../pydev/_pydevd_bundle/pydevd_utils.py | 62 +++++++++++++++++-- 4 files changed, 78 insertions(+), 59 deletions(-) diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_constants.py b/python/helpers/pydev/_pydevd_bundle/pydevd_constants.py index 698e52ffbfa8..5ecdaeb85a65 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_constants.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_constants.py @@ -52,6 +52,7 @@ if IS_JYTHON: IS_JYTH_LESS25 = True CYTHON_SUPPORTED = False +GC_SUPPORTED = False try: import platform @@ -60,6 +61,7 @@ except: pass else: if python_implementation == 'CPython': + GC_SUPPORTED = True # Only available for CPython! if ( (sys.version_info[0] == 2 and sys.version_info[1] >= 7) diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_frame.py b/python/helpers/pydev/_pydevd_bundle/pydevd_frame.py index 62f8c3896064..a6786acaf1c8 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_frame.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_frame.py @@ -1,4 +1,3 @@ -import inspect import linecache import os.path import re @@ -15,6 +14,7 @@ from _pydevd_bundle.pydevd_constants import STATE_SUSPEND, dict_contains, get_th dict_keys, dict_pop, RETURN_VALUES_DICT from _pydevd_bundle.pydevd_dont_trace_files import DONT_TRACE, PYDEV_FILE from _pydevd_bundle.pydevd_frame_utils import add_exception_to_frame, just_raised +from _pydevd_bundle.pydevd_utils import get_clsname_for_code from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame try: @@ -268,29 +268,15 @@ class PyDBFrame: # No longer cdef because object was dying when only a reference def manage_return_values(self, main_debugger, frame, event, arg): - def get_full_name_if_method(func_name): - # In the frame we have access to the code object, but not to the callable object, - # that's why we're trying to get the name of class for methods in such a tricky way: - # we are checking the first argument of the function (`self` or `cls` for methods). + def get_func_name(frame): + code_obj = frame.f_code + func_name = code_obj.co_name try: - code_obj = frame.f_code - if len(code_obj.co_varnames) > 0: - first_arg_name = code_obj.co_varnames[0] - first_arg_obj = frame.f_locals[first_arg_name] - if inspect.isclass(first_arg_obj): # class method - first_arg_class = first_arg_obj - else: # instance method - first_arg_class = first_arg_obj.__class__ - if hasattr(first_arg_class, func_name): - method = getattr(first_arg_class, func_name) - func_code = None - if hasattr(method, 'func_code'): # Python2 - func_code = method.func_code - elif hasattr(method, '__code__'): # Python3 - func_code = method.__code__ - if func_code and func_code == code_obj: - return first_arg_class.__name__ + "." + func_name - return func_name + cls_name = get_clsname_for_code(code_obj, frame) + if cls_name is not None: + return "%s.%s" % (cls_name, func_name) + else: + return func_name except: traceback.print_exc() return func_name @@ -298,11 +284,10 @@ class PyDBFrame: # No longer cdef because object was dying when only a reference try: if main_debugger.show_return_values: if event == "return" and hasattr(frame, "f_code") and hasattr(frame.f_code, "co_name"): - name = frame.f_code.co_name if hasattr(frame, "f_back") and hasattr(frame.f_back, "f_locals"): if RETURN_VALUES_DICT not in dict_keys(frame.f_back.f_locals): frame.f_back.f_locals[RETURN_VALUES_DICT] = {} - name = get_full_name_if_method(name) + name = get_func_name(frame) frame.f_back.f_locals[RETURN_VALUES_DICT][name] = arg if main_debugger.remove_return_values_flag: # Showing return values was turned off, we should remove them from locals dict. diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_signature.py b/python/helpers/pydev/_pydevd_bundle/pydevd_signature.py index d6fc4aaedc77..d8476199c233 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_signature.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_signature.py @@ -1,4 +1,3 @@ -import inspect try: import trace @@ -7,11 +6,11 @@ except ImportError: else: trace._warn = lambda *args: None # workaround for http://bugs.python.org/issue17143 (PY-8706) -import gc from _pydevd_bundle.pydevd_comm import CMD_SIGNATURE_CALL_TRACE, NetCommand from _pydevd_bundle import pydevd_vars from _pydevd_bundle.pydevd_constants import xrange, dict_iter_items from _pydevd_bundle import pydevd_utils +from _pydevd_bundle.pydevd_utils import get_clsname_for_code class Signature(object): def __init__(self, file, name): @@ -111,34 +110,13 @@ class SignatureFactory(object): clsname = self._caller_cache[code] else: self._caller_cache[code] = None - ## use of gc.get_referrers() was suggested by Michael Hudson - # all functions which refer to this code object - funcs = [f for f in gc.get_referrers(code) - if inspect.isfunction(f)] - # require len(func) == 1 to avoid ambiguity caused by calls to - # new.function(): "In the face of ambiguity, refuse the - # temptation to guess." - if len(funcs) == 1: - dicts = [d for d in gc.get_referrers(funcs[0]) - if isinstance(d, dict)] - if len(dicts) == 1: - classes = [c for c in gc.get_referrers(dicts[0]) - if hasattr(c, "__bases__") or inspect.isclass(c)] - elif len(dicts) > 1: #new-style classes - classes = [c for c in gc.get_referrers(dicts[1]) - if hasattr(c, "__bases__") or inspect.isclass(c)] - else: - classes = [] - - if len(classes) == 1: - # ditto for new.classobj() - clsname = classes[0].__name__ - # cache the result - assumption is that new.* is - # not called later to disturb this relationship - # _caller_cache could be flushed if functions in - # the new module get called. - self._caller_cache[code] = clsname - + clsname = get_clsname_for_code(code, frame) + if clsname is not None: + # cache the result - assumption is that new.* is + # not called later to disturb this relationship + # _caller_cache could be flushed if functions in + # the new module get called. + self._caller_cache[code] = clsname if clsname is not None: funcname = "%s.%s" % (clsname, funcname) diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_utils.py b/python/helpers/pydev/_pydevd_bundle/pydevd_utils.py index e529aedc0344..9b2ca6733fa2 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_utils.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_utils.py @@ -7,9 +7,14 @@ try: except: from urllib.parse import quote # @UnresolvedImport -from _pydevd_bundle import pydevd_constants +import inspect +from _pydevd_bundle.pydevd_constants import IS_PY3K, GC_SUPPORTED import sys from _pydev_bundle import pydev_log +try: + import gc +except ImportError: + GC_SUPPORTED = False def save_main_module(file, module_name): # patch provided by: Scott Schlesier - when script is run, it does not @@ -67,7 +72,7 @@ def compare_object_attrs(x, y): return x.__cmp__(y) except: - if pydevd_constants.IS_PY3K: + if IS_PY3K: return (to_string(x) > to_string(y)) - (to_string(x) < to_string(y)) else: return cmp(to_string(x), to_string(y)) @@ -91,7 +96,7 @@ def cmp_to_key(mycmp): return mycmp(self.obj, other.obj) != 0 return K -if pydevd_constants.IS_PY3K: +if IS_PY3K: def is_string(x): return isinstance(x, str) @@ -109,7 +114,7 @@ def print_exc(): if traceback: traceback.print_exc() -if pydevd_constants.IS_PY3K: +if IS_PY3K: def quote_smart(s, safe='/'): return quote(s, safe) else: @@ -120,6 +125,55 @@ else: return quote(s, safe) +def get_clsname_for_code(code, frame): + clsname = None + if GC_SUPPORTED: + ## use of gc.get_referrers() was suggested by Michael Hudson + # all functions which refer to this code object + funcs = [f for f in gc.get_referrers(code) + if inspect.isfunction(f)] + # require len(func) == 1 to avoid ambiguity caused by calls to + # new.function(): "In the face of ambiguity, refuse the + # temptation to guess." + if len(funcs) == 1: + dicts = [d for d in gc.get_referrers(funcs[0]) + if isinstance(d, dict)] + if len(dicts) == 1: + classes = [c for c in gc.get_referrers(dicts[0]) + if hasattr(c, "__bases__") or inspect.isclass(c)] + elif len(dicts) > 1: #new-style classes + classes = [c for c in gc.get_referrers(dicts[1]) + if hasattr(c, "__bases__") or inspect.isclass(c)] + else: + classes = [] + + if len(classes) == 1: + # ditto for new.classobj() + clsname = classes[0].__name__ + + if clsname is None: + # If gc is not supported we are checking the first argument of the function + # (`self` or `cls` for methods). + func_name = code.co_name + if len(code.co_varnames) > 0: + first_arg_name = code.co_varnames[0] + first_arg_obj = frame.f_locals[first_arg_name] + if inspect.isclass(first_arg_obj): # class method + first_arg_class = first_arg_obj + else: # instance method + first_arg_class = first_arg_obj.__class__ + if hasattr(first_arg_class, func_name): + method = getattr(first_arg_class, func_name) + func_code = None + if hasattr(method, 'func_code'): # Python2 + func_code = method.func_code + elif hasattr(method, '__code__'): # Python3 + func_code = method.__code__ + if func_code and func_code == code: + clsname = first_arg_class.__name__ + return clsname + + def _get_project_roots(project_roots_cache=[]): # Note: the project_roots_cache is the same instance among the many calls to the method if not project_roots_cache: