cpython/Tools/c-analyzer/c_analyzer/info.py

325 lines
9.7 KiB
Python

from collections import namedtuple
import os.path
from c_common import fsutil
from c_common.clsutil import classonly
import c_common.misc as _misc
from c_parser.info import (
KIND,
HighlevelParsedItem,
Declaration,
TypeDeclaration,
)
from c_parser.match import (
is_type_decl,
)
from .match import (
is_process_global,
)
IGNORED = _misc.Labeled('IGNORED')
UNKNOWN = _misc.Labeled('UNKNOWN')
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=fsutil.USE_CWD, **kwargs):
self.item.fix_filename(relroot, **kwargs)
return self
def as_rowdata(self, columns=None):
# XXX finish!
return self.item.as_rowdata(columns)
def render_rowdata(self, columns=None):
# XXX finish!
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=fsutil.USE_CWD, **kwargs):
if relroot and relroot is not fsutil.USE_CWD:
relroot = os.path.abspath(relroot)
for item in self._analyzed:
item.fix_filename(relroot, fixroot=False, **kwargs)
def _add_result(self, info, resolved):
analyzed = type(self).build_item(info, resolved)
self._analyzed[analyzed] = None
return analyzed