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