| # Copyright (C) 2020 Red Hat Inc. |
| # |
| # Authors: |
| # Eduardo Habkost <ehabkost@redhat.com> |
| # |
| # This work is licensed under the terms of the GNU GPL, version 2. See |
| # the COPYING file in the top-level directory. |
| import re |
| from itertools import chain |
| from typing import * |
| |
| from .regexps import * |
| from .patching import * |
| from .utils import * |
| |
| import logging |
| logger = logging.getLogger(__name__) |
| DBG = logger.debug |
| INFO = logger.info |
| WARN = logger.warning |
| |
| # simple expressions: |
| |
| RE_CONSTANT = OR(RE_STRING, RE_NUMBER) |
| |
| class DefineDirective(FileMatch): |
| """Match any #define directive""" |
| regexp = S(r'^[ \t]*#[ \t]*define', CPP_SPACE, NAMED('name', RE_IDENTIFIER), r'\b') |
| |
| class ExpressionDefine(FileMatch): |
| """Simple #define preprocessor directive for an expression""" |
| regexp = S(r'^[ \t]*#[ \t]*define', CPP_SPACE, NAMED('name', RE_IDENTIFIER), |
| CPP_SPACE, NAMED('value', RE_EXPRESSION), r'[ \t]*\n') |
| |
| def provided_identifiers(self) -> Iterable[RequiredIdentifier]: |
| yield RequiredIdentifier('constant', self.group('name')) |
| |
| class ConstantDefine(ExpressionDefine): |
| """Simple #define preprocessor directive for a number or string constant""" |
| regexp = S(r'^[ \t]*#[ \t]*define', CPP_SPACE, NAMED('name', RE_IDENTIFIER), |
| CPP_SPACE, NAMED('value', RE_CONSTANT), r'[ \t]*\n') |
| |
| |
| class TypeIdentifiers(NamedTuple): |
| """Type names found in type declarations""" |
| # TYPE_MYDEVICE |
| typename: Optional[str] |
| # MYDEVICE |
| uppercase: Optional[str] = None |
| # MyDevice |
| instancetype: Optional[str] = None |
| # MyDeviceClass |
| classtype: Optional[str] = None |
| # my_device |
| lowercase: Optional[str] = None |
| |
| def allfields(self): |
| return tuple(getattr(self, f) for f in self._fields) |
| |
| def merge(self, other: 'TypeIdentifiers') -> Optional['TypeIdentifiers']: |
| """Check if identifiers match, return new identifier with complete list""" |
| if any(not opt_compare(a, b) for a,b in zip(self, other)): |
| return None |
| return TypeIdentifiers(*(merge(a, b) for a,b in zip(self, other))) |
| |
| def __str__(self) -> str: |
| values = ((f, getattr(self, f)) for f in self._fields) |
| s = ', '.join('%s=%s' % (f,v) for f,v in values if v is not None) |
| return f'{s}' |
| |
| def check_consistency(self) -> List[str]: |
| """Check if identifiers are consistent with each other, |
| return list of problems (or empty list if everything seems consistent) |
| """ |
| r = [] |
| if self.typename is None: |
| r.append("typename (TYPE_MYDEVICE) is unavailable") |
| |
| if self.uppercase is None: |
| r.append("uppercase name is unavailable") |
| |
| if (self.instancetype is not None |
| and self.classtype is not None |
| and self.classtype != f'{self.instancetype}Class'): |
| r.append("class typedef %s doesn't match instance typedef %s" % |
| (self.classtype, self.instancetype)) |
| |
| if (self.uppercase is not None |
| and self.typename is not None |
| and f'TYPE_{self.uppercase}' != self.typename): |
| r.append("uppercase name (%s) doesn't match type name (%s)" % |
| (self.uppercase, self.typename)) |
| |
| return r |
| |
| class TypedefMatch(FileMatch): |
| """typedef declaration""" |
| def provided_identifiers(self) -> Iterable[RequiredIdentifier]: |
| yield RequiredIdentifier('type', self.group('name')) |
| |
| class SimpleTypedefMatch(TypedefMatch): |
| """Simple typedef declaration |
| (no replacement rules)""" |
| regexp = S(r'^[ \t]*typedef', SP, |
| NAMED('typedef_type', RE_TYPE), SP, |
| NAMED('name', RE_IDENTIFIER), r'\s*;[ \t]*\n') |
| |
| RE_MACRO_DEFINE = S(r'^[ \t]*#\s*define\s+', NAMED('name', RE_IDENTIFIER), |
| r'\s*\(\s*', RE_IDENTIFIER, r'\s*\)', CPP_SPACE) |
| |
| RE_STRUCT_ATTRIBUTE = r'QEMU_PACKED' |
| |
| # This doesn't parse the struct definitions completely, it just assumes |
| # the closing brackets are going to be in an unindented line: |
| RE_FULL_STRUCT = S('struct', SP, M(RE_IDENTIFIER, n='?', name='structname'), SP, |
| NAMED('body', r'{\n', |
| # acceptable inside the struct body: |
| # - lines starting with space or tab |
| # - empty lines |
| # - preprocessor directives |
| # - comments |
| OR(r'[ \t][^\n]*\n', |
| r'#[^\n]*\n', |
| r'\n', |
| S(r'[ \t]*', RE_COMMENT, r'[ \t]*\n'), |
| repeat='*?'), |
| r'}', M(RE_STRUCT_ATTRIBUTE, SP, n='*'))) |
| RE_STRUCT_TYPEDEF = S(r'^[ \t]*typedef', SP, RE_FULL_STRUCT, SP, |
| NAMED('name', RE_IDENTIFIER), r'\s*;[ \t]*\n') |
| |
| class FullStructTypedefMatch(TypedefMatch): |
| """typedef struct [SomeStruct] { ...} SomeType |
| Will be replaced by separate struct declaration + typedef |
| """ |
| regexp = RE_STRUCT_TYPEDEF |
| |
| def make_structname(self) -> str: |
| """Make struct name for struct+typedef split""" |
| name = self.group('structname') |
| if not name: |
| name = self.name |
| return name |
| |
| def strip_typedef(self) -> Patch: |
| """generate patch that will strip typedef from the struct declaration |
| |
| The caller is responsible for readding the typedef somewhere else. |
| """ |
| name = self.make_structname() |
| body = self.group('body') |
| return self.make_patch(f'struct {name} {body};\n') |
| |
| def make_simple_typedef(self) -> str: |
| structname = self.make_structname() |
| name = self.name |
| return f'typedef struct {structname} {name};\n' |
| |
| def move_typedef(self, position) -> Iterator[Patch]: |
| """Generate patches to move typedef elsewhere""" |
| yield self.strip_typedef() |
| yield Patch(position, position, self.make_simple_typedef()) |
| |
| def split_typedef(self) -> Iterator[Patch]: |
| """Split into struct definition + typedef in-place""" |
| yield self.strip_typedef() |
| yield self.append(self.make_simple_typedef()) |
| |
| class StructTypedefSplit(FullStructTypedefMatch): |
| """split struct+typedef declaration""" |
| def gen_patches(self) -> Iterator[Patch]: |
| if self.group('structname'): |
| yield from self.split_typedef() |
| |
| class DuplicatedTypedefs(SimpleTypedefMatch): |
| """Delete ALL duplicate typedefs (unsafe)""" |
| def gen_patches(self) -> Iterable[Patch]: |
| other_td = [td for td in chain(self.file.matches_of_type(SimpleTypedefMatch), |
| self.file.matches_of_type(FullStructTypedefMatch)) |
| if td.name == self.name] |
| DBG("other_td: %r", other_td) |
| if any(td.start() < self.start() for td in other_td): |
| # patch only if handling the first typedef |
| return |
| for td in other_td: |
| if isinstance(td, SimpleTypedefMatch): |
| DBG("other td: %r", td.match.groupdict()) |
| if td.group('typedef_type') != self.group('typedef_type'): |
| yield td.make_removal_patch() |
| elif isinstance(td, FullStructTypedefMatch): |
| DBG("other td: %r", td.match.groupdict()) |
| if self.group('typedef_type') == 'struct '+td.group('structname'): |
| yield td.strip_typedef() |
| |
| class QOMDuplicatedTypedefs(DuplicatedTypedefs): |
| """Delete duplicate typedefs if used by QOM type""" |
| def gen_patches(self) -> Iterable[Patch]: |
| qom_macros = [TypeCheckMacro, DeclareInstanceChecker, DeclareClassCheckers, DeclareObjCheckers] |
| qom_matches = chain(*(self.file.matches_of_type(t) for t in qom_macros)) |
| in_use = any(RequiredIdentifier('type', self.name) in m.required_identifiers() |
| for m in qom_matches) |
| if in_use: |
| yield from DuplicatedTypedefs.gen_patches(self) |
| |
| class QOMStructTypedefSplit(FullStructTypedefMatch): |
| """split struct+typedef declaration if used by QOM type""" |
| def gen_patches(self) -> Iterator[Patch]: |
| qom_macros = [TypeCheckMacro, DeclareInstanceChecker, DeclareClassCheckers, DeclareObjCheckers] |
| qom_matches = chain(*(self.file.matches_of_type(t) for t in qom_macros)) |
| in_use = any(RequiredIdentifier('type', self.name) in m.required_identifiers() |
| for m in qom_matches) |
| if in_use: |
| yield from self.split_typedef() |
| |
| def typedefs(file: FileInfo) -> Iterable[TypedefMatch]: |
| return (cast(TypedefMatch, m) |
| for m in chain(file.matches_of_type(SimpleTypedefMatch), |
| file.matches_of_type(FullStructTypedefMatch))) |
| |
| def find_typedef(f: FileInfo, name: Optional[str]) -> Optional[TypedefMatch]: |
| if not name: |
| return None |
| for td in typedefs(f): |
| if td.name == name: |
| return td |
| return None |
| |
| CHECKER_MACROS = ['OBJECT_CHECK', 'OBJECT_CLASS_CHECK', 'OBJECT_GET_CLASS'] |
| CheckerMacroName = Literal['OBJECT_CHECK', 'OBJECT_CLASS_CHECK', 'OBJECT_GET_CLASS'] |
| |
| RE_CHECK_MACRO = \ |
| S(RE_MACRO_DEFINE, |
| OR(*CHECKER_MACROS, name='checker'), |
| M(r'\s*\(\s*', OR(NAMED('typedefname', RE_IDENTIFIER), RE_TYPE, name='c_type'), r'\s*,', CPP_SPACE, |
| OPTIONAL_PARS(RE_IDENTIFIER), r',', CPP_SPACE, |
| NAMED('qom_typename', RE_IDENTIFIER), r'\s*\)\n', |
| n='?', name='check_args')) |
| |
| EXPECTED_CHECKER_SUFFIXES: List[Tuple[CheckerMacroName, str]] = [ |
| ('OBJECT_GET_CLASS', '_GET_CLASS'), |
| ('OBJECT_CLASS_CHECK', '_CLASS'), |
| ] |
| |
| class TypeCheckMacro(FileMatch): |
| """OBJECT_CHECK/OBJECT_CLASS_CHECK/OBJECT_GET_CLASS macro definitions |
| Will be replaced by DECLARE_*_CHECKERS macro |
| """ |
| regexp = RE_CHECK_MACRO |
| |
| @property |
| def checker(self) -> CheckerMacroName: |
| """Name of checker macro being used""" |
| return self.group('checker') # type: ignore |
| |
| @property |
| def typedefname(self) -> Optional[str]: |
| return self.group('typedefname') |
| |
| def find_typedef(self) -> Optional[TypedefMatch]: |
| return find_typedef(self.file, self.typedefname) |
| |
| def sanity_check(self) -> None: |
| DBG("groups: %r", self.match.groups()) |
| if not self.group('check_args'): |
| self.warn("type check macro not parsed completely: %s", self.name) |
| return |
| DBG("type identifiers: %r", self.type_identifiers) |
| if self.typedefname and self.find_typedef() is None: |
| self.warn("typedef used by %s not found", self.name) |
| |
| def find_matching_macros(self) -> List['TypeCheckMacro']: |
| """Find other check macros that generate the same macro names |
| |
| The returned list will always be sorted. |
| """ |
| my_ids = self.type_identifiers |
| assert my_ids |
| return [m for m in self.file.matches_of_type(TypeCheckMacro) |
| if m.type_identifiers is not None |
| and my_ids.uppercase is not None |
| and (my_ids.uppercase == m.type_identifiers.uppercase |
| or my_ids.typename == m.type_identifiers.typename)] |
| |
| def merge_ids(self, matches: List['TypeCheckMacro']) -> Optional[TypeIdentifiers]: |
| """Try to merge info about type identifiers from all matches in a list""" |
| if not matches: |
| return None |
| r = matches[0].type_identifiers |
| if r is None: |
| return None |
| for m in matches[1:]: |
| assert m.type_identifiers |
| new = r.merge(m.type_identifiers) |
| if new is None: |
| self.warn("macro %s identifiers (%s) don't match macro %s (%s)", |
| matches[0].name, r, m.name, m.type_identifiers) |
| return None |
| r = new |
| return r |
| |
| def required_identifiers(self) -> Iterable[RequiredIdentifier]: |
| yield RequiredIdentifier('include', '"qom/object.h"') |
| if self.type_identifiers is None: |
| return |
| # to make sure typedefs will be moved above all related macros, |
| # return dependencies from all of them, not just this match |
| for m in self.find_matching_macros(): |
| yield RequiredIdentifier('type', m.group('c_type')) |
| yield RequiredIdentifier('constant', m.group('qom_typename')) |
| |
| @property |
| def type_identifiers(self) -> Optional[TypeIdentifiers]: |
| """Extract type identifier information from match""" |
| typename = self.group('qom_typename') |
| c_type = self.group('c_type') |
| if not typename or not c_type: |
| return None |
| typedef = self.group('typedefname') |
| classtype = None |
| instancetype = None |
| uppercase = None |
| expected_suffix = dict(EXPECTED_CHECKER_SUFFIXES).get(self.checker) |
| |
| # here the available data depends on the checker macro being called: |
| # - we need to remove the suffix from the macro name |
| # - depending on the macro type, we know the class type name, or |
| # the instance type name |
| if self.checker in ('OBJECT_GET_CLASS', 'OBJECT_CLASS_CHECK'): |
| classtype = c_type |
| elif self.checker == 'OBJECT_CHECK': |
| instancetype = c_type |
| uppercase = self.name |
| else: |
| assert False |
| if expected_suffix and self.name.endswith(expected_suffix): |
| uppercase = self.name[:-len(expected_suffix)] |
| return TypeIdentifiers(typename=typename, classtype=classtype, |
| instancetype=instancetype, uppercase=uppercase) |
| |
| def gen_patches(self) -> Iterable[Patch]: |
| # the implementation is a bit tricky because we need to group |
| # macros dealing with the same type into a single declaration |
| if self.type_identifiers is None: |
| self.warn("couldn't extract type information from macro %s", self.name) |
| return |
| |
| if self.name == 'INTERFACE_CLASS': |
| # INTERFACE_CLASS is special and won't be patched |
| return |
| |
| for checker,suffix in EXPECTED_CHECKER_SUFFIXES: |
| if self.name.endswith(suffix): |
| if self.checker != checker: |
| self.warn("macro %s is using macro %s instead of %s", self.name, self.checker, checker) |
| return |
| break |
| |
| matches = self.find_matching_macros() |
| DBG("found %d matching macros: %s", len(matches), ' '.join(m.name for m in matches)) |
| # we will generate patches only when processing the first macro: |
| if matches[0].start != self.start: |
| DBG("skipping %s (will patch when handling %s)", self.name, matches[0].name) |
| return |
| |
| |
| ids = self.merge_ids(matches) |
| if ids is None: |
| DBG("type identifier mismatch, won't patch %s", self.name) |
| return |
| |
| if not ids.uppercase: |
| self.warn("macro %s doesn't follow the expected name pattern", self.name) |
| return |
| if not ids.typename: |
| self.warn("macro %s: couldn't extract type name", self.name) |
| return |
| |
| #issues = ids.check_consistency() |
| #if issues: |
| # for i in issues: |
| # self.warn("inconsistent identifiers: %s", i) |
| |
| names = [n for n in (ids.instancetype, ids.classtype, ids.uppercase, ids.typename) |
| if n is not None] |
| if len(set(names)) != len(names): |
| self.warn("duplicate names used by macro: %r", ids) |
| return |
| |
| assert ids.classtype or ids.instancetype |
| assert ids.typename |
| assert ids.uppercase |
| if ids.classtype and ids.instancetype: |
| new_decl = (f'DECLARE_OBJ_CHECKERS({ids.instancetype}, {ids.classtype},\n' |
| f' {ids.uppercase}, {ids.typename})\n') |
| elif ids.classtype: |
| new_decl = (f'DECLARE_CLASS_CHECKERS({ids.classtype}, {ids.uppercase},\n' |
| f' {ids.typename})\n') |
| elif ids.instancetype: |
| new_decl = (f'DECLARE_INSTANCE_CHECKER({ids.instancetype}, {ids.uppercase},\n' |
| f' {ids.typename})\n') |
| else: |
| assert False |
| |
| # we need to ensure the typedefs are already available |
| issues = [] |
| for t in [ids.instancetype, ids.classtype]: |
| if not t: |
| continue |
| if re.fullmatch(RE_STRUCT_TYPE, t): |
| self.info("type %s is not a typedef", t) |
| continue |
| td = find_typedef(self.file, t) |
| #if not td and self.allfiles.find_file('include/qemu/typedefs.h'): |
| # |
| if not td: |
| # it is OK if the typedef is in typedefs.h |
| f = self.allfiles.find_file('include/qemu/typedefs.h') |
| if f and find_typedef(f, t): |
| self.info("typedef %s found in typedefs.h", t) |
| continue |
| |
| issues.append("couldn't find typedef %s" % (t)) |
| elif td.start() > self.start(): |
| issues.append("typedef %s need to be moved earlier in the file" % (td.name)) |
| |
| for issue in issues: |
| self.warn(issue) |
| |
| if issues and not self.file.force: |
| return |
| |
| # delete all matching macros and add new declaration: |
| for m in matches: |
| yield m.make_patch('') |
| for issue in issues: |
| yield self.prepend("/* FIXME: %s */\n" % (issue)) |
| yield self.append(new_decl) |
| |
| class InterfaceCheckMacro(FileMatch): |
| """Type checking macro using INTERFACE_CHECK |
| Will be replaced by DECLARE_INTERFACE_CHECKER |
| """ |
| regexp = S(RE_MACRO_DEFINE, |
| 'INTERFACE_CHECK', |
| r'\s*\(\s*', OR(NAMED('instancetype', RE_IDENTIFIER), RE_TYPE, name='c_type'), |
| r'\s*,', CPP_SPACE, |
| OPTIONAL_PARS(RE_IDENTIFIER), r',', CPP_SPACE, |
| NAMED('qom_typename', RE_IDENTIFIER), r'\s*\)\n') |
| |
| def required_identifiers(self) -> Iterable[RequiredIdentifier]: |
| yield RequiredIdentifier('include', '"qom/object.h"') |
| yield RequiredIdentifier('type', self.group('instancetype')) |
| yield RequiredIdentifier('constant', self.group('qom_typename')) |
| |
| def gen_patches(self) -> Iterable[Patch]: |
| if self.file.filename_matches('qom/object.h'): |
| self.debug("skipping object.h") |
| return |
| |
| typename = self.group('qom_typename') |
| uppercase = self.name |
| instancetype = self.group('instancetype') |
| c = f"DECLARE_INTERFACE_CHECKER({instancetype}, {uppercase},\n"+\ |
| f" {typename})\n" |
| yield self.make_patch(c) |
| |
| |
| class TypeDeclaration(FileMatch): |
| """Parent class to all type declarations""" |
| @property |
| def instancetype(self) -> Optional[str]: |
| return self.getgroup('instancetype') |
| |
| @property |
| def classtype(self) -> Optional[str]: |
| return self.getgroup('classtype') |
| |
| @property |
| def typename(self) -> Optional[str]: |
| return self.getgroup('typename') |
| |
| class TypeCheckerDeclaration(TypeDeclaration): |
| """Parent class to all type checker declarations""" |
| @property |
| def typename(self) -> str: |
| return self.group('typename') |
| |
| @property |
| def uppercase(self) -> str: |
| return self.group('uppercase') |
| |
| class DeclareInstanceChecker(TypeCheckerDeclaration): |
| """DECLARE_INSTANCE_CHECKER use""" |
| #TODO: replace lonely DECLARE_INSTANCE_CHECKER with DECLARE_OBJ_CHECKERS |
| # if all types are found. |
| # This will require looking up the correct class type in the TypeInfo |
| # structs in another file |
| regexp = S(r'^[ \t]*DECLARE_INSTANCE_CHECKER\s*\(\s*', |
| NAMED('instancetype', RE_TYPE), r'\s*,\s*', |
| NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*', |
| OR(RE_IDENTIFIER, RE_STRING, RE_MACRO_CONCAT, RE_FUN_CALL, name='typename'), SP, |
| r'\)[ \t]*;?[ \t]*\n') |
| |
| def required_identifiers(self) -> Iterable[RequiredIdentifier]: |
| yield RequiredIdentifier('include', '"qom/object.h"') |
| yield RequiredIdentifier('constant', self.group('typename')) |
| yield RequiredIdentifier('type', self.group('instancetype')) |
| |
| class DeclareInterfaceChecker(TypeCheckerDeclaration): |
| """DECLARE_INTERFACE_CHECKER use""" |
| regexp = S(r'^[ \t]*DECLARE_INTERFACE_CHECKER\s*\(\s*', |
| NAMED('instancetype', RE_TYPE), r'\s*,\s*', |
| NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*', |
| OR(RE_IDENTIFIER, RE_STRING, RE_MACRO_CONCAT, RE_FUN_CALL, name='typename'), SP, |
| r'\)[ \t]*;?[ \t]*\n') |
| |
| def required_identifiers(self) -> Iterable[RequiredIdentifier]: |
| yield RequiredIdentifier('include', '"qom/object.h"') |
| yield RequiredIdentifier('constant', self.group('typename')) |
| yield RequiredIdentifier('type', self.group('instancetype')) |
| |
| class DeclareInstanceType(TypeDeclaration): |
| """DECLARE_INSTANCE_TYPE use""" |
| regexp = S(r'^[ \t]*DECLARE_INSTANCE_TYPE\s*\(\s*', |
| NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*', |
| NAMED('instancetype', RE_TYPE), SP, |
| r'\)[ \t]*;?[ \t]*\n') |
| |
| def required_identifiers(self) -> Iterable[RequiredIdentifier]: |
| yield RequiredIdentifier('include', '"qom/object.h"') |
| yield RequiredIdentifier('type', self.group('instancetype')) |
| |
| class DeclareClassType(TypeDeclaration): |
| """DECLARE_CLASS_TYPE use""" |
| regexp = S(r'^[ \t]*DECLARE_CLASS_TYPE\s*\(\s*', |
| NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*', |
| NAMED('classtype', RE_TYPE), SP, |
| r'\)[ \t]*;?[ \t]*\n') |
| |
| def required_identifiers(self) -> Iterable[RequiredIdentifier]: |
| yield RequiredIdentifier('include', '"qom/object.h"') |
| yield RequiredIdentifier('type', self.group('classtype')) |
| |
| |
| |
| class DeclareClassCheckers(TypeCheckerDeclaration): |
| """DECLARE_CLASS_CHECKER use""" |
| regexp = S(r'^[ \t]*DECLARE_CLASS_CHECKERS\s*\(\s*', |
| NAMED('classtype', RE_TYPE), r'\s*,\s*', |
| NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*', |
| OR(RE_IDENTIFIER, RE_STRING, RE_MACRO_CONCAT, RE_FUN_CALL, name='typename'), SP, |
| r'\)[ \t]*;?[ \t]*\n') |
| |
| def required_identifiers(self) -> Iterable[RequiredIdentifier]: |
| yield RequiredIdentifier('include', '"qom/object.h"') |
| yield RequiredIdentifier('constant', self.group('typename')) |
| yield RequiredIdentifier('type', self.group('classtype')) |
| |
| class DeclareObjCheckers(TypeCheckerDeclaration): |
| """DECLARE_OBJ_CHECKERS use""" |
| #TODO: detect when OBJECT_DECLARE_SIMPLE_TYPE can be used |
| regexp = S(r'^[ \t]*DECLARE_OBJ_CHECKERS\s*\(\s*', |
| NAMED('instancetype', RE_TYPE), r'\s*,\s*', |
| NAMED('classtype', RE_TYPE), r'\s*,\s*', |
| NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*', |
| OR(RE_IDENTIFIER, RE_STRING, RE_MACRO_CONCAT, RE_FUN_CALL, name='typename'), SP, |
| r'\)[ \t]*;?[ \t]*\n') |
| |
| def required_identifiers(self) -> Iterable[RequiredIdentifier]: |
| yield RequiredIdentifier('include', '"qom/object.h"') |
| yield RequiredIdentifier('constant', self.group('typename')) |
| yield RequiredIdentifier('type', self.group('classtype')) |
| yield RequiredIdentifier('type', self.group('instancetype')) |
| |
| class TypeDeclarationFixup(FileMatch): |
| """Common base class for code that will look at a set of type declarations""" |
| regexp = RE_FILE_BEGIN |
| def gen_patches(self) -> Iterable[Patch]: |
| if self.file.filename_matches('qom/object.h'): |
| self.debug("skipping object.h") |
| return |
| |
| # group checkers by uppercase name: |
| decl_types: List[Type[TypeDeclaration]] = [DeclareInstanceChecker, DeclareInstanceType, |
| DeclareClassCheckers, DeclareClassType, |
| DeclareObjCheckers] |
| checker_dict: Dict[str, List[TypeDeclaration]] = {} |
| for t in decl_types: |
| for m in self.file.matches_of_type(t): |
| checker_dict.setdefault(m.group('uppercase'), []).append(m) |
| self.debug("checker_dict: %r", checker_dict) |
| for uppercase,checkers in checker_dict.items(): |
| fields = ('instancetype', 'classtype', 'uppercase', 'typename') |
| fvalues = dict((field, set(getattr(m, field) for m in checkers |
| if getattr(m, field, None) is not None)) |
| for field in fields) |
| for field,values in fvalues.items(): |
| if len(values) > 1: |
| for c in checkers: |
| c.warn("%s mismatch (%s)", field, ' '.join(values)) |
| return |
| |
| field_dict = dict((f, v.pop() if v else None) for f,v in fvalues.items()) |
| yield from self.gen_patches_for_type(uppercase, checkers, field_dict) |
| |
| def find_conflicts(self, uppercase: str, checkers: List[TypeDeclaration]) -> bool: |
| """Look for conflicting declarations that would make it unsafe to add new ones""" |
| conflicting: List[FileMatch] = [] |
| # conflicts in the same file: |
| conflicting.extend(chain(self.file.find_matches(DefineDirective, uppercase), |
| self.file.find_matches(DeclareInterfaceChecker, uppercase, 'uppercase'), |
| self.file.find_matches(DeclareClassType, uppercase, 'uppercase'), |
| self.file.find_matches(DeclareInstanceType, uppercase, 'uppercase'))) |
| |
| # conflicts in another file: |
| conflicting.extend(o for o in chain(self.allfiles.find_matches(DeclareInstanceChecker, uppercase, 'uppercase'), |
| self.allfiles.find_matches(DeclareClassCheckers, uppercase, 'uppercase'), |
| self.allfiles.find_matches(DeclareInterfaceChecker, uppercase, 'uppercase'), |
| self.allfiles.find_matches(DefineDirective, uppercase)) |
| if o is not None and o.file != self.file |
| # if both are .c files, there's no conflict at all: |
| and not (o.file.filename.suffix == '.c' and |
| self.file.filename.suffix == '.c')) |
| |
| if conflicting: |
| for c in checkers: |
| c.warn("skipping due to conflicting %s macro", uppercase) |
| for o in conflicting: |
| if o is None: |
| continue |
| o.warn("conflicting %s macro is here", uppercase) |
| return True |
| |
| return False |
| |
| def gen_patches_for_type(self, uppercase: str, |
| checkers: List[TypeDeclaration], |
| fields: Dict[str, Optional[str]]) -> Iterable[Patch]: |
| """Should be reimplemented by subclasses""" |
| return |
| yield |
| |
| class DeclareVoidTypes(TypeDeclarationFixup): |
| """Add DECLARE_*_TYPE(..., void) when there's no declared type""" |
| regexp = RE_FILE_BEGIN |
| def gen_patches_for_type(self, uppercase: str, |
| checkers: List[TypeDeclaration], |
| fields: Dict[str, Optional[str]]) -> Iterable[Patch]: |
| if self.find_conflicts(uppercase, checkers): |
| return |
| |
| #_,last_checker = max((m.start(), m) for m in checkers) |
| _,first_checker = min((m.start(), m) for m in checkers) |
| |
| if not any(m.instancetype for m in checkers): |
| yield first_checker.prepend(f'DECLARE_INSTANCE_TYPE({uppercase}, void)\n') |
| if not any(m.classtype for m in checkers): |
| yield first_checker.prepend(f'DECLARE_CLASS_TYPE({uppercase}, void)\n') |
| |
| #if not all(len(v) == 1 for v in fvalues.values()): |
| # return |
| # |
| #final_values = dict((field, values.pop()) |
| # for field,values in fvalues.items()) |
| #s = (f"DECLARE_OBJ_CHECKERS({final_values['instancetype']}, {final_values['classtype']},\n"+ |
| # f" {final_values['uppercase']}, {final_values['typename']})\n") |
| #for c in checkers: |
| # yield c.make_removal_patch() |
| #yield last_checker.append(s) |
| |
| |
| class AddDeclareTypeName(TypeDeclarationFixup): |
| """Add DECLARE_TYPE_NAME declarations if necessary""" |
| def gen_patches_for_type(self, uppercase: str, |
| checkers: List[TypeDeclaration], |
| fields: Dict[str, Optional[str]]) -> Iterable[Patch]: |
| typename = fields.get('typename') |
| if typename is None: |
| self.warn("typename unavailable") |
| return |
| if typename == f'TYPE_{uppercase}': |
| self.info("already using TYPE_%s as type name", uppercase) |
| return |
| if self.file.find_match(DeclareTypeName, uppercase, 'uppercase'): |
| self.info("type name for %s already declared", uppercase) |
| return |
| _,first_checker = min((m.start(), m) for m in checkers) |
| s = f'DECLARE_TYPE_NAME({uppercase}, {typename})\n' |
| yield first_checker.prepend(s) |
| |
| class TrivialClassStruct(FileMatch): |
| """Trivial class struct""" |
| regexp = S(r'^[ \t]*struct\s*', NAMED('name', RE_IDENTIFIER), |
| r'\s*{\s*', NAMED('parent_struct', RE_IDENTIFIER), r'\s*parent(_class)?\s*;\s*};\n') |
| |
| class DeclareTypeName(FileMatch): |
| """DECLARE_TYPE_NAME usage""" |
| regexp = S(r'^[ \t]*DECLARE_TYPE_NAME\s*\(', |
| NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*', |
| OR(RE_IDENTIFIER, RE_STRING, RE_MACRO_CONCAT, RE_FUN_CALL, name='typename'), |
| r'\s*\);?[ \t]*\n') |
| |
| class ObjectDeclareType(TypeCheckerDeclaration): |
| """OBJECT_DECLARE_TYPE usage |
| Will be replaced with OBJECT_DECLARE_SIMPLE_TYPE if possible |
| """ |
| regexp = S(r'^[ \t]*OBJECT_DECLARE_TYPE\s*\(', |
| NAMED('instancetype', RE_TYPE), r'\s*,\s*', |
| NAMED('classtype', RE_TYPE), r'\s*,\s*', |
| NAMED('uppercase', RE_IDENTIFIER), SP, |
| r'\)[ \t]*;?[ \t]*\n') |
| |
| def gen_patches(self): |
| DBG("groups: %r", self.match.groupdict()) |
| trivial_struct = self.file.find_match(TrivialClassStruct, self.group('classtype')) |
| if trivial_struct: |
| d = self.match.groupdict().copy() |
| d['parent_struct'] = trivial_struct.group("parent_struct") |
| yield trivial_struct.make_removal_patch() |
| c = ("OBJECT_DECLARE_SIMPLE_TYPE(%(instancetype)s, %(lowercase)s,\n" |
| " %(uppercase)s, %(parent_struct)s)\n" % d) |
| yield self.make_patch(c) |
| |
| class ObjectDeclareSimpleType(TypeCheckerDeclaration): |
| """OBJECT_DECLARE_SIMPLE_TYPE usage""" |
| regexp = S(r'^[ \t]*OBJECT_DECLARE_SIMPLE_TYPE\s*\(', |
| NAMED('instancetype', RE_TYPE), r'\s*,\s*', |
| NAMED('uppercase', RE_IDENTIFIER), SP, |
| r'\)[ \t]*;?[ \t]*\n') |
| |
| class OldStyleObjectDeclareSimpleType(TypeCheckerDeclaration): |
| """OBJECT_DECLARE_SIMPLE_TYPE usage (old API)""" |
| regexp = S(r'^[ \t]*OBJECT_DECLARE_SIMPLE_TYPE\s*\(', |
| NAMED('instancetype', RE_TYPE), r'\s*,\s*', |
| NAMED('lowercase', RE_IDENTIFIER), r'\s*,\s*', |
| NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*', |
| NAMED('parent_classtype', RE_TYPE), SP, |
| r'\)[ \t]*;?[ \t]*\n') |
| |
| @property |
| def classtype(self) -> Optional[str]: |
| instancetype = self.instancetype |
| assert instancetype |
| return f"{instancetype}Class" |
| |
| def find_typename_uppercase(files: FileList, typename: str) -> Optional[str]: |
| """Try to find what's the right MODULE_OBJ_NAME for a given type name""" |
| decl = files.find_match(DeclareTypeName, name=typename, group='typename') |
| if decl: |
| return decl.group('uppercase') |
| if typename.startswith('TYPE_'): |
| return typename[len('TYPE_'):] |
| return None |
| |
| def find_type_checkers(files:FileList, name:str, group:str='uppercase') -> Iterable[TypeCheckerDeclaration]: |
| """Find usage of DECLARE*CHECKER macro""" |
| c: Type[TypeCheckerDeclaration] |
| for c in (DeclareInstanceChecker, DeclareClassCheckers, DeclareObjCheckers, ObjectDeclareType, ObjectDeclareSimpleType): |
| yield from files.find_matches(c, name=name, group=group) |
| |
| class Include(FileMatch): |
| """#include directive""" |
| regexp = RE_INCLUDE |
| def provided_identifiers(self) -> Iterable[RequiredIdentifier]: |
| yield RequiredIdentifier('include', self.group('includepath')) |
| |
| class InitialIncludes(FileMatch): |
| """Initial #include block""" |
| regexp = S(RE_FILE_BEGIN, |
| M(SP, RE_COMMENTS, |
| r'^[ \t]*#[ \t]*ifndef[ \t]+', RE_IDENTIFIER, r'[ \t]*\n', |
| n='?', name='ifndef_block'), |
| M(SP, RE_COMMENTS, |
| OR(RE_INCLUDE, RE_SIMPLEDEFINE), |
| n='*', name='includes')) |
| |
| class SymbolUserList(NamedTuple): |
| definitions: List[FileMatch] |
| users: List[FileMatch] |
| |
| class MoveSymbols(FileMatch): |
| """Handle missing symbols |
| - Move typedefs and defines when necessary |
| - Add missing #include lines when necessary |
| """ |
| regexp = RE_FILE_BEGIN |
| |
| def gen_patches(self) -> Iterator[Patch]: |
| if self.file.filename_matches('qom/object.h'): |
| self.debug("skipping object.h") |
| return |
| |
| index: Dict[RequiredIdentifier, SymbolUserList] = {} |
| definition_classes = [SimpleTypedefMatch, FullStructTypedefMatch, ConstantDefine, Include] |
| user_classes = [TypeCheckMacro, DeclareObjCheckers, DeclareInstanceChecker, DeclareClassCheckers, InterfaceCheckMacro] |
| |
| # first we scan for all symbol definitions and usage: |
| for dc in definition_classes: |
| defs = self.file.matches_of_type(dc) |
| for d in defs: |
| DBG("scanning %r", d) |
| for i in d.provided_identifiers(): |
| index.setdefault(i, SymbolUserList([], [])).definitions.append(d) |
| DBG("index: %r", list(index.keys())) |
| for uc in user_classes: |
| users = self.file.matches_of_type(uc) |
| for u in users: |
| for i in u.required_identifiers(): |
| index.setdefault(i, SymbolUserList([], [])).users.append(u) |
| |
| # validate all symbols: |
| for i,ul in index.items(): |
| if not ul.users: |
| # unused symbol |
| continue |
| |
| # symbol not defined |
| if len(ul.definitions) == 0: |
| if i.type == 'include': |
| includes, = self.file.matches_of_type(InitialIncludes) |
| #FIXME: don't do this if we're already inside qom/object.h |
| yield includes.append(f'#include {i.name}\n') |
| else: |
| u.warn("definition of %s %s not found in file", i.type, i.name) |
| continue |
| |
| # symbol defined twice: |
| if len(ul.definitions) > 1: |
| ul.definitions[1].warn("%s defined twice", i.name) |
| ul.definitions[0].warn("previously defined here") |
| continue |
| |
| # symbol defined. check if all users are after its definition: |
| assert len(ul.definitions) == 1 |
| definition = ul.definitions[0] |
| DBG("handling repositioning of %r", definition) |
| earliest = min(ul.users, key=lambda u: u.start()) |
| if earliest.start() > definition.start(): |
| DBG("%r is OK", definition) |
| continue |
| |
| DBG("%r needs to be moved", definition) |
| if isinstance(definition, SimpleTypedefMatch) \ |
| or isinstance(definition, ConstantDefine): |
| # simple typedef or define can be moved directly: |
| yield definition.make_removal_patch() |
| yield earliest.prepend(definition.group(0)) |
| elif isinstance(definition, FullStructTypedefMatch) \ |
| and definition.group('structname'): |
| # full struct typedef is more complex: we need to remove |
| # the typedef |
| yield from definition.move_typedef(earliest.start()) |
| else: |
| definition.warn("definition of %s %s needs to be moved earlier in the file", i.type, i.name) |
| earliest.warn("definition of %s %s is used here", i.type, i.name) |
| |
| |
| class EmptyPreprocessorConditional(FileMatch): |
| """Delete empty preprocessor conditionals""" |
| regexp = r'^[ \t]*#(if|ifdef)[ \t].*\n+[ \t]*#endif[ \t]*\n' |
| def gen_patches(self) -> Iterable[Patch]: |
| yield self.make_removal_patch() |