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 .regexps import *
10from .patching import *
11from .utils import *
12from .qom_macros import *
13
14TI_FIELDS = [ 'name', 'parent', 'abstract', 'interfaces',
15    'instance_size', 'instance_init', 'instance_post_init', 'instance_finalize',
16    'class_size', 'class_init', 'class_base_init', 'class_data']
17
18RE_TI_FIELD_NAME = OR(*TI_FIELDS)
19
20RE_TI_FIELD_INIT = S(r'[ \t]*', NAMED('comments', RE_COMMENTS),
21                     r'\.', NAMED('field', RE_TI_FIELD_NAME), r'\s*=\s*',
22                     NAMED('value', RE_EXPRESSION), r'[ \t]*,?[ \t]*\n')
23RE_TI_FIELDS = M(RE_TI_FIELD_INIT)
24
25RE_TYPEINFO_START = S(r'^[ \t]*', M(r'(static|const)\s+', name='modifiers'), r'TypeInfo\s+',
26                      NAMED('name', RE_IDENTIFIER), r'\s*=\s*{[ \t]*\n')
27
28ParsedArray = List[str]
29ParsedInitializerValue = Union[str, ParsedArray]
30class InitializerValue(NamedTuple):
31    raw: str
32    parsed: Optional[ParsedInitializerValue]
33    match: Optional[Match]
34
35class ArrayItem(FileMatch):
36    regexp = RE_ARRAY_ITEM
37
38class ArrayInitializer(FileMatch):
39    regexp = RE_ARRAY
40
41    def parsed(self) -> ParsedArray:
42        #DBG('parse_array: %r', m.group(0))
43        return [m.group('arrayitem') for m in self.group_finditer(ArrayItem, 'arrayitems')]
44
45class FieldInitializer(FileMatch):
46    regexp = RE_TI_FIELD_INIT
47
48    @property
49    def raw(self) -> str:
50        return self.group('value')
51
52    @property
53    def parsed(self) -> ParsedInitializerValue:
54        parsed: ParsedInitializerValue = self.raw
55        #DBG("parse_initializer_value: %r", s)
56        array = self.try_group_match(ArrayInitializer, 'value')
57        if array:
58            assert isinstance(array, ArrayInitializer)
59            return array.parsed()
60        return parsed
61
62TypeInfoInitializers = Dict[str, FieldInitializer]
63
64class TypeDefinition(FileMatch):
65    """
66    Common base class for type definitions (TypeInfo variables or OBJECT_DEFINE* macros)
67    """
68    @property
69    def instancetype(self) -> Optional[str]:
70        return self.group('instancetype')
71
72    @property
73    def classtype(self) -> Optional[str]:
74        return self.group('classtype')
75
76    @property
77    def uppercase(self) -> Optional[str]:
78        return self.group('uppercase')
79
80    @property
81    def parent_uppercase(self) -> str:
82        return self.group('parent_uppercase')
83
84    @property
85    def initializers(self) -> Optional[TypeInfoInitializers]:
86        if getattr(self, '_inititalizers', None):
87            self._initializers: TypeInfoInitializers
88            return self._initializers
89        fields = self.group('fields')
90        if fields is None:
91            return None
92        d = dict((fm.group('field'), fm)
93                  for fm in self.group_finditer(FieldInitializer, 'fields'))
94        self._initializers = d # type: ignore
95        return self._initializers
96
97
98class TypeInfoVar(TypeDefinition):
99    """TypeInfo variable declaration with initializer"""
100    regexp = S(NAMED('begin', RE_TYPEINFO_START),
101               M(NAMED('fields', RE_TI_FIELDS),
102                 NAMED('endcomments', SP, RE_COMMENTS),
103                 NAMED('end', r'};?\n'),
104                 n='?', name='fullspec'))
105
106    def is_static(self) -> bool:
107        return 'static' in self.group('modifiers')
108
109    def is_const(self) -> bool:
110        return 'const' in self.group('modifiers')
111
112    def is_full(self) -> bool:
113        return bool(self.group('fullspec'))
114
115    def get_initializers(self) -> TypeInfoInitializers:
116        """Helper for code that needs to deal with missing initializer info"""
117        if self.initializers is None:
118            return {}
119        return self.initializers
120
121    def get_raw_initializer_value(self, field: str, default: str = '') -> str:
122        initializers = self.get_initializers()
123        if field in initializers:
124            return initializers[field].raw
125        else:
126            return default
127
128    @property
129    def typename(self) -> Optional[str]:
130        return self.get_raw_initializer_value('name')
131
132    @property
133    def uppercase(self) -> Optional[str]:
134        typename = self.typename
135        if not typename:
136            return None
137        if not typename.startswith('TYPE_'):
138            return None
139        return typename[len('TYPE_'):]
140
141    @property
142    def classtype(self) -> Optional[str]:
143        class_size = self.get_raw_initializer_value('class_size')
144        if not class_size:
145            return None
146        m = re.fullmatch(RE_SIZEOF, class_size)
147        if not m:
148            return None
149        return m.group('sizeoftype')
150
151    @property
152    def instancetype(self) -> Optional[str]:
153        instance_size = self.get_raw_initializer_value('instance_size')
154        if not instance_size:
155            return None
156        m = re.fullmatch(RE_SIZEOF, instance_size)
157        if not m:
158            return None
159        return m.group('sizeoftype')
160
161
162    #def extract_identifiers(self) -> Optional[TypeIdentifiers]:
163    #    """Try to extract identifiers from names being used"""
164    #    DBG("extracting idenfiers from %s", self.name)
165        #uppercase = None
166        #if typename and re.fullmatch(RE_IDENTIFIER, typename) and typename.startswith("TYPE_"):
167        #    uppercase = typename[len('TYPE_'):]
168        #lowercase = None
169        #funcs = set()
170        #prefixes = set()
171        #for field,suffix in [('instance_init', '_init'),
172        #                     ('instance_finalize', '_finalize'),
173        #                     ('class_init', '_class_init')]:
174        #    if field not in values:
175        #        continue
176        #    func = values[field].raw
177        #    funcs.add(func)
178        #    if func.endswith(suffix):
179        #        prefixes.add(func[:-len(suffix)])
180        #    else:
181        #        self.warn("function name %s doesn't have expected %s suffix",
182        #                  func, suffix)
183        #if len(prefixes) == 1:
184        #    lowercase = prefixes.pop()
185        #elif len(prefixes) > 1:
186        #    self.warn("inconsistent function names: %s", ' '.join(funcs))
187
188        #.parent = TYPE_##PARENT_MODULE_OBJ_NAME, \
189        #return TypeIdentifiers(typename=typename,
190        #                       uppercase=uppercase, lowercase=lowercase,
191        #                       instancetype=instancetype, classtype=classtype)
192
193    def append_field(self, field: str, value: str) -> Patch:
194        """Generate patch appending a field initializer"""
195        content = f'    .{field} = {value},\n'
196        fm = self.group_match('fields')
197        assert fm
198        return fm.append(content)
199
200    def patch_field(self, field: str, replacement: str) -> Patch:
201        """Generate patch replacing a field initializer"""
202        initializers = self.initializers
203        assert initializers
204        value = initializers.get(field)
205        assert value
206        return value.make_patch(replacement)
207
208    def remove_field(self, field: str) -> Iterable[Patch]:
209        initializers = self.initializers
210        assert initializers
211        if field in initializers:
212            yield self.patch_field(field, '')
213
214    def remove_fields(self, *fields: str) -> Iterable[Patch]:
215        for f in fields:
216            yield from self.remove_field(f)
217
218    def patch_field_value(self, field: str, replacement: str) -> Patch:
219        """Replace just the value of a field initializer"""
220        initializers = self.initializers
221        assert initializers
222        value = initializers.get(field)
223        assert value
224        vm = value.group_match('value')
225        assert vm
226        return vm.make_patch(replacement)
227
228
229class RemoveRedundantClassSize(TypeInfoVar):
230    """Remove class_size when using OBJECT_DECLARE_SIMPLE_TYPE"""
231    def gen_patches(self) -> Iterable[Patch]:
232        initializers = self.initializers
233        if initializers is None:
234            return
235        if 'class_size' not in initializers:
236            return
237
238        self.debug("Handling %s", self.name)
239        m = re.fullmatch(RE_SIZEOF, initializers['class_size'].raw)
240        if not m:
241            self.warn("%s class_size is not sizeof?", self.name)
242            return
243        classtype = m.group('sizeoftype')
244        if not classtype.endswith('Class'):
245            self.warn("%s class size type (%s) is not *Class?", self.name, classtype)
246            return
247        self.debug("classtype is %s", classtype)
248        instancetype = classtype[:-len('Class')]
249        self.debug("intanceypte is %s", instancetype)
250        self.debug("searching for simpletype declaration using %s as InstanceType", instancetype)
251        decl = self.allfiles.find_match(OldStyleObjectDeclareSimpleType,
252                                        instancetype, 'instancetype')
253        if not decl:
254            self.debug("No simpletype declaration found for %s", instancetype)
255            return
256        self.debug("Found simple type declaration")
257        decl.debug("declaration is here")
258        yield from self.remove_field('class_size')
259
260class RemoveDeclareSimpleTypeArg(OldStyleObjectDeclareSimpleType):
261    """Remove class_size when using OBJECT_DECLARE_SIMPLE_TYPE"""
262    def gen_patches(self) -> Iterable[Patch]:
263        c = (f'OBJECT_DECLARE_SIMPLE_TYPE({self.group("instancetype")}, {self.group("lowercase")},\n'
264             f'                           {self.group("uppercase")})\n')
265        yield self.make_patch(c)
266
267class UseDeclareTypeExtended(TypeInfoVar):
268    """Replace TypeInfo variable with OBJECT_DEFINE_TYPE_EXTENDED"""
269    def gen_patches(self) -> Iterable[Patch]:
270        # this will just ensure the caches for find_match() and matches_for_type()
271        # will be loaded in advance:
272        find_type_checkers(self.allfiles, 'xxxxxxxxxxxxxxxxx')
273
274        if not self.is_static():
275            self.info("Skipping non-static TypeInfo variable")
276            return
277
278        type_info_macro = self.file.find_match(TypeInfoMacro, self.name)
279        if not type_info_macro:
280            self.warn("TYPE_INFO(%s) line not found", self.name)
281            return
282
283        values = self.initializers
284        if values is None:
285            return
286        if 'name' not in values:
287            self.warn("name not set in TypeInfo variable %s", self.name)
288            return
289
290        typename = values['name'].raw
291
292        if 'parent' not in values:
293            self.warn("parent not set in TypeInfo variable %s", self.name)
294            return
295        parent_typename = values['parent'].raw
296
297        instancetype = None
298        if 'instance_size' in values:
299            m = re.fullmatch(RE_SIZEOF, values['instance_size'].raw)
300            if m:
301                instancetype = m.group('sizeoftype')
302            else:
303                self.warn("can't extract instance type in TypeInfo variable %s", self.name)
304                self.warn("instance_size is set to: %r", values['instance_size'].raw)
305                return
306
307        classtype = None
308        if 'class_size' in values:
309            m = re.fullmatch(RE_SIZEOF, values['class_size'].raw)
310            if m:
311                classtype = m.group('sizeoftype')
312            else:
313                self.warn("can't extract class type in TypeInfo variable %s", self.name)
314                self.warn("class_size is set to: %r", values['class_size'].raw)
315                return
316
317        #for t in (typename, parent_typename):
318        #    if not re.fullmatch(RE_IDENTIFIER, t):
319        #        self.info("type name is not a macro/constant")
320        #        if instancetype or classtype:
321        #            self.warn("macro/constant type name is required for instance/class type")
322        #        if not self.file.force:
323        #            return
324
325        # Now, the challenge is to find out the right MODULE_OBJ_NAME for the
326        # type and for the parent type
327        self.info("TypeInfo variable for %s is here", typename)
328        uppercase = find_typename_uppercase(self.allfiles, typename)
329        if not uppercase:
330            self.info("Can't find right uppercase name for %s", typename)
331            if instancetype or classtype:
332                self.warn("Can't find right uppercase name for %s", typename)
333                self.warn("This will make type validation difficult in the future")
334            return
335
336        parent_uppercase = find_typename_uppercase(self.allfiles, parent_typename)
337        if not parent_uppercase:
338            self.info("Can't find right uppercase name for parent type (%s)", parent_typename)
339            if instancetype or classtype:
340                self.warn("Can't find right uppercase name for parent type (%s)", parent_typename)
341                self.warn("This will make type validation difficult in the future")
342            return
343
344        ok = True
345
346        #checkers: List[TypeCheckerDeclaration] = list(find_type_checkers(self.allfiles, uppercase))
347        #for c in checkers:
348        #    c.info("instance type checker declaration (%s) is here", c.group('uppercase'))
349        #if not checkers:
350        #    self.info("No type checkers declared for %s", uppercase)
351        #    if instancetype or classtype:
352        #        self.warn("Can't find where type checkers for %s (%s) are declared.  We will need them to validate sizes of %s",
353        #                  typename, uppercase, self.name)
354
355        if not instancetype:
356            instancetype = 'void'
357        if not classtype:
358            classtype = 'void'
359
360        #checker_instancetypes = set(c.instancetype for c in checkers
361        #                            if c.instancetype is not None)
362        #if len(checker_instancetypes) > 1:
363        #    self.warn("ambiguous set of type checkers")
364        #    for c in checkers:
365        #        c.warn("instancetype is %s here", c.instancetype)
366        #    ok = False
367        #elif len(checker_instancetypes) == 1:
368        #    checker_instancetype = checker_instancetypes.pop()
369        #    DBG("checker instance type: %r", checker_instancetype)
370        #    if instancetype != checker_instancetype:
371        #        self.warn("type at instance_size is %r.  Should instance_size be set to sizeof(%s) ?",
372        #                instancetype, checker_instancetype)
373        #        ok = False
374        #else:
375        #    if instancetype != 'void':
376        #        self.warn("instance type checker for %s (%s) not found", typename, instancetype)
377        #        ok = False
378
379        #checker_classtypes = set(c.classtype for c in checkers
380        #                         if c.classtype is not None)
381        #if len(checker_classtypes) > 1:
382        #    self.warn("ambiguous set of type checkers")
383        #    for c in checkers:
384        #        c.warn("classtype is %s here", c.classtype)
385        #    ok = False
386        #elif len(checker_classtypes) == 1:
387        #    checker_classtype = checker_classtypes.pop()
388        #    DBG("checker class type: %r", checker_classtype)
389        #    if classtype != checker_classtype:
390        #        self.warn("type at class_size is %r.  Should class_size be set to sizeof(%s) ?",
391        #                classtype, checker_classtype)
392        #        ok = False
393        #else:
394        #    if classtype != 'void':
395        #        self.warn("class type checker for %s (%s) not found", typename, classtype)
396        #        ok = False
397
398        #if not ok:
399        #    for c in checkers:
400        #        c.warn("Type checker declaration for %s (%s) is here",
401        #                           typename, type(c).__name__)
402        #    return
403
404        #if parent_decl is None:
405        #    self.warn("Can't find where parent type %s is declared", parent_typename)
406
407        #yield self.prepend(f'DECLARE_TYPE_NAME({uppercase}, {typename})\n')
408        #if not instancetype:
409        #    yield self.prepend(f'DECLARE_INSTANCE_TYPE({uppercase}, void)\n')
410        #if not classtype:
411        #    yield self.prepend(f'DECLARE_CLASS_TYPE({uppercase}, void)\n')
412        self.info("%s can be patched!", self.name)
413        replaced_fields = ['name', 'parent', 'instance_size', 'class_size']
414        begin = self.group_match('begin')
415        newbegin =  f'OBJECT_DEFINE_TYPE_EXTENDED({self.name},\n'
416        newbegin += f'                            {instancetype}, {classtype},\n'
417        newbegin += f'                            {uppercase}, {parent_uppercase}'
418        if set(values.keys()) - set(replaced_fields):
419            newbegin += ',\n'
420        yield begin.make_patch(newbegin)
421        yield from self.remove_fields(*replaced_fields)
422        end = self.group_match('end')
423        yield end.make_patch(')\n')
424        yield type_info_macro.make_removal_patch()
425
426class ObjectDefineTypeExtended(TypeDefinition):
427    """OBJECT_DEFINE_TYPE_EXTENDED usage"""
428    regexp = S(r'^[ \t]*OBJECT_DEFINE_TYPE_EXTENDED\s*\(\s*',
429               NAMED('name', RE_IDENTIFIER), r'\s*,\s*',
430               NAMED('instancetype', RE_IDENTIFIER), r'\s*,\s*',
431               NAMED('classtype', RE_IDENTIFIER), r'\s*,\s*',
432               NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*',
433               NAMED('parent_uppercase', RE_IDENTIFIER),
434               M(r',\s*\n',
435                 NAMED('fields', RE_TI_FIELDS),
436                 n='?'),
437               r'\s*\);?\n?')
438
439class ObjectDefineType(TypeDefinition):
440    """OBJECT_DEFINE_TYPE usage"""
441    regexp = S(r'^[ \t]*OBJECT_DEFINE_TYPE\s*\(\s*',
442               NAMED('lowercase', RE_IDENTIFIER), r'\s*,\s*',
443               NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*',
444               NAMED('parent_uppercase', RE_IDENTIFIER),
445               M(r',\s*\n',
446                 NAMED('fields', RE_TI_FIELDS),
447                 n='?'),
448               r'\s*\);?\n?')
449
450def find_type_definitions(files: FileList, uppercase: str) -> Iterable[TypeDefinition]:
451    types: List[Type[TypeDefinition]] = [TypeInfoVar, ObjectDefineType, ObjectDefineTypeExtended]
452    for t in types:
453        for m in files.matches_of_type(t):
454            m.debug("uppercase: %s", m.uppercase)
455    yield from (m for t in types
456                  for m in files.matches_of_type(t)
457                if m.uppercase == uppercase)
458
459class AddDeclareVoidClassType(TypeDeclarationFixup):
460    """Will add DECLARE_CLASS_TYPE(..., void) if possible"""
461    def gen_patches_for_type(self, uppercase: str,
462                             checkers: List[TypeDeclaration],
463                             fields: Dict[str, Optional[str]]) -> Iterable[Patch]:
464        defs = list(find_type_definitions(self.allfiles, uppercase))
465        if len(defs) > 1:
466            self.warn("multiple definitions for %s", uppercase)
467            for d in defs:
468                d.warn("definition found here")
469            return
470        elif len(defs) == 0:
471            self.warn("type definition for %s not found", uppercase)
472            return
473        d = defs[0]
474        if d.classtype is None:
475            d.info("definition for %s has classtype, skipping", uppercase)
476            return
477        class_type_checkers = [c for c in checkers
478                               if c.classtype is not None]
479        if class_type_checkers:
480            for c in class_type_checkers:
481                c.warn("class type checker for %s is present here", uppercase)
482            return
483
484        _,last_checker = max((m.start(), m) for m in checkers)
485        s = f'DECLARE_CLASS_TYPE({uppercase}, void)\n'
486        yield last_checker.append(s)
487
488class AddDeclareVoidInstanceType(FileMatch):
489    """Will add DECLARE_INSTANCE_TYPE(..., void) if possible"""
490    regexp = S(r'^[ \t]*#[ \t]*define', CPP_SPACE,
491               NAMED('name', r'TYPE_[a-zA-Z0-9_]+\b'),
492               CPP_SPACE, r'.*\n')
493
494    def gen_patches(self) -> Iterable[Patch]:
495        assert self.name.startswith('TYPE_')
496        uppercase = self.name[len('TYPE_'):]
497        defs = list(find_type_definitions(self.allfiles, uppercase))
498        if len(defs) > 1:
499            self.warn("multiple definitions for %s", uppercase)
500            for d in defs:
501                d.warn("definition found here")
502            return
503        elif len(defs) == 0:
504            self.warn("type definition for %s not found", uppercase)
505            return
506        d = defs[0]
507        instancetype = d.instancetype
508        if instancetype is not None and instancetype != 'void':
509            return
510
511        instance_checkers = [c for c in find_type_checkers(self.allfiles, uppercase)
512                             if c.instancetype]
513        if instance_checkers:
514            d.warn("instance type checker for %s already declared", uppercase)
515            for c in instance_checkers:
516                c.warn("instance checker for %s is here", uppercase)
517            return
518
519        s = f'DECLARE_INSTANCE_TYPE({uppercase}, void)\n'
520        yield self.append(s)
521
522class AddObjectDeclareType(DeclareObjCheckers):
523    """Will add OBJECT_DECLARE_TYPE(...) if possible"""
524    def gen_patches(self) -> Iterable[Patch]:
525        uppercase = self.uppercase
526        typename = self.group('typename')
527        instancetype = self.group('instancetype')
528        classtype = self.group('classtype')
529
530        if typename != f'TYPE_{uppercase}':
531            self.warn("type name mismatch: %s vs %s", typename, uppercase)
532            return
533
534        typedefs = [(t,self.allfiles.find_matches(SimpleTypedefMatch, t))
535                    for t in (instancetype, classtype)]
536        for t,tds in typedefs:
537            if not tds:
538                self.warn("typedef %s not found", t)
539                return
540            for td in tds:
541                td_type = td.group('typedef_type')
542                if td_type != f'struct {t}':
543                    self.warn("typedef mismatch: %s is defined as %s", t, td_type)
544                    td.warn("typedef is here")
545                    return
546
547        # look for reuse of same struct type
548        other_instance_checkers = [c for c in find_type_checkers(self.allfiles, instancetype, 'instancetype')
549                                if c.uppercase != uppercase]
550        if other_instance_checkers:
551            self.warn("typedef %s is being reused", instancetype)
552            for ic in other_instance_checkers:
553                ic.warn("%s is reused here", instancetype)
554            if not self.file.force:
555                return
556
557        decl_types: List[Type[TypeDeclaration]] = [DeclareClassCheckers, DeclareObjCheckers]
558        class_decls = [m for t in decl_types
559                       for m in self.allfiles.find_matches(t, uppercase, 'uppercase')]
560
561        defs = list(find_type_definitions(self.allfiles, uppercase))
562        if len(defs) > 1:
563            self.warn("multiple definitions for %s", uppercase)
564            for d in defs:
565                d.warn("definition found here")
566            if not self.file.force:
567                return
568        elif len(defs) == 0:
569            self.warn("type definition for %s not found", uppercase)
570            if not self.file.force:
571                return
572        else:
573            d = defs[0]
574            if d.instancetype != instancetype:
575                self.warn("mismatching instance type for %s (%s)", uppercase, instancetype)
576                d.warn("instance type declared here (%s)", d.instancetype)
577                if not self.file.force:
578                    return
579            if d.classtype != classtype:
580                self.warn("mismatching class type for %s (%s)", uppercase, classtype)
581                d.warn("class type declared here (%s)", d.classtype)
582                if not self.file.force:
583                    return
584
585        assert self.file.original_content
586        for t,tds in typedefs:
587            assert tds
588            for td in tds:
589                if td.file is not self.file:
590                    continue
591
592                # delete typedefs that are truly redundant:
593                # 1) defined after DECLARE_OBJ_CHECKERS
594                if td.start() > self.start():
595                    yield td.make_removal_patch()
596                # 2) defined before DECLARE_OBJ_CHECKERS, but unused
597                elif not re.search(r'\b'+t+r'\b', self.file.original_content[td.end():self.start()]):
598                    yield td.make_removal_patch()
599
600        c = (f'OBJECT_DECLARE_TYPE({instancetype}, {classtype}, {uppercase})\n')
601        yield self.make_patch(c)
602
603class AddObjectDeclareSimpleType(DeclareInstanceChecker):
604    """Will add OBJECT_DECLARE_SIMPLE_TYPE(...) if possible"""
605    def gen_patches(self) -> Iterable[Patch]:
606        uppercase = self.uppercase
607        typename = self.group('typename')
608        instancetype = self.group('instancetype')
609
610        if typename != f'TYPE_{uppercase}':
611            self.warn("type name mismatch: %s vs %s", typename, uppercase)
612            return
613
614        typedefs = [(t,self.allfiles.find_matches(SimpleTypedefMatch, t))
615                    for t in (instancetype,)]
616        for t,tds in typedefs:
617            if not tds:
618                self.warn("typedef %s not found", t)
619                return
620            for td in tds:
621                td_type = td.group('typedef_type')
622                if td_type != f'struct {t}':
623                    self.warn("typedef mismatch: %s is defined as %s", t, td_type)
624                    td.warn("typedef is here")
625                    return
626
627        # look for reuse of same struct type
628        other_instance_checkers = [c for c in find_type_checkers(self.allfiles, instancetype, 'instancetype')
629                                if c.uppercase != uppercase]
630        if other_instance_checkers:
631            self.warn("typedef %s is being reused", instancetype)
632            for ic in other_instance_checkers:
633                ic.warn("%s is reused here", instancetype)
634            if not self.file.force:
635                return
636
637        decl_types: List[Type[TypeDeclaration]] = [DeclareClassCheckers, DeclareObjCheckers]
638        class_decls = [m for t in decl_types
639                       for m in self.allfiles.find_matches(t, uppercase, 'uppercase')]
640        if class_decls:
641            self.warn("class type declared for %s", uppercase)
642            for cd in class_decls:
643                cd.warn("class declaration found here")
644            return
645
646        defs = list(find_type_definitions(self.allfiles, uppercase))
647        if len(defs) > 1:
648            self.warn("multiple definitions for %s", uppercase)
649            for d in defs:
650                d.warn("definition found here")
651            if not self.file.force:
652                return
653        elif len(defs) == 0:
654            self.warn("type definition for %s not found", uppercase)
655            if not self.file.force:
656                return
657        else:
658            d = defs[0]
659            if d.instancetype != instancetype:
660                self.warn("mismatching instance type for %s (%s)", uppercase, instancetype)
661                d.warn("instance type declared here (%s)", d.instancetype)
662                if not self.file.force:
663                    return
664            if d.classtype:
665                self.warn("class type set for %s", uppercase)
666                d.warn("class type declared here")
667                if not self.file.force:
668                    return
669
670        assert self.file.original_content
671        for t,tds in typedefs:
672            assert tds
673            for td in tds:
674                if td.file is not self.file:
675                    continue
676
677                # delete typedefs that are truly redundant:
678                # 1) defined after DECLARE_OBJ_CHECKERS
679                if td.start() > self.start():
680                    yield td.make_removal_patch()
681                # 2) defined before DECLARE_OBJ_CHECKERS, but unused
682                elif not re.search(r'\b'+t+r'\b', self.file.original_content[td.end():self.start()]):
683                    yield td.make_removal_patch()
684
685        c = (f'OBJECT_DECLARE_SIMPLE_TYPE({instancetype}, {uppercase})\n')
686        yield self.make_patch(c)
687
688
689class TypeInfoStringName(TypeInfoVar):
690    """Replace hardcoded type names with TYPE_ constant"""
691    def gen_patches(self) -> Iterable[Patch]:
692        values = self.initializers
693        if values is None:
694            return
695        if 'name' not in values:
696            self.warn("name not set in TypeInfo variable %s", self.name)
697            return
698        typename = values['name'].raw
699        if re.fullmatch(RE_IDENTIFIER, typename):
700            return
701
702        self.warn("name %s is not an identifier", typename)
703        #all_defines = [m for m in self.allfiles.matches_of_type(ExpressionDefine)]
704        #self.debug("all_defines: %r", all_defines)
705        constants = [m for m in self.allfiles.matches_of_type(ExpressionDefine)
706                     if m.group('value').strip() == typename.strip()]
707        if not constants:
708            self.warn("No macro for %s found", typename)
709            return
710        if len(constants) > 1:
711            self.warn("I don't know which macro to use: %r", constants)
712            return
713        yield self.patch_field_value('name', constants[0].name)
714
715class RedundantTypeSizes(TypeInfoVar):
716    """Remove redundant instance_size/class_size from TypeInfo vars"""
717    def gen_patches(self) -> Iterable[Patch]:
718        values = self.initializers
719        if values is None:
720            return
721        if 'name' not in values:
722            self.warn("name not set in TypeInfo variable %s", self.name)
723            return
724        typename = values['name'].raw
725        if 'parent' not in values:
726            self.warn("parent not set in TypeInfo variable %s", self.name)
727            return
728        parent_typename = values['parent'].raw
729
730        if 'instance_size' not in values and 'class_size' not in values:
731            self.debug("no need to validate %s", self.name)
732            return
733
734        instance_decls = find_type_checkers(self.allfiles, typename)
735        if instance_decls:
736            self.debug("won't touch TypeInfo var that has type checkers")
737            return
738
739        parent = find_type_info(self.allfiles, parent_typename)
740        if not parent:
741            self.warn("Can't find TypeInfo for %s", parent_typename)
742            return
743
744        if 'instance_size' in values and parent.get_raw_initializer_value('instance_size') != values['instance_size'].raw:
745            self.info("instance_size mismatch")
746            parent.info("parent type declared here")
747            return
748
749        if 'class_size' in values and parent.get_raw_initializer_value('class_size') != values['class_size'].raw:
750            self.info("class_size mismatch")
751            parent.info("parent type declared here")
752            return
753
754        self.debug("will patch variable %s", self.name)
755
756        if 'instance_size' in values:
757            self.debug("deleting instance_size")
758            yield self.patch_field('instance_size', '')
759
760        if 'class_size' in values:
761            self.debug("deleting class_size")
762            yield self.patch_field('class_size', '')
763
764
765#class TypeInfoVarInitFuncs(TypeInfoVar):
766#    """TypeInfo variable
767#    Will create missing init functions
768#    """
769#    def gen_patches(self) -> Iterable[Patch]:
770#        values = self.initializers
771#        if values is None:
772#            self.warn("type not parsed completely: %s", self.name)
773#            return
774#
775#        macro = self.file.find_match(TypeInfoVar, self.name)
776#        if macro is None:
777#            self.warn("No TYPE_INFO macro for %s", self.name)
778#            return
779#
780#        ids = self.extract_identifiers()
781#        if ids is None:
782#            return
783#
784#        DBG("identifiers extracted: %r", ids)
785#        fields = set(values.keys())
786#        if ids.lowercase:
787#            if 'instance_init' not in fields:
788#                yield self.prepend(('static void %s_init(Object *obj)\n'
789#                                    '{\n'
790#                                    '}\n\n') % (ids.lowercase))
791#                yield self.append_field('instance_init', ids.lowercase+'_init')
792#
793#            if 'instance_finalize' not in fields:
794#                yield self.prepend(('static void %s_finalize(Object *obj)\n'
795#                                    '{\n'
796#                                    '}\n\n') % (ids.lowercase))
797#                yield self.append_field('instance_finalize', ids.lowercase+'_finalize')
798#
799#
800#            if 'class_init' not in fields:
801#                yield self.prepend(('static void %s_class_init(ObjectClass *oc, void *data)\n'
802#                                    '{\n'
803#                                    '}\n\n') % (ids.lowercase))
804#                yield self.append_field('class_init', ids.lowercase+'_class_init')
805
806class TypeInitMacro(FileMatch):
807    """Use of type_init(...) macro"""
808    regexp = S(r'^[ \t]*type_init\s*\(\s*', NAMED('name', RE_IDENTIFIER), r'\s*\);?[ \t]*\n')
809
810class DeleteEmptyTypeInitFunc(TypeInitMacro):
811    """Delete empty function declared using type_init(...)"""
812    def gen_patches(self) -> Iterable[Patch]:
813        fn = self.file.find_match(StaticVoidFunction, self.name)
814        DBG("function for %s: %s", self.name, fn)
815        if fn and fn.body == '':
816            yield fn.make_patch('')
817            yield self.make_patch('')
818
819class StaticVoidFunction(FileMatch):
820    """simple static void function
821    (no replacement rules)
822    """
823    #NOTE: just like RE_FULL_STRUCT, this doesn't parse any of the body contents
824    #      of the function.  Tt will just look for "}" in the beginning of a line
825    regexp = S(r'static\s+void\s+', NAMED('name', RE_IDENTIFIER), r'\s*\(\s*void\s*\)\n',
826               r'{\n',
827               NAMED('body',
828                     # acceptable inside the function body:
829                     # - lines starting with space or tab
830                     # - empty lines
831                     # - preprocessor directives
832                     OR(r'[ \t][^\n]*\n',
833                        r'#[^\n]*\n',
834                        r'\n',
835                        repeat='*')),
836               r'};?\n')
837
838    @property
839    def body(self) -> str:
840        return self.group('body')
841
842    def has_preprocessor_directive(self) -> bool:
843        return bool(re.search(r'^[ \t]*#', self.body, re.MULTILINE))
844
845def find_containing_func(m: FileMatch) -> Optional['StaticVoidFunction']:
846    """Return function containing this match"""
847    for fn in m.file.matches_of_type(StaticVoidFunction):
848        if fn.contains(m):
849            return fn
850    return None
851
852class TypeRegisterStaticCall(FileMatch):
853    """type_register_static() call
854    Will be replaced by TYPE_INFO() macro
855    """
856    regexp = S(r'^[ \t]*', NAMED('func_name', 'type_register_static'),
857               r'\s*\(&\s*', NAMED('name', RE_IDENTIFIER), r'\s*\);[ \t]*\n')
858
859class UseTypeInfo(TypeRegisterStaticCall):
860    """Replace type_register_static() call with TYPE_INFO declaration"""
861    def gen_patches(self) -> Iterable[Patch]:
862        fn = find_containing_func(self)
863        if fn:
864            DBG("%r is inside %r", self, fn)
865            type_init = self.file.find_match(TypeInitMacro, fn.name)
866            if type_init is None:
867                self.warn("can't find type_init(%s) line", fn.name)
868                if not self.file.force:
869                    return
870        else:
871            self.warn("can't identify the function where type_register_static(&%s) is called", self.name)
872            if not self.file.force:
873                return
874
875        #if fn.has_preprocessor_directive() and not self.file.force:
876        #    self.warn("function %s has preprocessor directives, this requires --force", fn.name)
877        #    return
878
879        var = self.file.find_match(TypeInfoVar, self.name)
880        if var is None:
881            self.warn("can't find TypeInfo var declaration for %s", self.name)
882            return
883
884        if not var.is_full():
885            self.warn("variable declaration %s wasn't parsed fully", var.name)
886            if not self.file.force:
887                return
888
889        if fn and fn.contains(var):
890            self.warn("TypeInfo %s variable is inside a function", self.name)
891            if not self.file.force:
892                return
893
894        # delete type_register_static() call:
895        yield self.make_patch('')
896        # append TYPE_REGISTER(...) after variable declaration:
897        yield var.append(f'TYPE_INFO({self.name})\n')
898
899class TypeRegisterCall(FileMatch):
900    """type_register_static() call"""
901    regexp = S(r'^[ \t]*', NAMED('func_name', 'type_register'),
902               r'\s*\(&\s*', NAMED('name', RE_IDENTIFIER), r'\s*\);[ \t]*\n')
903
904class MakeTypeRegisterStatic(TypeRegisterCall):
905    """Make type_register() call static if variable is static const"""
906    def gen_patches(self):
907        var = self.file.find_match(TypeInfoVar, self.name)
908        if var is None:
909            self.warn("can't find TypeInfo var declaration for %s", self.name)
910            return
911        if var.is_static() and var.is_const():
912            yield self.group_match('func_name').make_patch('type_register_static')
913
914class MakeTypeRegisterNotStatic(TypeRegisterStaticCall):
915    """Make type_register() call static if variable is static const"""
916    def gen_patches(self):
917        var = self.file.find_match(TypeInfoVar, self.name)
918        if var is None:
919            self.warn("can't find TypeInfo var declaration for %s", self.name)
920            return
921        if not var.is_static() or not var.is_const():
922            yield self.group_match('func_name').make_patch('type_register')
923
924class TypeInfoMacro(FileMatch):
925    """TYPE_INFO macro usage"""
926    regexp = S(r'^[ \t]*TYPE_INFO\s*\(\s*', NAMED('name', RE_IDENTIFIER), r'\s*\)[ \t]*;?[ \t]*\n')
927
928def find_type_info(files: RegexpScanner, name: str) -> Optional[TypeInfoVar]:
929    ti = [ti for ti in files.matches_of_type(TypeInfoVar)
930            if ti.get_raw_initializer_value('name') == name]
931    DBG("type info vars: %r", ti)
932    if len(ti) > 1:
933        DBG("multiple TypeInfo vars found for %s", name)
934        return None
935    if len(ti) == 0:
936        DBG("no TypeInfo var found for %s", name)
937        return None
938    return ti[0]
939
940class CreateClassStruct(DeclareInstanceChecker):
941    """Replace DECLARE_INSTANCE_CHECKER with OBJECT_DECLARE_SIMPLE_TYPE"""
942    def gen_patches(self) -> Iterable[Patch]:
943        typename = self.group('typename')
944        DBG("looking for TypeInfo variable for %s", typename)
945        var = find_type_info(self.allfiles, typename)
946        if var is None:
947            self.warn("no TypeInfo var found for %s", typename)
948            return
949        assert var.initializers
950        if 'class_size' in var.initializers:
951            self.warn("class size already set for TypeInfo %s", var.name)
952            return
953        classtype = self.group('instancetype')+'Class'
954        return
955        yield
956        #TODO: need to find out what's the parent class type...
957        #yield var.append_field('class_size', f'sizeof({classtype})')
958        #c = (f'OBJECT_DECLARE_SIMPLE_TYPE({instancetype}, {lowercase},\n'
959        #     f'                           MODULE_OBJ_NAME, ParentClassType)\n')
960        #yield self.make_patch(c)
961
962def type_infos(file: FileInfo) -> Iterable[TypeInfoVar]:
963    return file.matches_of_type(TypeInfoVar)
964
965def full_types(file: FileInfo) -> Iterable[TypeInfoVar]:
966    return [t for t in type_infos(file) if t.is_full()]
967
968def partial_types(file: FileInfo) -> Iterable[TypeInfoVar]:
969    return [t for t in type_infos(file) if not t.is_full()]
970