Files
DebugInfo/scripts/dethunk/header_processor.py

341 lines
15 KiB
Python

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 (
args.should_erase_type(type_name)
or (
args.add_trivial_dynamic_initializer
and args.should_erase_type_dyninit(type_name)
)
)
):
# 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 args.should_ignore_forward_decl(decl):
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 args.should_ignore_generate_dynamic_initializer(full_name):
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)