1# Copyright (C) 2020 Red Hat Inc.
2#
3# Authors:
4#  Eduardo Habkost <ehabkost@redhat.com>
5#
6# This work is licensed under the terms of the GNU GPL, version 2.  See
7# the COPYING file in the top-level directory.
8import re
9from itertools import chain
10from typing import *
11
12from .regexps import *
13from .patching import *
14from .utils import *
15
16import logging
17logger = logging.getLogger(__name__)
18DBG = logger.debug
19INFO = logger.info
20WARN = logger.warning
21
22# simple expressions:
23
24RE_CONSTANT = OR(RE_STRING, RE_NUMBER)
25
26class DefineDirective(FileMatch):
27    """Match any #define directive"""
28    regexp = S(r'^[ \t]*#[ \t]*define', CPP_SPACE, NAMED('name', RE_IDENTIFIER), r'\b')
29
30class ExpressionDefine(FileMatch):
31    """Simple #define preprocessor directive for an expression"""
32    regexp = S(r'^[ \t]*#[ \t]*define', CPP_SPACE, NAMED('name', RE_IDENTIFIER),
33               CPP_SPACE, NAMED('value', RE_EXPRESSION), r'[ \t]*\n')
34
35    def provided_identifiers(self) -> Iterable[RequiredIdentifier]:
36        yield RequiredIdentifier('constant', self.group('name'))
37
38class ConstantDefine(ExpressionDefine):
39    """Simple #define preprocessor directive for a number or string constant"""
40    regexp = S(r'^[ \t]*#[ \t]*define', CPP_SPACE, NAMED('name', RE_IDENTIFIER),
41               CPP_SPACE, NAMED('value', RE_CONSTANT), r'[ \t]*\n')
42
43
44class TypeIdentifiers(NamedTuple):
45    """Type names found in type declarations"""
46    # TYPE_MYDEVICE
47    typename: Optional[str]
48    # MYDEVICE
49    uppercase: Optional[str] = None
50    # MyDevice
51    instancetype: Optional[str] = None
52    # MyDeviceClass
53    classtype: Optional[str] = None
54    # my_device
55    lowercase: Optional[str] = None
56
57    def allfields(self):
58        return tuple(getattr(self, f) for f in self._fields)
59
60    def merge(self, other: 'TypeIdentifiers') -> Optional['TypeIdentifiers']:
61        """Check if identifiers match, return new identifier with complete list"""
62        if any(not opt_compare(a, b) for a,b in zip(self, other)):
63            return None
64        return TypeIdentifiers(*(merge(a, b) for a,b in zip(self, other)))
65
66    def __str__(self) -> str:
67        values = ((f, getattr(self, f)) for f in self._fields)
68        s = ', '.join('%s=%s' % (f,v) for f,v in values if v is not None)
69        return f'{s}'
70
71    def check_consistency(self) -> List[str]:
72        """Check if identifiers are consistent with each other,
73        return list of problems (or empty list if everything seems consistent)
74        """
75        r = []
76        if self.typename is None:
77            r.append("typename (TYPE_MYDEVICE) is unavailable")
78
79        if self.uppercase is None:
80            r.append("uppercase name is unavailable")
81
82        if (self.instancetype is not None
83            and self.classtype is not None
84            and self.classtype != f'{self.instancetype}Class'):
85                r.append("class typedef %s doesn't match instance typedef %s" %
86                         (self.classtype, self.instancetype))
87
88        if (self.uppercase is not None
89            and self.typename is not None
90            and f'TYPE_{self.uppercase}' != self.typename):
91            r.append("uppercase name (%s) doesn't match type name (%s)" %
92                     (self.uppercase, self.typename))
93
94        return r
95
96class TypedefMatch(FileMatch):
97    """typedef declaration"""
98    def provided_identifiers(self) -> Iterable[RequiredIdentifier]:
99        yield RequiredIdentifier('type', self.group('name'))
100
101class SimpleTypedefMatch(TypedefMatch):
102    """Simple typedef declaration
103    (no replacement rules)"""
104    regexp = S(r'^[ \t]*typedef', SP,
105               NAMED('typedef_type', RE_TYPE), SP,
106               NAMED('name', RE_IDENTIFIER), r'\s*;[ \t]*\n')
107
108RE_MACRO_DEFINE = S(r'^[ \t]*#\s*define\s+', NAMED('name', RE_IDENTIFIER),
109                    r'\s*\(\s*', RE_IDENTIFIER, r'\s*\)', CPP_SPACE)
110
111RE_STRUCT_ATTRIBUTE = r'QEMU_PACKED'
112
113# This doesn't parse the struct definitions completely, it just assumes
114# the closing brackets are going to be in an unindented line:
115RE_FULL_STRUCT = S('struct', SP, M(RE_IDENTIFIER, n='?', name='structname'), SP,
116                   NAMED('body', r'{\n',
117                         # acceptable inside the struct body:
118                         # - lines starting with space or tab
119                         # - empty lines
120                         # - preprocessor directives
121                         # - comments
122                         OR(r'[ \t][^\n]*\n',
123                            r'#[^\n]*\n',
124                            r'\n',
125                            S(r'[ \t]*', RE_COMMENT, r'[ \t]*\n'),
126                            repeat='*?'),
127                         r'}', M(RE_STRUCT_ATTRIBUTE, SP, n='*')))
128RE_STRUCT_TYPEDEF = S(r'^[ \t]*typedef', SP, RE_FULL_STRUCT, SP,
129                      NAMED('name', RE_IDENTIFIER), r'\s*;[ \t]*\n')
130
131class FullStructTypedefMatch(TypedefMatch):
132    """typedef struct [SomeStruct] { ...} SomeType
133    Will be replaced by separate struct declaration + typedef
134    """
135    regexp = RE_STRUCT_TYPEDEF
136
137    def make_structname(self) -> str:
138        """Make struct name for struct+typedef split"""
139        name = self.group('structname')
140        if not name:
141            name = self.name
142        return name
143
144    def strip_typedef(self) -> Patch:
145        """generate patch that will strip typedef from the struct declaration
146
147        The caller is responsible for readding the typedef somewhere else.
148        """
149        name = self.make_structname()
150        body = self.group('body')
151        return self.make_patch(f'struct {name} {body};\n')
152
153    def make_simple_typedef(self) -> str:
154        structname = self.make_structname()
155        name = self.name
156        return f'typedef struct {structname} {name};\n'
157
158    def move_typedef(self, position) -> Iterator[Patch]:
159        """Generate patches to move typedef elsewhere"""
160        yield self.strip_typedef()
161        yield Patch(position, position, self.make_simple_typedef())
162
163    def split_typedef(self) -> Iterator[Patch]:
164        """Split into struct definition + typedef in-place"""
165        yield self.strip_typedef()
166        yield self.append(self.make_simple_typedef())
167
168class StructTypedefSplit(FullStructTypedefMatch):
169    """split struct+typedef declaration"""
170    def gen_patches(self) -> Iterator[Patch]:
171        if self.group('structname'):
172            yield from self.split_typedef()
173
174class DuplicatedTypedefs(SimpleTypedefMatch):
175    """Delete ALL duplicate typedefs (unsafe)"""
176    def gen_patches(self) -> Iterable[Patch]:
177        other_td = [td for td in chain(self.file.matches_of_type(SimpleTypedefMatch),
178                                       self.file.matches_of_type(FullStructTypedefMatch))
179                    if td.name == self.name]
180        DBG("other_td: %r", other_td)
181        if any(td.start() < self.start() for td in other_td):
182            # patch only if handling the first typedef
183            return
184        for td in other_td:
185            if isinstance(td, SimpleTypedefMatch):
186                DBG("other td: %r", td.match.groupdict())
187                if td.group('typedef_type') != self.group('typedef_type'):
188                    yield td.make_removal_patch()
189            elif isinstance(td, FullStructTypedefMatch):
190                DBG("other td: %r", td.match.groupdict())
191                if self.group('typedef_type') == 'struct '+td.group('structname'):
192                    yield td.strip_typedef()
193
194class QOMDuplicatedTypedefs(DuplicatedTypedefs):
195    """Delete duplicate typedefs if used by QOM type"""
196    def gen_patches(self) -> Iterable[Patch]:
197        qom_macros = [TypeCheckMacro, DeclareInstanceChecker, DeclareClassCheckers, DeclareObjCheckers]
198        qom_matches = chain(*(self.file.matches_of_type(t) for t in qom_macros))
199        in_use = any(RequiredIdentifier('type', self.name) in m.required_identifiers()
200                     for m in qom_matches)
201        if in_use:
202            yield from DuplicatedTypedefs.gen_patches(self)
203
204class QOMStructTypedefSplit(FullStructTypedefMatch):
205    """split struct+typedef declaration if used by QOM type"""
206    def gen_patches(self) -> Iterator[Patch]:
207        qom_macros = [TypeCheckMacro, DeclareInstanceChecker, DeclareClassCheckers, DeclareObjCheckers]
208        qom_matches = chain(*(self.file.matches_of_type(t) for t in qom_macros))
209        in_use = any(RequiredIdentifier('type', self.name) in m.required_identifiers()
210                     for m in qom_matches)
211        if in_use:
212            yield from self.split_typedef()
213
214def typedefs(file: FileInfo) -> Iterable[TypedefMatch]:
215    return (cast(TypedefMatch, m)
216            for m in chain(file.matches_of_type(SimpleTypedefMatch),
217                           file.matches_of_type(FullStructTypedefMatch)))
218
219def find_typedef(f: FileInfo, name: Optional[str]) -> Optional[TypedefMatch]:
220    if not name:
221        return None
222    for td in typedefs(f):
223        if td.name == name:
224            return td
225    return None
226
227CHECKER_MACROS = ['OBJECT_CHECK', 'OBJECT_CLASS_CHECK', 'OBJECT_GET_CLASS']
228CheckerMacroName = Literal['OBJECT_CHECK', 'OBJECT_CLASS_CHECK', 'OBJECT_GET_CLASS']
229
230RE_CHECK_MACRO = \
231    S(RE_MACRO_DEFINE,
232      OR(*CHECKER_MACROS, name='checker'),
233      M(r'\s*\(\s*', OR(NAMED('typedefname', RE_IDENTIFIER), RE_TYPE, name='c_type'), r'\s*,', CPP_SPACE,
234        OPTIONAL_PARS(RE_IDENTIFIER), r',', CPP_SPACE,
235        NAMED('qom_typename', RE_IDENTIFIER), r'\s*\)\n',
236        n='?', name='check_args'))
237
238EXPECTED_CHECKER_SUFFIXES: List[Tuple[CheckerMacroName, str]] = [
239    ('OBJECT_GET_CLASS', '_GET_CLASS'),
240    ('OBJECT_CLASS_CHECK', '_CLASS'),
241]
242
243class TypeCheckMacro(FileMatch):
244    """OBJECT_CHECK/OBJECT_CLASS_CHECK/OBJECT_GET_CLASS macro definitions
245    Will be replaced by DECLARE_*_CHECKERS macro
246    """
247    regexp = RE_CHECK_MACRO
248
249    @property
250    def checker(self) -> CheckerMacroName:
251        """Name of checker macro being used"""
252        return self.group('checker') # type: ignore
253
254    @property
255    def typedefname(self) -> Optional[str]:
256        return self.group('typedefname')
257
258    def find_typedef(self) -> Optional[TypedefMatch]:
259        return find_typedef(self.file, self.typedefname)
260
261    def sanity_check(self) -> None:
262        DBG("groups: %r", self.match.groups())
263        if not self.group('check_args'):
264            self.warn("type check macro not parsed completely: %s", self.name)
265            return
266        DBG("type identifiers: %r", self.type_identifiers)
267        if self.typedefname and self.find_typedef() is None:
268            self.warn("typedef used by %s not found", self.name)
269
270    def find_matching_macros(self) -> List['TypeCheckMacro']:
271        """Find other check macros that generate the same macro names
272
273        The returned list will always be sorted.
274        """
275        my_ids = self.type_identifiers
276        assert my_ids
277        return [m for m in self.file.matches_of_type(TypeCheckMacro)
278                if m.type_identifiers is not None
279                   and my_ids.uppercase is not None
280                   and (my_ids.uppercase == m.type_identifiers.uppercase
281                        or my_ids.typename == m.type_identifiers.typename)]
282
283    def merge_ids(self, matches: List['TypeCheckMacro']) -> Optional[TypeIdentifiers]:
284        """Try to merge info about type identifiers from all matches in a list"""
285        if not matches:
286            return None
287        r = matches[0].type_identifiers
288        if r is None:
289            return None
290        for m in matches[1:]:
291            assert m.type_identifiers
292            new = r.merge(m.type_identifiers)
293            if new is None:
294                self.warn("macro %s identifiers (%s) don't match macro %s (%s)",
295                          matches[0].name, r, m.name, m.type_identifiers)
296                return None
297            r = new
298        return r
299
300    def required_identifiers(self) -> Iterable[RequiredIdentifier]:
301        yield RequiredIdentifier('include', '"qom/object.h"')
302        if self.type_identifiers is None:
303            return
304        # to make sure typedefs will be moved above all related macros,
305        # return dependencies from all of them, not just this match
306        for m in self.find_matching_macros():
307            yield RequiredIdentifier('type', m.group('c_type'))
308            yield RequiredIdentifier('constant', m.group('qom_typename'))
309
310    @property
311    def type_identifiers(self) -> Optional[TypeIdentifiers]:
312        """Extract type identifier information from match"""
313        typename = self.group('qom_typename')
314        c_type = self.group('c_type')
315        if not typename or not c_type:
316            return None
317        typedef = self.group('typedefname')
318        classtype = None
319        instancetype = None
320        uppercase = None
321        expected_suffix = dict(EXPECTED_CHECKER_SUFFIXES).get(self.checker)
322
323        # here the available data depends on the checker macro being called:
324        # - we need to remove the suffix from the macro name
325        # - depending on the macro type, we know the class type name, or
326        #   the instance type name
327        if self.checker in ('OBJECT_GET_CLASS', 'OBJECT_CLASS_CHECK'):
328            classtype = c_type
329        elif self.checker == 'OBJECT_CHECK':
330            instancetype = c_type
331            uppercase = self.name
332        else:
333            assert False
334        if expected_suffix and self.name.endswith(expected_suffix):
335            uppercase = self.name[:-len(expected_suffix)]
336        return TypeIdentifiers(typename=typename, classtype=classtype,
337                               instancetype=instancetype, uppercase=uppercase)
338
339    def gen_patches(self) -> Iterable[Patch]:
340        # the implementation is a bit tricky because we need to group
341        # macros dealing with the same type into a single declaration
342        if self.type_identifiers is None:
343            self.warn("couldn't extract type information from macro %s", self.name)
344            return
345
346        if self.name == 'INTERFACE_CLASS':
347            # INTERFACE_CLASS is special and won't be patched
348            return
349
350        for checker,suffix in EXPECTED_CHECKER_SUFFIXES:
351            if self.name.endswith(suffix):
352                if self.checker != checker:
353                    self.warn("macro %s is using macro %s instead of %s", self.name, self.checker, checker)
354                    return
355                break
356
357        matches = self.find_matching_macros()
358        DBG("found %d matching macros: %s", len(matches), ' '.join(m.name for m in matches))
359        # we will generate patches only when processing the first macro:
360        if matches[0].start != self.start:
361            DBG("skipping %s (will patch when handling %s)", self.name, matches[0].name)
362            return
363
364
365        ids = self.merge_ids(matches)
366        if ids is None:
367            DBG("type identifier mismatch, won't patch %s", self.name)
368            return
369
370        if not ids.uppercase:
371            self.warn("macro %s doesn't follow the expected name pattern", self.name)
372            return
373        if not ids.typename:
374            self.warn("macro %s: couldn't extract type name", self.name)
375            return
376
377        #issues = ids.check_consistency()
378        #if issues:
379        #    for i in issues:
380        #        self.warn("inconsistent identifiers: %s", i)
381
382        names = [n for n in (ids.instancetype, ids.classtype, ids.uppercase, ids.typename)
383                 if n is not None]
384        if len(set(names)) != len(names):
385            self.warn("duplicate names used by macro: %r", ids)
386            return
387
388        assert ids.classtype or ids.instancetype
389        assert ids.typename
390        assert ids.uppercase
391        if ids.classtype and ids.instancetype:
392            new_decl = (f'DECLARE_OBJ_CHECKERS({ids.instancetype}, {ids.classtype},\n'
393                        f'                     {ids.uppercase}, {ids.typename})\n')
394        elif ids.classtype:
395            new_decl = (f'DECLARE_CLASS_CHECKERS({ids.classtype}, {ids.uppercase},\n'
396                        f'                       {ids.typename})\n')
397        elif ids.instancetype:
398            new_decl = (f'DECLARE_INSTANCE_CHECKER({ids.instancetype}, {ids.uppercase},\n'
399                        f'                         {ids.typename})\n')
400        else:
401            assert False
402
403        # we need to ensure the typedefs are already available
404        issues = []
405        for t in [ids.instancetype, ids.classtype]:
406            if not t:
407                continue
408            if re.fullmatch(RE_STRUCT_TYPE, t):
409                self.info("type %s is not a typedef", t)
410                continue
411            td = find_typedef(self.file, t)
412            #if not td and self.allfiles.find_file('include/qemu/typedefs.h'):
413            #
414            if not td:
415                # it is OK if the typedef is in typedefs.h
416                f = self.allfiles.find_file('include/qemu/typedefs.h')
417                if f and find_typedef(f, t):
418                    self.info("typedef %s found in typedefs.h", t)
419                    continue
420
421                issues.append("couldn't find typedef %s" % (t))
422            elif td.start() > self.start():
423                issues.append("typedef %s need to be moved earlier in the file" % (td.name))
424
425        for issue in issues:
426            self.warn(issue)
427
428        if issues and not self.file.force:
429            return
430
431        # delete all matching macros and add new declaration:
432        for m in matches:
433            yield m.make_patch('')
434        for issue in issues:
435            yield self.prepend("/* FIXME: %s */\n" % (issue))
436        yield self.append(new_decl)
437
438class InterfaceCheckMacro(FileMatch):
439    """Type checking macro using INTERFACE_CHECK
440    Will be replaced by DECLARE_INTERFACE_CHECKER
441    """
442    regexp = S(RE_MACRO_DEFINE,
443               'INTERFACE_CHECK',
444               r'\s*\(\s*', OR(NAMED('instancetype', RE_IDENTIFIER), RE_TYPE, name='c_type'),
445               r'\s*,', CPP_SPACE,
446               OPTIONAL_PARS(RE_IDENTIFIER), r',', CPP_SPACE,
447               NAMED('qom_typename', RE_IDENTIFIER), r'\s*\)\n')
448
449    def required_identifiers(self) -> Iterable[RequiredIdentifier]:
450        yield RequiredIdentifier('include', '"qom/object.h"')
451        yield RequiredIdentifier('type', self.group('instancetype'))
452        yield RequiredIdentifier('constant', self.group('qom_typename'))
453
454    def gen_patches(self) -> Iterable[Patch]:
455        if self.file.filename_matches('qom/object.h'):
456            self.debug("skipping object.h")
457            return
458
459        typename = self.group('qom_typename')
460        uppercase = self.name
461        instancetype = self.group('instancetype')
462        c = f"DECLARE_INTERFACE_CHECKER({instancetype}, {uppercase},\n"+\
463            f"                          {typename})\n"
464        yield self.make_patch(c)
465
466
467class TypeDeclaration(FileMatch):
468    """Parent class to all type declarations"""
469    @property
470    def instancetype(self) -> Optional[str]:
471        return self.getgroup('instancetype')
472
473    @property
474    def classtype(self) -> Optional[str]:
475        return self.getgroup('classtype')
476
477    @property
478    def typename(self) -> Optional[str]:
479        return self.getgroup('typename')
480
481class TypeCheckerDeclaration(TypeDeclaration):
482    """Parent class to all type checker declarations"""
483    @property
484    def typename(self) -> str:
485        return self.group('typename')
486
487    @property
488    def uppercase(self) -> str:
489        return self.group('uppercase')
490
491class DeclareInstanceChecker(TypeCheckerDeclaration):
492    """DECLARE_INSTANCE_CHECKER use"""
493    #TODO: replace lonely DECLARE_INSTANCE_CHECKER with DECLARE_OBJ_CHECKERS
494    #      if all types are found.
495    #      This will require looking up the correct class type in the TypeInfo
496    #      structs in another file
497    regexp = S(r'^[ \t]*DECLARE_INSTANCE_CHECKER\s*\(\s*',
498               NAMED('instancetype', RE_TYPE), r'\s*,\s*',
499               NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*',
500               OR(RE_IDENTIFIER, RE_STRING, RE_MACRO_CONCAT, RE_FUN_CALL, name='typename'), SP,
501               r'\)[ \t]*;?[ \t]*\n')
502
503    def required_identifiers(self) -> Iterable[RequiredIdentifier]:
504        yield RequiredIdentifier('include', '"qom/object.h"')
505        yield RequiredIdentifier('constant', self.group('typename'))
506        yield RequiredIdentifier('type', self.group('instancetype'))
507
508class DeclareInterfaceChecker(TypeCheckerDeclaration):
509    """DECLARE_INTERFACE_CHECKER use"""
510    regexp = S(r'^[ \t]*DECLARE_INTERFACE_CHECKER\s*\(\s*',
511               NAMED('instancetype', RE_TYPE), r'\s*,\s*',
512               NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*',
513               OR(RE_IDENTIFIER, RE_STRING, RE_MACRO_CONCAT, RE_FUN_CALL, name='typename'), SP,
514               r'\)[ \t]*;?[ \t]*\n')
515
516    def required_identifiers(self) -> Iterable[RequiredIdentifier]:
517        yield RequiredIdentifier('include', '"qom/object.h"')
518        yield RequiredIdentifier('constant', self.group('typename'))
519        yield RequiredIdentifier('type', self.group('instancetype'))
520
521class DeclareInstanceType(TypeDeclaration):
522    """DECLARE_INSTANCE_TYPE use"""
523    regexp = S(r'^[ \t]*DECLARE_INSTANCE_TYPE\s*\(\s*',
524               NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*',
525               NAMED('instancetype', RE_TYPE), SP,
526               r'\)[ \t]*;?[ \t]*\n')
527
528    def required_identifiers(self) -> Iterable[RequiredIdentifier]:
529        yield RequiredIdentifier('include', '"qom/object.h"')
530        yield RequiredIdentifier('type', self.group('instancetype'))
531
532class DeclareClassType(TypeDeclaration):
533    """DECLARE_CLASS_TYPE use"""
534    regexp = S(r'^[ \t]*DECLARE_CLASS_TYPE\s*\(\s*',
535               NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*',
536               NAMED('classtype', RE_TYPE), SP,
537               r'\)[ \t]*;?[ \t]*\n')
538
539    def required_identifiers(self) -> Iterable[RequiredIdentifier]:
540        yield RequiredIdentifier('include', '"qom/object.h"')
541        yield RequiredIdentifier('type', self.group('classtype'))
542
543
544
545class DeclareClassCheckers(TypeCheckerDeclaration):
546    """DECLARE_CLASS_CHECKER use"""
547    regexp = S(r'^[ \t]*DECLARE_CLASS_CHECKERS\s*\(\s*',
548               NAMED('classtype', RE_TYPE), r'\s*,\s*',
549               NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*',
550               OR(RE_IDENTIFIER, RE_STRING, RE_MACRO_CONCAT, RE_FUN_CALL, name='typename'), SP,
551               r'\)[ \t]*;?[ \t]*\n')
552
553    def required_identifiers(self) -> Iterable[RequiredIdentifier]:
554        yield RequiredIdentifier('include', '"qom/object.h"')
555        yield RequiredIdentifier('constant', self.group('typename'))
556        yield RequiredIdentifier('type', self.group('classtype'))
557
558class DeclareObjCheckers(TypeCheckerDeclaration):
559    """DECLARE_OBJ_CHECKERS use"""
560    #TODO: detect when OBJECT_DECLARE_SIMPLE_TYPE can be used
561    regexp = S(r'^[ \t]*DECLARE_OBJ_CHECKERS\s*\(\s*',
562               NAMED('instancetype', RE_TYPE), r'\s*,\s*',
563               NAMED('classtype', RE_TYPE), r'\s*,\s*',
564               NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*',
565               OR(RE_IDENTIFIER, RE_STRING, RE_MACRO_CONCAT, RE_FUN_CALL, name='typename'), SP,
566               r'\)[ \t]*;?[ \t]*\n')
567
568    def required_identifiers(self) -> Iterable[RequiredIdentifier]:
569        yield RequiredIdentifier('include', '"qom/object.h"')
570        yield RequiredIdentifier('constant', self.group('typename'))
571        yield RequiredIdentifier('type', self.group('classtype'))
572        yield RequiredIdentifier('type', self.group('instancetype'))
573
574class TypeDeclarationFixup(FileMatch):
575    """Common base class for code that will look at a set of type declarations"""
576    regexp = RE_FILE_BEGIN
577    def gen_patches(self) -> Iterable[Patch]:
578        if self.file.filename_matches('qom/object.h'):
579            self.debug("skipping object.h")
580            return
581
582        # group checkers by uppercase name:
583        decl_types: List[Type[TypeDeclaration]] = [DeclareInstanceChecker, DeclareInstanceType,
584                                                   DeclareClassCheckers, DeclareClassType,
585                                                   DeclareObjCheckers]
586        checker_dict: Dict[str, List[TypeDeclaration]] = {}
587        for t in decl_types:
588            for m in self.file.matches_of_type(t):
589                checker_dict.setdefault(m.group('uppercase'), []).append(m)
590        self.debug("checker_dict: %r", checker_dict)
591        for uppercase,checkers in checker_dict.items():
592            fields = ('instancetype', 'classtype', 'uppercase', 'typename')
593            fvalues = dict((field, set(getattr(m, field) for m in checkers
594                                       if getattr(m, field, None) is not None))
595                            for field in fields)
596            for field,values in fvalues.items():
597                if len(values) > 1:
598                    for c in checkers:
599                        c.warn("%s mismatch (%s)", field, ' '.join(values))
600                    return
601
602            field_dict = dict((f, v.pop() if v else None) for f,v in fvalues.items())
603            yield from self.gen_patches_for_type(uppercase, checkers, field_dict)
604
605    def find_conflicts(self, uppercase: str, checkers: List[TypeDeclaration]) -> bool:
606        """Look for conflicting declarations that would make it unsafe to add new ones"""
607        conflicting: List[FileMatch] = []
608        # conflicts in the same file:
609        conflicting.extend(chain(self.file.find_matches(DefineDirective, uppercase),
610                                 self.file.find_matches(DeclareInterfaceChecker, uppercase, 'uppercase'),
611                                 self.file.find_matches(DeclareClassType, uppercase, 'uppercase'),
612                                 self.file.find_matches(DeclareInstanceType, uppercase, 'uppercase')))
613
614        # conflicts in another file:
615        conflicting.extend(o for o in chain(self.allfiles.find_matches(DeclareInstanceChecker, uppercase, 'uppercase'),
616                                            self.allfiles.find_matches(DeclareClassCheckers, uppercase, 'uppercase'),
617                                            self.allfiles.find_matches(DeclareInterfaceChecker, uppercase, 'uppercase'),
618                                            self.allfiles.find_matches(DefineDirective, uppercase))
619                           if o is not None and o.file != self.file
620                               # if both are .c files, there's no conflict at all:
621                               and not (o.file.filename.suffix == '.c' and
622                                       self.file.filename.suffix == '.c'))
623
624        if conflicting:
625            for c in checkers:
626                c.warn("skipping due to conflicting %s macro", uppercase)
627            for o in conflicting:
628                if o is None:
629                    continue
630                o.warn("conflicting %s macro is here", uppercase)
631            return True
632
633        return False
634
635    def gen_patches_for_type(self, uppercase: str,
636                             checkers: List[TypeDeclaration],
637                             fields: Dict[str, Optional[str]]) -> Iterable[Patch]:
638        """Should be reimplemented by subclasses"""
639        return
640        yield
641
642class DeclareVoidTypes(TypeDeclarationFixup):
643    """Add DECLARE_*_TYPE(..., void) when there's no declared type"""
644    regexp = RE_FILE_BEGIN
645    def gen_patches_for_type(self, uppercase: str,
646                             checkers: List[TypeDeclaration],
647                             fields: Dict[str, Optional[str]]) -> Iterable[Patch]:
648        if self.find_conflicts(uppercase, checkers):
649            return
650
651        #_,last_checker = max((m.start(), m) for m in checkers)
652        _,first_checker = min((m.start(), m) for m in checkers)
653
654        if not any(m.instancetype for m in checkers):
655            yield first_checker.prepend(f'DECLARE_INSTANCE_TYPE({uppercase}, void)\n')
656        if not any(m.classtype for m in checkers):
657            yield first_checker.prepend(f'DECLARE_CLASS_TYPE({uppercase}, void)\n')
658
659        #if not all(len(v) == 1 for v in fvalues.values()):
660        #    return
661        #
662        #final_values = dict((field, values.pop())
663        #                    for field,values in fvalues.items())
664        #s = (f"DECLARE_OBJ_CHECKERS({final_values['instancetype']}, {final_values['classtype']},\n"+
665        #        f"                     {final_values['uppercase']}, {final_values['typename']})\n")
666        #for c in checkers:
667        #    yield c.make_removal_patch()
668        #yield last_checker.append(s)
669
670
671class AddDeclareTypeName(TypeDeclarationFixup):
672    """Add DECLARE_TYPE_NAME declarations if necessary"""
673    def gen_patches_for_type(self, uppercase: str,
674                             checkers: List[TypeDeclaration],
675                             fields: Dict[str, Optional[str]]) -> Iterable[Patch]:
676        typename = fields.get('typename')
677        if typename is None:
678            self.warn("typename unavailable")
679            return
680        if typename == f'TYPE_{uppercase}':
681            self.info("already using TYPE_%s as type name", uppercase)
682            return
683        if self.file.find_match(DeclareTypeName, uppercase, 'uppercase'):
684            self.info("type name for %s already declared", uppercase)
685            return
686        _,first_checker = min((m.start(), m) for m in checkers)
687        s = f'DECLARE_TYPE_NAME({uppercase}, {typename})\n'
688        yield first_checker.prepend(s)
689
690class TrivialClassStruct(FileMatch):
691    """Trivial class struct"""
692    regexp = S(r'^[ \t]*struct\s*', NAMED('name', RE_IDENTIFIER),
693               r'\s*{\s*', NAMED('parent_struct', RE_IDENTIFIER), r'\s*parent(_class)?\s*;\s*};\n')
694
695class DeclareTypeName(FileMatch):
696    """DECLARE_TYPE_NAME usage"""
697    regexp = S(r'^[ \t]*DECLARE_TYPE_NAME\s*\(',
698               NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*',
699               OR(RE_IDENTIFIER, RE_STRING, RE_MACRO_CONCAT, RE_FUN_CALL, name='typename'),
700               r'\s*\);?[ \t]*\n')
701
702class ObjectDeclareType(TypeCheckerDeclaration):
703    """OBJECT_DECLARE_TYPE usage
704    Will be replaced with OBJECT_DECLARE_SIMPLE_TYPE if possible
705    """
706    regexp = S(r'^[ \t]*OBJECT_DECLARE_TYPE\s*\(',
707               NAMED('instancetype', RE_TYPE), r'\s*,\s*',
708               NAMED('classtype', RE_TYPE), r'\s*,\s*',
709               NAMED('uppercase', RE_IDENTIFIER), SP,
710               r'\)[ \t]*;?[ \t]*\n')
711
712    def gen_patches(self):
713        DBG("groups: %r", self.match.groupdict())
714        trivial_struct = self.file.find_match(TrivialClassStruct, self.group('classtype'))
715        if trivial_struct:
716            d = self.match.groupdict().copy()
717            d['parent_struct'] = trivial_struct.group("parent_struct")
718            yield trivial_struct.make_removal_patch()
719            c = ("OBJECT_DECLARE_SIMPLE_TYPE(%(instancetype)s, %(lowercase)s,\n"
720                 "                           %(uppercase)s, %(parent_struct)s)\n" % d)
721            yield self.make_patch(c)
722
723class ObjectDeclareSimpleType(TypeCheckerDeclaration):
724    """OBJECT_DECLARE_SIMPLE_TYPE usage"""
725    regexp = S(r'^[ \t]*OBJECT_DECLARE_SIMPLE_TYPE\s*\(',
726               NAMED('instancetype', RE_TYPE), r'\s*,\s*',
727               NAMED('uppercase', RE_IDENTIFIER), SP,
728               r'\)[ \t]*;?[ \t]*\n')
729
730class OldStyleObjectDeclareSimpleType(TypeCheckerDeclaration):
731    """OBJECT_DECLARE_SIMPLE_TYPE usage (old API)"""
732    regexp = S(r'^[ \t]*OBJECT_DECLARE_SIMPLE_TYPE\s*\(',
733               NAMED('instancetype', RE_TYPE), r'\s*,\s*',
734               NAMED('lowercase', RE_IDENTIFIER), r'\s*,\s*',
735               NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*',
736               NAMED('parent_classtype', RE_TYPE), SP,
737               r'\)[ \t]*;?[ \t]*\n')
738
739    @property
740    def classtype(self) -> Optional[str]:
741        instancetype = self.instancetype
742        assert instancetype
743        return f"{instancetype}Class"
744
745def find_typename_uppercase(files: FileList, typename: str) -> Optional[str]:
746    """Try to find what's the right MODULE_OBJ_NAME for a given type name"""
747    decl = files.find_match(DeclareTypeName, name=typename, group='typename')
748    if decl:
749        return decl.group('uppercase')
750    if typename.startswith('TYPE_'):
751        return typename[len('TYPE_'):]
752    return None
753
754def find_type_checkers(files:FileList, name:str, group:str='uppercase') -> Iterable[TypeCheckerDeclaration]:
755    """Find usage of DECLARE*CHECKER macro"""
756    c: Type[TypeCheckerDeclaration]
757    for c in (DeclareInstanceChecker, DeclareClassCheckers, DeclareObjCheckers, ObjectDeclareType, ObjectDeclareSimpleType):
758        yield from files.find_matches(c, name=name, group=group)
759
760class Include(FileMatch):
761    """#include directive"""
762    regexp = RE_INCLUDE
763    def provided_identifiers(self) -> Iterable[RequiredIdentifier]:
764        yield RequiredIdentifier('include', self.group('includepath'))
765
766class InitialIncludes(FileMatch):
767    """Initial #include block"""
768    regexp = S(RE_FILE_BEGIN,
769               M(SP, RE_COMMENTS,
770                 r'^[ \t]*#[ \t]*ifndef[ \t]+', RE_IDENTIFIER, r'[ \t]*\n',
771                 n='?', name='ifndef_block'),
772               M(SP, RE_COMMENTS,
773                 OR(RE_INCLUDE, RE_SIMPLEDEFINE),
774                 n='*', name='includes'))
775
776class SymbolUserList(NamedTuple):
777    definitions: List[FileMatch]
778    users: List[FileMatch]
779
780class MoveSymbols(FileMatch):
781    """Handle missing symbols
782    - Move typedefs and defines when necessary
783    - Add missing #include lines when necessary
784    """
785    regexp = RE_FILE_BEGIN
786
787    def gen_patches(self) -> Iterator[Patch]:
788        if self.file.filename_matches('qom/object.h'):
789            self.debug("skipping object.h")
790            return
791
792        index: Dict[RequiredIdentifier, SymbolUserList] = {}
793        definition_classes = [SimpleTypedefMatch, FullStructTypedefMatch, ConstantDefine, Include]
794        user_classes = [TypeCheckMacro, DeclareObjCheckers, DeclareInstanceChecker, DeclareClassCheckers, InterfaceCheckMacro]
795
796        # first we scan for all symbol definitions and usage:
797        for dc in definition_classes:
798            defs = self.file.matches_of_type(dc)
799            for d in defs:
800                DBG("scanning %r", d)
801                for i in d.provided_identifiers():
802                    index.setdefault(i, SymbolUserList([], [])).definitions.append(d)
803        DBG("index: %r", list(index.keys()))
804        for uc in user_classes:
805            users = self.file.matches_of_type(uc)
806            for u in users:
807                for i in u.required_identifiers():
808                    index.setdefault(i, SymbolUserList([], [])).users.append(u)
809
810        # validate all symbols:
811        for i,ul in index.items():
812            if not ul.users:
813                # unused symbol
814                continue
815
816            # symbol not defined
817            if len(ul.definitions) == 0:
818                if i.type == 'include':
819                   includes, = self.file.matches_of_type(InitialIncludes)
820                   #FIXME: don't do this if we're already inside qom/object.h
821                   yield includes.append(f'#include {i.name}\n')
822                else:
823                    u.warn("definition of %s %s not found in file", i.type, i.name)
824                continue
825
826            # symbol defined twice:
827            if len(ul.definitions) > 1:
828                ul.definitions[1].warn("%s defined twice", i.name)
829                ul.definitions[0].warn("previously defined here")
830                continue
831
832            # symbol defined.  check if all users are after its definition:
833            assert len(ul.definitions) == 1
834            definition = ul.definitions[0]
835            DBG("handling repositioning of %r", definition)
836            earliest = min(ul.users, key=lambda u: u.start())
837            if earliest.start() > definition.start():
838                DBG("%r is OK", definition)
839                continue
840
841            DBG("%r needs to be moved", definition)
842            if isinstance(definition, SimpleTypedefMatch) \
843               or isinstance(definition, ConstantDefine):
844                # simple typedef or define can be moved directly:
845                yield definition.make_removal_patch()
846                yield earliest.prepend(definition.group(0))
847            elif isinstance(definition, FullStructTypedefMatch) \
848                 and definition.group('structname'):
849                # full struct typedef is more complex: we need to remove
850                # the typedef
851                yield from definition.move_typedef(earliest.start())
852            else:
853                definition.warn("definition of %s %s needs to be moved earlier in the file", i.type, i.name)
854                earliest.warn("definition of %s %s is used here", i.type, i.name)
855
856
857class EmptyPreprocessorConditional(FileMatch):
858    """Delete empty preprocessor conditionals"""
859    regexp = r'^[ \t]*#(if|ifdef)[ \t].*\n+[ \t]*#endif[ \t]*\n'
860    def gen_patches(self) -> Iterable[Patch]:
861        yield self.make_removal_patch()
862