194dfc0f3SEduardo Habkost# Copyright (C) 2020 Red Hat Inc.
294dfc0f3SEduardo Habkost#
394dfc0f3SEduardo Habkost# Authors:
494dfc0f3SEduardo Habkost#  Eduardo Habkost <ehabkost@redhat.com>
594dfc0f3SEduardo Habkost#
694dfc0f3SEduardo Habkost# This work is licensed under the terms of the GNU GPL, version 2.  See
794dfc0f3SEduardo Habkost# the COPYING file in the top-level directory.
894dfc0f3SEduardo Habkostimport re
994dfc0f3SEduardo Habkostfrom itertools import chain
1094dfc0f3SEduardo Habkostfrom typing import *
1194dfc0f3SEduardo Habkost
1294dfc0f3SEduardo Habkostfrom .regexps import *
1394dfc0f3SEduardo Habkostfrom .patching import *
1494dfc0f3SEduardo Habkostfrom .utils import *
1594dfc0f3SEduardo Habkost
1694dfc0f3SEduardo Habkostimport logging
1794dfc0f3SEduardo Habkostlogger = logging.getLogger(__name__)
1894dfc0f3SEduardo HabkostDBG = logger.debug
1994dfc0f3SEduardo HabkostINFO = logger.info
2094dfc0f3SEduardo HabkostWARN = logger.warning
2194dfc0f3SEduardo Habkost
2294dfc0f3SEduardo Habkost# simple expressions:
2394dfc0f3SEduardo Habkost
2494dfc0f3SEduardo HabkostRE_CONSTANT = OR(RE_STRING, RE_NUMBER)
2594dfc0f3SEduardo Habkost
264a15e5beSEduardo Habkostclass DefineDirective(FileMatch):
274a15e5beSEduardo Habkost    """Match any #define directive"""
284a15e5beSEduardo Habkost    regexp = S(r'^[ \t]*#[ \t]*define', CPP_SPACE, NAMED('name', RE_IDENTIFIER), r'\b')
294a15e5beSEduardo Habkost
304a15e5beSEduardo Habkostclass ExpressionDefine(FileMatch):
314a15e5beSEduardo Habkost    """Simple #define preprocessor directive for an expression"""
3294dfc0f3SEduardo Habkost    regexp = S(r'^[ \t]*#[ \t]*define', CPP_SPACE, NAMED('name', RE_IDENTIFIER),
334a15e5beSEduardo Habkost               CPP_SPACE, NAMED('value', RE_EXPRESSION), r'[ \t]*\n')
3494dfc0f3SEduardo Habkost
3594dfc0f3SEduardo Habkost    def provided_identifiers(self) -> Iterable[RequiredIdentifier]:
3694dfc0f3SEduardo Habkost        yield RequiredIdentifier('constant', self.group('name'))
3794dfc0f3SEduardo Habkost
384a15e5beSEduardo Habkostclass ConstantDefine(ExpressionDefine):
394a15e5beSEduardo Habkost    """Simple #define preprocessor directive for a number or string constant"""
404a15e5beSEduardo Habkost    regexp = S(r'^[ \t]*#[ \t]*define', CPP_SPACE, NAMED('name', RE_IDENTIFIER),
414a15e5beSEduardo Habkost               CPP_SPACE, NAMED('value', RE_CONSTANT), r'[ \t]*\n')
424a15e5beSEduardo Habkost
434a15e5beSEduardo Habkost
4494dfc0f3SEduardo Habkostclass TypeIdentifiers(NamedTuple):
4594dfc0f3SEduardo Habkost    """Type names found in type declarations"""
4694dfc0f3SEduardo Habkost    # TYPE_MYDEVICE
4794dfc0f3SEduardo Habkost    typename: Optional[str]
4894dfc0f3SEduardo Habkost    # MYDEVICE
4994dfc0f3SEduardo Habkost    uppercase: Optional[str] = None
5094dfc0f3SEduardo Habkost    # MyDevice
5194dfc0f3SEduardo Habkost    instancetype: Optional[str] = None
5294dfc0f3SEduardo Habkost    # MyDeviceClass
5394dfc0f3SEduardo Habkost    classtype: Optional[str] = None
5494dfc0f3SEduardo Habkost    # my_device
5594dfc0f3SEduardo Habkost    lowercase: Optional[str] = None
5694dfc0f3SEduardo Habkost
5794dfc0f3SEduardo Habkost    def allfields(self):
5894dfc0f3SEduardo Habkost        return tuple(getattr(self, f) for f in self._fields)
5994dfc0f3SEduardo Habkost
6094dfc0f3SEduardo Habkost    def merge(self, other: 'TypeIdentifiers') -> Optional['TypeIdentifiers']:
6194dfc0f3SEduardo Habkost        """Check if identifiers match, return new identifier with complete list"""
6294dfc0f3SEduardo Habkost        if any(not opt_compare(a, b) for a,b in zip(self, other)):
6394dfc0f3SEduardo Habkost            return None
6494dfc0f3SEduardo Habkost        return TypeIdentifiers(*(merge(a, b) for a,b in zip(self, other)))
6594dfc0f3SEduardo Habkost
6694dfc0f3SEduardo Habkost    def __str__(self) -> str:
6794dfc0f3SEduardo Habkost        values = ((f, getattr(self, f)) for f in self._fields)
6894dfc0f3SEduardo Habkost        s = ', '.join('%s=%s' % (f,v) for f,v in values if v is not None)
6994dfc0f3SEduardo Habkost        return f'{s}'
7094dfc0f3SEduardo Habkost
7194dfc0f3SEduardo Habkost    def check_consistency(self) -> List[str]:
7294dfc0f3SEduardo Habkost        """Check if identifiers are consistent with each other,
7394dfc0f3SEduardo Habkost        return list of problems (or empty list if everything seems consistent)
7494dfc0f3SEduardo Habkost        """
7594dfc0f3SEduardo Habkost        r = []
7694dfc0f3SEduardo Habkost        if self.typename is None:
7794dfc0f3SEduardo Habkost            r.append("typename (TYPE_MYDEVICE) is unavailable")
7894dfc0f3SEduardo Habkost
7994dfc0f3SEduardo Habkost        if self.uppercase is None:
8094dfc0f3SEduardo Habkost            r.append("uppercase name is unavailable")
8194dfc0f3SEduardo Habkost
8294dfc0f3SEduardo Habkost        if (self.instancetype is not None
8394dfc0f3SEduardo Habkost            and self.classtype is not None
8494dfc0f3SEduardo Habkost            and self.classtype != f'{self.instancetype}Class'):
8594dfc0f3SEduardo Habkost                r.append("class typedef %s doesn't match instance typedef %s" %
8694dfc0f3SEduardo Habkost                         (self.classtype, self.instancetype))
8794dfc0f3SEduardo Habkost
8894dfc0f3SEduardo Habkost        if (self.uppercase is not None
8994dfc0f3SEduardo Habkost            and self.typename is not None
9094dfc0f3SEduardo Habkost            and f'TYPE_{self.uppercase}' != self.typename):
9194dfc0f3SEduardo Habkost            r.append("uppercase name (%s) doesn't match type name (%s)" %
9294dfc0f3SEduardo Habkost                     (self.uppercase, self.typename))
9394dfc0f3SEduardo Habkost
9494dfc0f3SEduardo Habkost        return r
9594dfc0f3SEduardo Habkost
9694dfc0f3SEduardo Habkostclass TypedefMatch(FileMatch):
9794dfc0f3SEduardo Habkost    """typedef declaration"""
9894dfc0f3SEduardo Habkost    def provided_identifiers(self) -> Iterable[RequiredIdentifier]:
9994dfc0f3SEduardo Habkost        yield RequiredIdentifier('type', self.group('name'))
10094dfc0f3SEduardo Habkost
10194dfc0f3SEduardo Habkostclass SimpleTypedefMatch(TypedefMatch):
10294dfc0f3SEduardo Habkost    """Simple typedef declaration
10394dfc0f3SEduardo Habkost    (no replacement rules)"""
10494dfc0f3SEduardo Habkost    regexp = S(r'^[ \t]*typedef', SP,
10594dfc0f3SEduardo Habkost               NAMED('typedef_type', RE_TYPE), SP,
10694dfc0f3SEduardo Habkost               NAMED('name', RE_IDENTIFIER), r'\s*;[ \t]*\n')
10794dfc0f3SEduardo Habkost
10894dfc0f3SEduardo HabkostRE_MACRO_DEFINE = S(r'^[ \t]*#\s*define\s+', NAMED('name', RE_IDENTIFIER),
10994dfc0f3SEduardo Habkost                    r'\s*\(\s*', RE_IDENTIFIER, r'\s*\)', CPP_SPACE)
11094dfc0f3SEduardo Habkost
11194dfc0f3SEduardo HabkostRE_STRUCT_ATTRIBUTE = r'QEMU_PACKED'
11294dfc0f3SEduardo Habkost
11394dfc0f3SEduardo Habkost# This doesn't parse the struct definitions completely, it just assumes
11494dfc0f3SEduardo Habkost# the closing brackets are going to be in an unindented line:
11594dfc0f3SEduardo HabkostRE_FULL_STRUCT = S('struct', SP, M(RE_IDENTIFIER, n='?', name='structname'), SP,
11694dfc0f3SEduardo Habkost                   NAMED('body', r'{\n',
11794dfc0f3SEduardo Habkost                         # acceptable inside the struct body:
11894dfc0f3SEduardo Habkost                         # - lines starting with space or tab
11994dfc0f3SEduardo Habkost                         # - empty lines
12094dfc0f3SEduardo Habkost                         # - preprocessor directives
12194dfc0f3SEduardo Habkost                         # - comments
12294dfc0f3SEduardo Habkost                         OR(r'[ \t][^\n]*\n',
12394dfc0f3SEduardo Habkost                            r'#[^\n]*\n',
12494dfc0f3SEduardo Habkost                            r'\n',
12594dfc0f3SEduardo Habkost                            S(r'[ \t]*', RE_COMMENT, r'[ \t]*\n'),
12694dfc0f3SEduardo Habkost                            repeat='*?'),
12794dfc0f3SEduardo Habkost                         r'}', M(RE_STRUCT_ATTRIBUTE, SP, n='*')))
12894dfc0f3SEduardo HabkostRE_STRUCT_TYPEDEF = S(r'^[ \t]*typedef', SP, RE_FULL_STRUCT, SP,
12994dfc0f3SEduardo Habkost                      NAMED('name', RE_IDENTIFIER), r'\s*;[ \t]*\n')
13094dfc0f3SEduardo Habkost
13194dfc0f3SEduardo Habkostclass FullStructTypedefMatch(TypedefMatch):
13294dfc0f3SEduardo Habkost    """typedef struct [SomeStruct] { ...} SomeType
13394dfc0f3SEduardo Habkost    Will be replaced by separate struct declaration + typedef
13494dfc0f3SEduardo Habkost    """
13594dfc0f3SEduardo Habkost    regexp = RE_STRUCT_TYPEDEF
13694dfc0f3SEduardo Habkost
13794dfc0f3SEduardo Habkost    def make_structname(self) -> str:
13894dfc0f3SEduardo Habkost        """Make struct name for struct+typedef split"""
13994dfc0f3SEduardo Habkost        name = self.group('structname')
14094dfc0f3SEduardo Habkost        if not name:
14194dfc0f3SEduardo Habkost            name = self.name
14294dfc0f3SEduardo Habkost        return name
14394dfc0f3SEduardo Habkost
14494dfc0f3SEduardo Habkost    def strip_typedef(self) -> Patch:
145d30b5bc9SMichael Tokarev        """generate patch that will strip typedef from the struct declaration
14694dfc0f3SEduardo Habkost
14794dfc0f3SEduardo Habkost        The caller is responsible for readding the typedef somewhere else.
14894dfc0f3SEduardo Habkost        """
14994dfc0f3SEduardo Habkost        name = self.make_structname()
15094dfc0f3SEduardo Habkost        body = self.group('body')
15194dfc0f3SEduardo Habkost        return self.make_patch(f'struct {name} {body};\n')
15294dfc0f3SEduardo Habkost
15394dfc0f3SEduardo Habkost    def make_simple_typedef(self) -> str:
15494dfc0f3SEduardo Habkost        structname = self.make_structname()
15594dfc0f3SEduardo Habkost        name = self.name
15694dfc0f3SEduardo Habkost        return f'typedef struct {structname} {name};\n'
15794dfc0f3SEduardo Habkost
15894dfc0f3SEduardo Habkost    def move_typedef(self, position) -> Iterator[Patch]:
15994dfc0f3SEduardo Habkost        """Generate patches to move typedef elsewhere"""
16094dfc0f3SEduardo Habkost        yield self.strip_typedef()
16194dfc0f3SEduardo Habkost        yield Patch(position, position, self.make_simple_typedef())
16294dfc0f3SEduardo Habkost
16394dfc0f3SEduardo Habkost    def split_typedef(self) -> Iterator[Patch]:
16494dfc0f3SEduardo Habkost        """Split into struct definition + typedef in-place"""
16594dfc0f3SEduardo Habkost        yield self.strip_typedef()
16694dfc0f3SEduardo Habkost        yield self.append(self.make_simple_typedef())
16794dfc0f3SEduardo Habkost
16894dfc0f3SEduardo Habkostclass StructTypedefSplit(FullStructTypedefMatch):
16994dfc0f3SEduardo Habkost    """split struct+typedef declaration"""
17094dfc0f3SEduardo Habkost    def gen_patches(self) -> Iterator[Patch]:
17194dfc0f3SEduardo Habkost        if self.group('structname'):
17294dfc0f3SEduardo Habkost            yield from self.split_typedef()
17394dfc0f3SEduardo Habkost
17494dfc0f3SEduardo Habkostclass DuplicatedTypedefs(SimpleTypedefMatch):
17594dfc0f3SEduardo Habkost    """Delete ALL duplicate typedefs (unsafe)"""
17694dfc0f3SEduardo Habkost    def gen_patches(self) -> Iterable[Patch]:
17794dfc0f3SEduardo Habkost        other_td = [td for td in chain(self.file.matches_of_type(SimpleTypedefMatch),
17894dfc0f3SEduardo Habkost                                       self.file.matches_of_type(FullStructTypedefMatch))
17994dfc0f3SEduardo Habkost                    if td.name == self.name]
18094dfc0f3SEduardo Habkost        DBG("other_td: %r", other_td)
18194dfc0f3SEduardo Habkost        if any(td.start() < self.start() for td in other_td):
18294dfc0f3SEduardo Habkost            # patch only if handling the first typedef
18394dfc0f3SEduardo Habkost            return
18494dfc0f3SEduardo Habkost        for td in other_td:
18594dfc0f3SEduardo Habkost            if isinstance(td, SimpleTypedefMatch):
18694dfc0f3SEduardo Habkost                DBG("other td: %r", td.match.groupdict())
18794dfc0f3SEduardo Habkost                if td.group('typedef_type') != self.group('typedef_type'):
18894dfc0f3SEduardo Habkost                    yield td.make_removal_patch()
18994dfc0f3SEduardo Habkost            elif isinstance(td, FullStructTypedefMatch):
19094dfc0f3SEduardo Habkost                DBG("other td: %r", td.match.groupdict())
19194dfc0f3SEduardo Habkost                if self.group('typedef_type') == 'struct '+td.group('structname'):
19294dfc0f3SEduardo Habkost                    yield td.strip_typedef()
19394dfc0f3SEduardo Habkost
19494dfc0f3SEduardo Habkostclass QOMDuplicatedTypedefs(DuplicatedTypedefs):
19594dfc0f3SEduardo Habkost    """Delete duplicate typedefs if used by QOM type"""
19694dfc0f3SEduardo Habkost    def gen_patches(self) -> Iterable[Patch]:
19794dfc0f3SEduardo Habkost        qom_macros = [TypeCheckMacro, DeclareInstanceChecker, DeclareClassCheckers, DeclareObjCheckers]
19894dfc0f3SEduardo Habkost        qom_matches = chain(*(self.file.matches_of_type(t) for t in qom_macros))
19994dfc0f3SEduardo Habkost        in_use = any(RequiredIdentifier('type', self.name) in m.required_identifiers()
20094dfc0f3SEduardo Habkost                     for m in qom_matches)
20194dfc0f3SEduardo Habkost        if in_use:
20294dfc0f3SEduardo Habkost            yield from DuplicatedTypedefs.gen_patches(self)
20394dfc0f3SEduardo Habkost
20494dfc0f3SEduardo Habkostclass QOMStructTypedefSplit(FullStructTypedefMatch):
20594dfc0f3SEduardo Habkost    """split struct+typedef declaration if used by QOM type"""
20694dfc0f3SEduardo Habkost    def gen_patches(self) -> Iterator[Patch]:
20794dfc0f3SEduardo Habkost        qom_macros = [TypeCheckMacro, DeclareInstanceChecker, DeclareClassCheckers, DeclareObjCheckers]
20894dfc0f3SEduardo Habkost        qom_matches = chain(*(self.file.matches_of_type(t) for t in qom_macros))
20994dfc0f3SEduardo Habkost        in_use = any(RequiredIdentifier('type', self.name) in m.required_identifiers()
21094dfc0f3SEduardo Habkost                     for m in qom_matches)
21194dfc0f3SEduardo Habkost        if in_use:
21294dfc0f3SEduardo Habkost            yield from self.split_typedef()
21394dfc0f3SEduardo Habkost
21494dfc0f3SEduardo Habkostdef typedefs(file: FileInfo) -> Iterable[TypedefMatch]:
21594dfc0f3SEduardo Habkost    return (cast(TypedefMatch, m)
21694dfc0f3SEduardo Habkost            for m in chain(file.matches_of_type(SimpleTypedefMatch),
21794dfc0f3SEduardo Habkost                           file.matches_of_type(FullStructTypedefMatch)))
21894dfc0f3SEduardo Habkost
21994dfc0f3SEduardo Habkostdef find_typedef(f: FileInfo, name: Optional[str]) -> Optional[TypedefMatch]:
22094dfc0f3SEduardo Habkost    if not name:
22194dfc0f3SEduardo Habkost        return None
22294dfc0f3SEduardo Habkost    for td in typedefs(f):
22394dfc0f3SEduardo Habkost        if td.name == name:
22494dfc0f3SEduardo Habkost            return td
22594dfc0f3SEduardo Habkost    return None
22694dfc0f3SEduardo Habkost
22794dfc0f3SEduardo HabkostCHECKER_MACROS = ['OBJECT_CHECK', 'OBJECT_CLASS_CHECK', 'OBJECT_GET_CLASS']
22894dfc0f3SEduardo HabkostCheckerMacroName = Literal['OBJECT_CHECK', 'OBJECT_CLASS_CHECK', 'OBJECT_GET_CLASS']
22994dfc0f3SEduardo Habkost
23094dfc0f3SEduardo HabkostRE_CHECK_MACRO = \
23194dfc0f3SEduardo Habkost    S(RE_MACRO_DEFINE,
23294dfc0f3SEduardo Habkost      OR(*CHECKER_MACROS, name='checker'),
23394dfc0f3SEduardo Habkost      M(r'\s*\(\s*', OR(NAMED('typedefname', RE_IDENTIFIER), RE_TYPE, name='c_type'), r'\s*,', CPP_SPACE,
23494dfc0f3SEduardo Habkost        OPTIONAL_PARS(RE_IDENTIFIER), r',', CPP_SPACE,
23594dfc0f3SEduardo Habkost        NAMED('qom_typename', RE_IDENTIFIER), r'\s*\)\n',
23694dfc0f3SEduardo Habkost        n='?', name='check_args'))
23794dfc0f3SEduardo Habkost
23894dfc0f3SEduardo HabkostEXPECTED_CHECKER_SUFFIXES: List[Tuple[CheckerMacroName, str]] = [
23994dfc0f3SEduardo Habkost    ('OBJECT_GET_CLASS', '_GET_CLASS'),
24094dfc0f3SEduardo Habkost    ('OBJECT_CLASS_CHECK', '_CLASS'),
24194dfc0f3SEduardo Habkost]
24294dfc0f3SEduardo Habkost
24394dfc0f3SEduardo Habkostclass TypeCheckMacro(FileMatch):
24494dfc0f3SEduardo Habkost    """OBJECT_CHECK/OBJECT_CLASS_CHECK/OBJECT_GET_CLASS macro definitions
24594dfc0f3SEduardo Habkost    Will be replaced by DECLARE_*_CHECKERS macro
24694dfc0f3SEduardo Habkost    """
24794dfc0f3SEduardo Habkost    regexp = RE_CHECK_MACRO
24894dfc0f3SEduardo Habkost
24994dfc0f3SEduardo Habkost    @property
25094dfc0f3SEduardo Habkost    def checker(self) -> CheckerMacroName:
25194dfc0f3SEduardo Habkost        """Name of checker macro being used"""
2524a15e5beSEduardo Habkost        return self.group('checker') # type: ignore
25394dfc0f3SEduardo Habkost
25494dfc0f3SEduardo Habkost    @property
25594dfc0f3SEduardo Habkost    def typedefname(self) -> Optional[str]:
25694dfc0f3SEduardo Habkost        return self.group('typedefname')
25794dfc0f3SEduardo Habkost
25894dfc0f3SEduardo Habkost    def find_typedef(self) -> Optional[TypedefMatch]:
25994dfc0f3SEduardo Habkost        return find_typedef(self.file, self.typedefname)
26094dfc0f3SEduardo Habkost
26194dfc0f3SEduardo Habkost    def sanity_check(self) -> None:
26294dfc0f3SEduardo Habkost        DBG("groups: %r", self.match.groups())
26394dfc0f3SEduardo Habkost        if not self.group('check_args'):
26494dfc0f3SEduardo Habkost            self.warn("type check macro not parsed completely: %s", self.name)
26594dfc0f3SEduardo Habkost            return
26694dfc0f3SEduardo Habkost        DBG("type identifiers: %r", self.type_identifiers)
26794dfc0f3SEduardo Habkost        if self.typedefname and self.find_typedef() is None:
26894dfc0f3SEduardo Habkost            self.warn("typedef used by %s not found", self.name)
26994dfc0f3SEduardo Habkost
27094dfc0f3SEduardo Habkost    def find_matching_macros(self) -> List['TypeCheckMacro']:
27194dfc0f3SEduardo Habkost        """Find other check macros that generate the same macro names
27294dfc0f3SEduardo Habkost
27394dfc0f3SEduardo Habkost        The returned list will always be sorted.
27494dfc0f3SEduardo Habkost        """
27594dfc0f3SEduardo Habkost        my_ids = self.type_identifiers
27694dfc0f3SEduardo Habkost        assert my_ids
27794dfc0f3SEduardo Habkost        return [m for m in self.file.matches_of_type(TypeCheckMacro)
27894dfc0f3SEduardo Habkost                if m.type_identifiers is not None
27994dfc0f3SEduardo Habkost                   and my_ids.uppercase is not None
28094dfc0f3SEduardo Habkost                   and (my_ids.uppercase == m.type_identifiers.uppercase
28194dfc0f3SEduardo Habkost                        or my_ids.typename == m.type_identifiers.typename)]
28294dfc0f3SEduardo Habkost
28394dfc0f3SEduardo Habkost    def merge_ids(self, matches: List['TypeCheckMacro']) -> Optional[TypeIdentifiers]:
28494dfc0f3SEduardo Habkost        """Try to merge info about type identifiers from all matches in a list"""
28594dfc0f3SEduardo Habkost        if not matches:
28694dfc0f3SEduardo Habkost            return None
28794dfc0f3SEduardo Habkost        r = matches[0].type_identifiers
28894dfc0f3SEduardo Habkost        if r is None:
28994dfc0f3SEduardo Habkost            return None
29094dfc0f3SEduardo Habkost        for m in matches[1:]:
29194dfc0f3SEduardo Habkost            assert m.type_identifiers
29294dfc0f3SEduardo Habkost            new = r.merge(m.type_identifiers)
29394dfc0f3SEduardo Habkost            if new is None:
29494dfc0f3SEduardo Habkost                self.warn("macro %s identifiers (%s) don't match macro %s (%s)",
29594dfc0f3SEduardo Habkost                          matches[0].name, r, m.name, m.type_identifiers)
29694dfc0f3SEduardo Habkost                return None
29794dfc0f3SEduardo Habkost            r = new
29894dfc0f3SEduardo Habkost        return r
29994dfc0f3SEduardo Habkost
30094dfc0f3SEduardo Habkost    def required_identifiers(self) -> Iterable[RequiredIdentifier]:
30194dfc0f3SEduardo Habkost        yield RequiredIdentifier('include', '"qom/object.h"')
30294dfc0f3SEduardo Habkost        if self.type_identifiers is None:
30394dfc0f3SEduardo Habkost            return
30494dfc0f3SEduardo Habkost        # to make sure typedefs will be moved above all related macros,
30594dfc0f3SEduardo Habkost        # return dependencies from all of them, not just this match
30694dfc0f3SEduardo Habkost        for m in self.find_matching_macros():
30794dfc0f3SEduardo Habkost            yield RequiredIdentifier('type', m.group('c_type'))
30894dfc0f3SEduardo Habkost            yield RequiredIdentifier('constant', m.group('qom_typename'))
30994dfc0f3SEduardo Habkost
31094dfc0f3SEduardo Habkost    @property
31194dfc0f3SEduardo Habkost    def type_identifiers(self) -> Optional[TypeIdentifiers]:
31294dfc0f3SEduardo Habkost        """Extract type identifier information from match"""
31394dfc0f3SEduardo Habkost        typename = self.group('qom_typename')
31494dfc0f3SEduardo Habkost        c_type = self.group('c_type')
31594dfc0f3SEduardo Habkost        if not typename or not c_type:
31694dfc0f3SEduardo Habkost            return None
31794dfc0f3SEduardo Habkost        typedef = self.group('typedefname')
31894dfc0f3SEduardo Habkost        classtype = None
31994dfc0f3SEduardo Habkost        instancetype = None
32094dfc0f3SEduardo Habkost        uppercase = None
32194dfc0f3SEduardo Habkost        expected_suffix = dict(EXPECTED_CHECKER_SUFFIXES).get(self.checker)
32294dfc0f3SEduardo Habkost
32394dfc0f3SEduardo Habkost        # here the available data depends on the checker macro being called:
32494dfc0f3SEduardo Habkost        # - we need to remove the suffix from the macro name
32594dfc0f3SEduardo Habkost        # - depending on the macro type, we know the class type name, or
32694dfc0f3SEduardo Habkost        #   the instance type name
32794dfc0f3SEduardo Habkost        if self.checker in ('OBJECT_GET_CLASS', 'OBJECT_CLASS_CHECK'):
32894dfc0f3SEduardo Habkost            classtype = c_type
32994dfc0f3SEduardo Habkost        elif self.checker == 'OBJECT_CHECK':
33094dfc0f3SEduardo Habkost            instancetype = c_type
33194dfc0f3SEduardo Habkost            uppercase = self.name
33294dfc0f3SEduardo Habkost        else:
33394dfc0f3SEduardo Habkost            assert False
33494dfc0f3SEduardo Habkost        if expected_suffix and self.name.endswith(expected_suffix):
33594dfc0f3SEduardo Habkost            uppercase = self.name[:-len(expected_suffix)]
33694dfc0f3SEduardo Habkost        return TypeIdentifiers(typename=typename, classtype=classtype,
33794dfc0f3SEduardo Habkost                               instancetype=instancetype, uppercase=uppercase)
33894dfc0f3SEduardo Habkost
33994dfc0f3SEduardo Habkost    def gen_patches(self) -> Iterable[Patch]:
3404a15e5beSEduardo Habkost        # the implementation is a bit tricky because we need to group
3414a15e5beSEduardo Habkost        # macros dealing with the same type into a single declaration
34294dfc0f3SEduardo Habkost        if self.type_identifiers is None:
34394dfc0f3SEduardo Habkost            self.warn("couldn't extract type information from macro %s", self.name)
34494dfc0f3SEduardo Habkost            return
34594dfc0f3SEduardo Habkost
34694dfc0f3SEduardo Habkost        if self.name == 'INTERFACE_CLASS':
34794dfc0f3SEduardo Habkost            # INTERFACE_CLASS is special and won't be patched
34894dfc0f3SEduardo Habkost            return
34994dfc0f3SEduardo Habkost
35094dfc0f3SEduardo Habkost        for checker,suffix in EXPECTED_CHECKER_SUFFIXES:
35194dfc0f3SEduardo Habkost            if self.name.endswith(suffix):
35294dfc0f3SEduardo Habkost                if self.checker != checker:
35394dfc0f3SEduardo Habkost                    self.warn("macro %s is using macro %s instead of %s", self.name, self.checker, checker)
35494dfc0f3SEduardo Habkost                    return
35594dfc0f3SEduardo Habkost                break
35694dfc0f3SEduardo Habkost
35794dfc0f3SEduardo Habkost        matches = self.find_matching_macros()
35894dfc0f3SEduardo Habkost        DBG("found %d matching macros: %s", len(matches), ' '.join(m.name for m in matches))
35994dfc0f3SEduardo Habkost        # we will generate patches only when processing the first macro:
36094dfc0f3SEduardo Habkost        if matches[0].start != self.start:
36194dfc0f3SEduardo Habkost            DBG("skipping %s (will patch when handling %s)", self.name, matches[0].name)
36294dfc0f3SEduardo Habkost            return
36394dfc0f3SEduardo Habkost
36494dfc0f3SEduardo Habkost
36594dfc0f3SEduardo Habkost        ids = self.merge_ids(matches)
36694dfc0f3SEduardo Habkost        if ids is None:
36794dfc0f3SEduardo Habkost            DBG("type identifier mismatch, won't patch %s", self.name)
36894dfc0f3SEduardo Habkost            return
36994dfc0f3SEduardo Habkost
37094dfc0f3SEduardo Habkost        if not ids.uppercase:
37194dfc0f3SEduardo Habkost            self.warn("macro %s doesn't follow the expected name pattern", self.name)
37294dfc0f3SEduardo Habkost            return
37394dfc0f3SEduardo Habkost        if not ids.typename:
37494dfc0f3SEduardo Habkost            self.warn("macro %s: couldn't extract type name", self.name)
37594dfc0f3SEduardo Habkost            return
37694dfc0f3SEduardo Habkost
37794dfc0f3SEduardo Habkost        #issues = ids.check_consistency()
37894dfc0f3SEduardo Habkost        #if issues:
37994dfc0f3SEduardo Habkost        #    for i in issues:
38094dfc0f3SEduardo Habkost        #        self.warn("inconsistent identifiers: %s", i)
38194dfc0f3SEduardo Habkost
38294dfc0f3SEduardo Habkost        names = [n for n in (ids.instancetype, ids.classtype, ids.uppercase, ids.typename)
38394dfc0f3SEduardo Habkost                 if n is not None]
38494dfc0f3SEduardo Habkost        if len(set(names)) != len(names):
38594dfc0f3SEduardo Habkost            self.warn("duplicate names used by macro: %r", ids)
38694dfc0f3SEduardo Habkost            return
38794dfc0f3SEduardo Habkost
38894dfc0f3SEduardo Habkost        assert ids.classtype or ids.instancetype
38994dfc0f3SEduardo Habkost        assert ids.typename
39094dfc0f3SEduardo Habkost        assert ids.uppercase
39194dfc0f3SEduardo Habkost        if ids.classtype and ids.instancetype:
39294dfc0f3SEduardo Habkost            new_decl = (f'DECLARE_OBJ_CHECKERS({ids.instancetype}, {ids.classtype},\n'
39394dfc0f3SEduardo Habkost                        f'                     {ids.uppercase}, {ids.typename})\n')
39494dfc0f3SEduardo Habkost        elif ids.classtype:
39594dfc0f3SEduardo Habkost            new_decl = (f'DECLARE_CLASS_CHECKERS({ids.classtype}, {ids.uppercase},\n'
39694dfc0f3SEduardo Habkost                        f'                       {ids.typename})\n')
39794dfc0f3SEduardo Habkost        elif ids.instancetype:
39894dfc0f3SEduardo Habkost            new_decl = (f'DECLARE_INSTANCE_CHECKER({ids.instancetype}, {ids.uppercase},\n'
39994dfc0f3SEduardo Habkost                        f'                         {ids.typename})\n')
40094dfc0f3SEduardo Habkost        else:
40194dfc0f3SEduardo Habkost            assert False
40294dfc0f3SEduardo Habkost
40394dfc0f3SEduardo Habkost        # we need to ensure the typedefs are already available
40494dfc0f3SEduardo Habkost        issues = []
40594dfc0f3SEduardo Habkost        for t in [ids.instancetype, ids.classtype]:
40694dfc0f3SEduardo Habkost            if not t:
40794dfc0f3SEduardo Habkost                continue
40894dfc0f3SEduardo Habkost            if re.fullmatch(RE_STRUCT_TYPE, t):
40994dfc0f3SEduardo Habkost                self.info("type %s is not a typedef", t)
41094dfc0f3SEduardo Habkost                continue
41194dfc0f3SEduardo Habkost            td = find_typedef(self.file, t)
41294dfc0f3SEduardo Habkost            #if not td and self.allfiles.find_file('include/qemu/typedefs.h'):
41394dfc0f3SEduardo Habkost            #
41494dfc0f3SEduardo Habkost            if not td:
41594dfc0f3SEduardo Habkost                # it is OK if the typedef is in typedefs.h
41694dfc0f3SEduardo Habkost                f = self.allfiles.find_file('include/qemu/typedefs.h')
41794dfc0f3SEduardo Habkost                if f and find_typedef(f, t):
41894dfc0f3SEduardo Habkost                    self.info("typedef %s found in typedefs.h", t)
41994dfc0f3SEduardo Habkost                    continue
42094dfc0f3SEduardo Habkost
42194dfc0f3SEduardo Habkost                issues.append("couldn't find typedef %s" % (t))
42294dfc0f3SEduardo Habkost            elif td.start() > self.start():
42394dfc0f3SEduardo Habkost                issues.append("typedef %s need to be moved earlier in the file" % (td.name))
42494dfc0f3SEduardo Habkost
42594dfc0f3SEduardo Habkost        for issue in issues:
42694dfc0f3SEduardo Habkost            self.warn(issue)
42794dfc0f3SEduardo Habkost
42894dfc0f3SEduardo Habkost        if issues and not self.file.force:
42994dfc0f3SEduardo Habkost            return
43094dfc0f3SEduardo Habkost
43194dfc0f3SEduardo Habkost        # delete all matching macros and add new declaration:
43294dfc0f3SEduardo Habkost        for m in matches:
43394dfc0f3SEduardo Habkost            yield m.make_patch('')
43494dfc0f3SEduardo Habkost        for issue in issues:
43594dfc0f3SEduardo Habkost            yield self.prepend("/* FIXME: %s */\n" % (issue))
43694dfc0f3SEduardo Habkost        yield self.append(new_decl)
43794dfc0f3SEduardo Habkost
4384a15e5beSEduardo Habkostclass InterfaceCheckMacro(FileMatch):
4394a15e5beSEduardo Habkost    """Type checking macro using INTERFACE_CHECK
4404a15e5beSEduardo Habkost    Will be replaced by DECLARE_INTERFACE_CHECKER
44194dfc0f3SEduardo Habkost    """
4424a15e5beSEduardo Habkost    regexp = S(RE_MACRO_DEFINE,
4434a15e5beSEduardo Habkost               'INTERFACE_CHECK',
4444a15e5beSEduardo Habkost               r'\s*\(\s*', OR(NAMED('instancetype', RE_IDENTIFIER), RE_TYPE, name='c_type'),
4454a15e5beSEduardo Habkost               r'\s*,', CPP_SPACE,
4464a15e5beSEduardo Habkost               OPTIONAL_PARS(RE_IDENTIFIER), r',', CPP_SPACE,
4474a15e5beSEduardo Habkost               NAMED('qom_typename', RE_IDENTIFIER), r'\s*\)\n')
4484a15e5beSEduardo Habkost
4494a15e5beSEduardo Habkost    def required_identifiers(self) -> Iterable[RequiredIdentifier]:
4504a15e5beSEduardo Habkost        yield RequiredIdentifier('include', '"qom/object.h"')
4514a15e5beSEduardo Habkost        yield RequiredIdentifier('type', self.group('instancetype'))
4524a15e5beSEduardo Habkost        yield RequiredIdentifier('constant', self.group('qom_typename'))
4534a15e5beSEduardo Habkost
4544a15e5beSEduardo Habkost    def gen_patches(self) -> Iterable[Patch]:
4554a15e5beSEduardo Habkost        if self.file.filename_matches('qom/object.h'):
4564a15e5beSEduardo Habkost            self.debug("skipping object.h")
4574a15e5beSEduardo Habkost            return
4584a15e5beSEduardo Habkost
4594a15e5beSEduardo Habkost        typename = self.group('qom_typename')
4604a15e5beSEduardo Habkost        uppercase = self.name
4614a15e5beSEduardo Habkost        instancetype = self.group('instancetype')
4624a15e5beSEduardo Habkost        c = f"DECLARE_INTERFACE_CHECKER({instancetype}, {uppercase},\n"+\
4634a15e5beSEduardo Habkost            f"                          {typename})\n"
4644a15e5beSEduardo Habkost        yield self.make_patch(c)
4654a15e5beSEduardo Habkost
4664a15e5beSEduardo Habkost
4674a15e5beSEduardo Habkostclass TypeDeclaration(FileMatch):
4684a15e5beSEduardo Habkost    """Parent class to all type declarations"""
4694a15e5beSEduardo Habkost    @property
4704a15e5beSEduardo Habkost    def instancetype(self) -> Optional[str]:
4714a15e5beSEduardo Habkost        return self.getgroup('instancetype')
4724a15e5beSEduardo Habkost
4734a15e5beSEduardo Habkost    @property
4744a15e5beSEduardo Habkost    def classtype(self) -> Optional[str]:
4754a15e5beSEduardo Habkost        return self.getgroup('classtype')
4764a15e5beSEduardo Habkost
4774a15e5beSEduardo Habkost    @property
4784a15e5beSEduardo Habkost    def typename(self) -> Optional[str]:
4794a15e5beSEduardo Habkost        return self.getgroup('typename')
4804a15e5beSEduardo Habkost
4814a15e5beSEduardo Habkostclass TypeCheckerDeclaration(TypeDeclaration):
4824a15e5beSEduardo Habkost    """Parent class to all type checker declarations"""
4834a15e5beSEduardo Habkost    @property
4844a15e5beSEduardo Habkost    def typename(self) -> str:
4854a15e5beSEduardo Habkost        return self.group('typename')
4864a15e5beSEduardo Habkost
4874a15e5beSEduardo Habkost    @property
4884a15e5beSEduardo Habkost    def uppercase(self) -> str:
4894a15e5beSEduardo Habkost        return self.group('uppercase')
4904a15e5beSEduardo Habkost
4914a15e5beSEduardo Habkostclass DeclareInstanceChecker(TypeCheckerDeclaration):
4924a15e5beSEduardo Habkost    """DECLARE_INSTANCE_CHECKER use"""
49394dfc0f3SEduardo Habkost    #TODO: replace lonely DECLARE_INSTANCE_CHECKER with DECLARE_OBJ_CHECKERS
49494dfc0f3SEduardo Habkost    #      if all types are found.
49594dfc0f3SEduardo Habkost    #      This will require looking up the correct class type in the TypeInfo
49694dfc0f3SEduardo Habkost    #      structs in another file
49794dfc0f3SEduardo Habkost    regexp = S(r'^[ \t]*DECLARE_INSTANCE_CHECKER\s*\(\s*',
49894dfc0f3SEduardo Habkost               NAMED('instancetype', RE_TYPE), r'\s*,\s*',
49994dfc0f3SEduardo Habkost               NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*',
50094dfc0f3SEduardo Habkost               OR(RE_IDENTIFIER, RE_STRING, RE_MACRO_CONCAT, RE_FUN_CALL, name='typename'), SP,
50194dfc0f3SEduardo Habkost               r'\)[ \t]*;?[ \t]*\n')
50294dfc0f3SEduardo Habkost
50394dfc0f3SEduardo Habkost    def required_identifiers(self) -> Iterable[RequiredIdentifier]:
50494dfc0f3SEduardo Habkost        yield RequiredIdentifier('include', '"qom/object.h"')
50594dfc0f3SEduardo Habkost        yield RequiredIdentifier('constant', self.group('typename'))
50694dfc0f3SEduardo Habkost        yield RequiredIdentifier('type', self.group('instancetype'))
50794dfc0f3SEduardo Habkost
5084a15e5beSEduardo Habkostclass DeclareInterfaceChecker(TypeCheckerDeclaration):
5094a15e5beSEduardo Habkost    """DECLARE_INTERFACE_CHECKER use"""
5104a15e5beSEduardo Habkost    regexp = S(r'^[ \t]*DECLARE_INTERFACE_CHECKER\s*\(\s*',
5114a15e5beSEduardo Habkost               NAMED('instancetype', RE_TYPE), r'\s*,\s*',
5124a15e5beSEduardo Habkost               NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*',
5134a15e5beSEduardo Habkost               OR(RE_IDENTIFIER, RE_STRING, RE_MACRO_CONCAT, RE_FUN_CALL, name='typename'), SP,
5144a15e5beSEduardo Habkost               r'\)[ \t]*;?[ \t]*\n')
5154a15e5beSEduardo Habkost
5164a15e5beSEduardo Habkost    def required_identifiers(self) -> Iterable[RequiredIdentifier]:
5174a15e5beSEduardo Habkost        yield RequiredIdentifier('include', '"qom/object.h"')
5184a15e5beSEduardo Habkost        yield RequiredIdentifier('constant', self.group('typename'))
5194a15e5beSEduardo Habkost        yield RequiredIdentifier('type', self.group('instancetype'))
5204a15e5beSEduardo Habkost
5214a15e5beSEduardo Habkostclass DeclareInstanceType(TypeDeclaration):
5224a15e5beSEduardo Habkost    """DECLARE_INSTANCE_TYPE use"""
5234a15e5beSEduardo Habkost    regexp = S(r'^[ \t]*DECLARE_INSTANCE_TYPE\s*\(\s*',
5244a15e5beSEduardo Habkost               NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*',
5254a15e5beSEduardo Habkost               NAMED('instancetype', RE_TYPE), SP,
5264a15e5beSEduardo Habkost               r'\)[ \t]*;?[ \t]*\n')
5274a15e5beSEduardo Habkost
5284a15e5beSEduardo Habkost    def required_identifiers(self) -> Iterable[RequiredIdentifier]:
5294a15e5beSEduardo Habkost        yield RequiredIdentifier('include', '"qom/object.h"')
5304a15e5beSEduardo Habkost        yield RequiredIdentifier('type', self.group('instancetype'))
5314a15e5beSEduardo Habkost
5324a15e5beSEduardo Habkostclass DeclareClassType(TypeDeclaration):
5334a15e5beSEduardo Habkost    """DECLARE_CLASS_TYPE use"""
5344a15e5beSEduardo Habkost    regexp = S(r'^[ \t]*DECLARE_CLASS_TYPE\s*\(\s*',
5354a15e5beSEduardo Habkost               NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*',
5364a15e5beSEduardo Habkost               NAMED('classtype', RE_TYPE), SP,
5374a15e5beSEduardo Habkost               r'\)[ \t]*;?[ \t]*\n')
5384a15e5beSEduardo Habkost
5394a15e5beSEduardo Habkost    def required_identifiers(self) -> Iterable[RequiredIdentifier]:
5404a15e5beSEduardo Habkost        yield RequiredIdentifier('include', '"qom/object.h"')
5414a15e5beSEduardo Habkost        yield RequiredIdentifier('type', self.group('classtype'))
5424a15e5beSEduardo Habkost
5434a15e5beSEduardo Habkost
5444a15e5beSEduardo Habkost
5454a15e5beSEduardo Habkostclass DeclareClassCheckers(TypeCheckerDeclaration):
5464a15e5beSEduardo Habkost    """DECLARE_CLASS_CHECKER use"""
54794dfc0f3SEduardo Habkost    regexp = S(r'^[ \t]*DECLARE_CLASS_CHECKERS\s*\(\s*',
54894dfc0f3SEduardo Habkost               NAMED('classtype', RE_TYPE), r'\s*,\s*',
54994dfc0f3SEduardo Habkost               NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*',
55094dfc0f3SEduardo Habkost               OR(RE_IDENTIFIER, RE_STRING, RE_MACRO_CONCAT, RE_FUN_CALL, name='typename'), SP,
55194dfc0f3SEduardo Habkost               r'\)[ \t]*;?[ \t]*\n')
55294dfc0f3SEduardo Habkost
55394dfc0f3SEduardo Habkost    def required_identifiers(self) -> Iterable[RequiredIdentifier]:
55494dfc0f3SEduardo Habkost        yield RequiredIdentifier('include', '"qom/object.h"')
55594dfc0f3SEduardo Habkost        yield RequiredIdentifier('constant', self.group('typename'))
55694dfc0f3SEduardo Habkost        yield RequiredIdentifier('type', self.group('classtype'))
55794dfc0f3SEduardo Habkost
5584a15e5beSEduardo Habkostclass DeclareObjCheckers(TypeCheckerDeclaration):
5594a15e5beSEduardo Habkost    """DECLARE_OBJ_CHECKERS use"""
56094dfc0f3SEduardo Habkost    #TODO: detect when OBJECT_DECLARE_SIMPLE_TYPE can be used
56194dfc0f3SEduardo Habkost    regexp = S(r'^[ \t]*DECLARE_OBJ_CHECKERS\s*\(\s*',
56294dfc0f3SEduardo Habkost               NAMED('instancetype', RE_TYPE), r'\s*,\s*',
56394dfc0f3SEduardo Habkost               NAMED('classtype', RE_TYPE), r'\s*,\s*',
56494dfc0f3SEduardo Habkost               NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*',
56594dfc0f3SEduardo Habkost               OR(RE_IDENTIFIER, RE_STRING, RE_MACRO_CONCAT, RE_FUN_CALL, name='typename'), SP,
56694dfc0f3SEduardo Habkost               r'\)[ \t]*;?[ \t]*\n')
56794dfc0f3SEduardo Habkost
56894dfc0f3SEduardo Habkost    def required_identifiers(self) -> Iterable[RequiredIdentifier]:
56994dfc0f3SEduardo Habkost        yield RequiredIdentifier('include', '"qom/object.h"')
57094dfc0f3SEduardo Habkost        yield RequiredIdentifier('constant', self.group('typename'))
57194dfc0f3SEduardo Habkost        yield RequiredIdentifier('type', self.group('classtype'))
57294dfc0f3SEduardo Habkost        yield RequiredIdentifier('type', self.group('instancetype'))
57394dfc0f3SEduardo Habkost
5744a15e5beSEduardo Habkostclass TypeDeclarationFixup(FileMatch):
5754a15e5beSEduardo Habkost    """Common base class for code that will look at a set of type declarations"""
5764a15e5beSEduardo Habkost    regexp = RE_FILE_BEGIN
5774a15e5beSEduardo Habkost    def gen_patches(self) -> Iterable[Patch]:
5784a15e5beSEduardo Habkost        if self.file.filename_matches('qom/object.h'):
5794a15e5beSEduardo Habkost            self.debug("skipping object.h")
58094dfc0f3SEduardo Habkost            return
58194dfc0f3SEduardo Habkost
5824a15e5beSEduardo Habkost        # group checkers by uppercase name:
5834a15e5beSEduardo Habkost        decl_types: List[Type[TypeDeclaration]] = [DeclareInstanceChecker, DeclareInstanceType,
5844a15e5beSEduardo Habkost                                                   DeclareClassCheckers, DeclareClassType,
5854a15e5beSEduardo Habkost                                                   DeclareObjCheckers]
5864a15e5beSEduardo Habkost        checker_dict: Dict[str, List[TypeDeclaration]] = {}
5874a15e5beSEduardo Habkost        for t in decl_types:
5884a15e5beSEduardo Habkost            for m in self.file.matches_of_type(t):
5894a15e5beSEduardo Habkost                checker_dict.setdefault(m.group('uppercase'), []).append(m)
5904a15e5beSEduardo Habkost        self.debug("checker_dict: %r", checker_dict)
5914a15e5beSEduardo Habkost        for uppercase,checkers in checker_dict.items():
5924a15e5beSEduardo Habkost            fields = ('instancetype', 'classtype', 'uppercase', 'typename')
5934a15e5beSEduardo Habkost            fvalues = dict((field, set(getattr(m, field) for m in checkers
5944a15e5beSEduardo Habkost                                       if getattr(m, field, None) is not None))
5954a15e5beSEduardo Habkost                            for field in fields)
5964a15e5beSEduardo Habkost            for field,values in fvalues.items():
5974a15e5beSEduardo Habkost                if len(values) > 1:
5984a15e5beSEduardo Habkost                    for c in checkers:
5994a15e5beSEduardo Habkost                        c.warn("%s mismatch (%s)", field, ' '.join(values))
60094dfc0f3SEduardo Habkost                    return
60194dfc0f3SEduardo Habkost
6024a15e5beSEduardo Habkost            field_dict = dict((f, v.pop() if v else None) for f,v in fvalues.items())
6034a15e5beSEduardo Habkost            yield from self.gen_patches_for_type(uppercase, checkers, field_dict)
60494dfc0f3SEduardo Habkost
6054a15e5beSEduardo Habkost    def find_conflicts(self, uppercase: str, checkers: List[TypeDeclaration]) -> bool:
6064a15e5beSEduardo Habkost        """Look for conflicting declarations that would make it unsafe to add new ones"""
6074a15e5beSEduardo Habkost        conflicting: List[FileMatch] = []
6084a15e5beSEduardo Habkost        # conflicts in the same file:
6094a15e5beSEduardo Habkost        conflicting.extend(chain(self.file.find_matches(DefineDirective, uppercase),
6104a15e5beSEduardo Habkost                                 self.file.find_matches(DeclareInterfaceChecker, uppercase, 'uppercase'),
6114a15e5beSEduardo Habkost                                 self.file.find_matches(DeclareClassType, uppercase, 'uppercase'),
6124a15e5beSEduardo Habkost                                 self.file.find_matches(DeclareInstanceType, uppercase, 'uppercase')))
6134a15e5beSEduardo Habkost
6144a15e5beSEduardo Habkost        # conflicts in another file:
6154a15e5beSEduardo Habkost        conflicting.extend(o for o in chain(self.allfiles.find_matches(DeclareInstanceChecker, uppercase, 'uppercase'),
6164a15e5beSEduardo Habkost                                            self.allfiles.find_matches(DeclareClassCheckers, uppercase, 'uppercase'),
6174a15e5beSEduardo Habkost                                            self.allfiles.find_matches(DeclareInterfaceChecker, uppercase, 'uppercase'),
6184a15e5beSEduardo Habkost                                            self.allfiles.find_matches(DefineDirective, uppercase))
6194a15e5beSEduardo Habkost                           if o is not None and o.file != self.file
6204a15e5beSEduardo Habkost                               # if both are .c files, there's no conflict at all:
6214a15e5beSEduardo Habkost                               and not (o.file.filename.suffix == '.c' and
6224a15e5beSEduardo Habkost                                       self.file.filename.suffix == '.c'))
6234a15e5beSEduardo Habkost
6244a15e5beSEduardo Habkost        if conflicting:
6254a15e5beSEduardo Habkost            for c in checkers:
6264a15e5beSEduardo Habkost                c.warn("skipping due to conflicting %s macro", uppercase)
6274a15e5beSEduardo Habkost            for o in conflicting:
6284a15e5beSEduardo Habkost                if o is None:
6294a15e5beSEduardo Habkost                    continue
6304a15e5beSEduardo Habkost                o.warn("conflicting %s macro is here", uppercase)
6314a15e5beSEduardo Habkost            return True
6324a15e5beSEduardo Habkost
6334a15e5beSEduardo Habkost        return False
6344a15e5beSEduardo Habkost
6354a15e5beSEduardo Habkost    def gen_patches_for_type(self, uppercase: str,
6364a15e5beSEduardo Habkost                             checkers: List[TypeDeclaration],
6374a15e5beSEduardo Habkost                             fields: Dict[str, Optional[str]]) -> Iterable[Patch]:
6384a15e5beSEduardo Habkost        """Should be reimplemented by subclasses"""
6394a15e5beSEduardo Habkost        return
6404a15e5beSEduardo Habkost        yield
6414a15e5beSEduardo Habkost
6424a15e5beSEduardo Habkostclass DeclareVoidTypes(TypeDeclarationFixup):
6434a15e5beSEduardo Habkost    """Add DECLARE_*_TYPE(..., void) when there's no declared type"""
6444a15e5beSEduardo Habkost    regexp = RE_FILE_BEGIN
6454a15e5beSEduardo Habkost    def gen_patches_for_type(self, uppercase: str,
6464a15e5beSEduardo Habkost                             checkers: List[TypeDeclaration],
6474a15e5beSEduardo Habkost                             fields: Dict[str, Optional[str]]) -> Iterable[Patch]:
6484a15e5beSEduardo Habkost        if self.find_conflicts(uppercase, checkers):
6494a15e5beSEduardo Habkost            return
6504a15e5beSEduardo Habkost
6514a15e5beSEduardo Habkost        #_,last_checker = max((m.start(), m) for m in checkers)
6524a15e5beSEduardo Habkost        _,first_checker = min((m.start(), m) for m in checkers)
6534a15e5beSEduardo Habkost
6544a15e5beSEduardo Habkost        if not any(m.instancetype for m in checkers):
6554a15e5beSEduardo Habkost            yield first_checker.prepend(f'DECLARE_INSTANCE_TYPE({uppercase}, void)\n')
6564a15e5beSEduardo Habkost        if not any(m.classtype for m in checkers):
6574a15e5beSEduardo Habkost            yield first_checker.prepend(f'DECLARE_CLASS_TYPE({uppercase}, void)\n')
6584a15e5beSEduardo Habkost
6594a15e5beSEduardo Habkost        #if not all(len(v) == 1 for v in fvalues.values()):
6604a15e5beSEduardo Habkost        #    return
6614a15e5beSEduardo Habkost        #
6624a15e5beSEduardo Habkost        #final_values = dict((field, values.pop())
6634a15e5beSEduardo Habkost        #                    for field,values in fvalues.items())
6644a15e5beSEduardo Habkost        #s = (f"DECLARE_OBJ_CHECKERS({final_values['instancetype']}, {final_values['classtype']},\n"+
6654a15e5beSEduardo Habkost        #        f"                     {final_values['uppercase']}, {final_values['typename']})\n")
6664a15e5beSEduardo Habkost        #for c in checkers:
6674a15e5beSEduardo Habkost        #    yield c.make_removal_patch()
6684a15e5beSEduardo Habkost        #yield last_checker.append(s)
6694a15e5beSEduardo Habkost
6704a15e5beSEduardo Habkost
6714a15e5beSEduardo Habkostclass AddDeclareTypeName(TypeDeclarationFixup):
6724a15e5beSEduardo Habkost    """Add DECLARE_TYPE_NAME declarations if necessary"""
6734a15e5beSEduardo Habkost    def gen_patches_for_type(self, uppercase: str,
6744a15e5beSEduardo Habkost                             checkers: List[TypeDeclaration],
6754a15e5beSEduardo Habkost                             fields: Dict[str, Optional[str]]) -> Iterable[Patch]:
6764a15e5beSEduardo Habkost        typename = fields.get('typename')
6774a15e5beSEduardo Habkost        if typename is None:
6784a15e5beSEduardo Habkost            self.warn("typename unavailable")
6794a15e5beSEduardo Habkost            return
6804a15e5beSEduardo Habkost        if typename == f'TYPE_{uppercase}':
6814a15e5beSEduardo Habkost            self.info("already using TYPE_%s as type name", uppercase)
6824a15e5beSEduardo Habkost            return
6834a15e5beSEduardo Habkost        if self.file.find_match(DeclareTypeName, uppercase, 'uppercase'):
6844a15e5beSEduardo Habkost            self.info("type name for %s already declared", uppercase)
6854a15e5beSEduardo Habkost            return
6864a15e5beSEduardo Habkost        _,first_checker = min((m.start(), m) for m in checkers)
6874a15e5beSEduardo Habkost        s = f'DECLARE_TYPE_NAME({uppercase}, {typename})\n'
6884a15e5beSEduardo Habkost        yield first_checker.prepend(s)
68994dfc0f3SEduardo Habkost
69094dfc0f3SEduardo Habkostclass TrivialClassStruct(FileMatch):
69194dfc0f3SEduardo Habkost    """Trivial class struct"""
69294dfc0f3SEduardo Habkost    regexp = S(r'^[ \t]*struct\s*', NAMED('name', RE_IDENTIFIER),
69394dfc0f3SEduardo Habkost               r'\s*{\s*', NAMED('parent_struct', RE_IDENTIFIER), r'\s*parent(_class)?\s*;\s*};\n')
69494dfc0f3SEduardo Habkost
69594dfc0f3SEduardo Habkostclass DeclareTypeName(FileMatch):
69694dfc0f3SEduardo Habkost    """DECLARE_TYPE_NAME usage"""
69794dfc0f3SEduardo Habkost    regexp = S(r'^[ \t]*DECLARE_TYPE_NAME\s*\(',
69894dfc0f3SEduardo Habkost               NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*',
69994dfc0f3SEduardo Habkost               OR(RE_IDENTIFIER, RE_STRING, RE_MACRO_CONCAT, RE_FUN_CALL, name='typename'),
70094dfc0f3SEduardo Habkost               r'\s*\);?[ \t]*\n')
70194dfc0f3SEduardo Habkost
7024a15e5beSEduardo Habkostclass ObjectDeclareType(TypeCheckerDeclaration):
70394dfc0f3SEduardo Habkost    """OBJECT_DECLARE_TYPE usage
70494dfc0f3SEduardo Habkost    Will be replaced with OBJECT_DECLARE_SIMPLE_TYPE if possible
70594dfc0f3SEduardo Habkost    """
70694dfc0f3SEduardo Habkost    regexp = S(r'^[ \t]*OBJECT_DECLARE_TYPE\s*\(',
70794dfc0f3SEduardo Habkost               NAMED('instancetype', RE_TYPE), r'\s*,\s*',
70894dfc0f3SEduardo Habkost               NAMED('classtype', RE_TYPE), r'\s*,\s*',
70994dfc0f3SEduardo Habkost               NAMED('uppercase', RE_IDENTIFIER), SP,
71094dfc0f3SEduardo Habkost               r'\)[ \t]*;?[ \t]*\n')
71194dfc0f3SEduardo Habkost
71294dfc0f3SEduardo Habkost    def gen_patches(self):
71394dfc0f3SEduardo Habkost        DBG("groups: %r", self.match.groupdict())
71494dfc0f3SEduardo Habkost        trivial_struct = self.file.find_match(TrivialClassStruct, self.group('classtype'))
71594dfc0f3SEduardo Habkost        if trivial_struct:
71694dfc0f3SEduardo Habkost            d = self.match.groupdict().copy()
71794dfc0f3SEduardo Habkost            d['parent_struct'] = trivial_struct.group("parent_struct")
71894dfc0f3SEduardo Habkost            yield trivial_struct.make_removal_patch()
71994dfc0f3SEduardo Habkost            c = ("OBJECT_DECLARE_SIMPLE_TYPE(%(instancetype)s, %(lowercase)s,\n"
72094dfc0f3SEduardo Habkost                 "                           %(uppercase)s, %(parent_struct)s)\n" % d)
72194dfc0f3SEduardo Habkost            yield self.make_patch(c)
72294dfc0f3SEduardo Habkost
7234a15e5beSEduardo Habkostclass ObjectDeclareSimpleType(TypeCheckerDeclaration):
7244a15e5beSEduardo Habkost    """OBJECT_DECLARE_SIMPLE_TYPE usage"""
7254a15e5beSEduardo Habkost    regexp = S(r'^[ \t]*OBJECT_DECLARE_SIMPLE_TYPE\s*\(',
7264a15e5beSEduardo Habkost               NAMED('instancetype', RE_TYPE), r'\s*,\s*',
7274a15e5beSEduardo Habkost               NAMED('uppercase', RE_IDENTIFIER), SP,
7284a15e5beSEduardo Habkost               r'\)[ \t]*;?[ \t]*\n')
7294a15e5beSEduardo Habkost
7304a15e5beSEduardo Habkostclass OldStyleObjectDeclareSimpleType(TypeCheckerDeclaration):
7314a15e5beSEduardo Habkost    """OBJECT_DECLARE_SIMPLE_TYPE usage (old API)"""
7324a15e5beSEduardo Habkost    regexp = S(r'^[ \t]*OBJECT_DECLARE_SIMPLE_TYPE\s*\(',
7334a15e5beSEduardo Habkost               NAMED('instancetype', RE_TYPE), r'\s*,\s*',
7344a15e5beSEduardo Habkost               NAMED('lowercase', RE_IDENTIFIER), r'\s*,\s*',
7354a15e5beSEduardo Habkost               NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*',
7364a15e5beSEduardo Habkost               NAMED('parent_classtype', RE_TYPE), SP,
7374a15e5beSEduardo Habkost               r'\)[ \t]*;?[ \t]*\n')
7384a15e5beSEduardo Habkost
7394a15e5beSEduardo Habkost    @property
7404a15e5beSEduardo Habkost    def classtype(self) -> Optional[str]:
7414a15e5beSEduardo Habkost        instancetype = self.instancetype
7424a15e5beSEduardo Habkost        assert instancetype
7434a15e5beSEduardo Habkost        return f"{instancetype}Class"
7444a15e5beSEduardo Habkost
7454a15e5beSEduardo Habkostdef find_typename_uppercase(files: FileList, typename: str) -> Optional[str]:
7464a15e5beSEduardo Habkost    """Try to find what's the right MODULE_OBJ_NAME for a given type name"""
7474a15e5beSEduardo Habkost    decl = files.find_match(DeclareTypeName, name=typename, group='typename')
7484a15e5beSEduardo Habkost    if decl:
7494a15e5beSEduardo Habkost        return decl.group('uppercase')
7504a15e5beSEduardo Habkost    if typename.startswith('TYPE_'):
7514a15e5beSEduardo Habkost        return typename[len('TYPE_'):]
75294dfc0f3SEduardo Habkost    return None
75394dfc0f3SEduardo Habkost
7544a15e5beSEduardo Habkostdef find_type_checkers(files:FileList, name:str, group:str='uppercase') -> Iterable[TypeCheckerDeclaration]:
7554a15e5beSEduardo Habkost    """Find usage of DECLARE*CHECKER macro"""
7564a15e5beSEduardo Habkost    c: Type[TypeCheckerDeclaration]
7574a15e5beSEduardo Habkost    for c in (DeclareInstanceChecker, DeclareClassCheckers, DeclareObjCheckers, ObjectDeclareType, ObjectDeclareSimpleType):
7584a15e5beSEduardo Habkost        yield from files.find_matches(c, name=name, group=group)
75994dfc0f3SEduardo Habkost
76094dfc0f3SEduardo Habkostclass Include(FileMatch):
76194dfc0f3SEduardo Habkost    """#include directive"""
76294dfc0f3SEduardo Habkost    regexp = RE_INCLUDE
76394dfc0f3SEduardo Habkost    def provided_identifiers(self) -> Iterable[RequiredIdentifier]:
76494dfc0f3SEduardo Habkost        yield RequiredIdentifier('include', self.group('includepath'))
76594dfc0f3SEduardo Habkost
76694dfc0f3SEduardo Habkostclass InitialIncludes(FileMatch):
76794dfc0f3SEduardo Habkost    """Initial #include block"""
76894dfc0f3SEduardo Habkost    regexp = S(RE_FILE_BEGIN,
76994dfc0f3SEduardo Habkost               M(SP, RE_COMMENTS,
77094dfc0f3SEduardo Habkost                 r'^[ \t]*#[ \t]*ifndef[ \t]+', RE_IDENTIFIER, r'[ \t]*\n',
77194dfc0f3SEduardo Habkost                 n='?', name='ifndef_block'),
77294dfc0f3SEduardo Habkost               M(SP, RE_COMMENTS,
77394dfc0f3SEduardo Habkost                 OR(RE_INCLUDE, RE_SIMPLEDEFINE),
77494dfc0f3SEduardo Habkost                 n='*', name='includes'))
77594dfc0f3SEduardo Habkost
77694dfc0f3SEduardo Habkostclass SymbolUserList(NamedTuple):
77794dfc0f3SEduardo Habkost    definitions: List[FileMatch]
77894dfc0f3SEduardo Habkost    users: List[FileMatch]
77994dfc0f3SEduardo Habkost
78094dfc0f3SEduardo Habkostclass MoveSymbols(FileMatch):
78194dfc0f3SEduardo Habkost    """Handle missing symbols
78294dfc0f3SEduardo Habkost    - Move typedefs and defines when necessary
78394dfc0f3SEduardo Habkost    - Add missing #include lines when necessary
78494dfc0f3SEduardo Habkost    """
78594dfc0f3SEduardo Habkost    regexp = RE_FILE_BEGIN
78694dfc0f3SEduardo Habkost
78794dfc0f3SEduardo Habkost    def gen_patches(self) -> Iterator[Patch]:
7884a15e5beSEduardo Habkost        if self.file.filename_matches('qom/object.h'):
7894a15e5beSEduardo Habkost            self.debug("skipping object.h")
7904a15e5beSEduardo Habkost            return
7914a15e5beSEduardo Habkost
79294dfc0f3SEduardo Habkost        index: Dict[RequiredIdentifier, SymbolUserList] = {}
79394dfc0f3SEduardo Habkost        definition_classes = [SimpleTypedefMatch, FullStructTypedefMatch, ConstantDefine, Include]
7944a15e5beSEduardo Habkost        user_classes = [TypeCheckMacro, DeclareObjCheckers, DeclareInstanceChecker, DeclareClassCheckers, InterfaceCheckMacro]
79594dfc0f3SEduardo Habkost
79694dfc0f3SEduardo Habkost        # first we scan for all symbol definitions and usage:
79794dfc0f3SEduardo Habkost        for dc in definition_classes:
79894dfc0f3SEduardo Habkost            defs = self.file.matches_of_type(dc)
79994dfc0f3SEduardo Habkost            for d in defs:
80094dfc0f3SEduardo Habkost                DBG("scanning %r", d)
80194dfc0f3SEduardo Habkost                for i in d.provided_identifiers():
80294dfc0f3SEduardo Habkost                    index.setdefault(i, SymbolUserList([], [])).definitions.append(d)
80394dfc0f3SEduardo Habkost        DBG("index: %r", list(index.keys()))
80494dfc0f3SEduardo Habkost        for uc in user_classes:
80594dfc0f3SEduardo Habkost            users = self.file.matches_of_type(uc)
80694dfc0f3SEduardo Habkost            for u in users:
80794dfc0f3SEduardo Habkost                for i in u.required_identifiers():
80894dfc0f3SEduardo Habkost                    index.setdefault(i, SymbolUserList([], [])).users.append(u)
80994dfc0f3SEduardo Habkost
81094dfc0f3SEduardo Habkost        # validate all symbols:
81194dfc0f3SEduardo Habkost        for i,ul in index.items():
81294dfc0f3SEduardo Habkost            if not ul.users:
81394dfc0f3SEduardo Habkost                # unused symbol
81494dfc0f3SEduardo Habkost                continue
81594dfc0f3SEduardo Habkost
81694dfc0f3SEduardo Habkost            # symbol not defined
81794dfc0f3SEduardo Habkost            if len(ul.definitions) == 0:
81894dfc0f3SEduardo Habkost                if i.type == 'include':
81994dfc0f3SEduardo Habkost                   includes, = self.file.matches_of_type(InitialIncludes)
82094dfc0f3SEduardo Habkost                   #FIXME: don't do this if we're already inside qom/object.h
82194dfc0f3SEduardo Habkost                   yield includes.append(f'#include {i.name}\n')
82294dfc0f3SEduardo Habkost                else:
82394dfc0f3SEduardo Habkost                    u.warn("definition of %s %s not found in file", i.type, i.name)
82494dfc0f3SEduardo Habkost                continue
82594dfc0f3SEduardo Habkost
82694dfc0f3SEduardo Habkost            # symbol defined twice:
82794dfc0f3SEduardo Habkost            if len(ul.definitions) > 1:
82894dfc0f3SEduardo Habkost                ul.definitions[1].warn("%s defined twice", i.name)
82994dfc0f3SEduardo Habkost                ul.definitions[0].warn("previously defined here")
83094dfc0f3SEduardo Habkost                continue
83194dfc0f3SEduardo Habkost
83294dfc0f3SEduardo Habkost            # symbol defined.  check if all users are after its definition:
83394dfc0f3SEduardo Habkost            assert len(ul.definitions) == 1
83494dfc0f3SEduardo Habkost            definition = ul.definitions[0]
83594dfc0f3SEduardo Habkost            DBG("handling repositioning of %r", definition)
83694dfc0f3SEduardo Habkost            earliest = min(ul.users, key=lambda u: u.start())
83794dfc0f3SEduardo Habkost            if earliest.start() > definition.start():
83894dfc0f3SEduardo Habkost                DBG("%r is OK", definition)
83994dfc0f3SEduardo Habkost                continue
84094dfc0f3SEduardo Habkost
84194dfc0f3SEduardo Habkost            DBG("%r needs to be moved", definition)
84294dfc0f3SEduardo Habkost            if isinstance(definition, SimpleTypedefMatch) \
84394dfc0f3SEduardo Habkost               or isinstance(definition, ConstantDefine):
84494dfc0f3SEduardo Habkost                # simple typedef or define can be moved directly:
84594dfc0f3SEduardo Habkost                yield definition.make_removal_patch()
84694dfc0f3SEduardo Habkost                yield earliest.prepend(definition.group(0))
84794dfc0f3SEduardo Habkost            elif isinstance(definition, FullStructTypedefMatch) \
84894dfc0f3SEduardo Habkost                 and definition.group('structname'):
84994dfc0f3SEduardo Habkost                # full struct typedef is more complex: we need to remove
85094dfc0f3SEduardo Habkost                # the typedef
85194dfc0f3SEduardo Habkost                yield from definition.move_typedef(earliest.start())
85294dfc0f3SEduardo Habkost            else:
85394dfc0f3SEduardo Habkost                definition.warn("definition of %s %s needs to be moved earlier in the file", i.type, i.name)
85494dfc0f3SEduardo Habkost                earliest.warn("definition of %s %s is used here", i.type, i.name)
85594dfc0f3SEduardo Habkost
8564a15e5beSEduardo Habkost
8574a15e5beSEduardo Habkostclass EmptyPreprocessorConditional(FileMatch):
8584a15e5beSEduardo Habkost    """Delete empty preprocessor conditionals"""
8594a15e5beSEduardo Habkost    regexp = r'^[ \t]*#(if|ifdef)[ \t].*\n+[ \t]*#endif[ \t]*\n'
8604a15e5beSEduardo Habkost    def gen_patches(self) -> Iterable[Patch]:
8614a15e5beSEduardo Habkost        yield self.make_removal_patch()
862