summaryrefslogtreecommitdiff
path: root/Tools/c-analyzer/c_analyzer/info.py
diff options
context:
space:
mode:
Diffstat (limited to 'Tools/c-analyzer/c_analyzer/info.py')
-rw-r--r--Tools/c-analyzer/c_analyzer/info.py353
1 files changed, 353 insertions, 0 deletions
diff --git a/Tools/c-analyzer/c_analyzer/info.py b/Tools/c-analyzer/c_analyzer/info.py
new file mode 100644
index 0000000000..23d77611a4
--- /dev/null
+++ b/Tools/c-analyzer/c_analyzer/info.py
@@ -0,0 +1,353 @@
+from collections import namedtuple
+
+from c_common.clsutil import classonly
+import c_common.misc as _misc
+from c_parser.info import (
+ KIND,
+ HighlevelParsedItem,
+ Declaration,
+ TypeDeclaration,
+ is_type_decl,
+ is_process_global,
+)
+
+
+IGNORED = _misc.Labeled('IGNORED')
+UNKNOWN = _misc.Labeled('UNKNOWN')
+
+
+# XXX Use known.tsv for these?
+SYSTEM_TYPES = {
+ 'int8_t',
+ 'uint8_t',
+ 'int16_t',
+ 'uint16_t',
+ 'int32_t',
+ 'uint32_t',
+ 'int64_t',
+ 'uint64_t',
+ 'size_t',
+ 'ssize_t',
+ 'intptr_t',
+ 'uintptr_t',
+ 'wchar_t',
+ '',
+ # OS-specific
+ 'pthread_cond_t',
+ 'pthread_mutex_t',
+ 'pthread_key_t',
+ 'atomic_int',
+ 'atomic_uintptr_t',
+ '',
+ # lib-specific
+ 'WINDOW', # curses
+ 'XML_LChar',
+ 'XML_Size',
+ 'XML_Parser',
+ 'enum XML_Error',
+ 'enum XML_Status',
+ '',
+}
+
+
+def is_system_type(typespec):
+ return typespec in SYSTEM_TYPES
+
+
+class SystemType(TypeDeclaration):
+
+ def __init__(self, name):
+ super().__init__(None, name, None, None, _shortkey=name)
+
+
+class Analyzed:
+ _locked = False
+
+ @classonly
+ def is_target(cls, raw):
+ if isinstance(raw, HighlevelParsedItem):
+ return True
+ else:
+ return False
+
+ @classonly
+ def from_raw(cls, raw, **extra):
+ if isinstance(raw, cls):
+ if extra:
+ # XXX ?
+ raise NotImplementedError((raw, extra))
+ #return cls(raw.item, raw.typedecl, **raw._extra, **extra)
+ else:
+ return info
+ elif cls.is_target(raw):
+ return cls(raw, **extra)
+ else:
+ raise NotImplementedError((raw, extra))
+
+ @classonly
+ def from_resolved(cls, item, resolved, **extra):
+ if isinstance(resolved, TypeDeclaration):
+ return cls(item, typedecl=resolved, **extra)
+ else:
+ typedeps, extra = cls._parse_raw_resolved(item, resolved, extra)
+ if item.kind is KIND.ENUM:
+ if typedeps:
+ raise NotImplementedError((item, resolved, extra))
+ elif not typedeps:
+ raise NotImplementedError((item, resolved, extra))
+ return cls(item, typedeps, **extra or {})
+
+ @classonly
+ def _parse_raw_resolved(cls, item, resolved, extra_extra):
+ if resolved in (UNKNOWN, IGNORED):
+ return resolved, None
+ try:
+ typedeps, extra = resolved
+ except (TypeError, ValueError):
+ typedeps = extra = None
+ if extra:
+ # The resolved data takes precedence.
+ extra = dict(extra_extra, **extra)
+ if isinstance(typedeps, TypeDeclaration):
+ return typedeps, extra
+ elif typedeps in (None, UNKNOWN):
+ # It is still effectively unresolved.
+ return UNKNOWN, extra
+ elif None in typedeps or UNKNOWN in typedeps:
+ # It is still effectively unresolved.
+ return typedeps, extra
+ elif any(not isinstance(td, TypeDeclaration) for td in typedeps):
+ raise NotImplementedError((item, typedeps, extra))
+ return typedeps, extra
+
+ def __init__(self, item, typedecl=None, **extra):
+ assert item is not None
+ self.item = item
+ if typedecl in (UNKNOWN, IGNORED):
+ pass
+ elif item.kind is KIND.STRUCT or item.kind is KIND.UNION:
+ if isinstance(typedecl, TypeDeclaration):
+ raise NotImplementedError(item, typedecl)
+ elif typedecl is None:
+ typedecl = UNKNOWN
+ else:
+ typedecl = [UNKNOWN if d is None else d for d in typedecl]
+ elif typedecl is None:
+ typedecl = UNKNOWN
+ elif typedecl and not isinstance(typedecl, TypeDeclaration):
+ # All the other decls have a single type decl.
+ typedecl, = typedecl
+ if typedecl is None:
+ typedecl = UNKNOWN
+ self.typedecl = typedecl
+ self._extra = extra
+ self._locked = True
+
+ self._validate()
+
+ def _validate(self):
+ item = self.item
+ extra = self._extra
+ # Check item.
+ if not isinstance(item, HighlevelParsedItem):
+ raise ValueError(f'"item" must be a high-level parsed item, got {item!r}')
+ # Check extra.
+ for key, value in extra.items():
+ if key.startswith('_'):
+ raise ValueError(f'extra items starting with {"_"!r} not allowed, got {extra!r}')
+ if hasattr(item, key) and not callable(getattr(item, key)):
+ raise ValueError(f'extra cannot override item, got {value!r} for key {key!r}')
+
+ def __repr__(self):
+ kwargs = [
+ f'item={self.item!r}',
+ f'typedecl={self.typedecl!r}',
+ *(f'{k}={v!r}' for k, v in self._extra.items())
+ ]
+ return f'{type(self).__name__}({", ".join(kwargs)})'
+
+ def __str__(self):
+ try:
+ return self._str
+ except AttributeError:
+ self._str, = self.render('line')
+ return self._str
+
+ def __hash__(self):
+ return hash(self.item)
+
+ def __eq__(self, other):
+ if isinstance(other, Analyzed):
+ return self.item == other.item
+ elif isinstance(other, HighlevelParsedItem):
+ return self.item == other
+ elif type(other) is tuple:
+ return self.item == other
+ else:
+ return NotImplemented
+
+ def __gt__(self, other):
+ if isinstance(other, Analyzed):
+ return self.item > other.item
+ elif isinstance(other, HighlevelParsedItem):
+ return self.item > other
+ elif type(other) is tuple:
+ return self.item > other
+ else:
+ return NotImplemented
+
+ def __dir__(self):
+ names = set(super().__dir__())
+ names.update(self._extra)
+ names.remove('_locked')
+ return sorted(names)
+
+ def __getattr__(self, name):
+ if name.startswith('_'):
+ raise AttributeError(name)
+ # The item takes precedence over the extra data (except if callable).
+ try:
+ value = getattr(self.item, name)
+ if callable(value):
+ raise AttributeError(name)
+ except AttributeError:
+ try:
+ value = self._extra[name]
+ except KeyError:
+ pass
+ else:
+ # Speed things up the next time.
+ self.__dict__[name] = value
+ return value
+ raise # re-raise
+ else:
+ return value
+
+ def __setattr__(self, name, value):
+ if self._locked and name != '_str':
+ raise AttributeError(f'readonly ({name})')
+ super().__setattr__(name, value)
+
+ def __delattr__(self, name):
+ if self._locked:
+ raise AttributeError(f'readonly ({name})')
+ super().__delattr__(name)
+
+ @property
+ def decl(self):
+ if not isinstance(self.item, Declaration):
+ raise AttributeError('decl')
+ return self.item
+
+ @property
+ def signature(self):
+ # XXX vartype...
+ ...
+
+ @property
+ def istype(self):
+ return is_type_decl(self.item.kind)
+
+ @property
+ def is_known(self):
+ if self.typedecl in (UNKNOWN, IGNORED):
+ return False
+ elif isinstance(self.typedecl, TypeDeclaration):
+ return True
+ else:
+ return UNKNOWN not in self.typedecl
+
+ def fix_filename(self, relroot):
+ self.item.fix_filename(relroot)
+
+ def as_rowdata(self, columns=None):
+ # XXX finsih!
+ return self.item.as_rowdata(columns)
+
+ def render_rowdata(self, columns=None):
+ # XXX finsih!
+ return self.item.render_rowdata(columns)
+
+ def render(self, fmt='line', *, itemonly=False):
+ if fmt == 'raw':
+ yield repr(self)
+ return
+ rendered = self.item.render(fmt)
+ if itemonly or not self._extra:
+ yield from rendered
+ return
+ extra = self._render_extra(fmt)
+ if not extra:
+ yield from rendered
+ elif fmt in ('brief', 'line'):
+ rendered, = rendered
+ extra, = extra
+ yield f'{rendered}\t{extra}'
+ elif fmt == 'summary':
+ raise NotImplementedError(fmt)
+ elif fmt == 'full':
+ yield from rendered
+ for line in extra:
+ yield f'\t{line}'
+ else:
+ raise NotImplementedError(fmt)
+
+ def _render_extra(self, fmt):
+ if fmt in ('brief', 'line'):
+ yield str(self._extra)
+ else:
+ raise NotImplementedError(fmt)
+
+
+class Analysis:
+
+ _item_class = Analyzed
+
+ @classonly
+ def build_item(cls, info, resolved=None, **extra):
+ if resolved is None:
+ return cls._item_class.from_raw(info, **extra)
+ else:
+ return cls._item_class.from_resolved(info, resolved, **extra)
+
+ @classmethod
+ def from_results(cls, results):
+ self = cls()
+ for info, resolved in results:
+ self._add_result(info, resolved)
+ return self
+
+ def __init__(self, items=None):
+ self._analyzed = {type(self).build_item(item): None
+ for item in items or ()}
+
+ def __repr__(self):
+ return f'{type(self).__name__}({list(self._analyzed.keys())})'
+
+ def __iter__(self):
+ #yield from self.types
+ #yield from self.functions
+ #yield from self.variables
+ yield from self._analyzed
+
+ def __len__(self):
+ return len(self._analyzed)
+
+ def __getitem__(self, key):
+ if type(key) is int:
+ for i, val in enumerate(self._analyzed):
+ if i == key:
+ return val
+ else:
+ raise IndexError(key)
+ else:
+ return self._analyzed[key]
+
+ def fix_filenames(self, relroot):
+ for item in self._analyzed:
+ item.fix_filename(relroot)
+
+ def _add_result(self, info, resolved):
+ analyzed = type(self).build_item(info, resolved)
+ self._analyzed[analyzed] = None
+ return analyzed