mirror of
https://github.com/izzy2lost/xemu.git
synced 2026-07-06 00:20:22 -07:00
Merge remote-tracking branch 'remotes/ehabkost/tags/machine-next-pull-request' into staging
QOM queue, 2020-09-18 Fixes: * Error value corrections (Markus Armbruster) * Correct object_class_dynamic_cast_assert() documentation (Eduardo Habkost) * Ensure objects using QEMU_ALIGNED are properly aligned (Richard Henderson) QOM cleanups (Eduardo Habkost): * Rename some constants * Simplify parameters of OBJECT_DECLARE* macros * Additional DECLARE_*CHECKER* usage * Additional OBJECT_DECLARE_TYPE usage * Additional OBJECT_DECLARE_SIMPLE_TYPE usage # gpg: Signature made Fri 18 Sep 2020 21:45:29 BST # gpg: using RSA key 5A322FD5ABC4D3DBACCFD1AA2807936F984DC5A6 # gpg: issuer "ehabkost@redhat.com" # gpg: Good signature from "Eduardo Habkost <ehabkost@redhat.com>" [full] # Primary key fingerprint: 5A32 2FD5 ABC4 D3DB ACCF D1AA 2807 936F 984D C5A6 * remotes/ehabkost/tags/machine-next-pull-request: Use OBJECT_DECLARE_SIMPLE_TYPE when possible Use OBJECT_DECLARE_TYPE when possible qom: Remove module_obj_name parameter from OBJECT_DECLARE* macros qom: Remove ParentClassType argument from OBJECT_DECLARE_SIMPLE_TYPE scripts/codeconverter: Update to latest version target/s390x: Set instance_align on S390CPU TypeInfo target/riscv: Set instance_align on RISCVCPU TypeInfo target/ppc: Set instance_align on PowerPCCPU TypeInfo target/arm: Set instance_align on CPUARM TypeInfo qom: Allow objects to be allocated with increased alignment qom: Correct error values in two contracts qom: Clean up object_property_get_enum()'s error value qom: Correct object_class_dynamic_cast_assert() documentation sifive: Use DECLARE_*CHECKER* macros sifive: Move QOM typedefs and add missing includes sifive_u: Rename memmap enum constants sifive_e: Rename memmap enum constants Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
@@ -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
|
||||
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)
|
||||
# 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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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()
|
||||
#_,last_checker = max((m.start(), m) for m in checkers)
|
||||
_,first_checker = min((m.start(), m) for m in checkers)
|
||||
|
||||
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)
|
||||
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()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user