aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/pure-eval/pure_eval/my_getattr_static.py
blob: c750b1acc3fafb9edadedfba2b18c0e9a78ad05a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import types

from pure_eval.utils import of_type, CannotEval

_sentinel = object()


def _static_getmro(klass):
    return type.__dict__['__mro__'].__get__(klass)


def _check_instance(obj, attr):
    instance_dict = {}
    try:
        instance_dict = object.__getattribute__(obj, "__dict__")
    except AttributeError:
        pass
    return dict.get(instance_dict, attr, _sentinel)


def _check_class(klass, attr):
    for entry in _static_getmro(klass):
        if _shadowed_dict(type(entry)) is _sentinel:
            try:
                return entry.__dict__[attr]
            except KeyError:
                pass
        else:
            break
    return _sentinel


def _is_type(obj):
    try:
        _static_getmro(obj)
    except TypeError:
        return False
    return True


def _shadowed_dict(klass):
    dict_attr = type.__dict__["__dict__"]
    for entry in _static_getmro(klass):
        try:
            class_dict = dict_attr.__get__(entry)["__dict__"]
        except KeyError:
            pass
        else:
            if not (type(class_dict) is types.GetSetDescriptorType and
                    class_dict.__name__ == "__dict__" and
                    class_dict.__objclass__ is entry):
                return class_dict
    return _sentinel


def getattr_static(obj, attr):
    """Retrieve attributes without triggering dynamic lookup via the
       descriptor protocol,  __getattr__ or __getattribute__.

       Note: this function may not be able to retrieve all attributes
       that getattr can fetch (like dynamically created attributes)
       and may find attributes that getattr can't (like descriptors
       that raise AttributeError). It can also return descriptor objects
       instead of instance members in some cases. See the
       documentation for details.
    """
    instance_result = _sentinel
    if not _is_type(obj):
        klass = type(obj)
        dict_attr = _shadowed_dict(klass)
        if (dict_attr is _sentinel or
                type(dict_attr) is types.MemberDescriptorType):
            instance_result = _check_instance(obj, attr)
        else:
            raise CannotEval
    else:
        klass = obj

    klass_result = _check_class(klass, attr)

    if instance_result is not _sentinel and klass_result is not _sentinel:
        if (_check_class(type(klass_result), '__get__') is not _sentinel and
                _check_class(type(klass_result), '__set__') is not _sentinel):
            return _resolve_descriptor(klass_result, obj, klass)

    if instance_result is not _sentinel:
        return instance_result
    if klass_result is not _sentinel:
        get = _check_class(type(klass_result), '__get__')
        if get is _sentinel:
            return klass_result
        else:
            if obj is klass:
                instance = None
            else:
                instance = obj
            return _resolve_descriptor(klass_result, instance, klass)

    if obj is klass:
        # for types we check the metaclass too
        for entry in _static_getmro(type(klass)):
            if _shadowed_dict(type(entry)) is _sentinel:
                try:
                    result = entry.__dict__[attr]
                    get = _check_class(type(result), '__get__')
                    if get is not _sentinel:
                        raise CannotEval
                    return result
                except KeyError:
                    pass
    raise CannotEval


class _foo:
    __slots__ = ['foo']
    method = lambda: 0


slot_descriptor = _foo.foo
wrapper_descriptor = str.__dict__['__add__']
method_descriptor = str.__dict__['startswith']
user_method_descriptor = _foo.__dict__['method']

safe_descriptors_raw = [
    slot_descriptor,
    wrapper_descriptor,
    method_descriptor,
    user_method_descriptor,
]

safe_descriptor_types = list(map(type, safe_descriptors_raw))


def _resolve_descriptor(d, instance, owner):
    try:
        return type(of_type(d, *safe_descriptor_types)).__get__(d, instance, owner)
    except AttributeError as e:
        raise CannotEval from e