summaryrefslogtreecommitdiff
path: root/Tools/c-analyzer/c_analyzer/common/info.py
diff options
context:
space:
mode:
Diffstat (limited to 'Tools/c-analyzer/c_analyzer/common/info.py')
-rw-r--r--Tools/c-analyzer/c_analyzer/common/info.py138
1 files changed, 138 insertions, 0 deletions
diff --git a/Tools/c-analyzer/c_analyzer/common/info.py b/Tools/c-analyzer/c_analyzer/common/info.py
new file mode 100644
index 0000000000..3f3f8c5b05
--- /dev/null
+++ b/Tools/c-analyzer/c_analyzer/common/info.py
@@ -0,0 +1,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