| # Copyright (C) 2020 Red Hat Inc. |
| # |
| # Authors: |
| # Eduardo Habkost <ehabkost@redhat.com> |
| # |
| # This work is licensed under the terms of the GNU GPL, version 2. See |
| # the COPYING file in the top-level directory. |
| import re |
| from .regexps import * |
| from .patching import * |
| from .utils import * |
| from .qom_macros import * |
| |
| TI_FIELDS = [ 'name', 'parent', 'abstract', 'interfaces', |
| 'instance_size', 'instance_init', 'instance_post_init', 'instance_finalize', |
| 'class_size', 'class_init', 'class_base_init', 'class_data'] |
| |
| RE_TI_FIELD_NAME = OR(*TI_FIELDS) |
| |
| RE_TI_FIELD_INIT = S(r'[ \t]*', NAMED('comments', RE_COMMENTS), |
| r'\.', NAMED('field', RE_TI_FIELD_NAME), r'\s*=\s*', |
| NAMED('value', RE_EXPRESSION), r'[ \t]*,?[ \t]*\n') |
| RE_TI_FIELDS = M(RE_TI_FIELD_INIT) |
| |
| RE_TYPEINFO_START = S(r'^[ \t]*', M(r'(static|const)\s+', name='modifiers'), r'TypeInfo\s+', |
| NAMED('name', RE_IDENTIFIER), r'\s*=\s*{[ \t]*\n') |
| |
| ParsedArray = List[str] |
| ParsedInitializerValue = Union[str, ParsedArray] |
| class InitializerValue(NamedTuple): |
| raw: str |
| parsed: Optional[ParsedInitializerValue] |
| match: Optional[Match] |
| |
| class ArrayItem(FileMatch): |
| regexp = RE_ARRAY_ITEM |
| |
| class ArrayInitializer(FileMatch): |
| regexp = RE_ARRAY |
| |
| def parsed(self) -> ParsedArray: |
| #DBG('parse_array: %r', m.group(0)) |
| return [m.group('arrayitem') for m in self.group_finditer(ArrayItem, 'arrayitems')] |
| |
| class FieldInitializer(FileMatch): |
| regexp = RE_TI_FIELD_INIT |
| |
| @property |
| def raw(self) -> str: |
| return self.group('value') |
| |
| @property |
| def parsed(self) -> ParsedInitializerValue: |
| parsed: ParsedInitializerValue = self.raw |
| #DBG("parse_initializer_value: %r", s) |
| array = self.try_group_match(ArrayInitializer, 'value') |
| if array: |
| assert isinstance(array, ArrayInitializer) |
| return array.parsed() |
| return parsed |
| |
| TypeInfoInitializers = Dict[str, FieldInitializer] |
| |
| class TypeDefinition(FileMatch): |
| """ |
| Common base class for type definitions (TypeInfo variables or OBJECT_DEFINE* macros) |
| """ |
| @property |
| def instancetype(self) -> Optional[str]: |
| return self.group('instancetype') |
| |
| @property |
| def classtype(self) -> Optional[str]: |
| return self.group('classtype') |
| |
| @property |
| def uppercase(self) -> Optional[str]: |
| return self.group('uppercase') |
| |
| @property |
| def parent_uppercase(self) -> str: |
| return self.group('parent_uppercase') |
| |
| @property |
| def initializers(self) -> Optional[TypeInfoInitializers]: |
| if getattr(self, '_inititalizers', None): |
| self._initializers: TypeInfoInitializers |
| return self._initializers |
| fields = self.group('fields') |
| if fields is None: |
| return None |
| d = dict((fm.group('field'), fm) |
| for fm in self.group_finditer(FieldInitializer, 'fields')) |
| self._initializers = d # type: ignore |
| return self._initializers |
| |
| |
| class TypeInfoVar(TypeDefinition): |
| """TypeInfo variable declaration with initializer""" |
| regexp = S(NAMED('begin', RE_TYPEINFO_START), |
| M(NAMED('fields', RE_TI_FIELDS), |
| NAMED('endcomments', SP, RE_COMMENTS), |
| NAMED('end', r'};?\n'), |
| n='?', name='fullspec')) |
| |
| def is_static(self) -> bool: |
| return 'static' in self.group('modifiers') |
| |
| def is_const(self) -> bool: |
| return 'const' in self.group('modifiers') |
| |
| def is_full(self) -> bool: |
| return bool(self.group('fullspec')) |
| |
| def get_initializers(self) -> TypeInfoInitializers: |
| """Helper for code that needs to deal with missing initializer info""" |
| if self.initializers is None: |
| return {} |
| return self.initializers |
| |
| def get_raw_initializer_value(self, field: str, default: str = '') -> str: |
| initializers = self.get_initializers() |
| if field in initializers: |
| return initializers[field].raw |
| else: |
| return default |
| |
| @property |
| def typename(self) -> Optional[str]: |
| return self.get_raw_initializer_value('name') |
| |
| @property |
| def uppercase(self) -> Optional[str]: |
| typename = self.typename |
| if not typename: |
| return None |
| if not typename.startswith('TYPE_'): |
| return None |
| return typename[len('TYPE_'):] |
| |
| @property |
| def classtype(self) -> Optional[str]: |
| class_size = self.get_raw_initializer_value('class_size') |
| if not class_size: |
| return None |
| m = re.fullmatch(RE_SIZEOF, class_size) |
| if not m: |
| return None |
| return m.group('sizeoftype') |
| |
| @property |
| def instancetype(self) -> Optional[str]: |
| instance_size = self.get_raw_initializer_value('instance_size') |
| if not instance_size: |
| return None |
| m = re.fullmatch(RE_SIZEOF, instance_size) |
| if not m: |
| return None |
| return m.group('sizeoftype') |
| |
| |
| #def extract_identifiers(self) -> Optional[TypeIdentifiers]: |
| # """Try to extract identifiers from names being used""" |
| # DBG("extracting idenfiers from %s", self.name) |
| #uppercase = None |
| #if typename and re.fullmatch(RE_IDENTIFIER, typename) and typename.startswith("TYPE_"): |
| # uppercase = typename[len('TYPE_'):] |
| #lowercase = None |
| #funcs = set() |
| #prefixes = set() |
| #for field,suffix in [('instance_init', '_init'), |
| # ('instance_finalize', '_finalize'), |
| # ('class_init', '_class_init')]: |
| # if field not in values: |
| # continue |
| # func = values[field].raw |
| # funcs.add(func) |
| # if func.endswith(suffix): |
| # prefixes.add(func[:-len(suffix)]) |
| # else: |
| # self.warn("function name %s doesn't have expected %s suffix", |
| # func, suffix) |
| #if len(prefixes) == 1: |
| # lowercase = prefixes.pop() |
| #elif len(prefixes) > 1: |
| # self.warn("inconsistent function names: %s", ' '.join(funcs)) |
| |
| #.parent = TYPE_##PARENT_MODULE_OBJ_NAME, \ |
| #return TypeIdentifiers(typename=typename, |
| # uppercase=uppercase, lowercase=lowercase, |
| # instancetype=instancetype, classtype=classtype) |
| |
| def append_field(self, field: str, value: str) -> Patch: |
| """Generate patch appending a field initializer""" |
| content = f' .{field} = {value},\n' |
| fm = self.group_match('fields') |
| assert fm |
| return fm.append(content) |
| |
| def patch_field(self, field: str, replacement: str) -> Patch: |
| """Generate patch replacing a field initializer""" |
| initializers = self.initializers |
| assert initializers |
| value = initializers.get(field) |
| assert value |
| return value.make_patch(replacement) |
| |
| def remove_field(self, field: str) -> Iterable[Patch]: |
| initializers = self.initializers |
| assert initializers |
| if field in initializers: |
| yield self.patch_field(field, '') |
| |
| def remove_fields(self, *fields: str) -> Iterable[Patch]: |
| for f in fields: |
| yield from self.remove_field(f) |
| |
| def patch_field_value(self, field: str, replacement: str) -> Patch: |
| """Replace just the value of a field initializer""" |
| initializers = self.initializers |
| assert initializers |
| value = initializers.get(field) |
| assert value |
| vm = value.group_match('value') |
| assert vm |
| return vm.make_patch(replacement) |
| |
| |
| class RemoveRedundantClassSize(TypeInfoVar): |
| """Remove class_size when using OBJECT_DECLARE_SIMPLE_TYPE""" |
| def gen_patches(self) -> Iterable[Patch]: |
| initializers = self.initializers |
| if initializers is None: |
| return |
| if 'class_size' not in initializers: |
| return |
| |
| self.debug("Handling %s", self.name) |
| m = re.fullmatch(RE_SIZEOF, initializers['class_size'].raw) |
| if not m: |
| self.warn("%s class_size is not sizeof?", self.name) |
| return |
| classtype = m.group('sizeoftype') |
| if not classtype.endswith('Class'): |
| self.warn("%s class size type (%s) is not *Class?", self.name, classtype) |
| return |
| self.debug("classtype is %s", classtype) |
| instancetype = classtype[:-len('Class')] |
| self.debug("intanceypte is %s", instancetype) |
| self.debug("searching for simpletype declaration using %s as InstanceType", instancetype) |
| decl = self.allfiles.find_match(OldStyleObjectDeclareSimpleType, |
| instancetype, 'instancetype') |
| if not decl: |
| self.debug("No simpletype declaration found for %s", instancetype) |
| return |
| self.debug("Found simple type declaration") |
| decl.debug("declaration is here") |
| yield from self.remove_field('class_size') |
| |
| class RemoveDeclareSimpleTypeArg(OldStyleObjectDeclareSimpleType): |
| """Remove class_size when using OBJECT_DECLARE_SIMPLE_TYPE""" |
| def gen_patches(self) -> Iterable[Patch]: |
| c = (f'OBJECT_DECLARE_SIMPLE_TYPE({self.group("instancetype")}, {self.group("lowercase")},\n' |
| f' {self.group("uppercase")})\n') |
| yield self.make_patch(c) |
| |
| class UseDeclareTypeExtended(TypeInfoVar): |
| """Replace TypeInfo variable with OBJECT_DEFINE_TYPE_EXTENDED""" |
| def gen_patches(self) -> Iterable[Patch]: |
| # this will just ensure the caches for find_match() and matches_for_type() |
| # will be loaded in advance: |
| find_type_checkers(self.allfiles, 'xxxxxxxxxxxxxxxxx') |
| |
| if not self.is_static(): |
| self.info("Skipping non-static TypeInfo variable") |
| return |
| |
| type_info_macro = self.file.find_match(TypeInfoMacro, self.name) |
| if not type_info_macro: |
| self.warn("TYPE_INFO(%s) line not found", self.name) |
| return |
| |
| values = self.initializers |
| if values is None: |
| return |
| if 'name' not in values: |
| self.warn("name not set in TypeInfo variable %s", self.name) |
| return |
| |
| typename = values['name'].raw |
| |
| if 'parent' not in values: |
| self.warn("parent not set in TypeInfo variable %s", self.name) |
| return |
| parent_typename = values['parent'].raw |
| |
| instancetype = None |
| if 'instance_size' in values: |
| m = re.fullmatch(RE_SIZEOF, values['instance_size'].raw) |
| if m: |
| instancetype = m.group('sizeoftype') |
| else: |
| self.warn("can't extract instance type in TypeInfo variable %s", self.name) |
| self.warn("instance_size is set to: %r", values['instance_size'].raw) |
| return |
| |
| classtype = None |
| if 'class_size' in values: |
| m = re.fullmatch(RE_SIZEOF, values['class_size'].raw) |
| if m: |
| classtype = m.group('sizeoftype') |
| else: |
| self.warn("can't extract class type in TypeInfo variable %s", self.name) |
| self.warn("class_size is set to: %r", values['class_size'].raw) |
| return |
| |
| #for t in (typename, parent_typename): |
| # if not re.fullmatch(RE_IDENTIFIER, t): |
| # self.info("type name is not a macro/constant") |
| # if instancetype or classtype: |
| # self.warn("macro/constant type name is required for instance/class type") |
| # if not self.file.force: |
| # return |
| |
| # Now, the challenge is to find out the right MODULE_OBJ_NAME for the |
| # type and for the parent type |
| self.info("TypeInfo variable for %s is here", typename) |
| uppercase = find_typename_uppercase(self.allfiles, typename) |
| if not uppercase: |
| self.info("Can't find right uppercase name for %s", typename) |
| if instancetype or classtype: |
| self.warn("Can't find right uppercase name for %s", typename) |
| self.warn("This will make type validation difficult in the future") |
| return |
| |
| parent_uppercase = find_typename_uppercase(self.allfiles, parent_typename) |
| if not parent_uppercase: |
| self.info("Can't find right uppercase name for parent type (%s)", parent_typename) |
| if instancetype or classtype: |
| self.warn("Can't find right uppercase name for parent type (%s)", parent_typename) |
| self.warn("This will make type validation difficult in the future") |
| return |
| |
| ok = True |
| |
| #checkers: List[TypeCheckerDeclaration] = list(find_type_checkers(self.allfiles, uppercase)) |
| #for c in checkers: |
| # c.info("instance type checker declaration (%s) is here", c.group('uppercase')) |
| #if not checkers: |
| # self.info("No type checkers declared for %s", uppercase) |
| # if instancetype or classtype: |
| # self.warn("Can't find where type checkers for %s (%s) are declared. We will need them to validate sizes of %s", |
| # typename, uppercase, self.name) |
| |
| if not instancetype: |
| instancetype = 'void' |
| if not classtype: |
| classtype = 'void' |
| |
| #checker_instancetypes = set(c.instancetype for c in checkers |
| # if c.instancetype is not None) |
| #if len(checker_instancetypes) > 1: |
| # self.warn("ambiguous set of type checkers") |
| # for c in checkers: |
| # c.warn("instancetype is %s here", c.instancetype) |
| # ok = False |
| #elif len(checker_instancetypes) == 1: |
| # checker_instancetype = checker_instancetypes.pop() |
| # DBG("checker instance type: %r", checker_instancetype) |
| # if instancetype != checker_instancetype: |
| # self.warn("type at instance_size is %r. Should instance_size be set to sizeof(%s) ?", |
| # instancetype, checker_instancetype) |
| # ok = False |
| #else: |
| # if instancetype != 'void': |
| # self.warn("instance type checker for %s (%s) not found", typename, instancetype) |
| # ok = False |
| |
| #checker_classtypes = set(c.classtype for c in checkers |
| # if c.classtype is not None) |
| #if len(checker_classtypes) > 1: |
| # self.warn("ambiguous set of type checkers") |
| # for c in checkers: |
| # c.warn("classtype is %s here", c.classtype) |
| # ok = False |
| #elif len(checker_classtypes) == 1: |
| # checker_classtype = checker_classtypes.pop() |
| # DBG("checker class type: %r", checker_classtype) |
| # if classtype != checker_classtype: |
| # self.warn("type at class_size is %r. Should class_size be set to sizeof(%s) ?", |
| # classtype, checker_classtype) |
| # ok = False |
| #else: |
| # if classtype != 'void': |
| # self.warn("class type checker for %s (%s) not found", typename, classtype) |
| # ok = False |
| |
| #if not ok: |
| # for c in checkers: |
| # c.warn("Type checker declaration for %s (%s) is here", |
| # typename, type(c).__name__) |
| # return |
| |
| #if parent_decl is None: |
| # self.warn("Can't find where parent type %s is declared", parent_typename) |
| |
| #yield self.prepend(f'DECLARE_TYPE_NAME({uppercase}, {typename})\n') |
| #if not instancetype: |
| # yield self.prepend(f'DECLARE_INSTANCE_TYPE({uppercase}, void)\n') |
| #if not classtype: |
| # yield self.prepend(f'DECLARE_CLASS_TYPE({uppercase}, void)\n') |
| self.info("%s can be patched!", self.name) |
| replaced_fields = ['name', 'parent', 'instance_size', 'class_size'] |
| begin = self.group_match('begin') |
| newbegin = f'OBJECT_DEFINE_TYPE_EXTENDED({self.name},\n' |
| newbegin += f' {instancetype}, {classtype},\n' |
| newbegin += f' {uppercase}, {parent_uppercase}' |
| if set(values.keys()) - set(replaced_fields): |
| newbegin += ',\n' |
| yield begin.make_patch(newbegin) |
| yield from self.remove_fields(*replaced_fields) |
| end = self.group_match('end') |
| yield end.make_patch(')\n') |
| yield type_info_macro.make_removal_patch() |
| |
| class ObjectDefineTypeExtended(TypeDefinition): |
| """OBJECT_DEFINE_TYPE_EXTENDED usage""" |
| regexp = S(r'^[ \t]*OBJECT_DEFINE_TYPE_EXTENDED\s*\(\s*', |
| NAMED('name', RE_IDENTIFIER), r'\s*,\s*', |
| NAMED('instancetype', RE_IDENTIFIER), r'\s*,\s*', |
| NAMED('classtype', RE_IDENTIFIER), r'\s*,\s*', |
| NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*', |
| NAMED('parent_uppercase', RE_IDENTIFIER), |
| M(r',\s*\n', |
| NAMED('fields', RE_TI_FIELDS), |
| n='?'), |
| r'\s*\);?\n?') |
| |
| class ObjectDefineType(TypeDefinition): |
| """OBJECT_DEFINE_TYPE usage""" |
| regexp = S(r'^[ \t]*OBJECT_DEFINE_TYPE\s*\(\s*', |
| NAMED('lowercase', RE_IDENTIFIER), r'\s*,\s*', |
| NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*', |
| NAMED('parent_uppercase', RE_IDENTIFIER), |
| M(r',\s*\n', |
| NAMED('fields', RE_TI_FIELDS), |
| n='?'), |
| r'\s*\);?\n?') |
| |
| def find_type_definitions(files: FileList, uppercase: str) -> Iterable[TypeDefinition]: |
| types: List[Type[TypeDefinition]] = [TypeInfoVar, ObjectDefineType, ObjectDefineTypeExtended] |
| for t in types: |
| for m in files.matches_of_type(t): |
| m.debug("uppercase: %s", m.uppercase) |
| yield from (m for t in types |
| for m in files.matches_of_type(t) |
| if m.uppercase == uppercase) |
| |
| class AddDeclareVoidClassType(TypeDeclarationFixup): |
| """Will add DECLARE_CLASS_TYPE(..., void) if possible""" |
| def gen_patches_for_type(self, uppercase: str, |
| checkers: List[TypeDeclaration], |
| fields: Dict[str, Optional[str]]) -> Iterable[Patch]: |
| defs = list(find_type_definitions(self.allfiles, uppercase)) |
| if len(defs) > 1: |
| self.warn("multiple definitions for %s", uppercase) |
| for d in defs: |
| d.warn("definition found here") |
| return |
| elif len(defs) == 0: |
| self.warn("type definition for %s not found", uppercase) |
| return |
| d = defs[0] |
| if d.classtype is None: |
| d.info("definition for %s has classtype, skipping", uppercase) |
| return |
| class_type_checkers = [c for c in checkers |
| if c.classtype is not None] |
| if class_type_checkers: |
| for c in class_type_checkers: |
| c.warn("class type checker for %s is present here", uppercase) |
| return |
| |
| _,last_checker = max((m.start(), m) for m in checkers) |
| s = f'DECLARE_CLASS_TYPE({uppercase}, void)\n' |
| yield last_checker.append(s) |
| |
| class AddDeclareVoidInstanceType(FileMatch): |
| """Will add DECLARE_INSTANCE_TYPE(..., void) if possible""" |
| regexp = S(r'^[ \t]*#[ \t]*define', CPP_SPACE, |
| NAMED('name', r'TYPE_[a-zA-Z0-9_]+\b'), |
| CPP_SPACE, r'.*\n') |
| |
| def gen_patches(self) -> Iterable[Patch]: |
| assert self.name.startswith('TYPE_') |
| uppercase = self.name[len('TYPE_'):] |
| defs = list(find_type_definitions(self.allfiles, uppercase)) |
| if len(defs) > 1: |
| self.warn("multiple definitions for %s", uppercase) |
| for d in defs: |
| d.warn("definition found here") |
| return |
| elif len(defs) == 0: |
| self.warn("type definition for %s not found", uppercase) |
| return |
| d = defs[0] |
| instancetype = d.instancetype |
| if instancetype is not None and instancetype != 'void': |
| return |
| |
| instance_checkers = [c for c in find_type_checkers(self.allfiles, uppercase) |
| if c.instancetype] |
| if instance_checkers: |
| d.warn("instance type checker for %s already declared", uppercase) |
| for c in instance_checkers: |
| c.warn("instance checker for %s is here", uppercase) |
| return |
| |
| s = f'DECLARE_INSTANCE_TYPE({uppercase}, void)\n' |
| yield self.append(s) |
| |
| class AddObjectDeclareType(DeclareObjCheckers): |
| """Will add OBJECT_DECLARE_TYPE(...) if possible""" |
| def gen_patches(self) -> Iterable[Patch]: |
| uppercase = self.uppercase |
| typename = self.group('typename') |
| instancetype = self.group('instancetype') |
| classtype = self.group('classtype') |
| |
| if typename != f'TYPE_{uppercase}': |
| self.warn("type name mismatch: %s vs %s", typename, uppercase) |
| return |
| |
| typedefs = [(t,self.allfiles.find_matches(SimpleTypedefMatch, t)) |
| for t in (instancetype, classtype)] |
| for t,tds in typedefs: |
| if not tds: |
| self.warn("typedef %s not found", t) |
| return |
| for td in tds: |
| td_type = td.group('typedef_type') |
| if td_type != f'struct {t}': |
| self.warn("typedef mismatch: %s is defined as %s", t, td_type) |
| td.warn("typedef is here") |
| return |
| |
| # look for reuse of same struct type |
| other_instance_checkers = [c for c in find_type_checkers(self.allfiles, instancetype, 'instancetype') |
| if c.uppercase != uppercase] |
| if other_instance_checkers: |
| self.warn("typedef %s is being reused", instancetype) |
| for ic in other_instance_checkers: |
| ic.warn("%s is reused here", instancetype) |
| if not self.file.force: |
| return |
| |
| decl_types: List[Type[TypeDeclaration]] = [DeclareClassCheckers, DeclareObjCheckers] |
| class_decls = [m for t in decl_types |
| for m in self.allfiles.find_matches(t, uppercase, 'uppercase')] |
| |
| defs = list(find_type_definitions(self.allfiles, uppercase)) |
| if len(defs) > 1: |
| self.warn("multiple definitions for %s", uppercase) |
| for d in defs: |
| d.warn("definition found here") |
| if not self.file.force: |
| return |
| elif len(defs) == 0: |
| self.warn("type definition for %s not found", uppercase) |
| if not self.file.force: |
| return |
| else: |
| d = defs[0] |
| if d.instancetype != instancetype: |
| self.warn("mismatching instance type for %s (%s)", uppercase, instancetype) |
| d.warn("instance type declared here (%s)", d.instancetype) |
| if not self.file.force: |
| return |
| if d.classtype != classtype: |
| self.warn("mismatching class type for %s (%s)", uppercase, classtype) |
| d.warn("class type declared here (%s)", d.classtype) |
| if not self.file.force: |
| return |
| |
| assert self.file.original_content |
| for t,tds in typedefs: |
| assert tds |
| for td in tds: |
| if td.file is not self.file: |
| continue |
| |
| # delete typedefs that are truly redundant: |
| # 1) defined after DECLARE_OBJ_CHECKERS |
| if td.start() > self.start(): |
| yield td.make_removal_patch() |
| # 2) defined before DECLARE_OBJ_CHECKERS, but unused |
| elif not re.search(r'\b'+t+r'\b', self.file.original_content[td.end():self.start()]): |
| yield td.make_removal_patch() |
| |
| c = (f'OBJECT_DECLARE_TYPE({instancetype}, {classtype}, {uppercase})\n') |
| yield self.make_patch(c) |
| |
| class AddObjectDeclareSimpleType(DeclareInstanceChecker): |
| """Will add OBJECT_DECLARE_SIMPLE_TYPE(...) if possible""" |
| def gen_patches(self) -> Iterable[Patch]: |
| uppercase = self.uppercase |
| typename = self.group('typename') |
| instancetype = self.group('instancetype') |
| |
| if typename != f'TYPE_{uppercase}': |
| self.warn("type name mismatch: %s vs %s", typename, uppercase) |
| return |
| |
| typedefs = [(t,self.allfiles.find_matches(SimpleTypedefMatch, t)) |
| for t in (instancetype,)] |
| for t,tds in typedefs: |
| if not tds: |
| self.warn("typedef %s not found", t) |
| return |
| for td in tds: |
| td_type = td.group('typedef_type') |
| if td_type != f'struct {t}': |
| self.warn("typedef mismatch: %s is defined as %s", t, td_type) |
| td.warn("typedef is here") |
| return |
| |
| # look for reuse of same struct type |
| other_instance_checkers = [c for c in find_type_checkers(self.allfiles, instancetype, 'instancetype') |
| if c.uppercase != uppercase] |
| if other_instance_checkers: |
| self.warn("typedef %s is being reused", instancetype) |
| for ic in other_instance_checkers: |
| ic.warn("%s is reused here", instancetype) |
| if not self.file.force: |
| return |
| |
| decl_types: List[Type[TypeDeclaration]] = [DeclareClassCheckers, DeclareObjCheckers] |
| class_decls = [m for t in decl_types |
| for m in self.allfiles.find_matches(t, uppercase, 'uppercase')] |
| if class_decls: |
| self.warn("class type declared for %s", uppercase) |
| for cd in class_decls: |
| cd.warn("class declaration found here") |
| return |
| |
| defs = list(find_type_definitions(self.allfiles, uppercase)) |
| if len(defs) > 1: |
| self.warn("multiple definitions for %s", uppercase) |
| for d in defs: |
| d.warn("definition found here") |
| if not self.file.force: |
| return |
| elif len(defs) == 0: |
| self.warn("type definition for %s not found", uppercase) |
| if not self.file.force: |
| return |
| else: |
| d = defs[0] |
| if d.instancetype != instancetype: |
| self.warn("mismatching instance type for %s (%s)", uppercase, instancetype) |
| d.warn("instance type declared here (%s)", d.instancetype) |
| if not self.file.force: |
| return |
| if d.classtype: |
| self.warn("class type set for %s", uppercase) |
| d.warn("class type declared here") |
| if not self.file.force: |
| return |
| |
| assert self.file.original_content |
| for t,tds in typedefs: |
| assert tds |
| for td in tds: |
| if td.file is not self.file: |
| continue |
| |
| # delete typedefs that are truly redundant: |
| # 1) defined after DECLARE_OBJ_CHECKERS |
| if td.start() > self.start(): |
| yield td.make_removal_patch() |
| # 2) defined before DECLARE_OBJ_CHECKERS, but unused |
| elif not re.search(r'\b'+t+r'\b', self.file.original_content[td.end():self.start()]): |
| yield td.make_removal_patch() |
| |
| c = (f'OBJECT_DECLARE_SIMPLE_TYPE({instancetype}, {uppercase})\n') |
| yield self.make_patch(c) |
| |
| |
| class TypeInfoStringName(TypeInfoVar): |
| """Replace hardcoded type names with TYPE_ constant""" |
| def gen_patches(self) -> Iterable[Patch]: |
| values = self.initializers |
| if values is None: |
| return |
| if 'name' not in values: |
| self.warn("name not set in TypeInfo variable %s", self.name) |
| return |
| typename = values['name'].raw |
| if re.fullmatch(RE_IDENTIFIER, typename): |
| return |
| |
| self.warn("name %s is not an identifier", typename) |
| #all_defines = [m for m in self.allfiles.matches_of_type(ExpressionDefine)] |
| #self.debug("all_defines: %r", all_defines) |
| constants = [m for m in self.allfiles.matches_of_type(ExpressionDefine) |
| if m.group('value').strip() == typename.strip()] |
| if not constants: |
| self.warn("No macro for %s found", typename) |
| return |
| if len(constants) > 1: |
| self.warn("I don't know which macro to use: %r", constants) |
| return |
| yield self.patch_field_value('name', constants[0].name) |
| |
| class RedundantTypeSizes(TypeInfoVar): |
| """Remove redundant instance_size/class_size from TypeInfo vars""" |
| def gen_patches(self) -> Iterable[Patch]: |
| values = self.initializers |
| if values is None: |
| return |
| if 'name' not in values: |
| self.warn("name not set in TypeInfo variable %s", self.name) |
| return |
| typename = values['name'].raw |
| if 'parent' not in values: |
| self.warn("parent not set in TypeInfo variable %s", self.name) |
| return |
| parent_typename = values['parent'].raw |
| |
| if 'instance_size' not in values and 'class_size' not in values: |
| self.debug("no need to validate %s", self.name) |
| return |
| |
| instance_decls = find_type_checkers(self.allfiles, typename) |
| if instance_decls: |
| self.debug("won't touch TypeInfo var that has type checkers") |
| return |
| |
| parent = find_type_info(self.allfiles, parent_typename) |
| if not parent: |
| self.warn("Can't find TypeInfo for %s", parent_typename) |
| return |
| |
| if 'instance_size' in values and parent.get_raw_initializer_value('instance_size') != values['instance_size'].raw: |
| self.info("instance_size mismatch") |
| parent.info("parent type declared here") |
| return |
| |
| if 'class_size' in values and parent.get_raw_initializer_value('class_size') != values['class_size'].raw: |
| self.info("class_size mismatch") |
| parent.info("parent type declared here") |
| return |
| |
| self.debug("will patch variable %s", self.name) |
| |
| if 'instance_size' in values: |
| self.debug("deleting instance_size") |
| yield self.patch_field('instance_size', '') |
| |
| if 'class_size' in values: |
| self.debug("deleting class_size") |
| yield self.patch_field('class_size', '') |
| |
| |
| #class TypeInfoVarInitFuncs(TypeInfoVar): |
| # """TypeInfo variable |
| # Will create missing init functions |
| # """ |
| # def gen_patches(self) -> Iterable[Patch]: |
| # values = self.initializers |
| # if values is None: |
| # self.warn("type not parsed completely: %s", self.name) |
| # return |
| # |
| # macro = self.file.find_match(TypeInfoVar, self.name) |
| # if macro is None: |
| # self.warn("No TYPE_INFO macro for %s", self.name) |
| # return |
| # |
| # ids = self.extract_identifiers() |
| # if ids is None: |
| # return |
| # |
| # DBG("identifiers extracted: %r", ids) |
| # fields = set(values.keys()) |
| # if ids.lowercase: |
| # if 'instance_init' not in fields: |
| # yield self.prepend(('static void %s_init(Object *obj)\n' |
| # '{\n' |
| # '}\n\n') % (ids.lowercase)) |
| # yield self.append_field('instance_init', ids.lowercase+'_init') |
| # |
| # if 'instance_finalize' not in fields: |
| # yield self.prepend(('static void %s_finalize(Object *obj)\n' |
| # '{\n' |
| # '}\n\n') % (ids.lowercase)) |
| # yield self.append_field('instance_finalize', ids.lowercase+'_finalize') |
| # |
| # |
| # if 'class_init' not in fields: |
| # yield self.prepend(('static void %s_class_init(ObjectClass *oc, void *data)\n' |
| # '{\n' |
| # '}\n\n') % (ids.lowercase)) |
| # yield self.append_field('class_init', ids.lowercase+'_class_init') |
| |
| class TypeInitMacro(FileMatch): |
| """Use of type_init(...) macro""" |
| regexp = S(r'^[ \t]*type_init\s*\(\s*', NAMED('name', RE_IDENTIFIER), r'\s*\);?[ \t]*\n') |
| |
| class DeleteEmptyTypeInitFunc(TypeInitMacro): |
| """Delete empty function declared using type_init(...)""" |
| def gen_patches(self) -> Iterable[Patch]: |
| fn = self.file.find_match(StaticVoidFunction, self.name) |
| DBG("function for %s: %s", self.name, fn) |
| if fn and fn.body == '': |
| yield fn.make_patch('') |
| yield self.make_patch('') |
| |
| class StaticVoidFunction(FileMatch): |
| """simple static void function |
| (no replacement rules) |
| """ |
| #NOTE: just like RE_FULL_STRUCT, this doesn't parse any of the body contents |
| # of the function. Tt will just look for "}" in the beginning of a line |
| regexp = S(r'static\s+void\s+', NAMED('name', RE_IDENTIFIER), r'\s*\(\s*void\s*\)\n', |
| r'{\n', |
| NAMED('body', |
| # acceptable inside the function body: |
| # - lines starting with space or tab |
| # - empty lines |
| # - preprocessor directives |
| OR(r'[ \t][^\n]*\n', |
| r'#[^\n]*\n', |
| r'\n', |
| repeat='*')), |
| r'};?\n') |
| |
| @property |
| def body(self) -> str: |
| return self.group('body') |
| |
| def has_preprocessor_directive(self) -> bool: |
| return bool(re.search(r'^[ \t]*#', self.body, re.MULTILINE)) |
| |
| def find_containing_func(m: FileMatch) -> Optional['StaticVoidFunction']: |
| """Return function containing this match""" |
| for fn in m.file.matches_of_type(StaticVoidFunction): |
| if fn.contains(m): |
| return fn |
| return None |
| |
| class TypeRegisterStaticCall(FileMatch): |
| """type_register_static() call |
| Will be replaced by TYPE_INFO() macro |
| """ |
| regexp = S(r'^[ \t]*', NAMED('func_name', 'type_register_static'), |
| r'\s*\(&\s*', NAMED('name', RE_IDENTIFIER), r'\s*\);[ \t]*\n') |
| |
| class UseTypeInfo(TypeRegisterStaticCall): |
| """Replace type_register_static() call with TYPE_INFO declaration""" |
| def gen_patches(self) -> Iterable[Patch]: |
| fn = find_containing_func(self) |
| if fn: |
| DBG("%r is inside %r", self, fn) |
| type_init = self.file.find_match(TypeInitMacro, fn.name) |
| if type_init is None: |
| self.warn("can't find type_init(%s) line", fn.name) |
| if not self.file.force: |
| return |
| else: |
| self.warn("can't identify the function where type_register_static(&%s) is called", self.name) |
| if not self.file.force: |
| return |
| |
| #if fn.has_preprocessor_directive() and not self.file.force: |
| # self.warn("function %s has preprocessor directives, this requires --force", fn.name) |
| # return |
| |
| var = self.file.find_match(TypeInfoVar, self.name) |
| if var is None: |
| self.warn("can't find TypeInfo var declaration for %s", self.name) |
| return |
| |
| if not var.is_full(): |
| self.warn("variable declaration %s wasn't parsed fully", var.name) |
| if not self.file.force: |
| return |
| |
| if fn and fn.contains(var): |
| self.warn("TypeInfo %s variable is inside a function", self.name) |
| if not self.file.force: |
| return |
| |
| # delete type_register_static() call: |
| yield self.make_patch('') |
| # append TYPE_REGISTER(...) after variable declaration: |
| yield var.append(f'TYPE_INFO({self.name})\n') |
| |
| class TypeRegisterCall(FileMatch): |
| """type_register_static() call""" |
| regexp = S(r'^[ \t]*', NAMED('func_name', 'type_register'), |
| r'\s*\(&\s*', NAMED('name', RE_IDENTIFIER), r'\s*\);[ \t]*\n') |
| |
| class MakeTypeRegisterStatic(TypeRegisterCall): |
| """Make type_register() call static if variable is static const""" |
| def gen_patches(self): |
| var = self.file.find_match(TypeInfoVar, self.name) |
| if var is None: |
| self.warn("can't find TypeInfo var declaration for %s", self.name) |
| return |
| if var.is_static() and var.is_const(): |
| yield self.group_match('func_name').make_patch('type_register_static') |
| |
| class MakeTypeRegisterNotStatic(TypeRegisterStaticCall): |
| """Make type_register() call static if variable is static const""" |
| def gen_patches(self): |
| var = self.file.find_match(TypeInfoVar, self.name) |
| if var is None: |
| self.warn("can't find TypeInfo var declaration for %s", self.name) |
| return |
| if not var.is_static() or not var.is_const(): |
| yield self.group_match('func_name').make_patch('type_register') |
| |
| class TypeInfoMacro(FileMatch): |
| """TYPE_INFO macro usage""" |
| regexp = S(r'^[ \t]*TYPE_INFO\s*\(\s*', NAMED('name', RE_IDENTIFIER), r'\s*\)[ \t]*;?[ \t]*\n') |
| |
| def find_type_info(files: RegexpScanner, name: str) -> Optional[TypeInfoVar]: |
| ti = [ti for ti in files.matches_of_type(TypeInfoVar) |
| if ti.get_raw_initializer_value('name') == name] |
| DBG("type info vars: %r", ti) |
| if len(ti) > 1: |
| DBG("multiple TypeInfo vars found for %s", name) |
| return None |
| if len(ti) == 0: |
| DBG("no TypeInfo var found for %s", name) |
| return None |
| return ti[0] |
| |
| class CreateClassStruct(DeclareInstanceChecker): |
| """Replace DECLARE_INSTANCE_CHECKER with OBJECT_DECLARE_SIMPLE_TYPE""" |
| def gen_patches(self) -> Iterable[Patch]: |
| typename = self.group('typename') |
| DBG("looking for TypeInfo variable for %s", typename) |
| var = find_type_info(self.allfiles, typename) |
| if var is None: |
| self.warn("no TypeInfo var found for %s", typename) |
| return |
| assert var.initializers |
| if 'class_size' in var.initializers: |
| self.warn("class size already set for TypeInfo %s", var.name) |
| return |
| classtype = self.group('instancetype')+'Class' |
| return |
| yield |
| #TODO: need to find out what's the parent class type... |
| #yield var.append_field('class_size', f'sizeof({classtype})') |
| #c = (f'OBJECT_DECLARE_SIMPLE_TYPE({instancetype}, {lowercase},\n' |
| # f' MODULE_OBJ_NAME, ParentClassType)\n') |
| #yield self.make_patch(c) |
| |
| def type_infos(file: FileInfo) -> Iterable[TypeInfoVar]: |
| return file.matches_of_type(TypeInfoVar) |
| |
| def full_types(file: FileInfo) -> Iterable[TypeInfoVar]: |
| return [t for t in type_infos(file) if t.is_full()] |
| |
| def partial_types(file: FileInfo) -> Iterable[TypeInfoVar]: |
| return [t for t in type_infos(file) if not t.is_full()] |