summaryrefslogtreecommitdiff
path: root/Tools/c-analyzer/c_analyzer/common/info.py
blob: 3f3f8c5b05de594916f3f82ca804c02a4a6f5464 (plain)
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
from collections import namedtuple
import re

from .util import classonly, _NTBase

# XXX need tests:
# * ID.match()


UNKNOWN = '???'

NAME_RE = re.compile(r'^([a-zA-Z]|_\w*[a-zA-Z]\w*|[a-zA-Z]\w*)$')


class ID(_NTBase, namedtuple('ID', 'filename funcname name')):
    """A unique ID for a single symbol or declaration."""

    __slots__ = ()
    # XXX Add optional conditions (tuple of strings) field.
    #conditions = Slot()

    @classonly
    def from_raw(cls, raw):
        if not raw:
            return None
        if isinstance(raw, str):
            return cls(None, None, raw)
        try:
            name, = raw
            filename = None
        except ValueError:
            try:
                filename, name = raw
            except ValueError:
                return super().from_raw(raw)
        return cls(filename, None, name)

    def __new__(cls, filename, funcname, name):
        self = super().__new__(
                cls,
                filename=str(filename) if filename else None,
                funcname=str(funcname) if funcname else None,
                name=str(name) if name else None,
                )
        #cls.conditions.set(self, tuple(str(s) if s else None
        #                               for s in conditions or ()))
        return self

    def validate(self):
        """Fail if the object is invalid (i.e. init with bad data)."""
        if not self.name:
            raise TypeError('missing name')
        else:
            if not NAME_RE.match(self.name):
                raise ValueError(
                        f'name must be an identifier, got {self.name!r}')

        # Symbols from a binary might not have filename/funcname info.

        if self.funcname:
            if not self.filename:
                raise TypeError('missing filename')
            if not NAME_RE.match(self.funcname) and self.funcname != UNKNOWN:
                raise ValueError(
                        f'name must be an identifier, got {self.funcname!r}')

        # XXX Require the filename (at least UNKONWN)?
        # XXX Check the filename?

    @property
    def islocal(self):
        return self.funcname is not None

    def match(self, other, *,
              match_files=(lambda f1, f2: f1 == f2),
              ):
        """Return True if the two match.

        At least one of the two must be completely valid (no UNKNOWN
        anywhere).  Otherwise False is returned.  The remaining one
        *may* have UNKNOWN for both funcname and filename.  It must
        have a valid name though.

        The caller is responsible for knowing which of the two is valid
        (and which to use if both are valid).
        """
        # First check the name.
        if self.name is None:
            return False
        if other.name != self.name:
            return False

        # Then check the filename.
        if self.filename is None:
            return False
        if other.filename is None:
            return False
        if self.filename == UNKNOWN:
            # "other" must be the valid one.
            if other.funcname == UNKNOWN:
                return False
            elif self.funcname != UNKNOWN:
                # XXX Try matching funcname even though we don't
                # know the filename?
                raise NotImplementedError
            else:
                return True
        elif other.filename == UNKNOWN:
            # "self" must be the valid one.
            if self.funcname == UNKNOWN:
                return False
            elif other.funcname != UNKNOWN:
                # XXX Try matching funcname even though we don't
                # know the filename?
                raise NotImplementedError
            else:
                return True
        elif not match_files(self.filename, other.filename):
            return False

        # Finally, check the funcname.
        if self.funcname == UNKNOWN:
            # "other" must be the valid one.
            if other.funcname == UNKNOWN:
                return False
            else:
                return other.funcname is not None
        elif other.funcname == UNKNOWN:
            # "self" must be the valid one.
            if self.funcname == UNKNOWN:
                return False
            else:
                return self.funcname is not None
        elif self.funcname == other.funcname:
            # Both are valid.
            return True

        return False