Import QEMU upstream snapshot d2e570c
Upstream: https://gitlab.com/qemu-project/qemu.git Upstream-Commit: d2e570cc0f97b936902a5b1b86b73c0f5998b475
This commit is contained in:
commit
cf256aa081
11315 files changed
+3598369
No files matched your search
Whitespace-only changes.
@@ -0,0 +1,466 @@
|
||||
# Copyright (C) 2020 Red Hat Inc.
|
||||
#
|
||||
# Authors:
|
||||
# Eduardo Habkost <[email protected]>
|
||||
#
|
||||
# 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, Union
|
||||
from pathlib import Path
|
||||
from itertools import chain
|
||||
from tempfile import NamedTemporaryFile
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from io import StringIO
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
DBG = logger.debug
|
||||
INFO = logger.info
|
||||
WARN = logger.warning
|
||||
ERROR = logger.error
|
||||
|
||||
from .utils import *
|
||||
|
||||
T = TypeVar('T')
|
||||
|
||||
class Patch(NamedTuple):
|
||||
# start inside file.original_content
|
||||
start: int
|
||||
# end position inside file.original_content
|
||||
end: int
|
||||
# replacement string for file.original_content[start:end]
|
||||
replacement: str
|
||||
|
||||
IdentifierType = Literal['type', 'symbol', 'include', 'constant']
|
||||
class RequiredIdentifier(NamedTuple):
|
||||
type: IdentifierType
|
||||
name: str
|
||||
|
||||
class FileMatch:
|
||||
"""Base class for regex matches
|
||||
|
||||
Subclasses just need to set the `regexp` class attribute
|
||||
"""
|
||||
regexp: Optional[str] = None
|
||||
|
||||
def __init__(self, f: 'FileInfo', m: Match) -> None:
|
||||
self.file: 'FileInfo' = f
|
||||
self.match: Match[str] = m
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
if 'name' not in self.match.groupdict():
|
||||
return '[no name]'
|
||||
return self.group('name')
|
||||
|
||||
@classmethod
|
||||
def compiled_re(klass):
|
||||
return re.compile(klass.regexp, re.MULTILINE)
|
||||
|
||||
def start(self) -> int:
|
||||
return self.match.start()
|
||||
|
||||
def end(self) -> int:
|
||||
return self.match.end()
|
||||
|
||||
def line_col(self) -> LineAndColumn:
|
||||
return self.file.line_col(self.start())
|
||||
|
||||
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()
|
||||
logger.log(level, '%s:%d:%d: '+fmt, self.file.filename, pos.line, pos.col, *args)
|
||||
|
||||
def debug(self, fmt, *args) -> None:
|
||||
self.log(logging.DEBUG, fmt, *args)
|
||||
|
||||
def info(self, fmt, *args) -> None:
|
||||
self.log(logging.INFO, fmt, *args)
|
||||
|
||||
def warn(self, fmt, *args) -> None:
|
||||
self.log(logging.WARNING, fmt, *args)
|
||||
|
||||
def error(self, fmt, *args) -> None:
|
||||
self.log(logging.ERROR, fmt, *args)
|
||||
|
||||
def sub(self, original: str, replacement: str) -> str:
|
||||
"""Replace content
|
||||
|
||||
XXX: this won't use the match position, but will just
|
||||
replace all strings that look like the original match.
|
||||
This should be enough for all the patterns used in this
|
||||
script.
|
||||
"""
|
||||
return original.replace(self.group(0), replacement)
|
||||
|
||||
def sanity_check(self) -> None:
|
||||
"""Sanity check match, and print warnings if necessary"""
|
||||
pass
|
||||
|
||||
def replacement(self) -> Optional[str]:
|
||||
"""Return replacement text for pattern, to use new code conventions"""
|
||||
return None
|
||||
|
||||
def make_patch(self, replacement: str) -> 'Patch':
|
||||
"""Make patch replacing the content of this match"""
|
||||
return Patch(self.start(), self.end(), replacement)
|
||||
|
||||
def make_subpatch(self, start: int, end: int, replacement: str) -> 'Patch':
|
||||
return Patch(self.start() + start, self.start() + end, replacement)
|
||||
|
||||
def make_removal_patch(self) -> 'Patch':
|
||||
"""Make patch removing contents of match completely"""
|
||||
return self.make_patch('')
|
||||
|
||||
def append(self, s: str) -> 'Patch':
|
||||
"""Make patch appending string after this match"""
|
||||
return Patch(self.end(), self.end(), s)
|
||||
|
||||
def prepend(self, s: str) -> 'Patch':
|
||||
"""Make patch prepending string before this match"""
|
||||
return Patch(self.start(), self.start(), s)
|
||||
|
||||
def gen_patches(self) -> Iterable['Patch']:
|
||||
"""Patch source code contents to use new code patterns"""
|
||||
replacement = self.replacement()
|
||||
if replacement is not None:
|
||||
yield self.make_patch(replacement)
|
||||
|
||||
@classmethod
|
||||
def has_replacement_rule(klass) -> bool:
|
||||
return (klass.gen_patches is not FileMatch.gen_patches
|
||||
or klass.replacement is not FileMatch.replacement)
|
||||
|
||||
def contains(self, other: 'FileMatch') -> bool:
|
||||
return other.start() >= self.start() and other.end() <= self.end()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
start = self.file.line_col(self.start())
|
||||
end = self.file.line_col(self.end() - 1)
|
||||
return '<%s %s at %d:%d-%d:%d: %r>' % (self.__class__.__name__,
|
||||
self.name,
|
||||
start.line, start.col,
|
||||
end.line, end.col, self.group(0)[:100])
|
||||
|
||||
def required_identifiers(self) -> Iterable[RequiredIdentifier]:
|
||||
"""Can be implemented by subclasses to keep track of identifier references
|
||||
|
||||
This method will be used by the code that moves declarations around the file,
|
||||
to make sure we find the right spot for them.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def provided_identifiers(self) -> Iterable[RequiredIdentifier]:
|
||||
"""Can be implemented by subclasses to keep track of identifier references
|
||||
|
||||
This method will be used by the code that moves declarations around the file,
|
||||
to make sure we find the right spot for them.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@classmethod
|
||||
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)
|
||||
|
||||
@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
|
||||
yield from all_subclasses(sc)
|
||||
|
||||
def match_class_dict() -> Dict[str, Type[FileMatch]]:
|
||||
d = dict((t.__name__, t) for t in all_subclasses(FileMatch))
|
||||
return d
|
||||
|
||||
def names(matches: Iterable[FileMatch]) -> Iterable[str]:
|
||||
return [m.name for m in matches]
|
||||
|
||||
class PatchingError(Exception):
|
||||
pass
|
||||
|
||||
class OverLappingPatchesError(PatchingError):
|
||||
pass
|
||||
|
||||
def apply_patches(s: str, patches: Iterable[Patch]) -> str:
|
||||
"""Apply a sequence of patches to string
|
||||
|
||||
>>> apply_patches('abcdefg', [Patch(2,2,'xxx'), Patch(0, 1, 'yy')])
|
||||
'yybxxxcdefg'
|
||||
"""
|
||||
r = StringIO()
|
||||
last = 0
|
||||
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),
|
||||
p.replacement)
|
||||
if last > p.start:
|
||||
raise OverLappingPatchesError("Overlapping patch at position %d (%s), last patch at %d (%s)" % \
|
||||
(p.start, line_col(s, p.start), last, line_col(s, last)))
|
||||
r.write(s[last:p.start])
|
||||
r.write(p.replacement)
|
||||
last = p.end
|
||||
r.write(s[last:])
|
||||
return r.getvalue()
|
||||
|
||||
class RegexpScanner:
|
||||
def __init__(self) -> None:
|
||||
self.match_index: Dict[Type[Any], List[FileMatch]] = {}
|
||||
self.match_name_index: Dict[Tuple[Type[Any], str, str], Optional[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._matches_of_type(t))
|
||||
return self.match_index[t] # type: ignore
|
||||
|
||||
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: List[T] = []
|
||||
for m in self.matches_of_type(t):
|
||||
assert isinstance(m, FileMatch)
|
||||
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()
|
||||
|
||||
class FileInfo(RegexpScanner):
|
||||
filename: Path
|
||||
original_content: Optional[str] = None
|
||||
|
||||
def __init__(self, files: 'FileList', filename: os.PathLike, force:bool=False) -> None:
|
||||
super().__init__()
|
||||
self.allfiles = files
|
||||
self.filename = Path(filename)
|
||||
self.patches: List[Patch] = []
|
||||
self.force = force
|
||||
|
||||
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 _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.finditer(self.original_content)]
|
||||
DBG('%s: %d matches found for %s: %s', self.filename, len(matches),
|
||||
klass.__name__,' '.join(names(matches)))
|
||||
return matches
|
||||
|
||||
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.getgroup(group) == name:
|
||||
return m # type: ignore
|
||||
return None
|
||||
|
||||
def reset_content(self, s:str):
|
||||
self.original_content = s
|
||||
self.patches.clear()
|
||||
self.reset_index()
|
||||
self.allfiles.reset_index()
|
||||
|
||||
def load(self) -> None:
|
||||
if self.original_content is not None:
|
||||
return
|
||||
with open(self.filename, 'rt') as f:
|
||||
self.reset_content(f.read())
|
||||
|
||||
@property
|
||||
def all_matches(self) -> Iterable[FileMatch]:
|
||||
lists = list(self.match_index.values())
|
||||
return (m for l in lists
|
||||
for m in l)
|
||||
|
||||
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:
|
||||
DBG("default class names")
|
||||
class_names = list(name for name,klass in class_dict.items()
|
||||
if klass.has_replacement_rule())
|
||||
DBG("class_names: %r", class_names)
|
||||
for cn in class_names:
|
||||
matches = self.matches_of_type(class_dict[cn])
|
||||
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"""
|
||||
self.reset_content(self.get_patched_content())
|
||||
|
||||
def get_patched_content(self) -> str:
|
||||
assert self.original_content is not None
|
||||
return apply_patches(self.original_content, self.patches)
|
||||
|
||||
def write_to_file(self, f: IO[str]) -> None:
|
||||
f.write(self.get_patched_content())
|
||||
|
||||
def write_to_filename(self, filename: os.PathLike) -> None:
|
||||
with open(filename, 'wt') as of:
|
||||
self.write_to_file(of)
|
||||
|
||||
def patch_inplace(self) -> None:
|
||||
newfile = self.filename.with_suffix('.changed')
|
||||
self.write_to_filename(newfile)
|
||||
os.rename(newfile, self.filename)
|
||||
|
||||
def show_diff(self) -> None:
|
||||
with NamedTemporaryFile('wt') as f:
|
||||
self.write_to_file(f)
|
||||
f.flush()
|
||||
subprocess.call(['diff', '-u', self.filename, f.name])
|
||||
|
||||
def ref(self):
|
||||
return TypeInfoReference
|
||||
|
||||
class FileList(RegexpScanner):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.files: List[FileInfo] = []
|
||||
|
||||
def extend(self, *args, **kwargs):
|
||||
self.files.extend(*args, **kwargs)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(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: str) -> Optional[FileInfo]:
|
||||
"""Get file with path ending with @name"""
|
||||
for f in self.files:
|
||||
if f.filename_matches(name):
|
||||
return f
|
||||
else:
|
||||
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)
|
||||
@@ -0,0 +1,861 @@
|
||||
# Copyright (C) 2020 Red Hat Inc.
|
||||
#
|
||||
# Authors:
|
||||
# Eduardo Habkost <[email protected]>
|
||||
#
|
||||
# This work is licensed under the terms of the GNU GPL, version 2. See
|
||||
# the COPYING file in the top-level directory.
|
||||
import re
|
||||
from itertools import chain
|
||||
from typing import *
|
||||
|
||||
from .regexps import *
|
||||
from .patching import *
|
||||
from .utils import *
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
DBG = logger.debug
|
||||
INFO = logger.info
|
||||
WARN = logger.warning
|
||||
|
||||
# simple expressions:
|
||||
|
||||
RE_CONSTANT = OR(RE_STRING, RE_NUMBER)
|
||||
|
||||
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_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
|
||||
typename: Optional[str]
|
||||
# MYDEVICE
|
||||
uppercase: Optional[str] = None
|
||||
# MyDevice
|
||||
instancetype: Optional[str] = None
|
||||
# MyDeviceClass
|
||||
classtype: Optional[str] = None
|
||||
# my_device
|
||||
lowercase: Optional[str] = None
|
||||
|
||||
def allfields(self):
|
||||
return tuple(getattr(self, f) for f in self._fields)
|
||||
|
||||
def merge(self, other: 'TypeIdentifiers') -> Optional['TypeIdentifiers']:
|
||||
"""Check if identifiers match, return new identifier with complete list"""
|
||||
if any(not opt_compare(a, b) for a,b in zip(self, other)):
|
||||
return None
|
||||
return TypeIdentifiers(*(merge(a, b) for a,b in zip(self, other)))
|
||||
|
||||
def __str__(self) -> str:
|
||||
values = ((f, getattr(self, f)) for f in self._fields)
|
||||
s = ', '.join('%s=%s' % (f,v) for f,v in values if v is not None)
|
||||
return f'{s}'
|
||||
|
||||
def check_consistency(self) -> List[str]:
|
||||
"""Check if identifiers are consistent with each other,
|
||||
return list of problems (or empty list if everything seems consistent)
|
||||
"""
|
||||
r = []
|
||||
if self.typename is None:
|
||||
r.append("typename (TYPE_MYDEVICE) is unavailable")
|
||||
|
||||
if self.uppercase is None:
|
||||
r.append("uppercase name is unavailable")
|
||||
|
||||
if (self.instancetype is not None
|
||||
and self.classtype is not None
|
||||
and self.classtype != f'{self.instancetype}Class'):
|
||||
r.append("class typedef %s doesn't match instance typedef %s" %
|
||||
(self.classtype, self.instancetype))
|
||||
|
||||
if (self.uppercase is not None
|
||||
and self.typename is not None
|
||||
and f'TYPE_{self.uppercase}' != self.typename):
|
||||
r.append("uppercase name (%s) doesn't match type name (%s)" %
|
||||
(self.uppercase, self.typename))
|
||||
|
||||
return r
|
||||
|
||||
class TypedefMatch(FileMatch):
|
||||
"""typedef declaration"""
|
||||
def provided_identifiers(self) -> Iterable[RequiredIdentifier]:
|
||||
yield RequiredIdentifier('type', self.group('name'))
|
||||
|
||||
class SimpleTypedefMatch(TypedefMatch):
|
||||
"""Simple typedef declaration
|
||||
(no replacement rules)"""
|
||||
regexp = S(r'^[ \t]*typedef', SP,
|
||||
NAMED('typedef_type', RE_TYPE), SP,
|
||||
NAMED('name', RE_IDENTIFIER), r'\s*;[ \t]*\n')
|
||||
|
||||
RE_MACRO_DEFINE = S(r'^[ \t]*#\s*define\s+', NAMED('name', RE_IDENTIFIER),
|
||||
r'\s*\(\s*', RE_IDENTIFIER, r'\s*\)', CPP_SPACE)
|
||||
|
||||
RE_STRUCT_ATTRIBUTE = r'QEMU_PACKED'
|
||||
|
||||
# This doesn't parse the struct definitions completely, it just assumes
|
||||
# the closing brackets are going to be in an unindented line:
|
||||
RE_FULL_STRUCT = S('struct', SP, M(RE_IDENTIFIER, n='?', name='structname'), SP,
|
||||
NAMED('body', r'{\n',
|
||||
# acceptable inside the struct body:
|
||||
# - lines starting with space or tab
|
||||
# - empty lines
|
||||
# - preprocessor directives
|
||||
# - comments
|
||||
OR(r'[ \t][^\n]*\n',
|
||||
r'#[^\n]*\n',
|
||||
r'\n',
|
||||
S(r'[ \t]*', RE_COMMENT, r'[ \t]*\n'),
|
||||
repeat='*?'),
|
||||
r'}', M(RE_STRUCT_ATTRIBUTE, SP, n='*')))
|
||||
RE_STRUCT_TYPEDEF = S(r'^[ \t]*typedef', SP, RE_FULL_STRUCT, SP,
|
||||
NAMED('name', RE_IDENTIFIER), r'\s*;[ \t]*\n')
|
||||
|
||||
class FullStructTypedefMatch(TypedefMatch):
|
||||
"""typedef struct [SomeStruct] { ...} SomeType
|
||||
Will be replaced by separate struct declaration + typedef
|
||||
"""
|
||||
regexp = RE_STRUCT_TYPEDEF
|
||||
|
||||
def make_structname(self) -> str:
|
||||
"""Make struct name for struct+typedef split"""
|
||||
name = self.group('structname')
|
||||
if not name:
|
||||
name = self.name
|
||||
return name
|
||||
|
||||
def strip_typedef(self) -> Patch:
|
||||
"""generate patch that will strip typedef from the struct declaration
|
||||
|
||||
The caller is responsible for readding the typedef somewhere else.
|
||||
"""
|
||||
name = self.make_structname()
|
||||
body = self.group('body')
|
||||
return self.make_patch(f'struct {name} {body};\n')
|
||||
|
||||
def make_simple_typedef(self) -> str:
|
||||
structname = self.make_structname()
|
||||
name = self.name
|
||||
return f'typedef struct {structname} {name};\n'
|
||||
|
||||
def move_typedef(self, position) -> Iterator[Patch]:
|
||||
"""Generate patches to move typedef elsewhere"""
|
||||
yield self.strip_typedef()
|
||||
yield Patch(position, position, self.make_simple_typedef())
|
||||
|
||||
def split_typedef(self) -> Iterator[Patch]:
|
||||
"""Split into struct definition + typedef in-place"""
|
||||
yield self.strip_typedef()
|
||||
yield self.append(self.make_simple_typedef())
|
||||
|
||||
class StructTypedefSplit(FullStructTypedefMatch):
|
||||
"""split struct+typedef declaration"""
|
||||
def gen_patches(self) -> Iterator[Patch]:
|
||||
if self.group('structname'):
|
||||
yield from self.split_typedef()
|
||||
|
||||
class DuplicatedTypedefs(SimpleTypedefMatch):
|
||||
"""Delete ALL duplicate typedefs (unsafe)"""
|
||||
def gen_patches(self) -> Iterable[Patch]:
|
||||
other_td = [td for td in chain(self.file.matches_of_type(SimpleTypedefMatch),
|
||||
self.file.matches_of_type(FullStructTypedefMatch))
|
||||
if td.name == self.name]
|
||||
DBG("other_td: %r", other_td)
|
||||
if any(td.start() < self.start() for td in other_td):
|
||||
# patch only if handling the first typedef
|
||||
return
|
||||
for td in other_td:
|
||||
if isinstance(td, SimpleTypedefMatch):
|
||||
DBG("other td: %r", td.match.groupdict())
|
||||
if td.group('typedef_type') != self.group('typedef_type'):
|
||||
yield td.make_removal_patch()
|
||||
elif isinstance(td, FullStructTypedefMatch):
|
||||
DBG("other td: %r", td.match.groupdict())
|
||||
if self.group('typedef_type') == 'struct '+td.group('structname'):
|
||||
yield td.strip_typedef()
|
||||
|
||||
class QOMDuplicatedTypedefs(DuplicatedTypedefs):
|
||||
"""Delete duplicate typedefs if used by QOM type"""
|
||||
def gen_patches(self) -> Iterable[Patch]:
|
||||
qom_macros = [TypeCheckMacro, DeclareInstanceChecker, DeclareClassCheckers, DeclareObjCheckers]
|
||||
qom_matches = chain(*(self.file.matches_of_type(t) for t in qom_macros))
|
||||
in_use = any(RequiredIdentifier('type', self.name) in m.required_identifiers()
|
||||
for m in qom_matches)
|
||||
if in_use:
|
||||
yield from DuplicatedTypedefs.gen_patches(self)
|
||||
|
||||
class QOMStructTypedefSplit(FullStructTypedefMatch):
|
||||
"""split struct+typedef declaration if used by QOM type"""
|
||||
def gen_patches(self) -> Iterator[Patch]:
|
||||
qom_macros = [TypeCheckMacro, DeclareInstanceChecker, DeclareClassCheckers, DeclareObjCheckers]
|
||||
qom_matches = chain(*(self.file.matches_of_type(t) for t in qom_macros))
|
||||
in_use = any(RequiredIdentifier('type', self.name) in m.required_identifiers()
|
||||
for m in qom_matches)
|
||||
if in_use:
|
||||
yield from self.split_typedef()
|
||||
|
||||
def typedefs(file: FileInfo) -> Iterable[TypedefMatch]:
|
||||
return (cast(TypedefMatch, m)
|
||||
for m in chain(file.matches_of_type(SimpleTypedefMatch),
|
||||
file.matches_of_type(FullStructTypedefMatch)))
|
||||
|
||||
def find_typedef(f: FileInfo, name: Optional[str]) -> Optional[TypedefMatch]:
|
||||
if not name:
|
||||
return None
|
||||
for td in typedefs(f):
|
||||
if td.name == name:
|
||||
return td
|
||||
return None
|
||||
|
||||
CHECKER_MACROS = ['OBJECT_CHECK', 'OBJECT_CLASS_CHECK', 'OBJECT_GET_CLASS']
|
||||
CheckerMacroName = Literal['OBJECT_CHECK', 'OBJECT_CLASS_CHECK', 'OBJECT_GET_CLASS']
|
||||
|
||||
RE_CHECK_MACRO = \
|
||||
S(RE_MACRO_DEFINE,
|
||||
OR(*CHECKER_MACROS, name='checker'),
|
||||
M(r'\s*\(\s*', OR(NAMED('typedefname', 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',
|
||||
n='?', name='check_args'))
|
||||
|
||||
EXPECTED_CHECKER_SUFFIXES: List[Tuple[CheckerMacroName, str]] = [
|
||||
('OBJECT_GET_CLASS', '_GET_CLASS'),
|
||||
('OBJECT_CLASS_CHECK', '_CLASS'),
|
||||
]
|
||||
|
||||
class TypeCheckMacro(FileMatch):
|
||||
"""OBJECT_CHECK/OBJECT_CLASS_CHECK/OBJECT_GET_CLASS macro definitions
|
||||
Will be replaced by DECLARE_*_CHECKERS macro
|
||||
"""
|
||||
regexp = RE_CHECK_MACRO
|
||||
|
||||
@property
|
||||
def checker(self) -> CheckerMacroName:
|
||||
"""Name of checker macro being used"""
|
||||
return self.group('checker') # type: ignore
|
||||
|
||||
@property
|
||||
def typedefname(self) -> Optional[str]:
|
||||
return self.group('typedefname')
|
||||
|
||||
def find_typedef(self) -> Optional[TypedefMatch]:
|
||||
return find_typedef(self.file, self.typedefname)
|
||||
|
||||
def sanity_check(self) -> None:
|
||||
DBG("groups: %r", self.match.groups())
|
||||
if not self.group('check_args'):
|
||||
self.warn("type check macro not parsed completely: %s", self.name)
|
||||
return
|
||||
DBG("type identifiers: %r", self.type_identifiers)
|
||||
if self.typedefname and self.find_typedef() is None:
|
||||
self.warn("typedef used by %s not found", self.name)
|
||||
|
||||
def find_matching_macros(self) -> List['TypeCheckMacro']:
|
||||
"""Find other check macros that generate the same macro names
|
||||
|
||||
The returned list will always be sorted.
|
||||
"""
|
||||
my_ids = self.type_identifiers
|
||||
assert my_ids
|
||||
return [m for m in self.file.matches_of_type(TypeCheckMacro)
|
||||
if m.type_identifiers is not None
|
||||
and my_ids.uppercase is not None
|
||||
and (my_ids.uppercase == m.type_identifiers.uppercase
|
||||
or my_ids.typename == m.type_identifiers.typename)]
|
||||
|
||||
def merge_ids(self, matches: List['TypeCheckMacro']) -> Optional[TypeIdentifiers]:
|
||||
"""Try to merge info about type identifiers from all matches in a list"""
|
||||
if not matches:
|
||||
return None
|
||||
r = matches[0].type_identifiers
|
||||
if r is None:
|
||||
return None
|
||||
for m in matches[1:]:
|
||||
assert m.type_identifiers
|
||||
new = r.merge(m.type_identifiers)
|
||||
if new is None:
|
||||
self.warn("macro %s identifiers (%s) don't match macro %s (%s)",
|
||||
matches[0].name, r, m.name, m.type_identifiers)
|
||||
return None
|
||||
r = new
|
||||
return r
|
||||
|
||||
def required_identifiers(self) -> Iterable[RequiredIdentifier]:
|
||||
yield RequiredIdentifier('include', '"qom/object.h"')
|
||||
if self.type_identifiers is None:
|
||||
return
|
||||
# to make sure typedefs will be moved above all related macros,
|
||||
# return dependencies from all of them, not just this match
|
||||
for m in self.find_matching_macros():
|
||||
yield RequiredIdentifier('type', m.group('c_type'))
|
||||
yield RequiredIdentifier('constant', m.group('qom_typename'))
|
||||
|
||||
@property
|
||||
def type_identifiers(self) -> Optional[TypeIdentifiers]:
|
||||
"""Extract type identifier information from match"""
|
||||
typename = self.group('qom_typename')
|
||||
c_type = self.group('c_type')
|
||||
if not typename or not c_type:
|
||||
return None
|
||||
typedef = self.group('typedefname')
|
||||
classtype = None
|
||||
instancetype = None
|
||||
uppercase = None
|
||||
expected_suffix = dict(EXPECTED_CHECKER_SUFFIXES).get(self.checker)
|
||||
|
||||
# here the available data depends on the checker macro being called:
|
||||
# - we need to remove the suffix from the macro name
|
||||
# - depending on the macro type, we know the class type name, or
|
||||
# the instance type name
|
||||
if self.checker in ('OBJECT_GET_CLASS', 'OBJECT_CLASS_CHECK'):
|
||||
classtype = c_type
|
||||
elif self.checker == 'OBJECT_CHECK':
|
||||
instancetype = c_type
|
||||
uppercase = self.name
|
||||
else:
|
||||
assert False
|
||||
if expected_suffix and self.name.endswith(expected_suffix):
|
||||
uppercase = self.name[:-len(expected_suffix)]
|
||||
return TypeIdentifiers(typename=typename, classtype=classtype,
|
||||
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
|
||||
|
||||
if self.name == 'INTERFACE_CLASS':
|
||||
# INTERFACE_CLASS is special and won't be patched
|
||||
return
|
||||
|
||||
for checker,suffix in EXPECTED_CHECKER_SUFFIXES:
|
||||
if self.name.endswith(suffix):
|
||||
if self.checker != checker:
|
||||
self.warn("macro %s is using macro %s instead of %s", self.name, self.checker, checker)
|
||||
return
|
||||
break
|
||||
|
||||
matches = self.find_matching_macros()
|
||||
DBG("found %d matching macros: %s", len(matches), ' '.join(m.name for m in matches))
|
||||
# we will generate patches only when processing the first macro:
|
||||
if matches[0].start != self.start:
|
||||
DBG("skipping %s (will patch when handling %s)", self.name, matches[0].name)
|
||||
return
|
||||
|
||||
|
||||
ids = self.merge_ids(matches)
|
||||
if ids is None:
|
||||
DBG("type identifier mismatch, won't patch %s", self.name)
|
||||
return
|
||||
|
||||
if not ids.uppercase:
|
||||
self.warn("macro %s doesn't follow the expected name pattern", self.name)
|
||||
return
|
||||
if not ids.typename:
|
||||
self.warn("macro %s: couldn't extract type name", self.name)
|
||||
return
|
||||
|
||||
#issues = ids.check_consistency()
|
||||
#if issues:
|
||||
# for i in issues:
|
||||
# self.warn("inconsistent identifiers: %s", i)
|
||||
|
||||
names = [n for n in (ids.instancetype, ids.classtype, ids.uppercase, ids.typename)
|
||||
if n is not None]
|
||||
if len(set(names)) != len(names):
|
||||
self.warn("duplicate names used by macro: %r", ids)
|
||||
return
|
||||
|
||||
assert ids.classtype or ids.instancetype
|
||||
assert ids.typename
|
||||
assert ids.uppercase
|
||||
if ids.classtype and ids.instancetype:
|
||||
new_decl = (f'DECLARE_OBJ_CHECKERS({ids.instancetype}, {ids.classtype},\n'
|
||||
f' {ids.uppercase}, {ids.typename})\n')
|
||||
elif ids.classtype:
|
||||
new_decl = (f'DECLARE_CLASS_CHECKERS({ids.classtype}, {ids.uppercase},\n'
|
||||
f' {ids.typename})\n')
|
||||
elif ids.instancetype:
|
||||
new_decl = (f'DECLARE_INSTANCE_CHECKER({ids.instancetype}, {ids.uppercase},\n'
|
||||
f' {ids.typename})\n')
|
||||
else:
|
||||
assert False
|
||||
|
||||
# we need to ensure the typedefs are already available
|
||||
issues = []
|
||||
for t in [ids.instancetype, ids.classtype]:
|
||||
if not t:
|
||||
continue
|
||||
if re.fullmatch(RE_STRUCT_TYPE, t):
|
||||
self.info("type %s is not a typedef", t)
|
||||
continue
|
||||
td = find_typedef(self.file, t)
|
||||
#if not td and self.allfiles.find_file('include/qemu/typedefs.h'):
|
||||
#
|
||||
if not td:
|
||||
# it is OK if the typedef is in typedefs.h
|
||||
f = self.allfiles.find_file('include/qemu/typedefs.h')
|
||||
if f and find_typedef(f, t):
|
||||
self.info("typedef %s found in typedefs.h", t)
|
||||
continue
|
||||
|
||||
issues.append("couldn't find typedef %s" % (t))
|
||||
elif td.start() > self.start():
|
||||
issues.append("typedef %s need to be moved earlier in the file" % (td.name))
|
||||
|
||||
for issue in issues:
|
||||
self.warn(issue)
|
||||
|
||||
if issues and not self.file.force:
|
||||
return
|
||||
|
||||
# delete all matching macros and add new declaration:
|
||||
for m in matches:
|
||||
yield m.make_patch('')
|
||||
for issue in issues:
|
||||
yield self.prepend("/* FIXME: %s */\n" % (issue))
|
||||
yield self.append(new_decl)
|
||||
|
||||
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
|
||||
# structs in another file
|
||||
regexp = S(r'^[ \t]*DECLARE_INSTANCE_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 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*',
|
||||
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('classtype'))
|
||||
|
||||
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*',
|
||||
NAMED('classtype', 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('classtype'))
|
||||
yield RequiredIdentifier('type', self.group('instancetype'))
|
||||
|
||||
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
|
||||
|
||||
# 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
|
||||
|
||||
#_,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"""
|
||||
regexp = S(r'^[ \t]*struct\s*', NAMED('name', RE_IDENTIFIER),
|
||||
r'\s*{\s*', NAMED('parent_struct', RE_IDENTIFIER), r'\s*parent(_class)?\s*;\s*};\n')
|
||||
|
||||
class DeclareTypeName(FileMatch):
|
||||
"""DECLARE_TYPE_NAME usage"""
|
||||
regexp = S(r'^[ \t]*DECLARE_TYPE_NAME\s*\(',
|
||||
NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*',
|
||||
OR(RE_IDENTIFIER, RE_STRING, RE_MACRO_CONCAT, RE_FUN_CALL, name='typename'),
|
||||
r'\s*\);?[ \t]*\n')
|
||||
|
||||
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('uppercase', RE_IDENTIFIER), SP,
|
||||
r'\)[ \t]*;?[ \t]*\n')
|
||||
|
||||
def gen_patches(self):
|
||||
DBG("groups: %r", self.match.groupdict())
|
||||
trivial_struct = self.file.find_match(TrivialClassStruct, self.group('classtype'))
|
||||
if trivial_struct:
|
||||
d = self.match.groupdict().copy()
|
||||
d['parent_struct'] = trivial_struct.group("parent_struct")
|
||||
yield trivial_struct.make_removal_patch()
|
||||
c = ("OBJECT_DECLARE_SIMPLE_TYPE(%(instancetype)s, %(lowercase)s,\n"
|
||||
" %(uppercase)s, %(parent_struct)s)\n" % d)
|
||||
yield self.make_patch(c)
|
||||
|
||||
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"""
|
||||
regexp = RE_INCLUDE
|
||||
def provided_identifiers(self) -> Iterable[RequiredIdentifier]:
|
||||
yield RequiredIdentifier('include', self.group('includepath'))
|
||||
|
||||
class InitialIncludes(FileMatch):
|
||||
"""Initial #include block"""
|
||||
regexp = S(RE_FILE_BEGIN,
|
||||
M(SP, RE_COMMENTS,
|
||||
r'^[ \t]*#[ \t]*ifndef[ \t]+', RE_IDENTIFIER, r'[ \t]*\n',
|
||||
n='?', name='ifndef_block'),
|
||||
M(SP, RE_COMMENTS,
|
||||
OR(RE_INCLUDE, RE_SIMPLEDEFINE),
|
||||
n='*', name='includes'))
|
||||
|
||||
class SymbolUserList(NamedTuple):
|
||||
definitions: List[FileMatch]
|
||||
users: List[FileMatch]
|
||||
|
||||
class MoveSymbols(FileMatch):
|
||||
"""Handle missing symbols
|
||||
- Move typedefs and defines when necessary
|
||||
- Add missing #include lines when necessary
|
||||
"""
|
||||
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, InterfaceCheckMacro]
|
||||
|
||||
# first we scan for all symbol definitions and usage:
|
||||
for dc in definition_classes:
|
||||
defs = self.file.matches_of_type(dc)
|
||||
for d in defs:
|
||||
DBG("scanning %r", d)
|
||||
for i in d.provided_identifiers():
|
||||
index.setdefault(i, SymbolUserList([], [])).definitions.append(d)
|
||||
DBG("index: %r", list(index.keys()))
|
||||
for uc in user_classes:
|
||||
users = self.file.matches_of_type(uc)
|
||||
for u in users:
|
||||
for i in u.required_identifiers():
|
||||
index.setdefault(i, SymbolUserList([], [])).users.append(u)
|
||||
|
||||
# validate all symbols:
|
||||
for i,ul in index.items():
|
||||
if not ul.users:
|
||||
# unused symbol
|
||||
continue
|
||||
|
||||
# symbol not defined
|
||||
if len(ul.definitions) == 0:
|
||||
if i.type == 'include':
|
||||
includes, = self.file.matches_of_type(InitialIncludes)
|
||||
#FIXME: don't do this if we're already inside qom/object.h
|
||||
yield includes.append(f'#include {i.name}\n')
|
||||
else:
|
||||
u.warn("definition of %s %s not found in file", i.type, i.name)
|
||||
continue
|
||||
|
||||
# symbol defined twice:
|
||||
if len(ul.definitions) > 1:
|
||||
ul.definitions[1].warn("%s defined twice", i.name)
|
||||
ul.definitions[0].warn("previously defined here")
|
||||
continue
|
||||
|
||||
# symbol defined. check if all users are after its definition:
|
||||
assert len(ul.definitions) == 1
|
||||
definition = ul.definitions[0]
|
||||
DBG("handling repositioning of %r", definition)
|
||||
earliest = min(ul.users, key=lambda u: u.start())
|
||||
if earliest.start() > definition.start():
|
||||
DBG("%r is OK", definition)
|
||||
continue
|
||||
|
||||
DBG("%r needs to be moved", definition)
|
||||
if isinstance(definition, SimpleTypedefMatch) \
|
||||
or isinstance(definition, ConstantDefine):
|
||||
# simple typedef or define can be moved directly:
|
||||
yield definition.make_removal_patch()
|
||||
yield earliest.prepend(definition.group(0))
|
||||
elif isinstance(definition, FullStructTypedefMatch) \
|
||||
and definition.group('structname'):
|
||||
# full struct typedef is more complex: we need to remove
|
||||
# the typedef
|
||||
yield from definition.move_typedef(earliest.start())
|
||||
else:
|
||||
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()
|
||||
@@ -0,0 +1,950 @@
|
||||
# Copyright (C) 2020 Red Hat Inc.
|
||||
#
|
||||
# Authors:
|
||||
# Eduardo Habkost <[email protected]>
|
||||
#
|
||||
# This work is licensed under the terms of the GNU GPL, version 2. See
|
||||
# the COPYING file in the top-level directory.
|
||||
import re
|
||||
from .regexps import *
|
||||
from .patching import *
|
||||
from .utils import *
|
||||
from .qom_macros import *
|
||||
|
||||
TI_FIELDS = [ 'name', 'parent', 'abstract', 'interfaces',
|
||||
'instance_size', 'instance_init', 'instance_post_init', 'instance_finalize',
|
||||
'class_size', 'class_init', 'class_base_init', 'class_data']
|
||||
|
||||
RE_TI_FIELD_NAME = OR(*TI_FIELDS)
|
||||
|
||||
RE_TI_FIELD_INIT = S(r'[ \t]*', NAMED('comments', RE_COMMENTS),
|
||||
r'\.', NAMED('field', RE_TI_FIELD_NAME), r'\s*=\s*',
|
||||
NAMED('value', RE_EXPRESSION), r'[ \t]*,?[ \t]*\n')
|
||||
RE_TI_FIELDS = M(RE_TI_FIELD_INIT)
|
||||
|
||||
RE_TYPEINFO_START = S(r'^[ \t]*', M(r'(static|const)\s+', name='modifiers'), r'TypeInfo\s+',
|
||||
NAMED('name', RE_IDENTIFIER), r'\s*=\s*{[ \t]*\n')
|
||||
|
||||
ParsedArray = List[str]
|
||||
ParsedInitializerValue = Union[str, ParsedArray]
|
||||
class InitializerValue(NamedTuple):
|
||||
raw: str
|
||||
parsed: Optional[ParsedInitializerValue]
|
||||
match: Optional[Match]
|
||||
|
||||
class ArrayItem(FileMatch):
|
||||
regexp = RE_ARRAY_ITEM
|
||||
|
||||
class ArrayInitializer(FileMatch):
|
||||
regexp = RE_ARRAY
|
||||
|
||||
def parsed(self) -> ParsedArray:
|
||||
#DBG('parse_array: %r', m.group(0))
|
||||
return [m.group('arrayitem') for m in self.group_finditer(ArrayItem, 'arrayitems')]
|
||||
|
||||
class FieldInitializer(FileMatch):
|
||||
regexp = RE_TI_FIELD_INIT
|
||||
|
||||
@property
|
||||
def raw(self) -> str:
|
||||
return self.group('value')
|
||||
|
||||
@property
|
||||
def parsed(self) -> ParsedInitializerValue:
|
||||
parsed: ParsedInitializerValue = self.raw
|
||||
#DBG("parse_initializer_value: %r", s)
|
||||
array = self.try_group_match(ArrayInitializer, 'value')
|
||||
if array:
|
||||
assert isinstance(array, ArrayInitializer)
|
||||
return array.parsed()
|
||||
return parsed
|
||||
|
||||
TypeInfoInitializers = Dict[str, FieldInitializer]
|
||||
|
||||
class TypeDefinition(FileMatch):
|
||||
"""
|
||||
Common base class for type definitions (TypeInfo variables or OBJECT_DEFINE* macros)
|
||||
"""
|
||||
@property
|
||||
def instancetype(self) -> Optional[str]:
|
||||
return self.group('instancetype')
|
||||
|
||||
@property
|
||||
def classtype(self) -> Optional[str]:
|
||||
return self.group('classtype')
|
||||
|
||||
@property
|
||||
def uppercase(self) -> Optional[str]:
|
||||
return self.group('uppercase')
|
||||
|
||||
@property
|
||||
def parent_uppercase(self) -> str:
|
||||
return self.group('parent_uppercase')
|
||||
|
||||
@property
|
||||
def initializers(self) -> Optional[TypeInfoInitializers]:
|
||||
if getattr(self, '_inititalizers', None):
|
||||
self._initializers: TypeInfoInitializers
|
||||
return self._initializers
|
||||
fields = self.group('fields')
|
||||
if fields is None:
|
||||
return None
|
||||
d = dict((fm.group('field'), fm)
|
||||
for fm in self.group_finditer(FieldInitializer, 'fields'))
|
||||
self._initializers = d # type: ignore
|
||||
return self._initializers
|
||||
|
||||
|
||||
class TypeInfoVar(TypeDefinition):
|
||||
"""TypeInfo variable declaration with initializer"""
|
||||
regexp = S(NAMED('begin', RE_TYPEINFO_START),
|
||||
M(NAMED('fields', RE_TI_FIELDS),
|
||||
NAMED('endcomments', SP, RE_COMMENTS),
|
||||
NAMED('end', r'};?\n'),
|
||||
n='?', name='fullspec'))
|
||||
|
||||
def is_static(self) -> bool:
|
||||
return 'static' in self.group('modifiers')
|
||||
|
||||
def is_const(self) -> bool:
|
||||
return 'const' in self.group('modifiers')
|
||||
|
||||
def is_full(self) -> bool:
|
||||
return bool(self.group('fullspec'))
|
||||
|
||||
def get_initializers(self) -> TypeInfoInitializers:
|
||||
"""Helper for code that needs to deal with missing initializer info"""
|
||||
if self.initializers is None:
|
||||
return {}
|
||||
return self.initializers
|
||||
|
||||
def get_raw_initializer_value(self, field: str, default: str = '') -> str:
|
||||
initializers = self.get_initializers()
|
||||
if field in initializers:
|
||||
return initializers[field].raw
|
||||
else:
|
||||
return default
|
||||
|
||||
@property
|
||||
def typename(self) -> Optional[str]:
|
||||
return self.get_raw_initializer_value('name')
|
||||
|
||||
@property
|
||||
def uppercase(self) -> Optional[str]:
|
||||
typename = self.typename
|
||||
if not typename:
|
||||
return None
|
||||
if not typename.startswith('TYPE_'):
|
||||
return None
|
||||
return typename[len('TYPE_'):]
|
||||
|
||||
@property
|
||||
def classtype(self) -> Optional[str]:
|
||||
class_size = self.get_raw_initializer_value('class_size')
|
||||
if not class_size:
|
||||
return None
|
||||
m = re.fullmatch(RE_SIZEOF, class_size)
|
||||
if not m:
|
||||
return None
|
||||
return m.group('sizeoftype')
|
||||
|
||||
@property
|
||||
def instancetype(self) -> Optional[str]:
|
||||
instance_size = self.get_raw_initializer_value('instance_size')
|
||||
if not instance_size:
|
||||
return None
|
||||
m = re.fullmatch(RE_SIZEOF, instance_size)
|
||||
if not m:
|
||||
return None
|
||||
return m.group('sizeoftype')
|
||||
|
||||
|
||||
#def extract_identifiers(self) -> Optional[TypeIdentifiers]:
|
||||
# """Try to extract identifiers from names being used"""
|
||||
# DBG("extracting idenfiers from %s", self.name)
|
||||
#uppercase = None
|
||||
#if typename and re.fullmatch(RE_IDENTIFIER, typename) and typename.startswith("TYPE_"):
|
||||
# uppercase = typename[len('TYPE_'):]
|
||||
#lowercase = None
|
||||
#funcs = set()
|
||||
#prefixes = set()
|
||||
#for field,suffix in [('instance_init', '_init'),
|
||||
# ('instance_finalize', '_finalize'),
|
||||
# ('class_init', '_class_init')]:
|
||||
# if field not in values:
|
||||
# continue
|
||||
# func = values[field].raw
|
||||
# funcs.add(func)
|
||||
# if func.endswith(suffix):
|
||||
# prefixes.add(func[:-len(suffix)])
|
||||
# else:
|
||||
# self.warn("function name %s doesn't have expected %s suffix",
|
||||
# func, suffix)
|
||||
#if len(prefixes) == 1:
|
||||
# lowercase = prefixes.pop()
|
||||
#elif len(prefixes) > 1:
|
||||
# self.warn("inconsistent function names: %s", ' '.join(funcs))
|
||||
|
||||
#.parent = TYPE_##PARENT_MODULE_OBJ_NAME, \
|
||||
#return TypeIdentifiers(typename=typename,
|
||||
# uppercase=uppercase, lowercase=lowercase,
|
||||
# instancetype=instancetype, classtype=classtype)
|
||||
|
||||
def append_field(self, field: str, value: str) -> Patch:
|
||||
"""Generate patch appending a field initializer"""
|
||||
content = f' .{field} = {value},\n'
|
||||
fm = self.group_match('fields')
|
||||
assert fm
|
||||
return fm.append(content)
|
||||
|
||||
def patch_field(self, field: str, replacement: str) -> Patch:
|
||||
"""Generate patch replacing a field initializer"""
|
||||
initializers = self.initializers
|
||||
assert initializers
|
||||
value = initializers.get(field)
|
||||
assert value
|
||||
return value.make_patch(replacement)
|
||||
|
||||
def remove_field(self, field: str) -> Iterable[Patch]:
|
||||
initializers = self.initializers
|
||||
assert initializers
|
||||
if field in initializers:
|
||||
yield self.patch_field(field, '')
|
||||
|
||||
def remove_fields(self, *fields: str) -> Iterable[Patch]:
|
||||
for f in fields:
|
||||
yield from self.remove_field(f)
|
||||
|
||||
def patch_field_value(self, field: str, replacement: str) -> Patch:
|
||||
"""Replace just the value of a field initializer"""
|
||||
initializers = self.initializers
|
||||
assert initializers
|
||||
value = initializers.get(field)
|
||||
assert value
|
||||
vm = value.group_match('value')
|
||||
assert vm
|
||||
return vm.make_patch(replacement)
|
||||
|
||||
|
||||
class RemoveRedundantClassSize(TypeInfoVar):
|
||||
"""Remove class_size when using OBJECT_DECLARE_SIMPLE_TYPE"""
|
||||
def gen_patches(self) -> Iterable[Patch]:
|
||||
initializers = self.initializers
|
||||
if initializers is None:
|
||||
return
|
||||
if 'class_size' not in initializers:
|
||||
return
|
||||
|
||||
self.debug("Handling %s", self.name)
|
||||
m = re.fullmatch(RE_SIZEOF, initializers['class_size'].raw)
|
||||
if not m:
|
||||
self.warn("%s class_size is not sizeof?", self.name)
|
||||
return
|
||||
classtype = m.group('sizeoftype')
|
||||
if not classtype.endswith('Class'):
|
||||
self.warn("%s class size type (%s) is not *Class?", self.name, classtype)
|
||||
return
|
||||
self.debug("classtype is %s", classtype)
|
||||
instancetype = classtype[:-len('Class')]
|
||||
self.debug("intanceypte is %s", instancetype)
|
||||
self.debug("searching for simpletype declaration using %s as InstanceType", instancetype)
|
||||
decl = self.allfiles.find_match(OldStyleObjectDeclareSimpleType,
|
||||
instancetype, 'instancetype')
|
||||
if not decl:
|
||||
self.debug("No simpletype declaration found for %s", instancetype)
|
||||
return
|
||||
self.debug("Found simple type declaration")
|
||||
decl.debug("declaration is here")
|
||||
yield from self.remove_field('class_size')
|
||||
|
||||
class RemoveDeclareSimpleTypeArg(OldStyleObjectDeclareSimpleType):
|
||||
"""Remove class_size when using OBJECT_DECLARE_SIMPLE_TYPE"""
|
||||
def gen_patches(self) -> Iterable[Patch]:
|
||||
c = (f'OBJECT_DECLARE_SIMPLE_TYPE({self.group("instancetype")}, {self.group("lowercase")},\n'
|
||||
f' {self.group("uppercase")})\n')
|
||||
yield self.make_patch(c)
|
||||
|
||||
class UseDeclareTypeExtended(TypeInfoVar):
|
||||
"""Replace TypeInfo variable with OBJECT_DEFINE_TYPE_EXTENDED"""
|
||||
def gen_patches(self) -> Iterable[Patch]:
|
||||
# this will just ensure the caches for find_match() and matches_for_type()
|
||||
# will be loaded in advance:
|
||||
find_type_checkers(self.allfiles, 'xxxxxxxxxxxxxxxxx')
|
||||
|
||||
if not self.is_static():
|
||||
self.info("Skipping non-static TypeInfo variable")
|
||||
return
|
||||
|
||||
type_info_macro = self.file.find_match(TypeInfoMacro, self.name)
|
||||
if not type_info_macro:
|
||||
self.warn("TYPE_INFO(%s) line not found", self.name)
|
||||
return
|
||||
|
||||
values = self.initializers
|
||||
if values is None:
|
||||
return
|
||||
if 'name' not in values:
|
||||
self.warn("name not set in TypeInfo variable %s", self.name)
|
||||
return
|
||||
|
||||
typename = values['name'].raw
|
||||
|
||||
if 'parent' not in values:
|
||||
self.warn("parent not set in TypeInfo variable %s", self.name)
|
||||
return
|
||||
parent_typename = values['parent'].raw
|
||||
|
||||
instancetype = None
|
||||
if 'instance_size' in values:
|
||||
m = re.fullmatch(RE_SIZEOF, values['instance_size'].raw)
|
||||
if m:
|
||||
instancetype = m.group('sizeoftype')
|
||||
else:
|
||||
self.warn("can't extract instance type in TypeInfo variable %s", self.name)
|
||||
self.warn("instance_size is set to: %r", values['instance_size'].raw)
|
||||
return
|
||||
|
||||
classtype = None
|
||||
if 'class_size' in values:
|
||||
m = re.fullmatch(RE_SIZEOF, values['class_size'].raw)
|
||||
if m:
|
||||
classtype = m.group('sizeoftype')
|
||||
else:
|
||||
self.warn("can't extract class type in TypeInfo variable %s", self.name)
|
||||
self.warn("class_size is set to: %r", values['class_size'].raw)
|
||||
return
|
||||
|
||||
#for t in (typename, parent_typename):
|
||||
# if not re.fullmatch(RE_IDENTIFIER, t):
|
||||
# self.info("type name is not a macro/constant")
|
||||
# if instancetype or classtype:
|
||||
# self.warn("macro/constant type name is required for instance/class type")
|
||||
# if not self.file.force:
|
||||
# return
|
||||
|
||||
# Now, the challenge is to find out the right MODULE_OBJ_NAME for the
|
||||
# type and for the parent type
|
||||
self.info("TypeInfo variable for %s is here", typename)
|
||||
uppercase = find_typename_uppercase(self.allfiles, typename)
|
||||
if not uppercase:
|
||||
self.info("Can't find right uppercase name for %s", typename)
|
||||
if instancetype or classtype:
|
||||
self.warn("Can't find right uppercase name for %s", typename)
|
||||
self.warn("This will make type validation difficult in the future")
|
||||
return
|
||||
|
||||
parent_uppercase = find_typename_uppercase(self.allfiles, parent_typename)
|
||||
if not parent_uppercase:
|
||||
self.info("Can't find right uppercase name for parent type (%s)", parent_typename)
|
||||
if instancetype or classtype:
|
||||
self.warn("Can't find right uppercase name for parent type (%s)", parent_typename)
|
||||
self.warn("This will make type validation difficult in the future")
|
||||
return
|
||||
|
||||
ok = True
|
||||
|
||||
#checkers: List[TypeCheckerDeclaration] = list(find_type_checkers(self.allfiles, uppercase))
|
||||
#for c in checkers:
|
||||
# c.info("instance type checker declaration (%s) is here", c.group('uppercase'))
|
||||
#if not checkers:
|
||||
# self.info("No type checkers declared for %s", uppercase)
|
||||
# if instancetype or classtype:
|
||||
# self.warn("Can't find where type checkers for %s (%s) are declared. We will need them to validate sizes of %s",
|
||||
# typename, uppercase, self.name)
|
||||
|
||||
if not instancetype:
|
||||
instancetype = 'void'
|
||||
if not classtype:
|
||||
classtype = 'void'
|
||||
|
||||
#checker_instancetypes = set(c.instancetype for c in checkers
|
||||
# if c.instancetype is not None)
|
||||
#if len(checker_instancetypes) > 1:
|
||||
# self.warn("ambiguous set of type checkers")
|
||||
# for c in checkers:
|
||||
# c.warn("instancetype is %s here", c.instancetype)
|
||||
# ok = False
|
||||
#elif len(checker_instancetypes) == 1:
|
||||
# checker_instancetype = checker_instancetypes.pop()
|
||||
# DBG("checker instance type: %r", checker_instancetype)
|
||||
# if instancetype != checker_instancetype:
|
||||
# self.warn("type at instance_size is %r. Should instance_size be set to sizeof(%s) ?",
|
||||
# instancetype, checker_instancetype)
|
||||
# ok = False
|
||||
#else:
|
||||
# if instancetype != 'void':
|
||||
# self.warn("instance type checker for %s (%s) not found", typename, instancetype)
|
||||
# ok = False
|
||||
|
||||
#checker_classtypes = set(c.classtype for c in checkers
|
||||
# if c.classtype is not None)
|
||||
#if len(checker_classtypes) > 1:
|
||||
# self.warn("ambiguous set of type checkers")
|
||||
# for c in checkers:
|
||||
# c.warn("classtype is %s here", c.classtype)
|
||||
# ok = False
|
||||
#elif len(checker_classtypes) == 1:
|
||||
# checker_classtype = checker_classtypes.pop()
|
||||
# DBG("checker class type: %r", checker_classtype)
|
||||
# if classtype != checker_classtype:
|
||||
# self.warn("type at class_size is %r. Should class_size be set to sizeof(%s) ?",
|
||||
# classtype, checker_classtype)
|
||||
# ok = False
|
||||
#else:
|
||||
# if classtype != 'void':
|
||||
# self.warn("class type checker for %s (%s) not found", typename, classtype)
|
||||
# ok = False
|
||||
|
||||
#if not ok:
|
||||
# for c in checkers:
|
||||
# c.warn("Type checker declaration for %s (%s) is here",
|
||||
# typename, type(c).__name__)
|
||||
# return
|
||||
|
||||
#if parent_decl is None:
|
||||
# self.warn("Can't find where parent type %s is declared", parent_typename)
|
||||
|
||||
#yield self.prepend(f'DECLARE_TYPE_NAME({uppercase}, {typename})\n')
|
||||
#if not instancetype:
|
||||
# yield self.prepend(f'DECLARE_INSTANCE_TYPE({uppercase}, void)\n')
|
||||
#if not classtype:
|
||||
# yield self.prepend(f'DECLARE_CLASS_TYPE({uppercase}, void)\n')
|
||||
self.info("%s can be patched!", self.name)
|
||||
replaced_fields = ['name', 'parent', 'instance_size', 'class_size']
|
||||
begin = self.group_match('begin')
|
||||
newbegin = f'OBJECT_DEFINE_TYPE_EXTENDED({self.name},\n'
|
||||
newbegin += f' {instancetype}, {classtype},\n'
|
||||
newbegin += f' {uppercase}, {parent_uppercase}'
|
||||
if set(values.keys()) - set(replaced_fields):
|
||||
newbegin += ',\n'
|
||||
yield begin.make_patch(newbegin)
|
||||
yield from self.remove_fields(*replaced_fields)
|
||||
end = self.group_match('end')
|
||||
yield end.make_patch(')\n')
|
||||
yield type_info_macro.make_removal_patch()
|
||||
|
||||
class ObjectDefineTypeExtended(TypeDefinition):
|
||||
"""OBJECT_DEFINE_TYPE_EXTENDED usage"""
|
||||
regexp = S(r'^[ \t]*OBJECT_DEFINE_TYPE_EXTENDED\s*\(\s*',
|
||||
NAMED('name', RE_IDENTIFIER), r'\s*,\s*',
|
||||
NAMED('instancetype', RE_IDENTIFIER), r'\s*,\s*',
|
||||
NAMED('classtype', RE_IDENTIFIER), r'\s*,\s*',
|
||||
NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*',
|
||||
NAMED('parent_uppercase', RE_IDENTIFIER),
|
||||
M(r',\s*\n',
|
||||
NAMED('fields', RE_TI_FIELDS),
|
||||
n='?'),
|
||||
r'\s*\);?\n?')
|
||||
|
||||
class ObjectDefineType(TypeDefinition):
|
||||
"""OBJECT_DEFINE_TYPE usage"""
|
||||
regexp = S(r'^[ \t]*OBJECT_DEFINE_TYPE\s*\(\s*',
|
||||
NAMED('lowercase', RE_IDENTIFIER), r'\s*,\s*',
|
||||
NAMED('uppercase', RE_IDENTIFIER), r'\s*,\s*',
|
||||
NAMED('parent_uppercase', RE_IDENTIFIER),
|
||||
M(r',\s*\n',
|
||||
NAMED('fields', RE_TI_FIELDS),
|
||||
n='?'),
|
||||
r'\s*\);?\n?')
|
||||
|
||||
def find_type_definitions(files: FileList, uppercase: str) -> Iterable[TypeDefinition]:
|
||||
types: List[Type[TypeDefinition]] = [TypeInfoVar, ObjectDefineType, ObjectDefineTypeExtended]
|
||||
for t in types:
|
||||
for m in files.matches_of_type(t):
|
||||
m.debug("uppercase: %s", m.uppercase)
|
||||
yield from (m for t in types
|
||||
for m in files.matches_of_type(t)
|
||||
if m.uppercase == uppercase)
|
||||
|
||||
class AddDeclareVoidClassType(TypeDeclarationFixup):
|
||||
"""Will add DECLARE_CLASS_TYPE(..., void) if possible"""
|
||||
def gen_patches_for_type(self, uppercase: str,
|
||||
checkers: List[TypeDeclaration],
|
||||
fields: Dict[str, Optional[str]]) -> Iterable[Patch]:
|
||||
defs = list(find_type_definitions(self.allfiles, uppercase))
|
||||
if len(defs) > 1:
|
||||
self.warn("multiple definitions for %s", uppercase)
|
||||
for d in defs:
|
||||
d.warn("definition found here")
|
||||
return
|
||||
elif len(defs) == 0:
|
||||
self.warn("type definition for %s not found", uppercase)
|
||||
return
|
||||
d = defs[0]
|
||||
if d.classtype is None:
|
||||
d.info("definition for %s has classtype, skipping", uppercase)
|
||||
return
|
||||
class_type_checkers = [c for c in checkers
|
||||
if c.classtype is not None]
|
||||
if class_type_checkers:
|
||||
for c in class_type_checkers:
|
||||
c.warn("class type checker for %s is present here", uppercase)
|
||||
return
|
||||
|
||||
_,last_checker = max((m.start(), m) for m in checkers)
|
||||
s = f'DECLARE_CLASS_TYPE({uppercase}, void)\n'
|
||||
yield last_checker.append(s)
|
||||
|
||||
class AddDeclareVoidInstanceType(FileMatch):
|
||||
"""Will add DECLARE_INSTANCE_TYPE(..., void) if possible"""
|
||||
regexp = S(r'^[ \t]*#[ \t]*define', CPP_SPACE,
|
||||
NAMED('name', r'TYPE_[a-zA-Z0-9_]+\b'),
|
||||
CPP_SPACE, r'.*\n')
|
||||
|
||||
def gen_patches(self) -> Iterable[Patch]:
|
||||
assert self.name.startswith('TYPE_')
|
||||
uppercase = self.name[len('TYPE_'):]
|
||||
defs = list(find_type_definitions(self.allfiles, uppercase))
|
||||
if len(defs) > 1:
|
||||
self.warn("multiple definitions for %s", uppercase)
|
||||
for d in defs:
|
||||
d.warn("definition found here")
|
||||
return
|
||||
elif len(defs) == 0:
|
||||
self.warn("type definition for %s not found", uppercase)
|
||||
return
|
||||
d = defs[0]
|
||||
instancetype = d.instancetype
|
||||
if instancetype is not None and instancetype != 'void':
|
||||
return
|
||||
|
||||
instance_checkers = [c for c in find_type_checkers(self.allfiles, uppercase)
|
||||
if c.instancetype]
|
||||
if instance_checkers:
|
||||
d.warn("instance type checker for %s already declared", uppercase)
|
||||
for c in instance_checkers:
|
||||
c.warn("instance checker for %s is here", uppercase)
|
||||
return
|
||||
|
||||
s = f'DECLARE_INSTANCE_TYPE({uppercase}, void)\n'
|
||||
yield self.append(s)
|
||||
|
||||
class AddObjectDeclareType(DeclareObjCheckers):
|
||||
"""Will add OBJECT_DECLARE_TYPE(...) if possible"""
|
||||
def gen_patches(self) -> Iterable[Patch]:
|
||||
uppercase = self.uppercase
|
||||
typename = self.group('typename')
|
||||
instancetype = self.group('instancetype')
|
||||
classtype = self.group('classtype')
|
||||
|
||||
if typename != f'TYPE_{uppercase}':
|
||||
self.warn("type name mismatch: %s vs %s", typename, uppercase)
|
||||
return
|
||||
|
||||
typedefs = [(t,self.allfiles.find_matches(SimpleTypedefMatch, t))
|
||||
for t in (instancetype, classtype)]
|
||||
for t,tds in typedefs:
|
||||
if not tds:
|
||||
self.warn("typedef %s not found", t)
|
||||
return
|
||||
for td in tds:
|
||||
td_type = td.group('typedef_type')
|
||||
if td_type != f'struct {t}':
|
||||
self.warn("typedef mismatch: %s is defined as %s", t, td_type)
|
||||
td.warn("typedef is here")
|
||||
return
|
||||
|
||||
# look for reuse of same struct type
|
||||
other_instance_checkers = [c for c in find_type_checkers(self.allfiles, instancetype, 'instancetype')
|
||||
if c.uppercase != uppercase]
|
||||
if other_instance_checkers:
|
||||
self.warn("typedef %s is being reused", instancetype)
|
||||
for ic in other_instance_checkers:
|
||||
ic.warn("%s is reused here", instancetype)
|
||||
if not self.file.force:
|
||||
return
|
||||
|
||||
decl_types: List[Type[TypeDeclaration]] = [DeclareClassCheckers, DeclareObjCheckers]
|
||||
class_decls = [m for t in decl_types
|
||||
for m in self.allfiles.find_matches(t, uppercase, 'uppercase')]
|
||||
|
||||
defs = list(find_type_definitions(self.allfiles, uppercase))
|
||||
if len(defs) > 1:
|
||||
self.warn("multiple definitions for %s", uppercase)
|
||||
for d in defs:
|
||||
d.warn("definition found here")
|
||||
if not self.file.force:
|
||||
return
|
||||
elif len(defs) == 0:
|
||||
self.warn("type definition for %s not found", uppercase)
|
||||
if not self.file.force:
|
||||
return
|
||||
else:
|
||||
d = defs[0]
|
||||
if d.instancetype != instancetype:
|
||||
self.warn("mismatching instance type for %s (%s)", uppercase, instancetype)
|
||||
d.warn("instance type declared here (%s)", d.instancetype)
|
||||
if not self.file.force:
|
||||
return
|
||||
if d.classtype != classtype:
|
||||
self.warn("mismatching class type for %s (%s)", uppercase, classtype)
|
||||
d.warn("class type declared here (%s)", d.classtype)
|
||||
if not self.file.force:
|
||||
return
|
||||
|
||||
assert self.file.original_content
|
||||
for t,tds in typedefs:
|
||||
assert tds
|
||||
for td in tds:
|
||||
if td.file is not self.file:
|
||||
continue
|
||||
|
||||
# delete typedefs that are truly redundant:
|
||||
# 1) defined after DECLARE_OBJ_CHECKERS
|
||||
if td.start() > self.start():
|
||||
yield td.make_removal_patch()
|
||||
# 2) defined before DECLARE_OBJ_CHECKERS, but unused
|
||||
elif not re.search(r'\b'+t+r'\b', self.file.original_content[td.end():self.start()]):
|
||||
yield td.make_removal_patch()
|
||||
|
||||
c = (f'OBJECT_DECLARE_TYPE({instancetype}, {classtype}, {uppercase})\n')
|
||||
yield self.make_patch(c)
|
||||
|
||||
class AddObjectDeclareSimpleType(DeclareInstanceChecker):
|
||||
"""Will add OBJECT_DECLARE_SIMPLE_TYPE(...) if possible"""
|
||||
def gen_patches(self) -> Iterable[Patch]:
|
||||
uppercase = self.uppercase
|
||||
typename = self.group('typename')
|
||||
instancetype = self.group('instancetype')
|
||||
|
||||
if typename != f'TYPE_{uppercase}':
|
||||
self.warn("type name mismatch: %s vs %s", typename, uppercase)
|
||||
return
|
||||
|
||||
typedefs = [(t,self.allfiles.find_matches(SimpleTypedefMatch, t))
|
||||
for t in (instancetype,)]
|
||||
for t,tds in typedefs:
|
||||
if not tds:
|
||||
self.warn("typedef %s not found", t)
|
||||
return
|
||||
for td in tds:
|
||||
td_type = td.group('typedef_type')
|
||||
if td_type != f'struct {t}':
|
||||
self.warn("typedef mismatch: %s is defined as %s", t, td_type)
|
||||
td.warn("typedef is here")
|
||||
return
|
||||
|
||||
# look for reuse of same struct type
|
||||
other_instance_checkers = [c for c in find_type_checkers(self.allfiles, instancetype, 'instancetype')
|
||||
if c.uppercase != uppercase]
|
||||
if other_instance_checkers:
|
||||
self.warn("typedef %s is being reused", instancetype)
|
||||
for ic in other_instance_checkers:
|
||||
ic.warn("%s is reused here", instancetype)
|
||||
if not self.file.force:
|
||||
return
|
||||
|
||||
decl_types: List[Type[TypeDeclaration]] = [DeclareClassCheckers, DeclareObjCheckers]
|
||||
class_decls = [m for t in decl_types
|
||||
for m in self.allfiles.find_matches(t, uppercase, 'uppercase')]
|
||||
if class_decls:
|
||||
self.warn("class type declared for %s", uppercase)
|
||||
for cd in class_decls:
|
||||
cd.warn("class declaration found here")
|
||||
return
|
||||
|
||||
defs = list(find_type_definitions(self.allfiles, uppercase))
|
||||
if len(defs) > 1:
|
||||
self.warn("multiple definitions for %s", uppercase)
|
||||
for d in defs:
|
||||
d.warn("definition found here")
|
||||
if not self.file.force:
|
||||
return
|
||||
elif len(defs) == 0:
|
||||
self.warn("type definition for %s not found", uppercase)
|
||||
if not self.file.force:
|
||||
return
|
||||
else:
|
||||
d = defs[0]
|
||||
if d.instancetype != instancetype:
|
||||
self.warn("mismatching instance type for %s (%s)", uppercase, instancetype)
|
||||
d.warn("instance type declared here (%s)", d.instancetype)
|
||||
if not self.file.force:
|
||||
return
|
||||
if d.classtype:
|
||||
self.warn("class type set for %s", uppercase)
|
||||
d.warn("class type declared here")
|
||||
if not self.file.force:
|
||||
return
|
||||
|
||||
assert self.file.original_content
|
||||
for t,tds in typedefs:
|
||||
assert tds
|
||||
for td in tds:
|
||||
if td.file is not self.file:
|
||||
continue
|
||||
|
||||
# delete typedefs that are truly redundant:
|
||||
# 1) defined after DECLARE_OBJ_CHECKERS
|
||||
if td.start() > self.start():
|
||||
yield td.make_removal_patch()
|
||||
# 2) defined before DECLARE_OBJ_CHECKERS, but unused
|
||||
elif not re.search(r'\b'+t+r'\b', self.file.original_content[td.end():self.start()]):
|
||||
yield td.make_removal_patch()
|
||||
|
||||
c = (f'OBJECT_DECLARE_SIMPLE_TYPE({instancetype}, {uppercase})\n')
|
||||
yield self.make_patch(c)
|
||||
|
||||
|
||||
class TypeInfoStringName(TypeInfoVar):
|
||||
"""Replace hardcoded type names with TYPE_ constant"""
|
||||
def gen_patches(self) -> Iterable[Patch]:
|
||||
values = self.initializers
|
||||
if values is None:
|
||||
return
|
||||
if 'name' not in values:
|
||||
self.warn("name not set in TypeInfo variable %s", self.name)
|
||||
return
|
||||
typename = values['name'].raw
|
||||
if re.fullmatch(RE_IDENTIFIER, typename):
|
||||
return
|
||||
|
||||
self.warn("name %s is not an identifier", typename)
|
||||
#all_defines = [m for m in self.allfiles.matches_of_type(ExpressionDefine)]
|
||||
#self.debug("all_defines: %r", all_defines)
|
||||
constants = [m for m in self.allfiles.matches_of_type(ExpressionDefine)
|
||||
if m.group('value').strip() == typename.strip()]
|
||||
if not constants:
|
||||
self.warn("No macro for %s found", typename)
|
||||
return
|
||||
if len(constants) > 1:
|
||||
self.warn("I don't know which macro to use: %r", constants)
|
||||
return
|
||||
yield self.patch_field_value('name', constants[0].name)
|
||||
|
||||
class RedundantTypeSizes(TypeInfoVar):
|
||||
"""Remove redundant instance_size/class_size from TypeInfo vars"""
|
||||
def gen_patches(self) -> Iterable[Patch]:
|
||||
values = self.initializers
|
||||
if values is None:
|
||||
return
|
||||
if 'name' not in values:
|
||||
self.warn("name not set in TypeInfo variable %s", self.name)
|
||||
return
|
||||
typename = values['name'].raw
|
||||
if 'parent' not in values:
|
||||
self.warn("parent not set in TypeInfo variable %s", self.name)
|
||||
return
|
||||
parent_typename = values['parent'].raw
|
||||
|
||||
if 'instance_size' not in values and 'class_size' not in values:
|
||||
self.debug("no need to validate %s", self.name)
|
||||
return
|
||||
|
||||
instance_decls = find_type_checkers(self.allfiles, typename)
|
||||
if instance_decls:
|
||||
self.debug("won't touch TypeInfo var that has type checkers")
|
||||
return
|
||||
|
||||
parent = find_type_info(self.allfiles, parent_typename)
|
||||
if not parent:
|
||||
self.warn("Can't find TypeInfo for %s", parent_typename)
|
||||
return
|
||||
|
||||
if 'instance_size' in values and parent.get_raw_initializer_value('instance_size') != values['instance_size'].raw:
|
||||
self.info("instance_size mismatch")
|
||||
parent.info("parent type declared here")
|
||||
return
|
||||
|
||||
if 'class_size' in values and parent.get_raw_initializer_value('class_size') != values['class_size'].raw:
|
||||
self.info("class_size mismatch")
|
||||
parent.info("parent type declared here")
|
||||
return
|
||||
|
||||
self.debug("will patch variable %s", self.name)
|
||||
|
||||
if 'instance_size' in values:
|
||||
self.debug("deleting instance_size")
|
||||
yield self.patch_field('instance_size', '')
|
||||
|
||||
if 'class_size' in values:
|
||||
self.debug("deleting class_size")
|
||||
yield self.patch_field('class_size', '')
|
||||
|
||||
|
||||
#class TypeInfoVarInitFuncs(TypeInfoVar):
|
||||
# """TypeInfo variable
|
||||
# Will create missing init functions
|
||||
# """
|
||||
# def gen_patches(self) -> Iterable[Patch]:
|
||||
# values = self.initializers
|
||||
# if values is None:
|
||||
# self.warn("type not parsed completely: %s", self.name)
|
||||
# return
|
||||
#
|
||||
# macro = self.file.find_match(TypeInfoVar, self.name)
|
||||
# if macro is None:
|
||||
# self.warn("No TYPE_INFO macro for %s", self.name)
|
||||
# return
|
||||
#
|
||||
# ids = self.extract_identifiers()
|
||||
# if ids is None:
|
||||
# return
|
||||
#
|
||||
# DBG("identifiers extracted: %r", ids)
|
||||
# fields = set(values.keys())
|
||||
# if ids.lowercase:
|
||||
# if 'instance_init' not in fields:
|
||||
# yield self.prepend(('static void %s_init(Object *obj)\n'
|
||||
# '{\n'
|
||||
# '}\n\n') % (ids.lowercase))
|
||||
# yield self.append_field('instance_init', ids.lowercase+'_init')
|
||||
#
|
||||
# if 'instance_finalize' not in fields:
|
||||
# yield self.prepend(('static void %s_finalize(Object *obj)\n'
|
||||
# '{\n'
|
||||
# '}\n\n') % (ids.lowercase))
|
||||
# yield self.append_field('instance_finalize', ids.lowercase+'_finalize')
|
||||
#
|
||||
#
|
||||
# if 'class_init' not in fields:
|
||||
# yield self.prepend(('static void %s_class_init(ObjectClass *oc,\n'
|
||||
# 'const void *data)\n'
|
||||
# '{\n'
|
||||
# '}\n\n') % (ids.lowercase))
|
||||
# yield self.append_field('class_init', ids.lowercase+'_class_init')
|
||||
|
||||
class TypeInitMacro(FileMatch):
|
||||
"""Use of type_init(...) macro"""
|
||||
regexp = S(r'^[ \t]*type_init\s*\(\s*', NAMED('name', RE_IDENTIFIER), r'\s*\);?[ \t]*\n')
|
||||
|
||||
class DeleteEmptyTypeInitFunc(TypeInitMacro):
|
||||
"""Delete empty function declared using type_init(...)"""
|
||||
def gen_patches(self) -> Iterable[Patch]:
|
||||
fn = self.file.find_match(StaticVoidFunction, self.name)
|
||||
DBG("function for %s: %s", self.name, fn)
|
||||
if fn and fn.body == '':
|
||||
yield fn.make_patch('')
|
||||
yield self.make_patch('')
|
||||
|
||||
class StaticVoidFunction(FileMatch):
|
||||
"""simple static void function
|
||||
(no replacement rules)
|
||||
"""
|
||||
#NOTE: just like RE_FULL_STRUCT, this doesn't parse any of the body contents
|
||||
# of the function. Tt will just look for "}" in the beginning of a line
|
||||
regexp = S(r'static\s+void\s+', NAMED('name', RE_IDENTIFIER), r'\s*\(\s*void\s*\)\n',
|
||||
r'{\n',
|
||||
NAMED('body',
|
||||
# acceptable inside the function body:
|
||||
# - lines starting with space or tab
|
||||
# - empty lines
|
||||
# - preprocessor directives
|
||||
OR(r'[ \t][^\n]*\n',
|
||||
r'#[^\n]*\n',
|
||||
r'\n',
|
||||
repeat='*')),
|
||||
r'};?\n')
|
||||
|
||||
@property
|
||||
def body(self) -> str:
|
||||
return self.group('body')
|
||||
|
||||
def has_preprocessor_directive(self) -> bool:
|
||||
return bool(re.search(r'^[ \t]*#', self.body, re.MULTILINE))
|
||||
|
||||
def find_containing_func(m: FileMatch) -> Optional['StaticVoidFunction']:
|
||||
"""Return function containing this match"""
|
||||
for fn in m.file.matches_of_type(StaticVoidFunction):
|
||||
if fn.contains(m):
|
||||
return fn
|
||||
return None
|
||||
|
||||
class TypeRegisterStaticCall(FileMatch):
|
||||
"""type_register_static() call
|
||||
Will be replaced by TYPE_INFO() macro
|
||||
"""
|
||||
regexp = S(r'^[ \t]*', NAMED('func_name', 'type_register_static'),
|
||||
r'\s*\(&\s*', NAMED('name', RE_IDENTIFIER), r'\s*\);[ \t]*\n')
|
||||
|
||||
class UseTypeInfo(TypeRegisterStaticCall):
|
||||
"""Replace type_register_static() call with TYPE_INFO declaration"""
|
||||
def gen_patches(self) -> Iterable[Patch]:
|
||||
fn = find_containing_func(self)
|
||||
if fn:
|
||||
DBG("%r is inside %r", self, fn)
|
||||
type_init = self.file.find_match(TypeInitMacro, fn.name)
|
||||
if type_init is None:
|
||||
self.warn("can't find type_init(%s) line", fn.name)
|
||||
if not self.file.force:
|
||||
return
|
||||
else:
|
||||
self.warn("can't identify the function where type_register_static(&%s) is called", self.name)
|
||||
if not self.file.force:
|
||||
return
|
||||
|
||||
#if fn.has_preprocessor_directive() and not self.file.force:
|
||||
# self.warn("function %s has preprocessor directives, this requires --force", fn.name)
|
||||
# return
|
||||
|
||||
var = self.file.find_match(TypeInfoVar, self.name)
|
||||
if var is None:
|
||||
self.warn("can't find TypeInfo var declaration for %s", self.name)
|
||||
return
|
||||
|
||||
if not var.is_full():
|
||||
self.warn("variable declaration %s wasn't parsed fully", var.name)
|
||||
if not self.file.force:
|
||||
return
|
||||
|
||||
if fn and fn.contains(var):
|
||||
self.warn("TypeInfo %s variable is inside a function", self.name)
|
||||
if not self.file.force:
|
||||
return
|
||||
|
||||
# delete type_register_static() call:
|
||||
yield self.make_patch('')
|
||||
# append TYPE_REGISTER(...) after variable declaration:
|
||||
yield var.append(f'TYPE_INFO({self.name})\n')
|
||||
|
||||
class TypeRegisterCall(FileMatch):
|
||||
"""type_register_static() call"""
|
||||
regexp = S(r'^[ \t]*', NAMED('func_name', 'type_register'),
|
||||
r'\s*\(&\s*', NAMED('name', RE_IDENTIFIER), r'\s*\);[ \t]*\n')
|
||||
|
||||
class TypeInfoMacro(FileMatch):
|
||||
"""TYPE_INFO macro usage"""
|
||||
regexp = S(r'^[ \t]*TYPE_INFO\s*\(\s*', NAMED('name', RE_IDENTIFIER), r'\s*\)[ \t]*;?[ \t]*\n')
|
||||
|
||||
def find_type_info(files: RegexpScanner, name: str) -> Optional[TypeInfoVar]:
|
||||
ti = [ti for ti in files.matches_of_type(TypeInfoVar)
|
||||
if ti.get_raw_initializer_value('name') == name]
|
||||
DBG("type info vars: %r", ti)
|
||||
if len(ti) > 1:
|
||||
DBG("multiple TypeInfo vars found for %s", name)
|
||||
return None
|
||||
if len(ti) == 0:
|
||||
DBG("no TypeInfo var found for %s", name)
|
||||
return None
|
||||
return ti[0]
|
||||
|
||||
class CreateClassStruct(DeclareInstanceChecker):
|
||||
"""Replace DECLARE_INSTANCE_CHECKER with OBJECT_DECLARE_SIMPLE_TYPE"""
|
||||
def gen_patches(self) -> Iterable[Patch]:
|
||||
typename = self.group('typename')
|
||||
DBG("looking for TypeInfo variable for %s", typename)
|
||||
var = find_type_info(self.allfiles, typename)
|
||||
if var is None:
|
||||
self.warn("no TypeInfo var found for %s", typename)
|
||||
return
|
||||
assert var.initializers
|
||||
if 'class_size' in var.initializers:
|
||||
self.warn("class size already set for TypeInfo %s", var.name)
|
||||
return
|
||||
classtype = self.group('instancetype')+'Class'
|
||||
return
|
||||
yield
|
||||
#TODO: need to find out what's the parent class type...
|
||||
#yield var.append_field('class_size', f'sizeof({classtype})')
|
||||
#c = (f'OBJECT_DECLARE_SIMPLE_TYPE({instancetype}, {lowercase},\n'
|
||||
# f' MODULE_OBJ_NAME, ParentClassType)\n')
|
||||
#yield self.make_patch(c)
|
||||
|
||||
def type_infos(file: FileInfo) -> Iterable[TypeInfoVar]:
|
||||
return file.matches_of_type(TypeInfoVar)
|
||||
|
||||
def full_types(file: FileInfo) -> Iterable[TypeInfoVar]:
|
||||
return [t for t in type_infos(file) if t.is_full()]
|
||||
|
||||
def partial_types(file: FileInfo) -> Iterable[TypeInfoVar]:
|
||||
return [t for t in type_infos(file) if not t.is_full()]
|
||||
@@ -0,0 +1,118 @@
|
||||
# Copyright (C) 2020 Red Hat Inc.
|
||||
#
|
||||
# Authors:
|
||||
# Eduardo Habkost <[email protected]>
|
||||
#
|
||||
# This work is licensed under the terms of the GNU GPL, version 2. See
|
||||
# the COPYING file in the top-level directory.
|
||||
"""Helpers for creation of regular expressions"""
|
||||
import re
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
DBG = logger.debug
|
||||
INFO = logger.info
|
||||
WARN = logger.warning
|
||||
|
||||
def S(*regexps) -> str:
|
||||
"""Just a shortcut to concatenate multiple regexps more easily"""
|
||||
return ''.join(regexps)
|
||||
|
||||
def P(*regexps, name=None, capture=False, repeat='') -> str:
|
||||
"""Just add parenthesis around regexp(s), with optional name or repeat suffix"""
|
||||
s = S(*regexps)
|
||||
if name:
|
||||
return f'(?P<{name}>{s}){repeat}'
|
||||
elif capture:
|
||||
return f'({s}){repeat}'
|
||||
else:
|
||||
return f'(?:{s}){repeat}'
|
||||
|
||||
def NAMED(name, *regexps) -> str:
|
||||
"""Make named group using <P<name>...) syntax
|
||||
|
||||
>>> NAMED('mygroup', 'xyz', 'abc')
|
||||
'(?P<mygroup>xyzabc)'
|
||||
"""
|
||||
return P(*regexps, name=name)
|
||||
|
||||
def OR(*regexps, **kwargs) -> str:
|
||||
"""Build (a|b|c) regexp"""
|
||||
return P('|'.join(regexps), **kwargs)
|
||||
|
||||
def M(*regexps, n='*', name=None) -> str:
|
||||
"""Add repetition qualifier to regexp(s)
|
||||
|
||||
>>> M('a', 'b')
|
||||
'(?:ab)*'
|
||||
>>> M('a' , 'b', n='+')
|
||||
'(?:ab)+'
|
||||
>>> M('a' , 'b', n='{2,3}', name='name')
|
||||
'(?P<name>(?:ab){2,3})'
|
||||
"""
|
||||
r = P(*regexps, repeat=n)
|
||||
if name:
|
||||
r = NAMED(name, r)
|
||||
return r
|
||||
|
||||
# helper to make parenthesis optional around regexp
|
||||
OPTIONAL_PARS = lambda R: OR(S(r'\(\s*', R, r'\s*\)'), R)
|
||||
def test_optional_pars():
|
||||
r = OPTIONAL_PARS('abc')+'$'
|
||||
assert re.match(r, 'abc')
|
||||
assert re.match(r, '(abc)')
|
||||
assert not re.match(r, '(abcd)')
|
||||
assert not re.match(r, '(abc')
|
||||
assert not re.match(r, 'abc)')
|
||||
|
||||
|
||||
# this disables the MULTILINE flag, so it will match at the
|
||||
# beginning of the file:
|
||||
RE_FILE_BEGIN = r'(?-m:^)'
|
||||
|
||||
# C primitives:
|
||||
|
||||
SP = r'\s*'
|
||||
|
||||
RE_COMMENT = r'//[^\n]*$|/\*([^*]|\*[^/])*\*/'
|
||||
RE_COMMENTS = M(RE_COMMENT + SP)
|
||||
|
||||
RE_IDENTIFIER = r'[a-zA-Z_][a-zA-Z0-9_]*(?![a-zA-Z0-9])'
|
||||
RE_STRING = r'\"([^\"\\]|\\[a-z\"])*\"'
|
||||
RE_NUMBER = r'[0-9]+|0x[0-9a-fA-F]+'
|
||||
|
||||
# space or escaped newlines:
|
||||
CPP_SPACE = OR(r'\s', r'\\\n', repeat='+')
|
||||
|
||||
RE_PATH = '[a-zA-Z0-9/_.-]+'
|
||||
|
||||
RE_INCLUDEPATH = OR(S(r'\"', RE_PATH, r'\"'),
|
||||
S(r'<', RE_PATH, r'>'))
|
||||
|
||||
RE_INCLUDE = S(r'^[ \t]*#[ \t]*include[ \t]+', NAMED('includepath', RE_INCLUDEPATH), r'[ \t]*\n')
|
||||
RE_SIMPLEDEFINE = S(r'^[ \t]*#[ \t]*define[ \t]+', RE_IDENTIFIER, r'[ \t]*\n')
|
||||
|
||||
RE_STRUCT_TYPE = S(r'struct\s+', RE_IDENTIFIER)
|
||||
RE_TYPE = OR(RE_IDENTIFIER, RE_STRUCT_TYPE)
|
||||
|
||||
RE_MACRO_CONCAT = M(S(OR(RE_IDENTIFIER, RE_STRING), SP), n='{2,}')
|
||||
|
||||
RE_SIMPLE_VALUE = OR(RE_IDENTIFIER, RE_STRING, RE_NUMBER)
|
||||
|
||||
RE_FUN_CALL = S(RE_IDENTIFIER, r'\s*\(\s*', RE_SIMPLE_VALUE, r'\s*\)')
|
||||
RE_SIZEOF = S(r'sizeof\s*\(\s*', NAMED('sizeoftype', RE_TYPE), r'\s*\)')
|
||||
|
||||
RE_ADDRESS = S(r'&\s*', RE_IDENTIFIER)
|
||||
|
||||
RE_ARRAY_ITEM = S(r'{\s*', NAMED('arrayitem', M(RE_SIMPLE_VALUE, n='?')), r'\s*}\s*,?')
|
||||
RE_ARRAY_CAST = S(r'\(\s*', RE_IDENTIFIER, r'\s*\[\s*\]\)')
|
||||
RE_ARRAY_ITEMS = M(S(RE_ARRAY_ITEM, SP))
|
||||
RE_ARRAY = S(M(RE_ARRAY_CAST, n='?'), r'\s*{\s*',
|
||||
NAMED('arrayitems', RE_ARRAY_ITEMS),
|
||||
r'}')
|
||||
|
||||
# NOTE: this covers a very small subset of valid expressions
|
||||
|
||||
RE_EXPRESSION = OR(RE_SIZEOF, RE_FUN_CALL, RE_MACRO_CONCAT, RE_SIMPLE_VALUE,
|
||||
RE_ARRAY, RE_ADDRESS)
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
# Copyright (C) 2020 Red Hat Inc.
|
||||
#
|
||||
# Authors:
|
||||
# Eduardo Habkost <[email protected]>
|
||||
#
|
||||
# This work is licensed under the terms of the GNU GPL, version 2. See
|
||||
# the COPYING file in the top-level directory.
|
||||
from tempfile import NamedTemporaryFile
|
||||
from .patching import FileInfo, FileMatch, Patch, FileList
|
||||
from .regexps import *
|
||||
|
||||
class BasicPattern(FileMatch):
|
||||
regexp = '[abc]{3}'
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.group(0)
|
||||
|
||||
def replacement(self) -> str:
|
||||
# replace match with the middle character repeated 5 times
|
||||
return self.group(0)[1].upper()*5
|
||||
|
||||
def test_pattern_patching():
|
||||
of = NamedTemporaryFile('wt')
|
||||
of.writelines(['one line\n',
|
||||
'this pattern will be patched: defbbahij\n',
|
||||
'third line\n',
|
||||
'another pattern: jihaabfed'])
|
||||
of.flush()
|
||||
|
||||
files = FileList()
|
||||
f = FileInfo(files, of.name)
|
||||
f.load()
|
||||
matches = f.matches_of_type(BasicPattern)
|
||||
assert len(matches) == 2
|
||||
p2 = matches[1]
|
||||
|
||||
# manually add patch, to see if .append() works:
|
||||
f.patches.append(p2.append('XXX'))
|
||||
|
||||
# apply all patches:
|
||||
f.gen_patches(matches)
|
||||
patched = f.get_patched_content()
|
||||
assert patched == ('one line\n'+
|
||||
'this pattern will be patched: defBBBBBhij\n'+
|
||||
'third line\n'+
|
||||
'another pattern: jihAAAAAXXXfed')
|
||||
|
||||
class Function(FileMatch):
|
||||
regexp = S(r'BEGIN\s+', NAMED('name', RE_IDENTIFIER), r'\n',
|
||||
r'(.*\n)*?END\n')
|
||||
|
||||
class Statement(FileMatch):
|
||||
regexp = S(r'^\s*', NAMED('name', RE_IDENTIFIER), r'\(\)\n')
|
||||
|
||||
def test_container_match():
|
||||
of = NamedTemporaryFile('wt')
|
||||
of.writelines(['statement1()\n',
|
||||
'statement2()\n',
|
||||
'BEGIN function1\n',
|
||||
' statement3()\n',
|
||||
' statement4()\n',
|
||||
'END\n',
|
||||
'BEGIN function2\n',
|
||||
' statement5()\n',
|
||||
' statement6()\n',
|
||||
'END\n',
|
||||
'statement7()\n'])
|
||||
of.flush()
|
||||
|
||||
files = FileList()
|
||||
f = FileInfo(files, of.name)
|
||||
f.load()
|
||||
assert len(f.matches_of_type(Function)) == 2
|
||||
print(' '.join(m.name for m in f.matches_of_type(Statement)))
|
||||
assert len(f.matches_of_type(Statement)) == 7
|
||||
|
||||
f1 = f.find_match(Function, 'function1')
|
||||
f2 = f.find_match(Function, 'function2')
|
||||
st1 = f.find_match(Statement, 'statement1')
|
||||
st2 = f.find_match(Statement, 'statement2')
|
||||
st3 = f.find_match(Statement, 'statement3')
|
||||
st4 = f.find_match(Statement, 'statement4')
|
||||
st5 = f.find_match(Statement, 'statement5')
|
||||
st6 = f.find_match(Statement, 'statement6')
|
||||
st7 = f.find_match(Statement, 'statement7')
|
||||
|
||||
assert not f1.contains(st1)
|
||||
assert not f1.contains(st2)
|
||||
assert not f1.contains(st2)
|
||||
assert f1.contains(st3)
|
||||
assert f1.contains(st4)
|
||||
assert not f1.contains(st5)
|
||||
assert not f1.contains(st6)
|
||||
assert not f1.contains(st7)
|
||||
|
||||
assert not f2.contains(st1)
|
||||
assert not f2.contains(st2)
|
||||
assert not f2.contains(st2)
|
||||
assert not f2.contains(st3)
|
||||
assert not f2.contains(st4)
|
||||
assert f2.contains(st5)
|
||||
assert f2.contains(st6)
|
||||
assert not f2.contains(st7)
|
||||
@@ -0,0 +1,281 @@
|
||||
# Copyright (C) 2020 Red Hat Inc.
|
||||
#
|
||||
# Authors:
|
||||
# Eduardo Habkost <[email protected]>
|
||||
#
|
||||
# This work is licensed under the terms of the GNU GPL, version 2. See
|
||||
# the COPYING file in the top-level directory.
|
||||
from .regexps import *
|
||||
from .qom_macros import *
|
||||
from .qom_type_info import *
|
||||
|
||||
def test_res() -> None:
|
||||
def fullmatch(regexp, s):
|
||||
return re.fullmatch(regexp, s, re.MULTILINE)
|
||||
|
||||
assert fullmatch(RE_IDENTIFIER, 'sizeof')
|
||||
assert fullmatch(RE_IDENTIFIER, 'X86CPU')
|
||||
assert fullmatch(RE_FUN_CALL, 'sizeof(X86CPU)')
|
||||
assert fullmatch(RE_IDENTIFIER, 'X86_CPU_TYPE_NAME')
|
||||
assert fullmatch(RE_SIMPLE_VALUE, '"base"')
|
||||
print(RE_FUN_CALL)
|
||||
assert fullmatch(RE_FUN_CALL, 'X86_CPU_TYPE_NAME("base")')
|
||||
print(RE_TI_FIELD_INIT)
|
||||
assert fullmatch(RE_TI_FIELD_INIT, '.name = X86_CPU_TYPE_NAME("base"),\n')
|
||||
|
||||
|
||||
assert fullmatch(RE_MACRO_CONCAT, 'TYPE_ASPEED_GPIO "-ast2600"')
|
||||
assert fullmatch(RE_EXPRESSION, 'TYPE_ASPEED_GPIO "-ast2600"')
|
||||
|
||||
print(RE_MACRO_DEFINE)
|
||||
assert re.search(RE_MACRO_DEFINE, r'''
|
||||
#define OFFSET_CHECK(c) \
|
||||
do { \
|
||||
if (!(c)) { \
|
||||
goto bad_offset; \
|
||||
} \
|
||||
} while (0)
|
||||
''', re.MULTILINE)
|
||||
|
||||
print(RE_CHECK_MACRO)
|
||||
print(CPP_SPACE)
|
||||
assert not re.match(RE_CHECK_MACRO, r'''
|
||||
#define OFFSET_CHECK(c) \
|
||||
do { \
|
||||
if (!(c)) { \
|
||||
goto bad_offset; \
|
||||
} \
|
||||
} while (0)''', re.MULTILINE)
|
||||
|
||||
print(RE_CHECK_MACRO)
|
||||
assert fullmatch(RE_CHECK_MACRO, r'''#define PCI_DEVICE(obj) \
|
||||
OBJECT_CHECK(PCIDevice, (obj), TYPE_PCI_DEVICE)
|
||||
''')
|
||||
assert fullmatch(RE_CHECK_MACRO, r'''#define COLLIE_MACHINE(obj) \
|
||||
OBJECT_CHECK(CollieMachineState, obj, TYPE_COLLIE_MACHINE)
|
||||
''')
|
||||
|
||||
print(RE_TYPEINFO_START)
|
||||
assert re.search(RE_TYPEINFO_START, r'''
|
||||
cc->chr_open = file_chr_open;
|
||||
}
|
||||
|
||||
static const TypeInfo char_file_type_info = {
|
||||
.name = TYPE_CHARDEV_FILE,
|
||||
#ifdef _WIN32
|
||||
.parent = TYPE_CHARDEV_WIN,
|
||||
''', re.MULTILINE)
|
||||
assert re.search(RE_TYPEINFO_START, r'''
|
||||
TypeInfo ti = {
|
||||
.name = armsse_variants[i].name,
|
||||
.parent = TYPE_ARMSSE,
|
||||
.class_init = armsse_class_init,
|
||||
.class_data = &armsse_variants[i],
|
||||
};''', re.MULTILINE)
|
||||
|
||||
print(RE_ARRAY_ITEM)
|
||||
assert fullmatch(RE_ARRAY_ITEM, '{ TYPE_HOTPLUG_HANDLER },')
|
||||
assert fullmatch(RE_ARRAY_ITEM, '{ TYPE_ACPI_DEVICE_IF },')
|
||||
assert fullmatch(RE_ARRAY_ITEM, '{ }')
|
||||
assert fullmatch(RE_ARRAY_CAST, '(const InterfaceInfo[])')
|
||||
assert fullmatch(RE_ARRAY, '''(const InterfaceInfo[]) {
|
||||
{ TYPE_HOTPLUG_HANDLER },
|
||||
{ TYPE_ACPI_DEVICE_IF },
|
||||
{ }
|
||||
}''')
|
||||
print(RE_COMMENT)
|
||||
assert fullmatch(RE_COMMENT, r'''/* multi-line
|
||||
* comment
|
||||
*/''')
|
||||
|
||||
print(RE_TI_FIELDS)
|
||||
assert fullmatch(RE_TI_FIELDS,
|
||||
r'''/* could be TYPE_SYS_BUS_DEVICE (or LPC etc) */
|
||||
.parent = TYPE_DEVICE,
|
||||
''')
|
||||
assert fullmatch(RE_TI_FIELDS, r'''.name = TYPE_TPM_CRB,
|
||||
/* could be TYPE_SYS_BUS_DEVICE (or LPC etc) */
|
||||
.parent = TYPE_DEVICE,
|
||||
.instance_size = sizeof(CRBState),
|
||||
.class_init = tpm_crb_class_init,
|
||||
.interfaces = (const InterfaceInfo[]) {
|
||||
{ TYPE_TPM_IF },
|
||||
{ }
|
||||
}
|
||||
''')
|
||||
assert fullmatch(RE_TI_FIELDS + SP + RE_COMMENTS,
|
||||
r'''.name = TYPE_PALM_MISC_GPIO,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(PalmMiscGPIOState),
|
||||
.instance_init = palm_misc_gpio_init,
|
||||
/*
|
||||
* No class init required: device has no internal state so does not
|
||||
* need to set up reset or vmstate, and has no realize method.
|
||||
*/''')
|
||||
|
||||
print(TypeInfoVar.regexp)
|
||||
test_empty = 'static const TypeInfo x86_base_cpu_type_info = {\n'+\
|
||||
'};\n';
|
||||
assert fullmatch(TypeInfoVar.regexp, test_empty)
|
||||
|
||||
test_simple = r'''
|
||||
static const TypeInfo x86_base_cpu_type_info = {
|
||||
.name = X86_CPU_TYPE_NAME("base"),
|
||||
.parent = TYPE_X86_CPU,
|
||||
.class_init = x86_cpu_base_class_init,
|
||||
};
|
||||
'''
|
||||
assert re.search(TypeInfoVar.regexp, test_simple, re.MULTILINE)
|
||||
|
||||
test_interfaces = r'''
|
||||
static const TypeInfo acpi_ged_info = {
|
||||
.name = TYPE_ACPI_GED,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(AcpiGedState),
|
||||
.instance_init = acpi_ged_initfn,
|
||||
.class_init = acpi_ged_class_init,
|
||||
.interfaces = (const InterfaceInfo[]) {
|
||||
{ TYPE_HOTPLUG_HANDLER },
|
||||
{ TYPE_ACPI_DEVICE_IF },
|
||||
{ }
|
||||
}
|
||||
};
|
||||
'''
|
||||
assert re.search(TypeInfoVar.regexp, test_interfaces, re.MULTILINE)
|
||||
|
||||
test_comments = r'''
|
||||
static const TypeInfo palm_misc_gpio_info = {
|
||||
.name = TYPE_PALM_MISC_GPIO,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(PalmMiscGPIOState),
|
||||
.instance_init = palm_misc_gpio_init,
|
||||
/*
|
||||
* No class init required: device has no internal state so does not
|
||||
* need to set up reset or vmstate, and has no realize method.
|
||||
*/
|
||||
};
|
||||
'''
|
||||
assert re.search(TypeInfoVar.regexp, test_comments, re.MULTILINE)
|
||||
|
||||
test_comments = r'''
|
||||
static const TypeInfo tpm_crb_info = {
|
||||
.name = TYPE_TPM_CRB,
|
||||
/* could be TYPE_SYS_BUS_DEVICE (or LPC etc) */
|
||||
.parent = TYPE_DEVICE,
|
||||
.instance_size = sizeof(CRBState),
|
||||
.class_init = tpm_crb_class_init,
|
||||
.interfaces = (const InterfaceInfo[]) {
|
||||
{ TYPE_TPM_IF },
|
||||
{ }
|
||||
}
|
||||
};
|
||||
'''
|
||||
assert re.search(TypeInfoVar.regexp, test_comments, re.MULTILINE)
|
||||
|
||||
def test_struct_re():
|
||||
print('---')
|
||||
print(RE_STRUCT_TYPEDEF)
|
||||
assert re.search(RE_STRUCT_TYPEDEF, r'''
|
||||
typedef struct TCGState {
|
||||
AccelState parent_obj;
|
||||
|
||||
bool mttcg_enabled;
|
||||
unsigned long tb_size;
|
||||
} TCGState;
|
||||
''', re.MULTILINE)
|
||||
|
||||
assert re.search(RE_STRUCT_TYPEDEF, r'''
|
||||
typedef struct {
|
||||
ISADevice parent_obj;
|
||||
|
||||
QEMUSoundCard card;
|
||||
uint32_t freq;
|
||||
uint32_t port;
|
||||
int ticking[2];
|
||||
int enabled;
|
||||
int active;
|
||||
int bufpos;
|
||||
#ifdef DEBUG
|
||||
int64_t exp[2];
|
||||
#endif
|
||||
int16_t *mixbuf;
|
||||
uint64_t dexp[2];
|
||||
SWVoiceOut *voice;
|
||||
int left, pos, samples;
|
||||
FM_OPL *opl;
|
||||
PortioList port_list;
|
||||
} AdlibState;
|
||||
''', re.MULTILINE)
|
||||
|
||||
false_positive = r'''
|
||||
typedef struct dma_pagetable_entry {
|
||||
int32_t frame;
|
||||
int32_t owner;
|
||||
} A B C D E;
|
||||
struct foo {
|
||||
int x;
|
||||
} some_variable;
|
||||
'''
|
||||
assert not re.search(RE_STRUCT_TYPEDEF, false_positive, re.MULTILINE)
|
||||
|
||||
def test_initial_includes():
|
||||
print(InitialIncludes.regexp)
|
||||
c = '''
|
||||
#ifndef HW_FLASH_H
|
||||
#define HW_FLASH_H
|
||||
|
||||
/* NOR flash devices */
|
||||
|
||||
#include "qom/object.h"
|
||||
#include "exec/hwaddr.h"
|
||||
|
||||
/* pflash_cfi01.c */
|
||||
'''
|
||||
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')
|
||||
|
||||
c = '''#ifndef QEMU_VIRTIO_9P_H
|
||||
#define QEMU_VIRTIO_9P_H
|
||||
|
||||
#include "standard-headers/linux/virtio_9p.h"
|
||||
#include "hw/virtio/virtio.h"
|
||||
#include "9p.h"
|
||||
|
||||
|
||||
'''
|
||||
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')
|
||||
|
||||
c = '''#include "qom/object.h"
|
||||
/*
|
||||
* QEMU ES1370 emulation
|
||||
...
|
||||
*/
|
||||
|
||||
/* #define DEBUG_ES1370 */
|
||||
/* #define VERBOSE_ES1370 */
|
||||
#define SILENT_ES1370
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "hw/audio/model.h"
|
||||
#include "qemu/audio.h"
|
||||
#include "hw/pci/pci.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "qemu/module.h"
|
||||
#include "system/dma.h"
|
||||
|
||||
/* Missing stuff:
|
||||
SCTRL_P[12](END|ST)INC
|
||||
'''
|
||||
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 "system/dma.h"\n')
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
# Copyright (C) 2020 Red Hat Inc.
|
||||
#
|
||||
# Authors:
|
||||
# Eduardo Habkost <[email protected]>
|
||||
#
|
||||
# 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 *
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
DBG = logger.debug
|
||||
INFO = logger.info
|
||||
WARN = logger.warning
|
||||
|
||||
T = TypeVar('T')
|
||||
def opt_compare(a: T, b: T) -> bool:
|
||||
"""Compare two values, ignoring mismatches if one of them is None"""
|
||||
return (a is None) or (b is None) or (a == b)
|
||||
|
||||
def merge(a: T, b: T) -> T:
|
||||
"""Merge two values if they matched using opt_compare()"""
|
||||
assert opt_compare(a, b)
|
||||
if a is None:
|
||||
return b
|
||||
else:
|
||||
return a
|
||||
|
||||
def test_comp_merge():
|
||||
assert opt_compare(None, 1) == True
|
||||
assert opt_compare(2, None) == True
|
||||
assert opt_compare(1, 1) == True
|
||||
assert opt_compare(1, 2) == False
|
||||
|
||||
assert merge(None, None) is None
|
||||
assert merge(None, 10) == 10
|
||||
assert merge(10, None) == 10
|
||||
assert merge(10, 10) == 10
|
||||
|
||||
|
||||
LineNumber = NewType('LineNumber', int)
|
||||
ColumnNumber = NewType('ColumnNumber', int)
|
||||
class LineAndColumn(NamedTuple):
|
||||
line: int
|
||||
col: int
|
||||
|
||||
def __str__(self):
|
||||
return '%d:%d' % (self.line, self.col)
|
||||
|
||||
def line_col(s, position: int) -> LineAndColumn:
|
||||
"""Return line and column for a char position in string
|
||||
|
||||
Character position starts in 0, but lines and columns start in 1.
|
||||
"""
|
||||
before = s[:position]
|
||||
lines = before.split('\n')
|
||||
line = len(lines)
|
||||
col = len(lines[-1]) + 1
|
||||
return LineAndColumn(line, col)
|
||||
|
||||
def test_line_col():
|
||||
assert line_col('abc\ndefg\nhijkl', 0) == (1, 1)
|
||||
assert line_col('abc\ndefg\nhijkl', 2) == (1, 3)
|
||||
assert line_col('abc\ndefg\nhijkl', 3) == (1, 4)
|
||||
assert line_col('abc\ndefg\nhijkl', 4) == (2, 1)
|
||||
assert line_col('abc\ndefg\nhijkl', 10) == (3, 2)
|
||||
|
||||
def not_optional(arg: Optional[T]) -> T:
|
||||
assert arg is not None
|
||||
return arg
|
||||
|
||||
__all__ = ['not_optional', 'opt_compare', 'merge', 'line_col', 'LineAndColumn']
|
||||
Executable
+123
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env python3
|
||||
# QEMU library
|
||||
#
|
||||
# Copyright (C) 2020 Red Hat Inc.
|
||||
#
|
||||
# Authors:
|
||||
# Eduardo Habkost <[email protected]>
|
||||
#
|
||||
# This work is licensed under the terms of the GNU GPL, version 2. See
|
||||
# the COPYING file in the top-level directory.
|
||||
#
|
||||
import sys
|
||||
import argparse
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
from typing import *
|
||||
|
||||
from codeconverter.patching import FileInfo, match_class_dict, FileList
|
||||
import codeconverter.qom_macros
|
||||
from codeconverter.qom_type_info import TI_FIELDS, type_infos, TypeInfoVar
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
DBG = logger.debug
|
||||
INFO = logger.info
|
||||
WARN = logger.warning
|
||||
|
||||
def process_all_files(parser: argparse.ArgumentParser, args: argparse.Namespace) -> None:
|
||||
DBG("filenames: %r", args.filenames)
|
||||
|
||||
files = FileList()
|
||||
files.extend(FileInfo(files, fn, args.force) for fn in args.filenames)
|
||||
for f in files:
|
||||
DBG('opening %s', f.filename)
|
||||
f.load()
|
||||
|
||||
if args.table:
|
||||
fields = ['filename', 'variable_name'] + TI_FIELDS
|
||||
print('\t'.join(fields))
|
||||
for f in files:
|
||||
for t in f.matches_of_type(TypeInfoVar):
|
||||
assert isinstance(t, TypeInfoVar)
|
||||
values = [f.filename, t.name] + \
|
||||
[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)
|
||||
values = [v.replace('\n', ' ').replace('"', '') for v in values]
|
||||
print('\t'.join(values))
|
||||
return
|
||||
|
||||
match_classes = match_class_dict()
|
||||
if not args.patterns:
|
||||
parser.error("--pattern is required")
|
||||
|
||||
classes = [p for arg in args.patterns
|
||||
for p in re.split(r'[\s,]', arg)
|
||||
if p.strip()]
|
||||
for c in 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)
|
||||
files.patch_content(max_passes=args.passes, class_names=classes)
|
||||
|
||||
for f in files:
|
||||
#alltypes.extend(f.type_infos)
|
||||
#full_types.extend(f.full_types())
|
||||
|
||||
if not args.dry_run:
|
||||
if args.inplace:
|
||||
f.patch_inplace()
|
||||
if args.diff:
|
||||
f.show_diff()
|
||||
if not args.diff and not args.inplace:
|
||||
f.write_to_file(sys.stdout)
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
PATTERN_HELP = ('\n'.join(" %s: %s" % (n, str(c.__doc__).strip())
|
||||
for (n,c) in sorted(match_class_dict().items())
|
||||
if c.has_replacement_rule()))
|
||||
|
||||
def main() -> None:
|
||||
p = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
p.add_argument('filenames', nargs='+')
|
||||
p.add_argument('--passes', type=int, default=1,
|
||||
help="Number of passes (0 means unlimited)")
|
||||
p.add_argument('--pattern', required=True, action='append',
|
||||
default=[], dest='patterns',
|
||||
help="Pattern to scan for")
|
||||
p.add_argument('--inplace', '-i', action='store_true',
|
||||
help="Patch file in place")
|
||||
p.add_argument('--dry-run', action='store_true',
|
||||
help="Don't patch files or print patching results")
|
||||
p.add_argument('--force', '-f', action='store_true',
|
||||
help="Perform changes even if not completely safe")
|
||||
p.add_argument('--diff', action='store_true',
|
||||
help="Print diff output on stdout")
|
||||
p.add_argument('--debug', '-d', action='store_true',
|
||||
help="Enable debugging")
|
||||
p.add_argument('--verbose', '-v', action='store_true',
|
||||
help="Verbose logging on stderr")
|
||||
p.add_argument('--table', action='store_true',
|
||||
help="Print CSV table of type information")
|
||||
p.add_argument_group("Valid pattern names",
|
||||
PATTERN_HELP)
|
||||
args = p.parse_args()
|
||||
|
||||
loglevel = (logging.DEBUG if args.debug
|
||||
else logging.INFO if args.verbose
|
||||
else logging.WARN)
|
||||
logging.basicConfig(format='%(levelname)s: %(message)s', level=loglevel)
|
||||
DBG("args: %r", args)
|
||||
process_all_files(p, args)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in new issue
Block a user