diff options
Diffstat (limited to 'Tools/c-analyzer/cpython/_analyzer.py')
-rw-r--r-- | Tools/c-analyzer/cpython/_analyzer.py | 348 |
1 files changed, 348 insertions, 0 deletions
diff --git a/Tools/c-analyzer/cpython/_analyzer.py b/Tools/c-analyzer/cpython/_analyzer.py new file mode 100644 index 00000000000..98f8888651e --- /dev/null +++ b/Tools/c-analyzer/cpython/_analyzer.py @@ -0,0 +1,348 @@ +import os.path +import re + +from c_common.clsutil import classonly +from c_parser.info import ( + KIND, + DeclID, + Declaration, + TypeDeclaration, + TypeDef, + Struct, + Member, + FIXED_TYPE, + is_type_decl, + is_pots, + is_funcptr, + is_process_global, + is_fixed_type, + is_immutable, +) +import c_analyzer as _c_analyzer +import c_analyzer.info as _info +import c_analyzer.datafiles as _datafiles +from . import _parser, REPO_ROOT + + +_DATA_DIR = os.path.dirname(__file__) +KNOWN_FILE = os.path.join(_DATA_DIR, 'known.tsv') +IGNORED_FILE = os.path.join(_DATA_DIR, 'ignored.tsv') +KNOWN_IN_DOT_C = { + 'struct _odictobject': False, + 'PyTupleObject': False, + 'struct _typeobject': False, + 'struct _arena': True, # ??? + 'struct _frame': False, + 'struct _ts': True, # ??? + 'struct PyCodeObject': False, + 'struct _is': True, # ??? + 'PyWideStringList': True, # ??? + # recursive + 'struct _dictkeysobject': False, +} +# These are loaded from the respective .tsv files upon first use. +_KNOWN = { + # {(file, ID) | ID => info | bool} + #'PyWideStringList': True, +} +#_KNOWN = {(Struct(None, typeid.partition(' ')[-1], None) +# if typeid.startswith('struct ') +# else TypeDef(None, typeid, None) +# ): ([], {'unsupported': None if supported else True}) +# for typeid, supported in _KNOWN_IN_DOT_C.items()} +_IGNORED = { + # {ID => reason} +} + +KINDS = frozenset((*KIND.TYPES, KIND.VARIABLE)) + + +def read_known(): + if not _KNOWN: + # Cache a copy the first time. + extracols = None # XXX + #extracols = ['unsupported'] + known = _datafiles.read_known(KNOWN_FILE, extracols, REPO_ROOT) + # For now we ignore known.values() (i.e. "extra"). + types, _ = _datafiles.analyze_known( + known, + analyze_resolved=analyze_resolved, + ) + _KNOWN.update(types) + return _KNOWN.copy() + + +def write_known(): + raise NotImplementedError + datafiles.write_known(decls, IGNORED_FILE, ['unsupported'], relroot=REPO_ROOT) + + +def read_ignored(): + if not _IGNORED: + _IGNORED.update(_datafiles.read_ignored(IGNORED_FILE)) + return dict(_IGNORED) + + +def write_ignored(): + raise NotImplementedError + datafiles.write_ignored(variables, IGNORED_FILE) + + +def analyze(filenames, *, + skip_objects=False, + **kwargs + ): + if skip_objects: + # XXX Set up a filter. + raise NotImplementedError + + known = read_known() + + decls = iter_decls(filenames) + results = _c_analyzer.analyze_decls( + decls, + known, + analyze_resolved=analyze_resolved, + ) + analysis = Analysis.from_results(results) + + return analysis + + +def iter_decls(filenames, **kwargs): + decls = _c_analyzer.iter_decls( + filenames, + # We ignore functions (and statements). + kinds=KINDS, + parse_files=_parser.parse_files, + **kwargs + ) + for decl in decls: + if not decl.data: + # Ignore forward declarations. + continue + yield decl + + +def analyze_resolved(resolved, decl, types, knowntypes, extra=None): + if decl.kind not in KINDS: + # Skip it! + return None + + typedeps = resolved + if typedeps is _info.UNKNOWN: + if decl.kind in (KIND.STRUCT, KIND.UNION): + typedeps = [typedeps] * len(decl.members) + else: + typedeps = [typedeps] + #assert isinstance(typedeps, (list, TypeDeclaration)), typedeps + + if extra is None: + extra = {} + elif 'unsupported' in extra: + raise NotImplementedError((decl, extra)) + + unsupported = _check_unsupported(decl, typedeps, types, knowntypes) + extra['unsupported'] = unsupported + + return typedeps, extra + + +def _check_unsupported(decl, typedeps, types, knowntypes): + if typedeps is None: + raise NotImplementedError(decl) + + if decl.kind in (KIND.STRUCT, KIND.UNION): + return _check_members(decl, typedeps, types, knowntypes) + elif decl.kind is KIND.ENUM: + if typedeps: + raise NotImplementedError((decl, typedeps)) + return None + else: + return _check_typedep(decl, typedeps, types, knowntypes) + + +def _check_members(decl, typedeps, types, knowntypes): + if isinstance(typedeps, TypeDeclaration): + raise NotImplementedError((decl, typedeps)) + + #members = decl.members or () # A forward decl has no members. + members = decl.members + if not members: + # A forward decl has no members, but that shouldn't surface here.. + raise NotImplementedError(decl) + if len(members) != len(typedeps): + raise NotImplementedError((decl, typedeps)) + + unsupported = [] + for member, typedecl in zip(members, typedeps): + checked = _check_typedep(member, typedecl, types, knowntypes) + unsupported.append(checked) + if any(None if v is FIXED_TYPE else v for v in unsupported): + return unsupported + elif FIXED_TYPE in unsupported: + return FIXED_TYPE + else: + return None + + +def _check_typedep(decl, typedecl, types, knowntypes): + if not isinstance(typedecl, TypeDeclaration): + if hasattr(type(typedecl), '__len__'): + if len(typedecl) == 1: + typedecl, = typedecl + if typedecl is None: + # XXX Fail? + return 'typespec (missing)' + elif typedecl is _info.UNKNOWN: + # XXX Is this right? + return 'typespec (unknown)' + elif not isinstance(typedecl, TypeDeclaration): + raise NotImplementedError((decl, typedecl)) + + if isinstance(decl, Member): + return _check_vartype(decl, typedecl, types, knowntypes) + elif not isinstance(decl, Declaration): + raise NotImplementedError(decl) + elif decl.kind is KIND.TYPEDEF: + return _check_vartype(decl, typedecl, types, knowntypes) + elif decl.kind is KIND.VARIABLE: + if not is_process_global(decl): + return None + checked = _check_vartype(decl, typedecl, types, knowntypes) + return 'mutable' if checked is FIXED_TYPE else checked + else: + raise NotImplementedError(decl) + + +def _check_vartype(decl, typedecl, types, knowntypes): + """Return failure reason.""" + checked = _check_typespec(decl, typedecl, types, knowntypes) + if checked: + return checked + if is_immutable(decl.vartype): + return None + if is_fixed_type(decl.vartype): + return FIXED_TYPE + return 'mutable' + + +def _check_typespec(decl, typedecl, types, knowntypes): + typespec = decl.vartype.typespec + if typedecl is not None: + found = types.get(typedecl) + if found is None: + found = knowntypes.get(typedecl) + + if found is not None: + _, extra = found + if extra is None: + # XXX Under what circumstances does this happen? + extra = {} + unsupported = extra.get('unsupported') + if unsupported is FIXED_TYPE: + unsupported = None + return 'typespec' if unsupported else None + # Fall back to default known types. + if is_pots(typespec): + return None + elif _info.is_system_type(typespec): + return None + elif is_funcptr(decl.vartype): + return None + return 'typespec' + + +class Analyzed(_info.Analyzed): + + @classonly + def is_target(cls, raw): + if not super().is_target(raw): + return False + if raw.kind not in KINDS: + return False + return True + + #@classonly + #def _parse_raw_result(cls, result, extra): + # typedecl, extra = super()._parse_raw_result(result, extra) + # if typedecl is None: + # return None, extra + # raise NotImplementedError + + def __init__(self, item, typedecl=None, *, unsupported=None, **extra): + if 'unsupported' in extra: + raise NotImplementedError((item, typedecl, unsupported, extra)) + if not unsupported: + unsupported = None + elif isinstance(unsupported, (str, TypeDeclaration)): + unsupported = (unsupported,) + elif unsupported is not FIXED_TYPE: + unsupported = tuple(unsupported) + self.unsupported = unsupported + extra['unsupported'] = self.unsupported # ...for __repr__(), etc. + if self.unsupported is None: + #self.supported = None + self.supported = True + elif self.unsupported is FIXED_TYPE: + if item.kind is KIND.VARIABLE: + raise NotImplementedError(item, typedecl, unsupported) + self.supported = True + else: + self.supported = not self.unsupported + super().__init__(item, typedecl, **extra) + + def render(self, fmt='line', *, itemonly=False): + if fmt == 'raw': + yield repr(self) + return + rendered = super().render(fmt, itemonly=itemonly) + # XXX ??? + #if itemonly: + # yield from rendered + supported = self._supported + if fmt in ('line', 'brief'): + rendered, = rendered + parts = [ + '+' if supported else '-' if supported is False else '', + rendered, + ] + yield '\t'.join(parts) + elif fmt == 'summary': + raise NotImplementedError(fmt) + elif fmt == 'full': + yield from rendered + if supported: + yield f'\tsupported:\t{supported}' + else: + raise NotImplementedError(fmt) + + +class Analysis(_info.Analysis): + _item_class = Analyzed + + @classonly + def build_item(cls, info, result=None): + if not isinstance(info, Declaration) or info.kind not in KINDS: + raise NotImplementedError((info, result)) + return super().build_item(info, result) + + +def check_globals(analysis): + # yield (data, failure) + ignored = read_ignored() + for item in analysis: + if item.kind != KIND.VARIABLE: + continue + if item.supported: + continue + if item.id in ignored: + continue + reason = item.unsupported + if not reason: + reason = '???' + elif not isinstance(reason, str): + if len(reason) == 1: + reason, = reason + reason = f'({reason})' + yield item, f'not supported {reason:20}\t{item.storage or ""} {item.vartype}' |