diff options
Diffstat (limited to 'Tools/c-analyzer/c_analyzer/info.py')
-rw-r--r-- | Tools/c-analyzer/c_analyzer/info.py | 353 |
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 |