Move the common function for getting class name for code object (PY-19650)

This commit is contained in:
Elizaveta Shashkova
2016-06-29 19:39:23 +03:00
parent 38ec3267af
commit 76ddc0d37b
4 changed files with 78 additions and 59 deletions
@@ -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)
@@ -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.
@@ -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)
@@ -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: