refactor: new project structure.
This commit is contained in:
7
scripts/bootstrap/main.py
Normal file
7
scripts/bootstrap/main.py
Normal file
@@ -0,0 +1,7 @@
|
||||
# import WHAT
|
||||
|
||||
def main():
|
||||
pass
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
113
scripts/dethunk/header_preprocessor.py
Normal file
113
scripts/dethunk/header_preprocessor.py
Normal file
@@ -0,0 +1,113 @@
|
||||
import os
|
||||
|
||||
import util.cpp_language as CppUtil
|
||||
import util.string as StrUtil
|
||||
|
||||
# storage
|
||||
defined_classes = dict()
|
||||
defined_classes_path = dict()
|
||||
empty_class_all_names = set()
|
||||
|
||||
|
||||
class ClassDefine:
|
||||
rpath = str()
|
||||
is_template = bool()
|
||||
is_empty = bool()
|
||||
|
||||
def __init__(self, rpath: str, is_template: bool, is_empty: bool):
|
||||
self.rpath = rpath
|
||||
self.is_template = is_template
|
||||
self.is_empty = is_empty
|
||||
|
||||
|
||||
def add_class_record(path: str, namespace: str, class_name: str, is_template: bool, is_empty: bool):
|
||||
assert len(path) > 0 and len(class_name) > 0
|
||||
if namespace not in defined_classes:
|
||||
defined_classes[namespace] = {}
|
||||
assert class_name not in defined_classes[namespace], (
|
||||
f'path = {path}, ns = {namespace}, cl = {class_name}'
|
||||
)
|
||||
|
||||
rpath = path[path.find('src/') + 4 :]
|
||||
defined_classes[namespace][class_name] = ClassDefine(rpath, is_template, is_empty)
|
||||
|
||||
if rpath not in defined_classes_path:
|
||||
defined_classes_path[rpath] = {}
|
||||
if namespace not in defined_classes_path[rpath]:
|
||||
defined_classes_path[rpath][namespace] = set()
|
||||
defined_classes_path[rpath][namespace].add(class_name)
|
||||
|
||||
if is_empty:
|
||||
all_name = ''
|
||||
if namespace != '':
|
||||
all_name += namespace + '::'
|
||||
all_name += class_name
|
||||
empty_class_all_names.add(all_name)
|
||||
|
||||
|
||||
def query_class_record_strict(namespace_decl: str, class_decl: str) -> ClassDefine:
|
||||
assert namespace_decl in defined_classes, f'namespace = "{namespace_decl}" is not recorded.'
|
||||
assert class_decl in defined_classes[namespace_decl], (
|
||||
f'namespace = "{namespace_decl}", class = {class_decl} is not recorded.'
|
||||
)
|
||||
|
||||
return defined_classes[namespace_decl][class_decl]
|
||||
|
||||
|
||||
def process(path_to_file: str):
|
||||
assert os.path.isfile(path_to_file)
|
||||
|
||||
if path_to_file.endswith('_HeaderOutputPredefine.h'):
|
||||
return
|
||||
|
||||
with open(path_to_file, 'r', encoding='utf-8') as file:
|
||||
# states
|
||||
in_forward_declaration_list = False
|
||||
current_namespace = []
|
||||
current_classes = []
|
||||
|
||||
# tmp
|
||||
content = ''
|
||||
for line in file.readlines():
|
||||
stripped_line = line.strip()
|
||||
|
||||
# record forward declarations
|
||||
if stripped_line.startswith('// auto generated forward declare list'):
|
||||
in_forward_declaration_list = True
|
||||
if stripped_line.startswith('// clang-format on') and in_forward_declaration_list:
|
||||
in_forward_declaration_list = False
|
||||
|
||||
# record namespace & classes
|
||||
if not in_forward_declaration_list:
|
||||
founded_cl = CppUtil.find_class_definition(line)
|
||||
if founded_cl: # ignore anonymous classes.
|
||||
is_template = (
|
||||
content[content.rfind('\n', 0, -1) :].strip().startswith('template ')
|
||||
)
|
||||
is_empty = stripped_line.endswith('{};')
|
||||
nested_cl = ''
|
||||
if current_classes:
|
||||
for pair in current_classes:
|
||||
nested_cl += pair[1] + '::'
|
||||
add_class_record(
|
||||
path_to_file,
|
||||
'::'.join(current_namespace),
|
||||
nested_cl + founded_cl,
|
||||
is_template,
|
||||
is_empty,
|
||||
)
|
||||
class_keyword_pos, _ = StrUtil.find_mb(line, 'class ', 'struct ', 'union ')
|
||||
assert class_keyword_pos != -1, f"path = {path_to_file}, line = '{line}'"
|
||||
|
||||
if not is_empty:
|
||||
current_classes.append([class_keyword_pos, founded_cl])
|
||||
founded_ns = CppUtil.find_namespace_declaration(line)
|
||||
if founded_ns:
|
||||
current_namespace.append(founded_ns)
|
||||
if '// namespace' in stripped_line:
|
||||
current_namespace.pop()
|
||||
|
||||
if current_classes and line.startswith(' ' * (current_classes[-1][0]) + '};'):
|
||||
current_classes.pop()
|
||||
|
||||
content += line
|
||||
361
scripts/dethunk/header_processor.py
Normal file
361
scripts/dethunk/header_processor.py
Normal file
@@ -0,0 +1,361 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
import header_preprocessor as HeaderPreProcessor
|
||||
|
||||
from options import Options
|
||||
|
||||
import util.cpp_language as CppUtil
|
||||
import util.string as StrUtil
|
||||
|
||||
|
||||
def try_translate_forward_declaration(
|
||||
decl: str, typeset: list
|
||||
) -> HeaderPreProcessor.ClassDefine | None:
|
||||
find_decl = CppUtil.find_class_forward_declaration(decl)
|
||||
assert find_decl, f'decl = {decl}'
|
||||
|
||||
namespace = find_decl.namespace_decl
|
||||
clazz = find_decl.class_decl
|
||||
|
||||
if not CppUtil.is_full_type_required_for_typeset(namespace, clazz, typeset):
|
||||
return None
|
||||
|
||||
return HeaderPreProcessor.query_class_record_strict(namespace, clazz)
|
||||
|
||||
|
||||
def process(path_to_file: str, args: Options):
|
||||
assert os.path.isfile(path_to_file)
|
||||
|
||||
if path_to_file.endswith('_HeaderOutputPredefine.h'):
|
||||
return
|
||||
|
||||
RECORDED_THUNKS = []
|
||||
if args.remove_constructor_thunk:
|
||||
RECORDED_THUNKS.append('// constructor thunks')
|
||||
if args.remove_destructor_thunk:
|
||||
RECORDED_THUNKS.append('// destructor thunk')
|
||||
if args.remove_virtual_table_pointer_thunk:
|
||||
RECORDED_THUNKS.append('// vftables')
|
||||
if args.remove_virtual_function_thunk:
|
||||
RECORDED_THUNKS.append('// virtual function thunks')
|
||||
|
||||
with open(path_to_file, 'r', encoding='utf-8') as file:
|
||||
# states
|
||||
is_modified = False
|
||||
in_useless_thunk = False
|
||||
in_static_variable = False
|
||||
in_member_variable = False
|
||||
in_forward_declaration_list = False
|
||||
in_invalid_default_ctors = False
|
||||
has_typed_storage = False
|
||||
forward_declarations = []
|
||||
member_variable_types = []
|
||||
current_unions = []
|
||||
current_classes = []
|
||||
|
||||
ll_typed_regex = re.compile(r'TypedStorage<(\d+), (\d+), (.*?)> (\w+);')
|
||||
ll_untyped_regex = re.compile(r'UntypedStorage<(\d+), (\d+)> (\w+);')
|
||||
|
||||
# tmp
|
||||
content = ''
|
||||
for line in file.readlines():
|
||||
stripped_line = line.strip()
|
||||
|
||||
# restore static member variable:
|
||||
if args.restore_static_variable and '// static variables' in line:
|
||||
in_static_variable = True
|
||||
is_modified = True
|
||||
if in_static_variable and stripped_line.endswith(';'):
|
||||
if not stripped_line.startswith('MCAPI'): # declaration may not be on one line
|
||||
begin_pos = content.rfind('MCAPI')
|
||||
stripped_line = content[begin_pos:] + stripped_line
|
||||
content = content[:begin_pos]
|
||||
stripped_line = stripped_line.strip()
|
||||
|
||||
# remove parameter list (convert to static variable)
|
||||
call_spec_pos = stripped_line.rfind('()')
|
||||
assert call_spec_pos != -1
|
||||
|
||||
stripped_line = stripped_line[:call_spec_pos] + stripped_line[call_spec_pos + 2 :]
|
||||
|
||||
# remove reference
|
||||
refsym_pos = stripped_line.rfind('&') # T&
|
||||
tlpsym_pos = stripped_line.rfind('>') # ::std::add_lvalue_reference_t<T>
|
||||
assert refsym_pos != -1 or tlpsym_pos != -1, f'in {path_to_file}'
|
||||
if tlpsym_pos == -1 or refsym_pos > tlpsym_pos:
|
||||
stripped_line = stripped_line[:refsym_pos] + stripped_line[refsym_pos + 1 :]
|
||||
elif refsym_pos == -1 or tlpsym_pos > refsym_pos:
|
||||
# C-style arrays must have '[]' written after the variable name
|
||||
stripped_line = stripped_line[:tlpsym_pos] + '>' + stripped_line[tlpsym_pos:]
|
||||
stripped_line = stripped_line.replace(
|
||||
'::std::add_lvalue_reference_t<',
|
||||
'::std::remove_reference_t<::std::add_lvalue_reference_t<',
|
||||
)
|
||||
|
||||
if args.fix_msvc_c2734 and stripped_line.find('static ') == -1:
|
||||
stripped_line = 'extern ' + stripped_line
|
||||
|
||||
content += f'\t{stripped_line}\n'
|
||||
continue
|
||||
|
||||
# restore member variable:
|
||||
if args.restore_member_variable and stripped_line.startswith(
|
||||
'::ll::'
|
||||
): # union { ... };
|
||||
in_member_variable = True
|
||||
is_modified = True
|
||||
if in_member_variable and stripped_line.endswith(';'):
|
||||
if not stripped_line.startswith('::ll::'):
|
||||
begin_pos = content.rfind('::ll::')
|
||||
stripped_line = content[begin_pos:] + stripped_line
|
||||
content = content[:begin_pos]
|
||||
stripped_line = stripped_line.strip()
|
||||
|
||||
# ::ll::TypedStorage<Alignment, Size, T> mVar;
|
||||
if 'TypedStorage' in stripped_line:
|
||||
has_typed_storage = True
|
||||
matched = ll_typed_regex.search(StrUtil.flatten(stripped_line))
|
||||
assert matched and matched.lastindex == 4, (
|
||||
f'in {path_to_file}, line="{stripped_line}"'
|
||||
)
|
||||
|
||||
align = int(matched[1])
|
||||
size = int(matched[2])
|
||||
type_name = matched[3]
|
||||
var_name = matched[4]
|
||||
|
||||
if (
|
||||
not StrUtil.endswith_m(type_name, '&', '*')
|
||||
and args.fix_size_for_type_with_empty_template_class
|
||||
):
|
||||
for empty_class in HeaderPreProcessor.empty_class_all_names:
|
||||
if empty_class in type_name or (
|
||||
args.erase_extra_invalid_types
|
||||
and (
|
||||
'WeakRef<' in type_name # TODO: remove it.
|
||||
or '::entt::basic_registry<' in type_name
|
||||
or '::Bedrock::Application::ThreadOwner<' in type_name
|
||||
or '::OwnerPtr<::EntityContext>' in type_name
|
||||
or (
|
||||
args.add_trivial_dynamic_initializer
|
||||
and '::std::variant<' in type_name # TODO: ...
|
||||
)
|
||||
or (
|
||||
args.add_trivial_dynamic_initializer
|
||||
and type_name # unique_ptr with user deleter, TODO: ...
|
||||
== '::std::unique_ptr<::RakNet::RakPeerInterface, void (*)(::RakNet::RakPeerInterface*)>'
|
||||
)
|
||||
)
|
||||
):
|
||||
# print(f'erased: {type_name}')
|
||||
content += (
|
||||
f'\talignas({align}) std::byte {var_name}_TYPEERASED[{size}];\n'
|
||||
)
|
||||
in_member_variable = False
|
||||
break
|
||||
if not in_member_variable:
|
||||
continue
|
||||
|
||||
if args.add_trivial_dynamic_initializer:
|
||||
type_name = type_name.replace(' const', '')
|
||||
|
||||
member_variable_types.append(type_name)
|
||||
|
||||
security_check = ''
|
||||
if args.add_sizeof_alignof_static_assertions and not current_unions:
|
||||
security_check += f'\tstatic_assert(sizeof({var_name}) == {size});\n'
|
||||
# TODO: ensure alignment requirements.
|
||||
# security_check += (
|
||||
# f'\tstatic_assert(alignof(decltype({var_name})) == {align});\n'
|
||||
# )
|
||||
|
||||
if type_name.endswith(']'): # is c-style array
|
||||
array_defs = type_name[type_name.find('[') : type_name.rfind(']') + 1]
|
||||
type_name = type_name[: type_name.find('[')]
|
||||
var_name = var_name + array_defs
|
||||
|
||||
if type_name.endswith('&'): # is reference type
|
||||
type_name = type_name[:-1] + '*'
|
||||
|
||||
ptr_id_pos, ptr_id = StrUtil.find_m(type_name, '(*)', '::*)')
|
||||
if -1 != ptr_id_pos and not CppUtil.find_template_name(
|
||||
type_name, ptr_id, disable_regex_word_bound=True
|
||||
): # is c-style function ptr or member ptr
|
||||
# it's a trick, make sure `ptr` endswith '*)'
|
||||
ptr_id_len = len(ptr_id) - 1
|
||||
type_name = (
|
||||
type_name[: ptr_id_pos + ptr_id_len]
|
||||
+ var_name
|
||||
+ type_name[ptr_id_pos + ptr_id_len :]
|
||||
)
|
||||
var_name = ''
|
||||
|
||||
content += f'\t{type_name} {var_name};\n{security_check}'
|
||||
|
||||
in_member_variable = False
|
||||
continue
|
||||
|
||||
# ::ll::UntypedStorage<Alignment, Size> mVar;
|
||||
if 'UntypedStorage' in stripped_line:
|
||||
matched = ll_untyped_regex.search(StrUtil.flatten(stripped_line))
|
||||
assert matched and matched.lastindex == 3, (
|
||||
f'in {path_to_file}, line="{stripped_line}"'
|
||||
)
|
||||
|
||||
align = matched[1]
|
||||
size = matched[2]
|
||||
var_name = matched[3]
|
||||
|
||||
content += f'\talignas({align}) std::byte {var_name}[{size}];\n'
|
||||
|
||||
in_member_variable = False
|
||||
continue
|
||||
|
||||
assert False, 'unreachable'
|
||||
|
||||
union_keyword_pos = line.find('union ')
|
||||
if not in_forward_declaration_list and union_keyword_pos != -1:
|
||||
current_unions.append(union_keyword_pos)
|
||||
if current_unions and line.startswith(' ' * current_unions[-1] + '};'):
|
||||
current_unions.pop()
|
||||
|
||||
if not in_forward_declaration_list:
|
||||
founded_cl = CppUtil.find_class_definition(line)
|
||||
if founded_cl:
|
||||
class_keyword_pos, _ = StrUtil.find_mb(line, 'class ', 'struct ', 'union ')
|
||||
assert class_keyword_pos != -1, f"path = {path_to_file}, line = '{line}'"
|
||||
|
||||
if not stripped_line.endswith('{};'):
|
||||
current_classes.append([class_keyword_pos, founded_cl])
|
||||
|
||||
if current_classes and line.startswith(' ' * (current_classes[-1][0]) + '};'):
|
||||
current_classes.pop()
|
||||
|
||||
# fix forward declarations
|
||||
if stripped_line.startswith('// auto generated forward declare list'):
|
||||
in_forward_declaration_list = True
|
||||
if in_forward_declaration_list and StrUtil.startswith_m(
|
||||
stripped_line, 'class ', 'struct ', 'union ', 'namespace '
|
||||
):
|
||||
forward_declarations.append(stripped_line)
|
||||
if stripped_line.startswith('// clang-format on') and in_forward_declaration_list:
|
||||
in_forward_declaration_list = False
|
||||
|
||||
# remove useless thunks.
|
||||
if '// NOLINTEND' in line and (in_useless_thunk or in_static_variable):
|
||||
in_useless_thunk = False
|
||||
in_static_variable = False
|
||||
continue # don't add this line.
|
||||
for token in RECORDED_THUNKS:
|
||||
if token in line:
|
||||
in_useless_thunk = True
|
||||
is_modified = True
|
||||
|
||||
# remove previous access specifier.
|
||||
content = content[: content.rfind('public:')]
|
||||
content = content[: content.rfind('\n')] # for nested classes
|
||||
|
||||
if args.add_trivial_dynamic_initializer:
|
||||
if in_invalid_default_ctors:
|
||||
if 'operator=' in line or 'const&' in line or '()' in line:
|
||||
content += '\t// ' + stripped_line + '\n'
|
||||
continue
|
||||
else:
|
||||
in_invalid_default_ctors = False
|
||||
if '// prevent constructor by default' in line:
|
||||
in_invalid_default_ctors = True
|
||||
|
||||
if current_classes and stripped_line.endswith(';'):
|
||||
begin_pos = None
|
||||
if not stripped_line.startswith('MCAPI'):
|
||||
begin_pos = content.rfind('MCAPI')
|
||||
check_pos = content.rfind(';')
|
||||
if begin_pos > check_pos:
|
||||
stripped_line = content[begin_pos:] + stripped_line
|
||||
stripped_line = StrUtil.flatten(stripped_line)
|
||||
class_name = current_classes[-1][1]
|
||||
if (
|
||||
f' {class_name}()' not in stripped_line
|
||||
and f' {class_name}(' in stripped_line
|
||||
):
|
||||
if begin_pos:
|
||||
content = content[:begin_pos]
|
||||
content += '\t// ' + stripped_line + '\n'
|
||||
continue
|
||||
|
||||
if not in_useless_thunk:
|
||||
content += line
|
||||
if args.fix_includes_for_member_variables and has_typed_storage:
|
||||
for decl in forward_declarations:
|
||||
if args.add_trivial_dynamic_initializer and 'CommandParameterData' in decl:
|
||||
# see CommandRegistry.h, to resolve circular reference.
|
||||
continue
|
||||
class_define = try_translate_forward_declaration(decl, member_variable_types)
|
||||
if class_define:
|
||||
is_modified = True
|
||||
content = content.replace(decl, f'#include "{class_define.rpath}"\n')
|
||||
continue
|
||||
if args.add_trivial_dynamic_initializer:
|
||||
path = path_to_file[path_to_file.find('src/') + 4 :]
|
||||
if path in HeaderPreProcessor.defined_classes_path:
|
||||
for ns, classes in HeaderPreProcessor.defined_classes_path[path].items():
|
||||
for cl in classes:
|
||||
if HeaderPreProcessor.defined_classes[ns][cl].is_template:
|
||||
continue
|
||||
var_name = 'dummy__'
|
||||
if ns:
|
||||
var_name += ns.replace('::', '_') + '_'
|
||||
var_name += cl.replace('::', '_')
|
||||
full_name = ns
|
||||
if ns:
|
||||
full_name += '::'
|
||||
full_name += cl
|
||||
if (
|
||||
full_name
|
||||
in [
|
||||
'Json::ValueIteratorBase', # std::vector<T>::iterator
|
||||
'Json::ValueConstIterator',
|
||||
'Json::ValueIterator',
|
||||
'MemoryStream', # std::istream
|
||||
'Core::FileStream', # ::std::iostream
|
||||
'CommandRegistry::Overload', # circular references
|
||||
'Bedrock::UniqueOwnerPointer', # bug?
|
||||
'MovementDataExtractionUtility::StorageStorage::StorageTupleT', # shit template
|
||||
]
|
||||
):
|
||||
continue
|
||||
is_modified = True
|
||||
content += f'\ninline {full_name} {var_name};'
|
||||
# rm pure virtual funcs.
|
||||
is_modified = True
|
||||
content = (
|
||||
content.replace(' = 0;', ';')
|
||||
.replace('::std::reference_wrapper<', '::x_std::reference_wrapper<')
|
||||
.replace('::Bedrock::NotNullNonOwnerPtr<', '::x_Bedrock::NotNullNonOwnerPtr<')
|
||||
.replace('::gsl::not_null<', '::x_gsl::not_null<')
|
||||
.replace('::leveldb::EnvWrapper', '::x_leveldb::EnvWrapper')
|
||||
)
|
||||
if is_modified:
|
||||
with open(path_to_file, 'w', encoding='utf-8') as wfile:
|
||||
wfile.write(content)
|
||||
|
||||
|
||||
def iterate(args: Options):
|
||||
assert os.path.isdir(args.base_dir)
|
||||
|
||||
def _traverse_header(path_to_dir: str, fun):
|
||||
for root, dirs, files in os.walk(path_to_dir):
|
||||
for file in files:
|
||||
if CppUtil.is_header_file(file):
|
||||
path = os.path.join(root, file)
|
||||
fun(path)
|
||||
|
||||
def _preprocess(path_to_file):
|
||||
HeaderPreProcessor.process(path_to_file)
|
||||
|
||||
def _process(path_to_file):
|
||||
process(path_to_file, args)
|
||||
|
||||
_traverse_header(args.base_dir, _preprocess)
|
||||
_traverse_header(args.base_dir, _process)
|
||||
31
scripts/dethunk/main.py
Normal file
31
scripts/dethunk/main.py
Normal file
@@ -0,0 +1,31 @@
|
||||
import argparse
|
||||
|
||||
import header_processor as HeaderProcessor
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser('dethunk')
|
||||
|
||||
parser.add_argument('path', help='Path to header project.')
|
||||
|
||||
parser.add_argument('--remove-constructor-thunk', action='store_true')
|
||||
parser.add_argument('--remove-destructor-thunk', action='store_true')
|
||||
parser.add_argument('--remove-virtual-table-pointer-thunk', action='store_true')
|
||||
parser.add_argument('--remove-virtual-function-thunk', action='store_true')
|
||||
parser.add_argument('--restore-static-variable', action='store_true')
|
||||
parser.add_argument('--restore-member-variable', action='store_true')
|
||||
|
||||
parser.add_argument('--all', action='store_true', help='Apply all remove/restore options.')
|
||||
|
||||
parser.add_argument('--preset-extract-names', action='store_true')
|
||||
parser.add_argument('--preset-extract-types', action='store_true')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
HeaderProcessor.iterate(HeaderProcessor.Options(args))
|
||||
|
||||
print('done.')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
96
scripts/dethunk/options.py
Normal file
96
scripts/dethunk/options.py
Normal file
@@ -0,0 +1,96 @@
|
||||
"""
|
||||
Runtime Options
|
||||
"""
|
||||
|
||||
|
||||
class Options:
|
||||
base_dir = str()
|
||||
|
||||
# functions
|
||||
remove_constructor_thunk = bool()
|
||||
remove_destructor_thunk = bool()
|
||||
remove_virtual_function_thunk = bool()
|
||||
|
||||
# variables
|
||||
remove_virtual_table_pointer_thunk = bool()
|
||||
restore_static_variable = bool()
|
||||
restore_member_variable = bool()
|
||||
|
||||
# others (for class member variable)
|
||||
|
||||
# * only takes effect for TypedStorage, since the TypedStorage wrapper makes the full type unnecessary.
|
||||
fix_includes_for_member_variables = True
|
||||
# * template definitions cannot be generated for headergen and may be wrong. class sizes may be wrong if
|
||||
# * empty templates are used in TypedStorage.
|
||||
# * this option will erase the type of the empty template (convert to uchar[size]).
|
||||
fix_size_for_type_with_empty_template_class = True
|
||||
# * this option will and add sizeof & alignof static assertions to members. (only takes effect for TypedStorage)
|
||||
add_sizeof_alignof_static_assertions = True
|
||||
# * some types of template definitions are not accurate.
|
||||
erase_extra_invalid_types = True
|
||||
# * try to initialize each class dynamically to ensure that the generated debug information is complete.
|
||||
# * this will ENSURE that all classes have a default constructor.
|
||||
add_trivial_dynamic_initializer = True
|
||||
|
||||
# others (for static class member viriable)
|
||||
|
||||
# * in msvc, 'const' object must be initialized if not 'extern' (c2734)
|
||||
# * see also https://reviews.llvm.org/D45978
|
||||
fix_msvc_c2734 = True
|
||||
|
||||
def __init__(self, args):
|
||||
self.base_dir = args.path
|
||||
self.remove_constructor_thunk = args.remove_constructor_thunk
|
||||
self.remove_destructor_thunk = args.remove_destructor_thunk
|
||||
self.remove_virtual_function_thunk = args.remove_virtual_function_thunk
|
||||
self.remove_virtual_table_pointer_thunk = args.remove_virtual_table_pointer_thunk
|
||||
self.restore_static_variable = args.restore_static_variable
|
||||
self.restore_member_variable = args.restore_member_variable
|
||||
|
||||
if args.preset_extract_names:
|
||||
self.apply_preset_extract_names()
|
||||
|
||||
if args.preset_extract_types:
|
||||
self.apply_preset_extract_types()
|
||||
|
||||
if args.all:
|
||||
self.set_all(True)
|
||||
|
||||
# others
|
||||
self.judge_other_options()
|
||||
|
||||
def set_function(self, opt: bool):
|
||||
self.remove_constructor_thunk = opt
|
||||
self.remove_destructor_thunk = opt
|
||||
self.remove_virtual_function_thunk = opt
|
||||
|
||||
def set_variable(self, opt: bool):
|
||||
self.remove_virtual_table_pointer_thunk = opt
|
||||
self.restore_static_variable = opt
|
||||
self.restore_member_variable = opt
|
||||
|
||||
def set_all(self, opt: bool):
|
||||
self.set_function(opt)
|
||||
self.set_variable(opt)
|
||||
|
||||
def apply_preset_extract_names(self):
|
||||
self.remove_constructor_thunk = True
|
||||
self.remove_destructor_thunk = True
|
||||
self.remove_virtual_function_thunk = True
|
||||
self.remove_virtual_table_pointer_thunk = True
|
||||
self.restore_static_variable = True
|
||||
|
||||
def apply_preset_extract_types(self):
|
||||
self.apply_preset_extract_names()
|
||||
self.restore_member_variable = True
|
||||
|
||||
def judge_other_options(self):
|
||||
if not self.restore_member_variable:
|
||||
self.fix_includes_for_member_variables = False
|
||||
self.fix_size_for_type_with_empty_template_class = False
|
||||
self.add_sizeof_alignof_static_assertions = False
|
||||
self.erase_extra_invalid_types = False
|
||||
self.add_trivial_dynamic_initializer = False
|
||||
|
||||
if not self.restore_static_variable:
|
||||
self.fix_msvc_c2734 = False
|
||||
202
scripts/dethunk/util/cpp_language.py
Normal file
202
scripts/dethunk/util/cpp_language.py
Normal file
@@ -0,0 +1,202 @@
|
||||
"""
|
||||
C++ Language Utility
|
||||
* some methods may not be designed to be universal.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
import util.string as StrUtil
|
||||
|
||||
|
||||
def is_header_file(path: str):
|
||||
return path.endswith('.h') or path.endswith('.hpp')
|
||||
|
||||
|
||||
def find_class_definition(line: str) -> str | None:
|
||||
# KEYWORD A (no quotation mark)
|
||||
# KEYWORD A :
|
||||
# KEYWORD A {
|
||||
# KEYWORD A { ... }; (in single line)
|
||||
|
||||
keyword_pos, keyword = StrUtil.find_mb(line, 'enum class ', 'class ', 'struct ', 'union ')
|
||||
if keyword_pos == -1 or keyword == 'enum class ':
|
||||
return None
|
||||
|
||||
keyword_size = len(keyword) - 1 # not class defs
|
||||
|
||||
left_brace_pos = line.find('{')
|
||||
semicolon_pos = line.find(';')
|
||||
if semicolon_pos != -1 and (left_brace_pos == -1 or semicolon_pos < left_brace_pos):
|
||||
return None # is forward decl
|
||||
|
||||
end_pos = len(line)
|
||||
colon_pos = line.find(':')
|
||||
l_angle_bracket_pos = line.find('<')
|
||||
if left_brace_pos != -1:
|
||||
end_pos = min(end_pos, left_brace_pos)
|
||||
if colon_pos != -1:
|
||||
end_pos = min(end_pos, colon_pos)
|
||||
if l_angle_bracket_pos != -1 and l_angle_bracket_pos < colon_pos:
|
||||
return None # template specialization (is not supported)
|
||||
|
||||
result = line[keyword_pos + keyword_size : end_pos].strip()
|
||||
if '>' in result:
|
||||
return None
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class ForwardDeclaration:
|
||||
namespace_decl = str()
|
||||
class_decl = str()
|
||||
|
||||
def __init__(self, namespace_decl: str, class_decl: str):
|
||||
self.namespace_decl = namespace_decl
|
||||
self.class_decl = class_decl
|
||||
|
||||
|
||||
def find_class_forward_declaration(line: str) -> ForwardDeclaration | None:
|
||||
# class A;
|
||||
# namespace B { class A; }
|
||||
|
||||
namespace_decl = ''
|
||||
class_decl = ''
|
||||
|
||||
namespace_pos = line.find('namespace')
|
||||
left_brace_pos = line.find('{')
|
||||
if namespace_pos != -1 and left_brace_pos != -1:
|
||||
namespace_decl = line[namespace_pos + len('namespace') : left_brace_pos].strip()
|
||||
|
||||
keyword_pos, keyword = StrUtil.find_mb(line, 'class ', 'struct ', 'union ')
|
||||
if keyword_pos == -1:
|
||||
return None
|
||||
|
||||
keyword_size = len(keyword) - 1 # not class defs
|
||||
|
||||
semicolon_pos = line.find(';')
|
||||
if semicolon_pos == -1:
|
||||
return None
|
||||
|
||||
class_decl = line[keyword_pos + keyword_size : semicolon_pos].strip()
|
||||
return ForwardDeclaration(namespace_decl, class_decl)
|
||||
|
||||
|
||||
def find_namespace_declaration(line: str) -> str | None:
|
||||
namespace_pos = line.find('namespace')
|
||||
left_brace_pos = line.find('{')
|
||||
if namespace_pos == -1 or left_brace_pos == -1:
|
||||
return None
|
||||
|
||||
return line[namespace_pos + len('namespace') : left_brace_pos].strip()
|
||||
|
||||
|
||||
def find_template_name(full: str, what: str, disable_regex_word_bound: bool = False):
|
||||
def _impl(endpos: int):
|
||||
while True:
|
||||
r_angle_bracket_pos = full.rfind('>', 0, endpos)
|
||||
l_angle_bracket_pos = full.rfind('<', 0, endpos)
|
||||
if l_angle_bracket_pos == -1:
|
||||
return None
|
||||
if r_angle_bracket_pos > l_angle_bracket_pos:
|
||||
endpos = l_angle_bracket_pos
|
||||
continue
|
||||
ret = full[:l_angle_bracket_pos]
|
||||
matched_non_name = list(re.finditer(r'[^a-zA-Z_]', ret))
|
||||
if len(matched_non_name) > 0:
|
||||
ret = ret[matched_non_name[-1].start() + 1 :]
|
||||
assert len(ret) > 0
|
||||
return ret
|
||||
|
||||
if not disable_regex_word_bound:
|
||||
for matched in re.finditer(rf'\b{re.escape(what)}\b', full):
|
||||
endpos = matched.start()
|
||||
result = _impl(endpos)
|
||||
if result:
|
||||
return result
|
||||
else:
|
||||
endpos = full.find(what)
|
||||
if endpos == -1:
|
||||
return None
|
||||
return _impl(endpos)
|
||||
|
||||
|
||||
def is_full_type_required(namespace_decl: str, class_decl: str, type_decl: str):
|
||||
"""
|
||||
Determine whether `class_decl` requires a full type for `type_decl`.
|
||||
|
||||
TODO: namespace_decl (currently UNUSED).
|
||||
"""
|
||||
|
||||
# Y: T
|
||||
# Y: std::optional<T>
|
||||
# Y: std::variant<T>
|
||||
# Y: std::array<T, _>
|
||||
# Y: std::pair<T, T>
|
||||
# Y: std::unordered_set<T>
|
||||
# Y: std::unordered_map<T, _>
|
||||
# Y: std::deque<T> // under msstl only
|
||||
# Y: std::queue<T> // under msstl only
|
||||
# N: T&
|
||||
# N: T*
|
||||
# N: std::map<T, T>
|
||||
# N: std::shared_ptr<T>
|
||||
# N: std::unique_ptr<T>
|
||||
# N: std::weak_ptr<T>
|
||||
# N: std::vector<T>
|
||||
# N: std::set<T>
|
||||
# N: std::unordered_map<_, T>
|
||||
# N: std::function<T(T)>
|
||||
|
||||
def is_subtk_ends_with(full: str, tk: str, whats: list):
|
||||
founded = False
|
||||
for matched in re.finditer(rf'\b{re.escape(tk)}\b', full):
|
||||
founded = True
|
||||
if len(full) > matched.end():
|
||||
for what in whats:
|
||||
if full[matched.end() : matched.end() + len(what)] == what:
|
||||
return founded, True
|
||||
return founded, False
|
||||
|
||||
# is reference or pointer type?
|
||||
founded, is_endswith = is_subtk_ends_with(
|
||||
type_decl, class_decl, ['&', '*', ' const&', ' const*']
|
||||
)
|
||||
|
||||
if not founded or is_endswith:
|
||||
return False
|
||||
|
||||
# is template params?
|
||||
template_name = find_template_name(type_decl, class_decl)
|
||||
if not template_name:
|
||||
return True # moreover, is not a template parameter
|
||||
|
||||
if template_name in [ # forward declarations are allowed.
|
||||
'map',
|
||||
'shared_ptr',
|
||||
'unique_ptr',
|
||||
'weak_ptr',
|
||||
# 'vector',
|
||||
'queue',
|
||||
'set',
|
||||
'function',
|
||||
]:
|
||||
return False
|
||||
elif template_name in [ # full type is required.
|
||||
'optional',
|
||||
'variant',
|
||||
'array',
|
||||
'pair',
|
||||
'unordered_set',
|
||||
'deque',
|
||||
'queue',
|
||||
]:
|
||||
return True
|
||||
else:
|
||||
return True # by default, we assume that a full type is required.
|
||||
|
||||
|
||||
def is_full_type_required_for_typeset(namespace_decl: str, class_decl: str, typeset: list):
|
||||
for type_decl in typeset:
|
||||
if is_full_type_required(namespace_decl, class_decl, type_decl):
|
||||
return True
|
||||
return False
|
||||
61
scripts/dethunk/util/string.py
Normal file
61
scripts/dethunk/util/string.py
Normal file
@@ -0,0 +1,61 @@
|
||||
"""
|
||||
String Utiltiy
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
|
||||
def startswith_m(con: str, *args) -> bool:
|
||||
for arg in args:
|
||||
if con.startswith(arg):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def endswith_m(con: str, *args) -> bool:
|
||||
for arg in args:
|
||||
if con.endswith(arg):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def find_mb(con: str, *args) -> int: # bounded
|
||||
r_pos = -1
|
||||
r_arg = None
|
||||
for arg in args:
|
||||
matched = re.search(rf'\b{re.escape(arg)}\b', con)
|
||||
if not matched:
|
||||
continue
|
||||
pos = matched.start()
|
||||
if pos != -1 and (r_pos == -1 or pos < r_pos):
|
||||
r_pos = pos
|
||||
r_arg = arg
|
||||
return r_pos, r_arg
|
||||
|
||||
|
||||
def find_m(con: str, *args) -> int:
|
||||
r_pos = -1
|
||||
r_arg = None
|
||||
for arg in args:
|
||||
pos = con.find(arg)
|
||||
if pos != -1 and (r_pos == -1 or pos < r_pos):
|
||||
r_pos = pos
|
||||
r_arg = arg
|
||||
return r_pos, r_arg
|
||||
|
||||
|
||||
def rfind_m(con: str, *args) -> int:
|
||||
r_pos = -1
|
||||
r_arg = None
|
||||
for arg in args:
|
||||
pos = con.rfind(arg)
|
||||
if pos > r_pos:
|
||||
r_pos = pos
|
||||
r_arg = arg
|
||||
return r_pos, r_arg
|
||||
|
||||
|
||||
def flatten(con: str):
|
||||
# typed storage can be very complex, so we need to preprocess it.
|
||||
# TODO: find a better way.
|
||||
return re.sub(r'\s+', ' ', con).replace('< ', '<').replace(':: ', '::')
|
||||
Reference in New Issue
Block a user