diff --git a/jedi/evaluate/compiled/access.py b/jedi/evaluate/compiled/access.py index 3072bc56..0a27935b 100644 --- a/jedi/evaluate/compiled/access.py +++ b/jedi/evaluate/compiled/access.py @@ -45,18 +45,6 @@ WrapperDescriptorType = type(set.__iter__) object_class_dict = type.__dict__["__dict__"].__get__(object) ClassMethodDescriptorType = type(object_class_dict['__subclasshook__']) -ALLOWED_DESCRIPTOR_ACCESS = ( - types.FunctionType, - types.GetSetDescriptorType, - types.MemberDescriptorType, - MethodDescriptorType, - WrapperDescriptorType, - ClassMethodDescriptorType, - staticmethod, - classmethod, -) - - def _a_generator(foo): """Used to have an object to return for generators.""" yield 42 @@ -83,6 +71,32 @@ _OPERATORS = { } _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 is_get_descriptor and type(attr) in ALLOWED_DESCRIPTOR_ACCESS: + # In case of descriptors that have get methods we cannot return + # it's value, because that would mean code execution. + return getattr(obj, name) + return attr + SignatureParam = namedtuple('SignatureParam', 'name has_default default has_annotation annotation') @@ -247,13 +261,19 @@ class DirectObjectAccess(object): @_force_unicode_decorator def get_repr(self): + builtins = 'builtins', '__builtin__' + + if inspect.ismodule(self._obj): + return repr(self._obj) # Try to avoid execution of the property. + if safe_getattr(self._obj, '__module__', default='') in builtins: + return repr(self._obj) + type_ = type(self._obj) if type_ == type: return type.__repr__(self._obj) - builtins = 'builtins', '__builtin__' - if getattr_static(type_, '__module__', default='') in builtins: + if safe_getattr(type_, '__module__', default='') in builtins: # Allow direct execution of repr for builtins. return repr(self._obj) return object.__repr__(self._obj) @@ -281,8 +301,7 @@ class DirectObjectAccess(object): except AttributeError: return False, False else: - if is_get_descriptor \ - and not type(attr) in ALLOWED_DESCRIPTOR_ACCESS: + if is_get_descriptor and type(attr) not in ALLOWED_DESCRIPTOR_ACCESS: # In case of descriptors that have get methods we cannot return # it's value, because that would mean code execution. return True, True diff --git a/jedi/evaluate/compiled/context.py b/jedi/evaluate/compiled/context.py index 2dcf7a69..e3aa3df9 100644 --- a/jedi/evaluate/compiled/context.py +++ b/jedi/evaluate/compiled/context.py @@ -5,6 +5,7 @@ import re from functools import partial from jedi import debug +from jedi._compatibility import force_unicode from jedi.cache import underscore_memoization, memoize_method from jedi.evaluate.filters import AbstractFilter, AbstractNameDefinition, \ ContextNameMixin @@ -313,6 +314,9 @@ class CompiledObjectFilter(AbstractFilter): if not has_attribute: return [] + # Always use unicode objects in Python 2 from here. + name = force_unicode(name) + if is_descriptor: return [self._get_cached_name(name, is_empty=True)] @@ -442,7 +446,7 @@ def create_from_access_path(evaluator, access_path): for name, access in access_path.accesses: try: if parent_context is None: - faked = fake.get_faked_module(evaluator.latest_grammar, access_path.accesses[0][0]) + faked = fake.get_faked_module(evaluator, access_path.accesses[0][0]) else: faked = fake.get_faked_with_parent_context(parent_context, name) except fake.FakeDoesNotExist: diff --git a/jedi/evaluate/compiled/fake.py b/jedi/evaluate/compiled/fake.py index 76422a2c..5b1fa3a2 100644 --- a/jedi/evaluate/compiled/fake.py +++ b/jedi/evaluate/compiled/fake.py @@ -29,17 +29,18 @@ class FakeDoesNotExist(Exception): pass -def _load_faked_module(grammar, module_name): - if module_name == '__builtin__' and not is_py3: - module_name = 'builtins' - +def _load_faked_module(evaluator, module_name): try: return fake_modules[module_name] except KeyError: pass + check_module_name = module_name + if module_name == '__builtin__' and evaluator.environment.version_info.major == 2: + check_module_name = 'builtins' + try: - path = _path_dict[module_name] + path = _path_dict[check_module_name] except KeyError: fake_modules[module_name] = None return @@ -47,9 +48,9 @@ def _load_faked_module(grammar, module_name): with open(path) as f: source = f.read() - fake_modules[module_name] = m = grammar.parse(unicode(source)) + fake_modules[module_name] = m = evaluator.latest_grammar.parse(unicode(source)) - if module_name == 'builtins' and not is_py3: + if check_module_name != module_name: # There are two implementations of `open` for either python 2/3. # -> Rename the python2 version (`look at fake/builtins.pym`). open_func = _search_scope(m, 'open') @@ -74,8 +75,8 @@ def get_faked_with_parent_context(parent_context, name): raise FakeDoesNotExist -def get_faked_module(grammar, string_name): - module = _load_faked_module(grammar, string_name) +def get_faked_module(evaluator, string_name): + module = _load_faked_module(evaluator, string_name) if module is None: raise FakeDoesNotExist return module diff --git a/jedi/evaluate/pep0484.py b/jedi/evaluate/pep0484.py index 419cf400..7dba1b22 100644 --- a/jedi/evaluate/pep0484.py +++ b/jedi/evaluate/pep0484.py @@ -64,7 +64,7 @@ def _fix_forward_reference(context, node): if is_string(evaled_node): try: new_node = context.evaluator.grammar.parse( - _compatibility.unicode(evaled_node.get_safe_value()), + _compatibility.force_unicode(evaled_node.get_safe_value()), start_symbol='eval_input', error_recovery=False )