@@ -5,7 +5,7 @@
#
# This work is licensed under the terms of the GNU GPL, version 2. See
# the COPYING file in the top-level directory.
-from typing import IO, Match, NamedTuple, Optional, Literal, Iterable, Type, Dict, List, Any, TypeVar, NewType, Tuple
+from typing import IO, Match, NamedTuple, Optional, Literal, Iterable, Type, Dict, List, Any, TypeVar, NewType, Tuple, Union
from pathlib import Path
from itertools import chain
from tempfile import NamedTemporaryFile
@@ -47,7 +47,7 @@ class FileMatch:
def __init__(self, f: 'FileInfo', m: Match) -> None:
self.file: 'FileInfo' = f
- self.match: Match = m
+ self.match: Match[str] = m
@property
def name(self) -> str:
@@ -68,8 +68,13 @@ class FileMatch:
def line_col(self) -> LineAndColumn:
return self.file.line_col(self.start())
- def group(self, *args):
- return self.match.group(*args)
+ def group(self, group: Union[int, str]) -> str:
+ return self.match.group(group)
+
+ def getgroup(self, group: str) -> Optional[str]:
+ if group not in self.match.groupdict():
+ return None
+ return self.match.group(group)
def log(self, level, fmt, *args) -> None:
pos = self.line_col()
@@ -163,18 +168,51 @@ class FileMatch:
raise NotImplementedError()
@classmethod
- def find_matches(klass, content: str) -> Iterable[Match]:
- """Generate match objects for class
+ def finditer(klass, content: str, pos=0, endpos=-1) -> Iterable[Match]:
+ """Helper for re.finditer()"""
+ if endpos >= 0:
+ content = content[:endpos]
+ return klass.compiled_re().finditer(content, pos)
- Might be reimplemented by subclasses if they
- intend to look for matches using a different method.
- """
- return klass.compiled_re().finditer(content)
+ @classmethod
+ def domatch(klass, content: str, pos=0, endpos=-1) -> Optional[Match]:
+ """Helper for re.match()"""
+ if endpos >= 0:
+ content = content[:endpos]
+ return klass.compiled_re().match(content, pos)
+
+ def group_finditer(self, klass: Type['FileMatch'], group: Union[str, int]) -> Iterable['FileMatch']:
+ assert self.file.original_content
+ return (klass(self.file, m)
+ for m in klass.finditer(self.file.original_content,
+ self.match.start(group),
+ self.match.end(group)))
+
+ def try_group_match(self, klass: Type['FileMatch'], group: Union[str, int]) -> Optional['FileMatch']:
+ assert self.file.original_content
+ m = klass.domatch(self.file.original_content,
+ self.match.start(group),
+ self.match.end(group))
+ if not m:
+ return None
+ else:
+ return klass(self.file, m)
+
+ def group_match(self, group: Union[str, int]) -> 'FileMatch':
+ m = self.try_group_match(FullMatch, group)
+ assert m
+ return m
@property
def allfiles(self) -> 'FileList':
return self.file.allfiles
+class FullMatch(FileMatch):
+ """Regexp that will match all contents of string
+ Useful when used with group_match()
+ """
+ regexp = r'(?s).*' # (?s) is re.DOTALL
+
def all_subclasses(c: Type[FileMatch]) -> Iterable[Type[FileMatch]]:
for sc in c.__subclasses__():
yield sc
@@ -201,7 +239,15 @@ def apply_patches(s: str, patches: Iterable[Patch]) -> str:
"""
r = StringIO()
last = 0
- for p in sorted(patches):
+ def patch_sort_key(item: Tuple[int, Patch]) -> Tuple[int, int, int]:
+ """Patches are sorted by byte position,
+ patches at the same byte position are applied in the order
+ they were generated.
+ """
+ i,p = item
+ return (p.start, p.end, i)
+
+ for i,p in sorted(enumerate(patches), key=patch_sort_key):
DBG("Applying patch at position %d (%s) - %d (%s): %r",
p.start, line_col(s, p.start),
p.end, line_col(s, p.end),
@@ -220,26 +266,35 @@ class RegexpScanner:
self.match_index: Dict[Type[Any], List[FileMatch]] = {}
self.match_name_index: Dict[Tuple[Type[Any], str, str], Optional[FileMatch]] = {}
- def _find_matches(self, klass: Type[Any]) -> Iterable[FileMatch]:
+ def _matches_of_type(self, klass: Type[Any]) -> Iterable[FileMatch]:
raise NotImplementedError()
def matches_of_type(self, t: Type[T]) -> List[T]:
if t not in self.match_index:
- self.match_index[t] = list(self._find_matches(t))
- return self.match_index[t] # type: ignore
+ self.match_index[t] = list(self._matches_of_type(t))
+ return self.match_index[t] # type: ignore
- def find_match(self, t: Type[T], name: str, group: str='name') -> Optional[T]:
+ def find_matches(self, t: Type[T], name: str, group: str='name') -> List[T]:
indexkey = (t, name, group)
if indexkey in self.match_name_index:
return self.match_name_index[indexkey] # type: ignore
- r: Optional[T] = None
+ r: List[T] = []
for m in self.matches_of_type(t):
assert isinstance(m, FileMatch)
- if m.group(group) == name:
- r = m # type: ignore
+ if m.getgroup(group) == name:
+ r.append(m) # type: ignore
self.match_name_index[indexkey] = r # type: ignore
return r
+ def find_match(self, t: Type[T], name: str, group: str='name') -> Optional[T]:
+ l = self.find_matches(t, name, group)
+ if not l:
+ return None
+ if len(l) > 1:
+ logger.warn("multiple matches found for %r (%s=%r)", t, group, name)
+ return None
+ return l[0]
+
def reset_index(self) -> None:
self.match_index.clear()
self.match_name_index.clear()
@@ -258,18 +313,22 @@ class FileInfo(RegexpScanner):
def __repr__(self) -> str:
return f'<FileInfo {repr(self.filename)}>'
+ def filename_matches(self, name: str) -> bool:
+ nameparts = Path(name).parts
+ return self.filename.parts[-len(nameparts):] == nameparts
+
def line_col(self, start: int) -> LineAndColumn:
"""Return line and column for a match object inside original_content"""
return line_col(self.original_content, start)
- def _find_matches(self, klass: Type[Any]) -> List[FileMatch]:
+ def _matches_of_type(self, klass: Type[Any]) -> List[FileMatch]:
"""Build FileMatch objects for each match of regexp"""
if not hasattr(klass, 'regexp') or klass.regexp is None:
return []
assert hasattr(klass, 'regexp')
DBG("%s: scanning for %s", self.filename, klass.__name__)
DBG("regexp: %s", klass.regexp)
- matches = [klass(self, m) for m in klass.find_matches(self.original_content)]
+ matches = [klass(self, m) for m in klass.finditer(self.original_content)]
DBG('%s: %d matches found for %s: %s', self.filename, len(matches),
klass.__name__,' '.join(names(matches)))
return matches
@@ -277,7 +336,7 @@ class FileInfo(RegexpScanner):
def find_match(self, t: Type[T], name: str, group: str='name') -> Optional[T]:
for m in self.matches_of_type(t):
assert isinstance(m, FileMatch)
- if m.group(group) == name:
+ if m.getgroup(group) == name:
return m # type: ignore
return None
@@ -299,7 +358,16 @@ class FileInfo(RegexpScanner):
return (m for l in lists
for m in l)
- def scan_for_matches(self, class_names: Optional[List[str]]=None) -> None:
+ def gen_patches(self, matches: List[FileMatch]) -> None:
+ for m in matches:
+ DBG("Generating patches for %r", m)
+ for i,p in enumerate(m.gen_patches()):
+ DBG("patch %d generated by %r:", i, m)
+ DBG("replace contents at %s-%s with %r",
+ self.line_col(p.start), self.line_col(p.end), p.replacement)
+ self.patches.append(p)
+
+ def scan_for_matches(self, class_names: Optional[List[str]]=None) -> Iterable[FileMatch]:
DBG("class names: %r", class_names)
class_dict = match_class_dict()
if class_names is None:
@@ -309,40 +377,9 @@ class FileInfo(RegexpScanner):
DBG("class_names: %r", class_names)
for cn in class_names:
matches = self.matches_of_type(class_dict[cn])
- if len(matches) > 0:
- DBG('%s: %d matches found for %s: %s', self.filename,
- len(matches), cn, ' '.join(names(matches)))
-
- def gen_patches(self) -> None:
- for m in self.all_matches:
- for i,p in enumerate(m.gen_patches()):
- DBG("patch %d generated by %r:", i, m)
- DBG("replace contents at %s-%s with %r",
- self.line_col(p.start), self.line_col(p.end), p.replacement)
- self.patches.append(p)
-
- def patch_content(self, max_passes=0, class_names: Optional[List[str]]=None) -> None:
- """Multi-pass content patching loop
-
- We run multiple passes because there are rules that will
- delete init functions once they become empty.
- """
- passes = 0
- total_patches = 0
- DBG("max_passes: %r", max_passes)
- while not max_passes or max_passes <= 0 or passes < max_passes:
- passes += 1
- self.scan_for_matches(class_names)
- self.gen_patches()
- DBG("patch content: pass %d: %d patches generated", passes, len(self.patches))
- total_patches += len(self.patches)
- if not self.patches:
- break
- try:
- self.apply_patches()
- except PatchingError:
- logger.exception("%s: failed to patch file", self.filename)
- DBG("%s: %d patches applied total in %d passes", self.filename, total_patches, passes)
+ DBG('%d matches found for %s: %s',
+ len(matches), cn, ' '.join(names(matches)))
+ yield from matches
def apply_patches(self) -> None:
"""Replace self.original_content after applying patches from self.patches"""
@@ -384,14 +421,46 @@ class FileList(RegexpScanner):
def __iter__(self):
return iter(self.files)
- def _find_matches(self, klass: Type[Any]) -> Iterable[FileMatch]:
- return chain(*(f._find_matches(klass) for f in self.files))
+ def _matches_of_type(self, klass: Type[Any]) -> Iterable[FileMatch]:
+ return chain(*(f._matches_of_type(klass) for f in self.files))
- def find_file(self, name) -> Optional[FileInfo]:
+ def find_file(self, name: str) -> Optional[FileInfo]:
"""Get file with path ending with @name"""
- nameparts = Path(name).parts
for f in self.files:
- if f.filename.parts[:len(nameparts)] == nameparts:
+ if f.filename_matches(name):
return f
else:
- return None
\ No newline at end of file
+ return None
+
+ def one_pass(self, class_names: List[str]) -> int:
+ total_patches = 0
+ for f in self.files:
+ INFO("Scanning file %s", f.filename)
+ matches = list(f.scan_for_matches(class_names))
+ INFO("Generating patches for file %s", f.filename)
+ f.gen_patches(matches)
+ total_patches += len(f.patches)
+ if total_patches:
+ for f in self.files:
+ try:
+ f.apply_patches()
+ except PatchingError:
+ logger.exception("%s: failed to patch file", f.filename)
+ return total_patches
+
+ def patch_content(self, max_passes, class_names: List[str]) -> None:
+ """Multi-pass content patching loop
+
+ We run multiple passes because there are rules that will
+ delete init functions once they become empty.
+ """
+ passes = 0
+ total_patches = 0
+ DBG("max_passes: %r", max_passes)
+ while not max_passes or max_passes <= 0 or passes < max_passes:
+ passes += 1
+ INFO("Running pass: %d", passes)
+ count = self.one_pass(class_names)
+ DBG("patch content: pass %d: %d patches generated", passes, count)
+ total_patches += count
+ DBG("%d patches applied total in %d passes", total_patches, passes)
@@ -23,16 +23,24 @@ WARN = logger.warning
RE_CONSTANT = OR(RE_STRING, RE_NUMBER)
-class ConstantDefine(FileMatch):
- """Simple #define preprocessor directive for a constant"""
- # if the macro contents are very simple, it might be included
- # in the match group 'value'
+class DefineDirective(FileMatch):
+ """Match any #define directive"""
+ regexp = S(r'^[ \t]*#[ \t]*define', CPP_SPACE, NAMED('name', RE_IDENTIFIER), r'\b')
+
+class ExpressionDefine(FileMatch):
+ """Simple #define preprocessor directive for an expression"""
regexp = S(r'^[ \t]*#[ \t]*define', CPP_SPACE, NAMED('name', RE_IDENTIFIER),
- CPP_SPACE, NAMED('value', RE_CONSTANT), r'[ \t]*\n')
+ CPP_SPACE, NAMED('value', RE_EXPRESSION), r'[ \t]*\n')
def provided_identifiers(self) -> Iterable[RequiredIdentifier]:
yield RequiredIdentifier('constant', self.group('name'))
+class ConstantDefine(ExpressionDefine):
+ """Simple #define preprocessor directive for a number or string constant"""
+ regexp = S(r'^[ \t]*#[ \t]*define', CPP_SPACE, NAMED('name', RE_IDENTIFIER),
+ CPP_SPACE, NAMED('value', RE_CONSTANT), r'[ \t]*\n')
+
+
class TypeIdentifiers(NamedTuple):
"""Type names found in type declarations"""
# TYPE_MYDEVICE
@@ -236,13 +244,12 @@ class TypeCheckMacro(FileMatch):
"""OBJECT_CHECK/OBJECT_CLASS_CHECK/OBJECT_GET_CLASS macro definitions
Will be replaced by DECLARE_*_CHECKERS macro
"""
- #TODO: handle and convert INTERFACE_CHECK macros
regexp = RE_CHECK_MACRO
@property
def checker(self) -> CheckerMacroName:
"""Name of checker macro being used"""
- return self.group('checker')
+ return self.group('checker') # type: ignore
@property
def typedefname(self) -> Optional[str]:
@@ -330,6 +337,8 @@ class TypeCheckMacro(FileMatch):
instancetype=instancetype, uppercase=uppercase)
def gen_patches(self) -> Iterable[Patch]:
+ # the implementation is a bit tricky because we need to group
+ # macros dealing with the same type into a single declaration
if self.type_identifiers is None:
self.warn("couldn't extract type information from macro %s", self.name)
return
@@ -426,10 +435,61 @@ class TypeCheckMacro(FileMatch):
yield self.prepend("/* FIXME: %s */\n" % (issue))
yield self.append(new_decl)
-class DeclareInstanceChecker(FileMatch):
- """DECLARE_INSTANCE_CHECKER use
- Will be replaced with DECLARE_OBJ_CHECKERS if possible
+class InterfaceCheckMacro(FileMatch):
+ """Type checking macro using INTERFACE_CHECK
+ Will be replaced by DECLARE_INTERFACE_CHECKER
"""
+ regexp = S(RE_MACRO_DEFINE,
+ 'INTERFACE_CHECK',
+ r'\s*\(\s*', OR(NAMED('instancetype', RE_IDENTIFIER), RE_TYPE, name='c_type'),
+ r'\s*,', CPP_SPACE,
+ OPTIONAL_PARS(RE_IDENTIFIER), r',', CPP_SPACE,
+ NAMED('qom_typename', RE_IDENTIFIER), r'\s*\)\n')
+
+ def required_identifiers(self) -> Iterable[RequiredIdentifier]:
+ yield RequiredIdentifier('include', '"qom/object.h"')
+ yield RequiredIdentifier('type', self.group('instancetype'))
+ yield RequiredIdentifier('constant', self.group('qom_typename'))
+
+ def gen_patches(self) -> Iterable[Patch]:
+ if self.file.filename_matches('qom/object.h'):
+ self.debug("skipping object.h")
+ return
+
+ typename = self.group('qom_typename')
+ uppercase = self.name
+ instancetype = self.group('instancetype')
+ c = f"DECLARE_INTERFACE_CHECKER({instancetype}, {uppercase},\n"+\
+ f" {typename})\n"
+ yield self.make_patch(c)
+
+
+class TypeDeclaration(FileMatch):
+ """Parent class to all type declarations"""
+ @property
+ def instancetype(self) -> Optional[str]:
+ return self.getgroup('instancetype')
+
+ @property
+ def classtype(self) -> Optional[str]:
+ return self.getgroup('classtype')
+
+ @property
+ def typename(self) -> Optional[str]:
+ return self.getgroup('typename')
+
+class TypeCheckerDeclaration(TypeDeclaration):
+ """Parent class to all type checker declarations"""
+ @property
+ def typename(self) -> str:
+ return self.group('typename')
+
+ @property
+ def uppercase(self) -> str:
+ return self.group('uppercase')
+
+class DeclareInstanceChecker(TypeCheckerDeclaration):
+ """DECLARE_INSTANCE_CHECKER use"""
#TODO: replace lonely DECLARE_INSTANCE_CHECKER with DECLARE_OBJ_CHECKERS
# if all types are found.
# This will require looking up the correct class type in the TypeInfo
@@ -445,8 +505,45 @@ class DeclareInstanceChecker(FileMatch):
yield RequiredIdentifier('constant', self.group('typename'))
yield RequiredIdentifier('type', self.group('instancetype'))
-class DeclareClassCheckers(FileMatch):
- """DECLARE_INSTANCE_CHECKER use"""
+class DeclareInterfaceChecker(TypeCheckerDeclaration):
+ """DECLARE_INTERFACE_CHECKER use"""
+ regexp = S(r'^[ \t]*DECLARE_INTERFACE_CHECKER\s*\(\s*',
+ NAMED('instancetype', RE_TYPE), r'\s*,\s*',
+ NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*',
+ OR(RE_IDENTIFIER, RE_STRING, RE_MACRO_CONCAT, RE_FUN_CALL, name='typename'), SP,
+ r'\)[ \t]*;?[ \t]*\n')
+
+ def required_identifiers(self) -> Iterable[RequiredIdentifier]:
+ yield RequiredIdentifier('include', '"qom/object.h"')
+ yield RequiredIdentifier('constant', self.group('typename'))
+ yield RequiredIdentifier('type', self.group('instancetype'))
+
+class DeclareInstanceType(TypeDeclaration):
+ """DECLARE_INSTANCE_TYPE use"""
+ regexp = S(r'^[ \t]*DECLARE_INSTANCE_TYPE\s*\(\s*',
+ NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*',
+ NAMED('instancetype', RE_TYPE), SP,
+ r'\)[ \t]*;?[ \t]*\n')
+
+ def required_identifiers(self) -> Iterable[RequiredIdentifier]:
+ yield RequiredIdentifier('include', '"qom/object.h"')
+ yield RequiredIdentifier('type', self.group('instancetype'))
+
+class DeclareClassType(TypeDeclaration):
+ """DECLARE_CLASS_TYPE use"""
+ regexp = S(r'^[ \t]*DECLARE_CLASS_TYPE\s*\(\s*',
+ NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*',
+ NAMED('classtype', RE_TYPE), SP,
+ r'\)[ \t]*;?[ \t]*\n')
+
+ def required_identifiers(self) -> Iterable[RequiredIdentifier]:
+ yield RequiredIdentifier('include', '"qom/object.h"')
+ yield RequiredIdentifier('type', self.group('classtype'))
+
+
+
+class DeclareClassCheckers(TypeCheckerDeclaration):
+ """DECLARE_CLASS_CHECKER use"""
regexp = S(r'^[ \t]*DECLARE_CLASS_CHECKERS\s*\(\s*',
NAMED('classtype', RE_TYPE), r'\s*,\s*',
NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*',
@@ -458,10 +555,8 @@ class DeclareClassCheckers(FileMatch):
yield RequiredIdentifier('constant', self.group('typename'))
yield RequiredIdentifier('type', self.group('classtype'))
-class DeclareObjCheckers(FileMatch):
- """DECLARE_OBJ_CHECKERS use
- Will be replaced with OBJECT_DECLARE_TYPE if possible
- """
+class DeclareObjCheckers(TypeCheckerDeclaration):
+ """DECLARE_OBJ_CHECKERS use"""
#TODO: detect when OBJECT_DECLARE_SIMPLE_TYPE can be used
regexp = S(r'^[ \t]*DECLARE_OBJ_CHECKERS\s*\(\s*',
NAMED('instancetype', RE_TYPE), r'\s*,\s*',
@@ -476,44 +571,121 @@ class DeclareObjCheckers(FileMatch):
yield RequiredIdentifier('type', self.group('classtype'))
yield RequiredIdentifier('type', self.group('instancetype'))
- def gen_patches(self):
- ids = TypeIdentifiers(uppercase=self.group('uppercase'),
- typename=self.group('typename'),
- classtype=self.group('classtype'),
- instancetype=self.group('instancetype'))
- issues = ids.check_consistency()
- if issues:
- for i in issues:
- self.warn("inconsistent identifiers: %s", i)
+class TypeDeclarationFixup(FileMatch):
+ """Common base class for code that will look at a set of type declarations"""
+ regexp = RE_FILE_BEGIN
+ def gen_patches(self) -> Iterable[Patch]:
+ if self.file.filename_matches('qom/object.h'):
+ self.debug("skipping object.h")
return
- if self.group('typename') != 'TYPE_'+self.group('uppercase'):
- self.warn("type %s mismatch with uppercase name %s", ids.typename, ids.uppercase)
- return
+ # group checkers by uppercase name:
+ decl_types: List[Type[TypeDeclaration]] = [DeclareInstanceChecker, DeclareInstanceType,
+ DeclareClassCheckers, DeclareClassType,
+ DeclareObjCheckers]
+ checker_dict: Dict[str, List[TypeDeclaration]] = {}
+ for t in decl_types:
+ for m in self.file.matches_of_type(t):
+ checker_dict.setdefault(m.group('uppercase'), []).append(m)
+ self.debug("checker_dict: %r", checker_dict)
+ for uppercase,checkers in checker_dict.items():
+ fields = ('instancetype', 'classtype', 'uppercase', 'typename')
+ fvalues = dict((field, set(getattr(m, field) for m in checkers
+ if getattr(m, field, None) is not None))
+ for field in fields)
+ for field,values in fvalues.items():
+ if len(values) > 1:
+ for c in checkers:
+ c.warn("%s mismatch (%s)", field, ' '.join(values))
+ return
- typedefs = [(t,self.file.find_match(SimpleTypedefMatch, t))
- for t in (ids.instancetype, ids.classtype)]
- for t,td in typedefs:
- if td is None:
- self.warn("typedef %s not found", t)
- break
- if td.start() > self.start():
- self.warn("typedef %s needs to be move earlier in the file", t)
- break
- #HACK: check if typedef is used between its definition and the macro
- #TODO: check if the only match is inside the "struct { ... }" declaration
- if re.search(r'\b'+t+r'\b', self.file.original_content[td.end():self.start()]):
- self.warn("typedef %s can't be moved, it is used before the macro", t)
- break
- else:
- for t,td in typedefs:
- yield td.make_removal_patch()
+ field_dict = dict((f, v.pop() if v else None) for f,v in fvalues.items())
+ yield from self.gen_patches_for_type(uppercase, checkers, field_dict)
+
+ def find_conflicts(self, uppercase: str, checkers: List[TypeDeclaration]) -> bool:
+ """Look for conflicting declarations that would make it unsafe to add new ones"""
+ conflicting: List[FileMatch] = []
+ # conflicts in the same file:
+ conflicting.extend(chain(self.file.find_matches(DefineDirective, uppercase),
+ self.file.find_matches(DeclareInterfaceChecker, uppercase, 'uppercase'),
+ self.file.find_matches(DeclareClassType, uppercase, 'uppercase'),
+ self.file.find_matches(DeclareInstanceType, uppercase, 'uppercase')))
+
+ # conflicts in another file:
+ conflicting.extend(o for o in chain(self.allfiles.find_matches(DeclareInstanceChecker, uppercase, 'uppercase'),
+ self.allfiles.find_matches(DeclareClassCheckers, uppercase, 'uppercase'),
+ self.allfiles.find_matches(DeclareInterfaceChecker, uppercase, 'uppercase'),
+ self.allfiles.find_matches(DefineDirective, uppercase))
+ if o is not None and o.file != self.file
+ # if both are .c files, there's no conflict at all:
+ and not (o.file.filename.suffix == '.c' and
+ self.file.filename.suffix == '.c'))
+
+ if conflicting:
+ for c in checkers:
+ c.warn("skipping due to conflicting %s macro", uppercase)
+ for o in conflicting:
+ if o is None:
+ continue
+ o.warn("conflicting %s macro is here", uppercase)
+ return True
- lowercase = ids.uppercase.lower()
- # all is OK, we can replace the macro!
- c = (f'OBJECT_DECLARE_TYPE({ids.instancetype}, {ids.classtype},\n'
- f' {lowercase}, {ids.uppercase})\n')
- yield self.make_patch(c)
+ return False
+
+ def gen_patches_for_type(self, uppercase: str,
+ checkers: List[TypeDeclaration],
+ fields: Dict[str, Optional[str]]) -> Iterable[Patch]:
+ """Should be reimplemented by subclasses"""
+ return
+ yield
+
+class DeclareVoidTypes(TypeDeclarationFixup):
+ """Add DECLARE_*_TYPE(..., void) when there's no declared type"""
+ regexp = RE_FILE_BEGIN
+ def gen_patches_for_type(self, uppercase: str,
+ checkers: List[TypeDeclaration],
+ fields: Dict[str, Optional[str]]) -> Iterable[Patch]:
+ if self.find_conflicts(uppercase, checkers):
+ return
+
+ #_,last_checker = max((m.start(), m) for m in checkers)
+ _,first_checker = min((m.start(), m) for m in checkers)
+
+ if not any(m.instancetype for m in checkers):
+ yield first_checker.prepend(f'DECLARE_INSTANCE_TYPE({uppercase}, void)\n')
+ if not any(m.classtype for m in checkers):
+ yield first_checker.prepend(f'DECLARE_CLASS_TYPE({uppercase}, void)\n')
+
+ #if not all(len(v) == 1 for v in fvalues.values()):
+ # return
+ #
+ #final_values = dict((field, values.pop())
+ # for field,values in fvalues.items())
+ #s = (f"DECLARE_OBJ_CHECKERS({final_values['instancetype']}, {final_values['classtype']},\n"+
+ # f" {final_values['uppercase']}, {final_values['typename']})\n")
+ #for c in checkers:
+ # yield c.make_removal_patch()
+ #yield last_checker.append(s)
+
+
+class AddDeclareTypeName(TypeDeclarationFixup):
+ """Add DECLARE_TYPE_NAME declarations if necessary"""
+ def gen_patches_for_type(self, uppercase: str,
+ checkers: List[TypeDeclaration],
+ fields: Dict[str, Optional[str]]) -> Iterable[Patch]:
+ typename = fields.get('typename')
+ if typename is None:
+ self.warn("typename unavailable")
+ return
+ if typename == f'TYPE_{uppercase}':
+ self.info("already using TYPE_%s as type name", uppercase)
+ return
+ if self.file.find_match(DeclareTypeName, uppercase, 'uppercase'):
+ self.info("type name for %s already declared", uppercase)
+ return
+ _,first_checker = min((m.start(), m) for m in checkers)
+ s = f'DECLARE_TYPE_NAME({uppercase}, {typename})\n'
+ yield first_checker.prepend(s)
class TrivialClassStruct(FileMatch):
"""Trivial class struct"""
@@ -527,14 +699,13 @@ class DeclareTypeName(FileMatch):
OR(RE_IDENTIFIER, RE_STRING, RE_MACRO_CONCAT, RE_FUN_CALL, name='typename'),
r'\s*\);?[ \t]*\n')
-class ObjectDeclareType(FileMatch):
+class ObjectDeclareType(TypeCheckerDeclaration):
"""OBJECT_DECLARE_TYPE usage
Will be replaced with OBJECT_DECLARE_SIMPLE_TYPE if possible
"""
regexp = S(r'^[ \t]*OBJECT_DECLARE_TYPE\s*\(',
NAMED('instancetype', RE_TYPE), r'\s*,\s*',
NAMED('classtype', RE_TYPE), r'\s*,\s*',
- NAMED('lowercase', RE_IDENTIFIER), r'\s*,\s*',
NAMED('uppercase', RE_IDENTIFIER), SP,
r'\)[ \t]*;?[ \t]*\n')
@@ -549,14 +720,42 @@ class ObjectDeclareType(FileMatch):
" %(uppercase)s, %(parent_struct)s)\n" % d)
yield self.make_patch(c)
-def find_type_declaration(files: FileList, typename: str) -> Optional[FileMatch]:
- """Find usage of DECLARE*CHECKER macro"""
- for c in (DeclareInstanceChecker, DeclareClassCheckers, DeclareObjCheckers, DeclareTypeName):
- d = files.find_match(c, name=typename, group='typename')
- if d:
- return d
+class ObjectDeclareSimpleType(TypeCheckerDeclaration):
+ """OBJECT_DECLARE_SIMPLE_TYPE usage"""
+ regexp = S(r'^[ \t]*OBJECT_DECLARE_SIMPLE_TYPE\s*\(',
+ NAMED('instancetype', RE_TYPE), r'\s*,\s*',
+ NAMED('uppercase', RE_IDENTIFIER), SP,
+ r'\)[ \t]*;?[ \t]*\n')
+
+class OldStyleObjectDeclareSimpleType(TypeCheckerDeclaration):
+ """OBJECT_DECLARE_SIMPLE_TYPE usage (old API)"""
+ regexp = S(r'^[ \t]*OBJECT_DECLARE_SIMPLE_TYPE\s*\(',
+ NAMED('instancetype', RE_TYPE), r'\s*,\s*',
+ NAMED('lowercase', RE_IDENTIFIER), r'\s*,\s*',
+ NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*',
+ NAMED('parent_classtype', RE_TYPE), SP,
+ r'\)[ \t]*;?[ \t]*\n')
+
+ @property
+ def classtype(self) -> Optional[str]:
+ instancetype = self.instancetype
+ assert instancetype
+ return f"{instancetype}Class"
+
+def find_typename_uppercase(files: FileList, typename: str) -> Optional[str]:
+ """Try to find what's the right MODULE_OBJ_NAME for a given type name"""
+ decl = files.find_match(DeclareTypeName, name=typename, group='typename')
+ if decl:
+ return decl.group('uppercase')
+ if typename.startswith('TYPE_'):
+ return typename[len('TYPE_'):]
return None
+def find_type_checkers(files:FileList, name:str, group:str='uppercase') -> Iterable[TypeCheckerDeclaration]:
+ """Find usage of DECLARE*CHECKER macro"""
+ c: Type[TypeCheckerDeclaration]
+ for c in (DeclareInstanceChecker, DeclareClassCheckers, DeclareObjCheckers, ObjectDeclareType, ObjectDeclareSimpleType):
+ yield from files.find_matches(c, name=name, group=group)
class Include(FileMatch):
"""#include directive"""
@@ -586,9 +785,13 @@ class MoveSymbols(FileMatch):
regexp = RE_FILE_BEGIN
def gen_patches(self) -> Iterator[Patch]:
+ if self.file.filename_matches('qom/object.h'):
+ self.debug("skipping object.h")
+ return
+
index: Dict[RequiredIdentifier, SymbolUserList] = {}
definition_classes = [SimpleTypedefMatch, FullStructTypedefMatch, ConstantDefine, Include]
- user_classes = [TypeCheckMacro, DeclareObjCheckers, DeclareInstanceChecker, DeclareClassCheckers]
+ user_classes = [TypeCheckMacro, DeclareObjCheckers, DeclareInstanceChecker, DeclareClassCheckers, InterfaceCheckMacro]
# first we scan for all symbol definitions and usage:
for dc in definition_classes:
@@ -650,3 +853,9 @@ class MoveSymbols(FileMatch):
definition.warn("definition of %s %s needs to be moved earlier in the file", i.type, i.name)
earliest.warn("definition of %s %s is used here", i.type, i.name)
+
+class EmptyPreprocessorConditional(FileMatch):
+ """Delete empty preprocessor conditionals"""
+ regexp = r'^[ \t]*#(if|ifdef)[ \t].*\n+[ \t]*#endif[ \t]*\n'
+ def gen_patches(self) -> Iterable[Patch]:
+ yield self.make_removal_patch()
@@ -24,11 +24,6 @@ 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')
-RE_TYPEINFO_DEF = S(RE_TYPEINFO_START,
- M(NAMED('fields', RE_TI_FIELDS),
- SP, NAMED('endcomments', RE_COMMENTS),
- r'};?\n',
- n='?', name='fullspec'))
ParsedArray = List[str]
ParsedInitializerValue = Union[str, ParsedArray]
@@ -36,26 +31,55 @@ class InitializerValue(NamedTuple):
raw: str
parsed: Optional[ParsedInitializerValue]
match: Optional[Match]
-TypeInfoInitializers = Dict[str, InitializerValue]
-
-def parse_array(m: Match) -> ParsedArray:
- #DBG('parse_array: %r', m.group(0))
- return [m.group('arrayitem') for m in re.finditer(RE_ARRAY_ITEM, m.group('arrayitems'))]
-
-def parse_initializer_value(m: Match, s: str) -> InitializerValue:
- parsed: Optional[ParsedInitializerValue] = None
- #DBG("parse_initializer_value: %r", s)
- array = re.match(RE_ARRAY, s)
- if array:
- parsed = parse_array(array)
- return InitializerValue(s, parsed, m)
-
-class TypeInfoVar(FileMatch):
- """TypeInfo variable declaration with initializer
- Will be replaced by OBJECT_DEFINE_TYPE_EXTENDED macro
- (not implemented yet)
+
+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)
"""
- regexp = RE_TYPEINFO_DEF
+ @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]:
@@ -65,14 +89,26 @@ class TypeInfoVar(FileMatch):
fields = self.group('fields')
if fields is None:
return None
- d = dict((fm.group('field'), parse_initializer_value(fm, fm.group('value')))
- for fm in re.finditer(RE_TI_FIELD_INIT, fields))
- self._initializers = d
- return d
+ 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'))
@@ -82,8 +118,46 @@ class TypeInfoVar(FileMatch):
return {}
return self.initializers
- def get_initializer_value(self, field: str) -> InitializerValue:
- return self.get_initializers().get(field, InitializerValue('', '', None))
+ 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"""
@@ -116,32 +190,105 @@ class TypeInfoVar(FileMatch):
# uppercase=uppercase, lowercase=lowercase,
# instancetype=instancetype, classtype=classtype)
- def append_field(self, field, value) -> Patch:
+ def append_field(self, field: str, value: str) -> Patch:
"""Generate patch appending a field initializer"""
content = f' .{field} = {value},\n'
- return Patch(self.match.end('fields'), self.match.end('fields'),
- content)
+ 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"""
- values = self.initializers
- assert values
- value = values.get(field)
+ initializers = self.initializers
+ assert initializers
+ value = initializers.get(field)
assert value
- fm = value.match
- assert fm
- fstart = self.match.start('fields') + fm.start()
- fend = self.match.start('fields') + fm.end()
- return Patch(fstart, fend, replacement)
+ 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
@@ -167,49 +314,403 @@ class TypeInfoVar(FileMatch):
self.warn("class_size is set to: %r", values['class_size'].raw)
return
- #NOTE: this will NOT work after declarations are converted
- # to OBJECT_DECLARE*
+ #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
- instance_decl = find_type_declaration(self.allfiles, typename)
- parent_decl = find_type_declaration(self.allfiles, parent_typename)
-
self.info("TypeInfo variable for %s is here", typename)
- if instance_decl:
- instance_decl.info("instance type declaration (%s) is here", instance_decl.match.group('uppercase'))
- if parent_decl:
- parent_decl.info("parent type declaration (%s) is here", parent_decl.match.group('uppercase'))
+ 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
- ok = True
- if (instance_decl is None and (instancetype or classtype)):
- self.warn("Can't find where type checkers for %s are declared. We need them to validate sizes of %s", typename, self.name)
- ok = False
-
- if (instance_decl is not None
- and 'instancetype' in instance_decl.match.groupdict()
- and instancetype != instance_decl.group('instancetype')):
- self.warn("type at instance_size is %r. Should instance_size be set to sizeof(%s) ?",
- instancetype, instance_decl.group('instancetype'))
- instance_decl.warn("Type checker declaration for %s is here", typename)
- ok = False
- if (instance_decl is not None
- and 'classtype' in instance_decl.match.groupdict()
- and classtype != instance_decl.group('classtype')):
- self.warn("type at class_size is %r. Should class_size be set to sizeof(%s) ?",
- classtype, instance_decl.group('classtype'))
- instance_decl.warn("Type checker declaration for %s is here", typename)
- ok = False
-
- if not ok:
+ 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)
- return
- yield
+ 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"""
@@ -230,8 +731,8 @@ class RedundantTypeSizes(TypeInfoVar):
self.debug("no need to validate %s", self.name)
return
- instance_decl = find_type_declaration(self.allfiles, typename)
- if instance_decl:
+ instance_decls = find_type_checkers(self.allfiles, typename)
+ if instance_decls:
self.debug("won't touch TypeInfo var that has type checkers")
return
@@ -240,12 +741,12 @@ class RedundantTypeSizes(TypeInfoVar):
self.warn("Can't find TypeInfo for %s", parent_typename)
return
- if 'instance_size' in values and parent.get_initializer_value('instance_size').raw != values['instance_size'].raw:
+ 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_initializer_value('class_size').raw != values['class_size'].raw:
+ 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
@@ -303,10 +804,11 @@ class RedundantTypeSizes(TypeInfoVar):
# yield self.append_field('class_init', ids.lowercase+'_class_init')
class TypeInitMacro(FileMatch):
- """type_init(...) macro use
- Will be deleted if function is empty
- """
+ """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)
@@ -331,7 +833,7 @@ class StaticVoidFunction(FileMatch):
r'#[^\n]*\n',
r'\n',
repeat='*')),
- r'}\n')
+ r'};?\n')
@property
def body(self) -> str:
@@ -340,34 +842,40 @@ class StaticVoidFunction(FileMatch):
def has_preprocessor_directive(self) -> bool:
return bool(re.search(r'^[ \t]*#', self.body, re.MULTILINE))
-class TypeRegisterCall(FileMatch):
+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]*type_register_static\s*\(&\s*', NAMED('name', RE_IDENTIFIER), r'\s*\);[ \t]*\n')
-
- def function(self) -> Optional['StaticVoidFunction']:
- """Return function containing this call"""
- for m in self.file.matches_of_type(StaticVoidFunction):
- if m.contains(self):
- return m
- return None
+ 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 = self.function()
- if fn is None:
- self.warn("can't find function where type_register_static(&%s) is called", self.name)
- return
+ 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
- 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)
- 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)
@@ -375,24 +883,51 @@ class TypeRegisterCall(FileMatch):
if not var.is_full():
self.warn("variable declaration %s wasn't parsed fully", var.name)
- return
+ if not self.file.force:
+ return
- if fn.contains(var):
+ if fn and fn.contains(var):
self.warn("TypeInfo %s variable is inside a function", self.name)
- return
+ 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_initializer_value('name').raw == name]
+ 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)
@@ -31,7 +31,6 @@ def test_pattern_patching():
files = FileList()
f = FileInfo(files, of.name)
f.load()
- f.scan_for_matches()
matches = f.matches_of_type(BasicPattern)
assert len(matches) == 2
p2 = matches[1]
@@ -40,7 +39,7 @@ def test_pattern_patching():
f.patches.append(p2.append('XXX'))
# apply all patches:
- f.gen_patches()
+ f.gen_patches(matches)
patched = f.get_patched_content()
assert patched == ('one line\n'+
'this pattern will be patched: defBBBBBhij\n'+
@@ -9,7 +9,7 @@ from .regexps import *
from .qom_macros import *
from .qom_type_info import *
-def test_res():
+def test_res() -> None:
def fullmatch(regexp, s):
return re.fullmatch(regexp, s, re.MULTILINE)
@@ -113,10 +113,10 @@ static const TypeInfo char_file_type_info = {
* need to set up reset or vmstate, and has no realize method.
*/''')
- print(RE_TYPEINFO_DEF)
+ print(TypeInfoVar.regexp)
test_empty = 'static const TypeInfo x86_base_cpu_type_info = {\n'+\
'};\n';
- assert fullmatch(RE_TYPEINFO_DEF, test_empty)
+ assert fullmatch(TypeInfoVar.regexp, test_empty)
test_simple = r'''
static const TypeInfo x86_base_cpu_type_info = {
@@ -125,7 +125,7 @@ static const TypeInfo char_file_type_info = {
.class_init = x86_cpu_base_class_init,
};
'''
- assert re.search(RE_TYPEINFO_DEF, test_simple, re.MULTILINE)
+ assert re.search(TypeInfoVar.regexp, test_simple, re.MULTILINE)
test_interfaces = r'''
static const TypeInfo acpi_ged_info = {
@@ -141,7 +141,7 @@ static const TypeInfo char_file_type_info = {
}
};
'''
- assert re.search(RE_TYPEINFO_DEF, test_interfaces, re.MULTILINE)
+ assert re.search(TypeInfoVar.regexp, test_interfaces, re.MULTILINE)
test_comments = r'''
static const TypeInfo palm_misc_gpio_info = {
@@ -155,7 +155,7 @@ static const TypeInfo char_file_type_info = {
*/
};
'''
- assert re.search(RE_TYPEINFO_DEF, test_comments, re.MULTILINE)
+ assert re.search(TypeInfoVar.regexp, test_comments, re.MULTILINE)
test_comments = r'''
static const TypeInfo tpm_crb_info = {
@@ -170,7 +170,7 @@ static const TypeInfo char_file_type_info = {
}
};
'''
- assert re.search(RE_TYPEINFO_DEF, test_comments, re.MULTILINE)
+ assert re.search(TypeInfoVar.regexp, test_comments, re.MULTILINE)
def test_struct_re():
print('---')
@@ -232,8 +232,8 @@ def test_initial_includes():
/* pflash_cfi01.c */
'''
- print(repr(list(m.groupdict() for m in re.finditer(InitialIncludes.regexp, c, re.MULTILINE))))
- m = re.match(InitialIncludes.regexp, c, re.MULTILINE)
+ print(repr(list(m.groupdict() for m in InitialIncludes.finditer(c))))
+ m = InitialIncludes.domatch(c)
assert m
print(repr(m.group(0)))
assert m.group(0).endswith('#include "exec/hwaddr.h"\n')
@@ -247,8 +247,8 @@ def test_initial_includes():
'''
- print(repr(list(m.groupdict() for m in re.finditer(InitialIncludes.regexp, c, re.MULTILINE))))
- m = re.match(InitialIncludes.regexp, c, re.MULTILINE)
+ print(repr(list(m.groupdict() for m in InitialIncludes.finditer(c))))
+ m = InitialIncludes.domatch(c)
assert m
print(repr(m.group(0)))
assert m.group(0).endswith('#include "9p.h"\n')
@@ -274,8 +274,8 @@ def test_initial_includes():
/* Missing stuff:
SCTRL_P[12](END|ST)INC
'''
- print(repr(list(m.groupdict() for m in re.finditer(InitialIncludes.regexp, c, re.MULTILINE))))
- m = re.match(InitialIncludes.regexp, c, re.MULTILINE)
+ print(repr(list(m.groupdict() for m in InitialIncludes.finditer(c))))
+ m = InitialIncludes.domatch(c)
assert m
print(repr(m.group(0)))
assert m.group(0).endswith('#include "sysemu/dma.h"\n')
@@ -42,7 +42,7 @@ def process_all_files(parser: argparse.ArgumentParser, args: argparse.Namespace)
for t in f.matches_of_type(TypeInfoVar):
assert isinstance(t, TypeInfoVar)
values = [f.filename, t.name] + \
- [t.get_initializer_value(f).raw
+ [t.get_raw_initializer_value(f)
for f in TI_FIELDS]
DBG('values: %r', values)
assert all('\t' not in v for v in values)
@@ -55,18 +55,18 @@ def process_all_files(parser: argparse.ArgumentParser, args: argparse.Namespace)
parser.error("--pattern is required")
classes = [p for arg in args.patterns
- for p in re.split(r'[\s,]', arg)]
+ for p in re.split(r'[\s,]', arg)
+ if p.strip()]
for c in classes:
- if c not in match_classes:
+ if c not in match_classes \
+ or not match_classes[c].regexp:
print("Invalid pattern name: %s" % (c), file=sys.stderr)
print("Valid patterns:", file=sys.stderr)
print(PATTERN_HELP, file=sys.stderr)
sys.exit(1)
DBG("classes: %r", classes)
- for f in files:
- DBG("patching contents of %s", f.filename)
- f.patch_content(max_passes=args.passes, class_names=classes)
+ files.patch_content(max_passes=args.passes, class_names=classes)
for f in files:
#alltypes.extend(f.type_infos)