diff options
Diffstat (limited to 'Tools/c-analyzer/c_analyzer/analyze.py')
-rw-r--r-- | Tools/c-analyzer/c_analyzer/analyze.py | 307 |
1 files changed, 307 insertions, 0 deletions
diff --git a/Tools/c-analyzer/c_analyzer/analyze.py b/Tools/c-analyzer/c_analyzer/analyze.py new file mode 100644 index 0000000000..d8ae915e42 --- /dev/null +++ b/Tools/c-analyzer/c_analyzer/analyze.py @@ -0,0 +1,307 @@ +from c_parser.info import ( + KIND, + TypeDeclaration, + POTSType, + FuncPtr, + is_pots, + is_funcptr, +) +from .info import ( + IGNORED, + UNKNOWN, + is_system_type, + SystemType, +) + + +def get_typespecs(typedecls): + typespecs = {} + for decl in typedecls: + if decl.shortkey not in typespecs: + typespecs[decl.shortkey] = [decl] + else: + typespecs[decl.shortkey].append(decl) + return typespecs + + +def analyze_decl(decl, typespecs, knowntypespecs, types, knowntypes, *, + analyze_resolved=None): + resolved = resolve_decl(decl, typespecs, knowntypespecs, types) + if resolved is None: + # The decl is supposed to be skipped or ignored. + return None + if analyze_resolved is None: + return resolved, None + return analyze_resolved(resolved, decl, types, knowntypes) + +# This alias helps us avoid name collisions. +_analyze_decl = analyze_decl + + +def analyze_type_decls(types, analyze_decl, handle_unresolved=True): + unresolved = set(types) + while unresolved: + updated = [] + for decl in unresolved: + resolved = analyze_decl(decl) + if resolved is None: + # The decl should be skipped or ignored. + types[decl] = IGNORED + updated.append(decl) + continue + typedeps, _ = resolved + if typedeps is None: + raise NotImplementedError(decl) + if UNKNOWN in typedeps: + # At least one dependency is unknown, so this decl + # is not resolvable. + types[decl] = UNKNOWN + updated.append(decl) + continue + if None in typedeps: + # XXX + # Handle direct recursive types first. + nonrecursive = 1 + if decl.kind is KIND.STRUCT or decl.kind is KIND.UNION: + nonrecursive = 0 + i = 0 + for member, dep in zip(decl.members, typedeps): + if dep is None: + if member.vartype.typespec != decl.shortkey: + nonrecursive += 1 + else: + typedeps[i] = decl + i += 1 + if nonrecursive: + # We don't have all dependencies resolved yet. + continue + types[decl] = resolved + updated.append(decl) + if updated: + for decl in updated: + unresolved.remove(decl) + else: + # XXX + # Handle indirect recursive types. + ... + # We couldn't resolve the rest. + # Let the caller deal with it! + break + if unresolved and handle_unresolved: + if handle_unresolved is True: + handle_unresolved = _handle_unresolved + handle_unresolved(unresolved, types, analyze_decl) + + +def resolve_decl(decl, typespecs, knowntypespecs, types): + if decl.kind is KIND.ENUM: + typedeps = [] + else: + if decl.kind is KIND.VARIABLE: + vartypes = [decl.vartype] + elif decl.kind is KIND.FUNCTION: + vartypes = [decl.signature.returntype] + elif decl.kind is KIND.TYPEDEF: + vartypes = [decl.vartype] + elif decl.kind is KIND.STRUCT or decl.kind is KIND.UNION: + vartypes = [m.vartype for m in decl.members] + else: + # Skip this one! + return None + + typedeps = [] + for vartype in vartypes: + typespec = vartype.typespec + if is_pots(typespec): + typedecl = POTSType(typespec) + elif is_system_type(typespec): + typedecl = SystemType(typespec) + elif is_funcptr(vartype): + typedecl = FuncPtr(vartype) + else: + typedecl = find_typedecl(decl, typespec, typespecs) + if typedecl is None: + typedecl = find_typedecl(decl, typespec, knowntypespecs) + elif not isinstance(typedecl, TypeDeclaration): + raise NotImplementedError(repr(typedecl)) + if typedecl is None: + # We couldn't find it! + typedecl = UNKNOWN + elif typedecl not in types: + # XXX How can this happen? + typedecl = UNKNOWN + elif types[typedecl] is UNKNOWN: + typedecl = UNKNOWN + elif types[typedecl] is IGNORED: + # We don't care if it didn't resolve. + pass + elif types[typedecl] is None: + # The typedecl for the typespec hasn't been resolved yet. + typedecl = None + typedeps.append(typedecl) + return typedeps + + +def find_typedecl(decl, typespec, typespecs): + specdecls = typespecs.get(typespec) + if not specdecls: + return None + + filename = decl.filename + + if len(specdecls) == 1: + typedecl, = specdecls + if '-' in typespec and typedecl.filename != filename: + # Inlined types are always in the same file. + return None + return typedecl + + # Decide which one to return. + candidates = [] + samefile = None + for typedecl in specdecls: + type_filename = typedecl.filename + if type_filename == filename: + if samefile is not None: + # We expect type names to be unique in a file. + raise NotImplementedError((decl, samefile, typedecl)) + samefile = typedecl + elif filename.endswith('.c') and not type_filename.endswith('.h'): + # If the decl is in a source file then we expect the + # type to be in the same file or in a header file. + continue + candidates.append(typedecl) + if not candidates: + return None + elif len(candidates) == 1: + winner, = candidates + # XXX Check for inline? + elif '-' in typespec: + # Inlined types are always in the same file. + winner = samefile + elif samefile is not None: + # Favor types in the same file. + winner = samefile + else: + # We don't know which to return. + raise NotImplementedError((decl, candidates)) + + return winner + + +############################# +# handling unresolved decls + +class Skipped(TypeDeclaration): + def __init__(self): + _file = _name = _data = _parent = None + super().__init__(_file, _name, _data, _parent, _shortkey='<skipped>') +_SKIPPED = Skipped() +del Skipped + + +def _handle_unresolved(unresolved, types, analyze_decl): + #raise NotImplementedError(unresolved) + + dump = True + dump = False + if dump: + print() + for decl in types: # Preserve the original order. + if decl not in unresolved: + assert types[decl] is not None, decl + if types[decl] in (UNKNOWN, IGNORED): + unresolved.add(decl) + if dump: + _dump_unresolved(decl, types, analyze_decl) + print() + else: + assert types[decl][0] is not None, (decl, types[decl]) + assert None not in types[decl][0], (decl, types[decl]) + else: + assert types[decl] is None + if dump: + _dump_unresolved(decl, types, analyze_decl) + print() + #raise NotImplementedError + + for decl in unresolved: + types[decl] = ([_SKIPPED], None) + + for decl in types: + assert types[decl] + + +def _dump_unresolved(decl, types, analyze_decl): + if isinstance(decl, str): + typespec = decl + decl, = (d for d in types if d.shortkey == typespec) + elif type(decl) is tuple: + filename, typespec = decl + if '-' in typespec: + found = [d for d in types + if d.shortkey == typespec and d.filename == filename] + #if not found: + # raise NotImplementedError(decl) + decl, = found + else: + found = [d for d in types if d.shortkey == typespec] + if not found: + print(f'*** {typespec} ???') + return + #raise NotImplementedError(decl) + else: + decl, = found + resolved = analyze_decl(decl) + if resolved: + typedeps, _ = resolved or (None, None) + + if decl.kind is KIND.STRUCT or decl.kind is KIND.UNION: + print(f'*** {decl.shortkey} {decl.filename}') + for member, mtype in zip(decl.members, typedeps): + typespec = member.vartype.typespec + if typespec == decl.shortkey: + print(f' ~~~~: {typespec:20} - {member!r}') + continue + status = None + if is_pots(typespec): + mtype = typespec + status = 'okay' + elif is_system_type(typespec): + mtype = typespec + status = 'okay' + elif mtype is None: + if '-' in member.vartype.typespec: + mtype, = [d for d in types + if d.shortkey == member.vartype.typespec + and d.filename == decl.filename] + else: + found = [d for d in types + if d.shortkey == typespec] + if not found: + print(f' ???: {typespec:20}') + continue + mtype, = found + if status is None: + status = 'okay' if types.get(mtype) else 'oops' + if mtype is _SKIPPED: + status = 'okay' + mtype = '<skipped>' + elif isinstance(mtype, FuncPtr): + status = 'okay' + mtype = str(mtype.vartype) + elif not isinstance(mtype, str): + if hasattr(mtype, 'vartype'): + if is_funcptr(mtype.vartype): + status = 'okay' + mtype = str(mtype).rpartition('(')[0].rstrip() + status = ' okay' if status == 'okay' else f'--> {status}' + print(f' {status}: {typespec:20} - {member!r} ({mtype})') + else: + print(f'*** {decl} ({decl.vartype!r})') + if decl.vartype.typespec.startswith('struct ') or is_funcptr(decl): + _dump_unresolved( + (decl.filename, decl.vartype.typespec), + types, + analyze_decl, + ) |