| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562 |
- import inspect
- import types
- import traceback
- import sys
- import operator as op
- from collections import namedtuple
- import warnings
- import re
- import builtins
- import typing
- from pathlib import Path
- from typing import Optional, Tuple
- from jedi.inference.compiled.getattr_static import getattr_static
- ALLOWED_GETITEM_TYPES = (str, list, tuple, bytes, bytearray, dict)
- MethodDescriptorType = type(str.replace)
- # These are not considered classes and access is granted even though they have
- # a __class__ attribute.
- NOT_CLASS_TYPES = (
- types.BuiltinFunctionType,
- types.CodeType,
- types.FrameType,
- types.FunctionType,
- types.GeneratorType,
- types.GetSetDescriptorType,
- types.LambdaType,
- types.MemberDescriptorType,
- types.MethodType,
- types.ModuleType,
- types.TracebackType,
- MethodDescriptorType,
- types.MappingProxyType,
- types.SimpleNamespace,
- types.DynamicClassAttribute,
- )
- # Those types don't exist in typing.
- MethodDescriptorType = type(str.replace)
- WrapperDescriptorType = type(set.__iter__)
- # `object.__subclasshook__` is an already executed descriptor.
- object_class_dict = type.__dict__["__dict__"].__get__(object) # type: ignore[index]
- ClassMethodDescriptorType = type(object_class_dict['__subclasshook__'])
- _sentinel = object()
- # Maps Python syntax to the operator module.
- COMPARISON_OPERATORS = {
- '==': op.eq,
- '!=': op.ne,
- 'is': op.is_,
- 'is not': op.is_not,
- '<': op.lt,
- '<=': op.le,
- '>': op.gt,
- '>=': op.ge,
- }
- _OPERATORS = {
- '+': op.add,
- '-': op.sub,
- }
- _OPERATORS.update(COMPARISON_OPERATORS)
- ALLOWED_DESCRIPTOR_ACCESS = (
- types.FunctionType,
- types.GetSetDescriptorType,
- types.MemberDescriptorType,
- MethodDescriptorType,
- WrapperDescriptorType,
- ClassMethodDescriptorType,
- staticmethod,
- classmethod,
- )
- def safe_getattr(obj, name, default=_sentinel):
- try:
- attr, is_get_descriptor = getattr_static(obj, name)
- except AttributeError:
- if default is _sentinel:
- raise
- return default
- else:
- if isinstance(attr, ALLOWED_DESCRIPTOR_ACCESS):
- # In case of descriptors that have get methods we cannot return
- # it's value, because that would mean code execution.
- # Since it's an isinstance call, code execution is still possible,
- # but this is not really a security feature, but much more of a
- # safety feature. Code execution is basically always possible when
- # a module is imported. This is here so people don't shoot
- # themselves in the foot.
- return getattr(obj, name)
- return attr
- SignatureParam = namedtuple(
- 'SignatureParam',
- 'name has_default default default_string has_annotation annotation annotation_string kind_name'
- )
- def shorten_repr(func):
- def wrapper(self):
- r = func(self)
- if len(r) > 50:
- r = r[:50] + '..'
- return r
- return wrapper
- def create_access(inference_state, obj):
- return inference_state.compiled_subprocess.get_or_create_access_handle(obj)
- def load_module(inference_state, dotted_name, sys_path):
- temp, sys.path = sys.path, sys_path
- try:
- __import__(dotted_name)
- except ImportError:
- # If a module is "corrupt" or not really a Python module or whatever.
- warnings.warn(
- "Module %s not importable in path %s." % (dotted_name, sys_path),
- UserWarning,
- stacklevel=2,
- )
- return None
- except Exception:
- # Since __import__ pretty much makes code execution possible, just
- # catch any error here and print it.
- warnings.warn(
- "Cannot import:\n%s" % traceback.format_exc(), UserWarning, stacklevel=2
- )
- return None
- finally:
- sys.path = temp
- # Just access the cache after import, because of #59 as well as the very
- # complicated import structure of Python.
- module = sys.modules[dotted_name]
- return create_access_path(inference_state, module)
- class AccessPath:
- def __init__(self, accesses):
- self.accesses = accesses
- def create_access_path(inference_state, obj) -> AccessPath:
- access = create_access(inference_state, obj)
- return AccessPath(access.get_access_path_tuples())
- def get_api_type(obj):
- if inspect.isclass(obj):
- return 'class'
- elif inspect.ismodule(obj):
- return 'module'
- elif inspect.isbuiltin(obj) or inspect.ismethod(obj) \
- or inspect.ismethoddescriptor(obj) or inspect.isfunction(obj):
- return 'function'
- # Everything else...
- return 'instance'
- class DirectObjectAccess:
- def __init__(self, inference_state, obj):
- self._inference_state = inference_state
- self._obj = obj
- def __repr__(self):
- return '%s(%s)' % (self.__class__.__name__, self.get_repr())
- def _create_access(self, obj):
- return create_access(self._inference_state, obj)
- def _create_access_path(self, obj) -> AccessPath:
- return create_access_path(self._inference_state, obj)
- def py__bool__(self):
- return bool(self._obj)
- def py__file__(self) -> Optional[Path]:
- try:
- return Path(self._obj.__file__)
- except AttributeError:
- return None
- def py__doc__(self):
- return inspect.getdoc(self._obj) or ''
- def py__name__(self):
- if not _is_class_instance(self._obj) or \
- inspect.ismethoddescriptor(self._obj): # slots
- cls = self._obj
- else:
- try:
- cls = self._obj.__class__
- except AttributeError:
- # happens with numpy.core.umath._UFUNC_API (you get it
- # automatically by doing `import numpy`.
- return None
- try:
- return cls.__name__
- except AttributeError:
- return None
- def py__mro__accesses(self):
- return tuple(self._create_access_path(cls) for cls in self._obj.__mro__[1:])
- def py__getitem__all_values(self):
- if isinstance(self._obj, dict):
- return [self._create_access_path(v) for v in self._obj.values()]
- if isinstance(self._obj, (list, tuple)):
- return [self._create_access_path(v) for v in self._obj]
- if self.is_instance():
- cls = DirectObjectAccess(self._inference_state, self._obj.__class__)
- return cls.py__getitem__all_values()
- try:
- getitem = self._obj.__getitem__
- except AttributeError:
- pass
- else:
- annotation = DirectObjectAccess(self._inference_state, getitem).get_return_annotation()
- if annotation is not None:
- return [annotation]
- return None
- def py__simple_getitem__(self, index, *, safe=True):
- if safe and type(self._obj) not in ALLOWED_GETITEM_TYPES:
- # Get rid of side effects, we won't call custom `__getitem__`s.
- return None
- return self._create_access_path(self._obj[index])
- def py__iter__list(self):
- try:
- iter_method = self._obj.__iter__
- except AttributeError:
- return None
- else:
- p = DirectObjectAccess(self._inference_state, iter_method).get_return_annotation()
- if p is not None:
- return [p]
- if type(self._obj) not in ALLOWED_GETITEM_TYPES:
- # Get rid of side effects, we won't call custom `__getitem__`s.
- return []
- lst = []
- for i, part in enumerate(self._obj):
- if i > 20:
- # Should not go crazy with large iterators
- break
- lst.append(self._create_access_path(part))
- return lst
- def py__class__(self):
- return self._create_access_path(self._obj.__class__)
- def py__bases__(self):
- return [self._create_access_path(base) for base in self._obj.__bases__]
- def py__path__(self):
- paths = getattr(self._obj, '__path__', None)
- # Avoid some weird hacks that would just fail, because they cannot be
- # used by pickle.
- if not isinstance(paths, list) \
- or not all(isinstance(p, str) for p in paths):
- return None
- return paths
- @shorten_repr
- def get_repr(self):
- if inspect.ismodule(self._obj):
- return repr(self._obj)
- # Try to avoid execution of the property.
- if safe_getattr(self._obj, '__module__', default='') == 'builtins':
- return repr(self._obj)
- type_ = type(self._obj)
- if type_ == type:
- return type.__repr__(self._obj)
- if safe_getattr(type_, '__module__', default='') == 'builtins':
- # Allow direct execution of repr for builtins.
- return repr(self._obj)
- return object.__repr__(self._obj)
- def is_class(self):
- return inspect.isclass(self._obj)
- def is_function(self):
- return inspect.isfunction(self._obj) or inspect.ismethod(self._obj)
- def is_module(self):
- return inspect.ismodule(self._obj)
- def is_instance(self):
- return _is_class_instance(self._obj)
- def ismethoddescriptor(self):
- return inspect.ismethoddescriptor(self._obj)
- def get_qualified_names(self):
- def try_to_get_name(obj):
- return getattr(obj, '__qualname__', getattr(obj, '__name__', None))
- if self.is_module():
- return ()
- name = try_to_get_name(self._obj)
- if name is None:
- name = try_to_get_name(type(self._obj))
- if name is None:
- return ()
- return tuple(name.split('.'))
- def dir(self):
- return dir(self._obj)
- def has_iter(self):
- try:
- iter(self._obj)
- return True
- except TypeError:
- return False
- def is_allowed_getattr(self, name, safe=True) -> Tuple[bool, bool, Optional[AccessPath]]:
- # TODO this API is ugly.
- try:
- attr, is_get_descriptor = getattr_static(self._obj, name)
- except AttributeError:
- if not safe:
- # Unsafe is mostly used to check for __getattr__/__getattribute__.
- # getattr_static works for properties, but the underscore methods
- # are just ignored (because it's safer and avoids more code
- # execution). See also GH #1378.
- # Avoid warnings, see comment in the next function.
- with warnings.catch_warnings(record=True):
- warnings.simplefilter("always")
- try:
- return hasattr(self._obj, name), False, None
- except Exception:
- # Obviously has an attribute (probably a property) that
- # gets executed, so just avoid all exceptions here.
- pass
- return False, False, None
- else:
- if is_get_descriptor and type(attr) not in ALLOWED_DESCRIPTOR_ACCESS:
- if isinstance(attr, property):
- if hasattr(attr.fget, '__annotations__'):
- a = DirectObjectAccess(self._inference_state, attr.fget)
- return True, True, a.get_return_annotation()
- # In case of descriptors that have get methods we cannot return
- # it's value, because that would mean code execution.
- return True, True, None
- return True, False, None
- def getattr_paths(self, name, default=_sentinel):
- try:
- # Make sure no warnings are printed here, this is autocompletion,
- # warnings should not be shown. See also GH #1383.
- with warnings.catch_warnings(record=True):
- warnings.simplefilter("always")
- return_obj = getattr(self._obj, name)
- except Exception as e:
- if default is _sentinel:
- if isinstance(e, AttributeError):
- # Happens e.g. in properties of
- # PyQt4.QtGui.QStyleOptionComboBox.currentText
- # -> just set it to None
- raise
- # Just in case anything happens, return an AttributeError. It
- # should not crash.
- raise AttributeError
- return_obj = default
- access = self._create_access(return_obj)
- if inspect.ismodule(return_obj):
- return [access]
- try:
- module = return_obj.__module__
- except AttributeError:
- pass
- else:
- if module is not None and isinstance(module, str):
- try:
- __import__(module)
- # For some modules like _sqlite3, the __module__ for classes is
- # different, in this case it's sqlite3. So we have to try to
- # load that "original" module, because it's not loaded yet. If
- # we don't do that, we don't really have a "parent" module and
- # we would fall back to builtins.
- except ImportError:
- pass
- module = inspect.getmodule(return_obj)
- if module is None:
- module = inspect.getmodule(type(return_obj))
- if module is None:
- module = builtins
- return [self._create_access(module), access]
- def get_safe_value(self):
- if type(self._obj) in (bool, bytes, float, int, str, slice) or self._obj is None:
- return self._obj
- raise ValueError("Object is type %s and not simple" % type(self._obj))
- def get_api_type(self):
- return get_api_type(self._obj)
- def get_array_type(self):
- if isinstance(self._obj, dict):
- return 'dict'
- return None
- def get_key_paths(self):
- def iter_partial_keys():
- # We could use list(keys()), but that might take a lot more memory.
- for (i, k) in enumerate(self._obj.keys()):
- # Limit key listing at some point. This is artificial, but this
- # way we don't get stalled because of slow completions
- if i > 50:
- break
- yield k
- return [self._create_access_path(k) for k in iter_partial_keys()]
- def get_access_path_tuples(self):
- accesses = [create_access(self._inference_state, o) for o in self._get_objects_path()]
- return [(access.py__name__(), access) for access in accesses]
- def _get_objects_path(self):
- def get():
- obj = self._obj
- yield obj
- try:
- obj = obj.__objclass__
- except AttributeError:
- pass
- else:
- yield obj
- try:
- # Returns a dotted string path.
- imp_plz = obj.__module__
- except AttributeError:
- # Unfortunately in some cases like `int` there's no __module__
- if not inspect.ismodule(obj):
- yield builtins
- else:
- if imp_plz is None:
- # Happens for example in `(_ for _ in []).send.__module__`.
- yield builtins
- else:
- try:
- yield sys.modules[imp_plz]
- except KeyError:
- # __module__ can be something arbitrary that doesn't exist.
- yield builtins
- return list(reversed(list(get())))
- def execute_operation(self, other_access_handle, operator):
- other_access = other_access_handle.access
- op = _OPERATORS[operator]
- return self._create_access_path(op(self._obj, other_access._obj))
- def get_annotation_name_and_args(self):
- """
- Returns Tuple[Optional[str], Tuple[AccessPath, ...]]
- """
- name = None
- args = ()
- if safe_getattr(self._obj, '__module__', default='') == 'typing':
- m = re.match(r'typing.(\w+)\[', repr(self._obj))
- if m is not None:
- name = m.group(1)
- import typing
- if sys.version_info >= (3, 8):
- args = typing.get_args(self._obj)
- else:
- args = safe_getattr(self._obj, '__args__', default=None)
- return name, tuple(self._create_access_path(arg) for arg in args)
- def needs_type_completions(self):
- return inspect.isclass(self._obj) and self._obj != type
- def _annotation_to_str(self, annotation):
- return inspect.formatannotation(annotation)
- def get_signature_params(self):
- return [
- SignatureParam(
- name=p.name,
- has_default=p.default is not p.empty,
- default=self._create_access_path(p.default),
- default_string=repr(p.default),
- has_annotation=p.annotation is not p.empty,
- annotation=self._create_access_path(p.annotation),
- annotation_string=self._annotation_to_str(p.annotation),
- kind_name=str(p.kind)
- ) for p in self._get_signature().parameters.values()
- ]
- def _get_signature(self):
- obj = self._obj
- try:
- return inspect.signature(obj)
- except (RuntimeError, TypeError):
- # Reading the code of the function in Python 3.6 implies there are
- # at least these errors that might occur if something is wrong with
- # the signature. In that case we just want a simple escape for now.
- raise ValueError
- def get_return_annotation(self) -> Optional[AccessPath]:
- try:
- o = self._obj.__annotations__.get('return')
- except AttributeError:
- return None
- if o is None:
- return None
- try:
- o = typing.get_type_hints(self._obj).get('return')
- except Exception:
- pass
- return self._create_access_path(o)
- def negate(self):
- return self._create_access_path(-self._obj)
- def get_dir_infos(self):
- """
- Used to return a couple of infos that are needed when accessing the sub
- objects of an objects
- """
- tuples = dict(
- (name, self.is_allowed_getattr(name))
- for name in self.dir()
- )
- return self.needs_type_completions(), tuples
- def _is_class_instance(obj):
- """Like inspect.* methods."""
- try:
- cls = obj.__class__
- except AttributeError:
- return False
- else:
- # The isinstance check for cls is just there so issubclass doesn't
- # raise an exception.
- return cls != type and isinstance(cls, type) and not issubclass(cls, NOT_CLASS_TYPES)
|