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')
27RE_TYPEINFO_DEF = S(RE_TYPEINFO_START,
28                    M(NAMED('fields', RE_TI_FIELDS),
29                      SP, NAMED('endcomments', RE_COMMENTS),
30                      r'};?\n',
31                      n='?', name='fullspec'))
32
33ParsedArray = List[str]
34ParsedInitializerValue = Union[str, ParsedArray]
35class InitializerValue(NamedTuple):
36    raw: str
37    parsed: Optional[ParsedInitializerValue]
38    match: Optional[Match]
39TypeInfoInitializers = Dict[str, InitializerValue]
40
41def parse_array(m: Match) -> ParsedArray:
42    #DBG('parse_array: %r', m.group(0))
43    return [m.group('arrayitem') for m in re.finditer(RE_ARRAY_ITEM, m.group('arrayitems'))]
44
45def parse_initializer_value(m: Match, s: str) -> InitializerValue:
46    parsed: Optional[ParsedInitializerValue] = None
47    #DBG("parse_initializer_value: %r", s)
48    array = re.match(RE_ARRAY, s)
49    if array:
50        parsed = parse_array(array)
51    return InitializerValue(s, parsed, m)
52
53class TypeInfoVar(FileMatch):
54    """TypeInfo variable declaration with initializer
55    Will be replaced by OBJECT_DEFINE_TYPE_EXTENDED macro
56    (not implemented yet)
57    """
58    regexp = RE_TYPEINFO_DEF
59
60    @property
61    def initializers(self) -> Optional[TypeInfoInitializers]:
62        if getattr(self, '_inititalizers', None):
63            self._initializers: TypeInfoInitializers
64            return self._initializers
65        fields = self.group('fields')
66        if fields is None:
67            return None
68        d = dict((fm.group('field'), parse_initializer_value(fm, fm.group('value')))
69                  for fm in re.finditer(RE_TI_FIELD_INIT, fields))
70        self._initializers = d
71        return d
72
73    def is_static(self) -> bool:
74        return 'static' in self.group('modifiers')
75
76    def is_full(self) -> bool:
77        return bool(self.group('fullspec'))
78
79    def get_initializers(self) -> TypeInfoInitializers:
80        """Helper for code that needs to deal with missing initializer info"""
81        if self.initializers is None:
82            return {}
83        return self.initializers
84
85    def get_initializer_value(self, field: str) -> InitializerValue:
86        return self.get_initializers().get(field, InitializerValue('', '', None))
87
88    #def extract_identifiers(self) -> Optional[TypeIdentifiers]:
89    #    """Try to extract identifiers from names being used"""
90    #    DBG("extracting idenfiers from %s", self.name)
91        #uppercase = None
92        #if typename and re.fullmatch(RE_IDENTIFIER, typename) and typename.startswith("TYPE_"):
93        #    uppercase = typename[len('TYPE_'):]
94        #lowercase = None
95        #funcs = set()
96        #prefixes = set()
97        #for field,suffix in [('instance_init', '_init'),
98        #                     ('instance_finalize', '_finalize'),
99        #                     ('class_init', '_class_init')]:
100        #    if field not in values:
101        #        continue
102        #    func = values[field].raw
103        #    funcs.add(func)
104        #    if func.endswith(suffix):
105        #        prefixes.add(func[:-len(suffix)])
106        #    else:
107        #        self.warn("function name %s doesn't have expected %s suffix",
108        #                  func, suffix)
109        #if len(prefixes) == 1:
110        #    lowercase = prefixes.pop()
111        #elif len(prefixes) > 1:
112        #    self.warn("inconsistent function names: %s", ' '.join(funcs))
113
114        #.parent = TYPE_##PARENT_MODULE_OBJ_NAME, \
115        #return TypeIdentifiers(typename=typename,
116        #                       uppercase=uppercase, lowercase=lowercase,
117        #                       instancetype=instancetype, classtype=classtype)
118
119    def append_field(self, field, value) -> Patch:
120        """Generate patch appending a field initializer"""
121        content = f'    .{field} = {value},\n'
122        return Patch(self.match.end('fields'), self.match.end('fields'),
123                     content)
124
125    def patch_field(self, field: str, replacement: str) -> Patch:
126        """Generate patch replacing a field initializer"""
127        values = self.initializers
128        assert values
129        value = values.get(field)
130        assert value
131        fm = value.match
132        assert fm
133        fstart = self.match.start('fields') + fm.start()
134        fend = self.match.start('fields') + fm.end()
135        return Patch(fstart, fend, replacement)
136
137    def gen_patches(self) -> Iterable[Patch]:
138        values = self.initializers
139        if values is None:
140            return
141        if 'name' not in values:
142            self.warn("name not set in TypeInfo variable %s", self.name)
143            return
144        typename = values['name'].raw
145        if 'parent' not in values:
146            self.warn("parent not set in TypeInfo variable %s", self.name)
147            return
148        parent_typename = values['parent'].raw
149
150        instancetype = None
151        if 'instance_size' in values:
152            m = re.fullmatch(RE_SIZEOF, values['instance_size'].raw)
153            if m:
154                instancetype = m.group('sizeoftype')
155            else:
156                self.warn("can't extract instance type in TypeInfo variable %s", self.name)
157                self.warn("instance_size is set to: %r", values['instance_size'].raw)
158                return
159
160        classtype = None
161        if 'class_size' in values:
162            m = re.fullmatch(RE_SIZEOF, values['class_size'].raw)
163            if m:
164                classtype = m.group('sizeoftype')
165            else:
166                self.warn("can't extract class type in TypeInfo variable %s", self.name)
167                self.warn("class_size is set to: %r", values['class_size'].raw)
168                return
169
170        #NOTE: this will NOT work after declarations are converted
171        #      to OBJECT_DECLARE*
172
173        # Now, the challenge is to find out the right MODULE_OBJ_NAME for the
174        # type and for the parent type
175        instance_decl = find_type_declaration(self.allfiles, typename)
176        parent_decl = find_type_declaration(self.allfiles, parent_typename)
177
178        self.info("TypeInfo variable for %s is here", typename)
179        if instance_decl:
180            instance_decl.info("instance type declaration (%s) is here", instance_decl.match.group('uppercase'))
181        if parent_decl:
182            parent_decl.info("parent type declaration (%s) is here", parent_decl.match.group('uppercase'))
183
184        ok = True
185        if (instance_decl is None and (instancetype or classtype)):
186            self.warn("Can't find where type checkers for %s are declared.  We need them to validate sizes of %s", typename, self.name)
187            ok = False
188
189        if (instance_decl is not None
190            and 'instancetype' in instance_decl.match.groupdict()
191            and instancetype != instance_decl.group('instancetype')):
192            self.warn("type at instance_size is %r.  Should instance_size be set to sizeof(%s) ?",
193                      instancetype, instance_decl.group('instancetype'))
194            instance_decl.warn("Type checker declaration for %s is here", typename)
195            ok = False
196        if (instance_decl is not None
197            and 'classtype' in instance_decl.match.groupdict()
198            and classtype != instance_decl.group('classtype')):
199            self.warn("type at class_size is %r.  Should class_size be set to sizeof(%s) ?",
200                      classtype, instance_decl.group('classtype'))
201            instance_decl.warn("Type checker declaration for %s is here", typename)
202            ok = False
203
204        if not ok:
205            return
206
207        #if parent_decl is None:
208        #    self.warn("Can't find where parent type %s is declared", parent_typename)
209
210        self.info("%s can be patched!", self.name)
211        return
212        yield
213
214class RedundantTypeSizes(TypeInfoVar):
215    """Remove redundant instance_size/class_size from TypeInfo vars"""
216    def gen_patches(self) -> Iterable[Patch]:
217        values = self.initializers
218        if values is None:
219            return
220        if 'name' not in values:
221            self.warn("name not set in TypeInfo variable %s", self.name)
222            return
223        typename = values['name'].raw
224        if 'parent' not in values:
225            self.warn("parent not set in TypeInfo variable %s", self.name)
226            return
227        parent_typename = values['parent'].raw
228
229        if 'instance_size' not in values and 'class_size' not in values:
230            self.debug("no need to validate %s", self.name)
231            return
232
233        instance_decl = find_type_declaration(self.allfiles, typename)
234        if instance_decl:
235            self.debug("won't touch TypeInfo var that has type checkers")
236            return
237
238        parent = find_type_info(self.allfiles, parent_typename)
239        if not parent:
240            self.warn("Can't find TypeInfo for %s", parent_typename)
241            return
242
243        if 'instance_size' in values and parent.get_initializer_value('instance_size').raw != values['instance_size'].raw:
244            self.info("instance_size mismatch")
245            parent.info("parent type declared here")
246            return
247
248        if 'class_size' in values and parent.get_initializer_value('class_size').raw != values['class_size'].raw:
249            self.info("class_size mismatch")
250            parent.info("parent type declared here")
251            return
252
253        self.debug("will patch variable %s", self.name)
254
255        if 'instance_size' in values:
256            self.debug("deleting instance_size")
257            yield self.patch_field('instance_size', '')
258
259        if 'class_size' in values:
260            self.debug("deleting class_size")
261            yield self.patch_field('class_size', '')
262
263
264#class TypeInfoVarInitFuncs(TypeInfoVar):
265#    """TypeInfo variable
266#    Will create missing init functions
267#    """
268#    def gen_patches(self) -> Iterable[Patch]:
269#        values = self.initializers
270#        if values is None:
271#            self.warn("type not parsed completely: %s", self.name)
272#            return
273#
274#        macro = self.file.find_match(TypeInfoVar, self.name)
275#        if macro is None:
276#            self.warn("No TYPE_INFO macro for %s", self.name)
277#            return
278#
279#        ids = self.extract_identifiers()
280#        if ids is None:
281#            return
282#
283#        DBG("identifiers extracted: %r", ids)
284#        fields = set(values.keys())
285#        if ids.lowercase:
286#            if 'instance_init' not in fields:
287#                yield self.prepend(('static void %s_init(Object *obj)\n'
288#                                    '{\n'
289#                                    '}\n\n') % (ids.lowercase))
290#                yield self.append_field('instance_init', ids.lowercase+'_init')
291#
292#            if 'instance_finalize' not in fields:
293#                yield self.prepend(('static void %s_finalize(Object *obj)\n'
294#                                    '{\n'
295#                                    '}\n\n') % (ids.lowercase))
296#                yield self.append_field('instance_finalize', ids.lowercase+'_finalize')
297#
298#
299#            if 'class_init' not in fields:
300#                yield self.prepend(('static void %s_class_init(ObjectClass *oc, void *data)\n'
301#                                    '{\n'
302#                                    '}\n\n') % (ids.lowercase))
303#                yield self.append_field('class_init', ids.lowercase+'_class_init')
304
305class TypeInitMacro(FileMatch):
306    """type_init(...) macro use
307    Will be deleted if function is empty
308    """
309    regexp = S(r'^[ \t]*type_init\s*\(\s*', NAMED('name', RE_IDENTIFIER), r'\s*\);?[ \t]*\n')
310    def gen_patches(self) -> Iterable[Patch]:
311        fn = self.file.find_match(StaticVoidFunction, self.name)
312        DBG("function for %s: %s", self.name, fn)
313        if fn and fn.body == '':
314            yield fn.make_patch('')
315            yield self.make_patch('')
316
317class StaticVoidFunction(FileMatch):
318    """simple static void function
319    (no replacement rules)
320    """
321    #NOTE: just like RE_FULL_STRUCT, this doesn't parse any of the body contents
322    #      of the function.  Tt will just look for "}" in the beginning of a line
323    regexp = S(r'static\s+void\s+', NAMED('name', RE_IDENTIFIER), r'\s*\(\s*void\s*\)\n',
324               r'{\n',
325               NAMED('body',
326                     # acceptable inside the function body:
327                     # - lines starting with space or tab
328                     # - empty lines
329                     # - preprocessor directives
330                     OR(r'[ \t][^\n]*\n',
331                        r'#[^\n]*\n',
332                        r'\n',
333                        repeat='*')),
334               r'}\n')
335
336    @property
337    def body(self) -> str:
338        return self.group('body')
339
340    def has_preprocessor_directive(self) -> bool:
341        return bool(re.search(r'^[ \t]*#', self.body, re.MULTILINE))
342
343class TypeRegisterCall(FileMatch):
344    """type_register_static() call
345    Will be replaced by TYPE_INFO() macro
346    """
347    regexp = S(r'^[ \t]*type_register_static\s*\(&\s*', NAMED('name', RE_IDENTIFIER), r'\s*\);[ \t]*\n')
348
349    def function(self) -> Optional['StaticVoidFunction']:
350        """Return function containing this call"""
351        for m in self.file.matches_of_type(StaticVoidFunction):
352            if m.contains(self):
353                return m
354        return None
355
356    def gen_patches(self) -> Iterable[Patch]:
357        fn = self.function()
358        if fn is None:
359            self.warn("can't find function where type_register_static(&%s) is called", self.name)
360            return
361
362        #if fn.has_preprocessor_directive() and not self.file.force:
363        #    self.warn("function %s has preprocessor directives, this requires --force", fn.name)
364        #    return
365
366        type_init = self.file.find_match(TypeInitMacro, fn.name)
367        if type_init is None:
368            self.warn("can't find type_init(%s) line", fn.name)
369            return
370
371        var = self.file.find_match(TypeInfoVar, self.name)
372        if var is None:
373            self.warn("can't find TypeInfo var declaration for %s", self.name)
374            return
375
376        if not var.is_full():
377            self.warn("variable declaration %s wasn't parsed fully", var.name)
378            return
379
380        if fn.contains(var):
381            self.warn("TypeInfo %s variable is inside a function", self.name)
382            return
383
384        # delete type_register_static() call:
385        yield self.make_patch('')
386        # append TYPE_REGISTER(...) after variable declaration:
387        yield var.append(f'TYPE_INFO({self.name})\n')
388
389class TypeInfoMacro(FileMatch):
390    """TYPE_INFO macro usage"""
391    regexp = S(r'^[ \t]*TYPE_INFO\s*\(\s*', NAMED('name', RE_IDENTIFIER), r'\s*\)[ \t]*;?[ \t]*\n')
392
393def find_type_info(files: RegexpScanner, name: str) -> Optional[TypeInfoVar]:
394    ti = [ti for ti in files.matches_of_type(TypeInfoVar)
395            if ti.get_initializer_value('name').raw == name]
396    DBG("type info vars: %r", ti)
397    if len(ti) > 1:
398        DBG("multiple TypeInfo vars found for %s", name)
399        return None
400    if len(ti) == 0:
401        DBG("no TypeInfo var found for %s", name)
402        return None
403    return ti[0]
404
405class CreateClassStruct(DeclareInstanceChecker):
406    """Replace DECLARE_INSTANCE_CHECKER with OBJECT_DECLARE_SIMPLE_TYPE"""
407    def gen_patches(self) -> Iterable[Patch]:
408        typename = self.group('typename')
409        DBG("looking for TypeInfo variable for %s", typename)
410        var = find_type_info(self.allfiles, typename)
411        if var is None:
412            self.warn("no TypeInfo var found for %s", typename)
413            return
414        assert var.initializers
415        if 'class_size' in var.initializers:
416            self.warn("class size already set for TypeInfo %s", var.name)
417            return
418        classtype = self.group('instancetype')+'Class'
419        return
420        yield
421        #TODO: need to find out what's the parent class type...
422        #yield var.append_field('class_size', f'sizeof({classtype})')
423        #c = (f'OBJECT_DECLARE_SIMPLE_TYPE({instancetype}, {lowercase},\n'
424        #     f'                           MODULE_OBJ_NAME, ParentClassType)\n')
425        #yield self.make_patch(c)
426
427def type_infos(file: FileInfo) -> Iterable[TypeInfoVar]:
428    return file.matches_of_type(TypeInfoVar)
429
430def full_types(file: FileInfo) -> Iterable[TypeInfoVar]:
431    return [t for t in type_infos(file) if t.is_full()]
432
433def partial_types(file: FileInfo) -> Iterable[TypeInfoVar]:
434    return [t for t in type_infos(file) if not t.is_full()]
435