tp/tools/classify_tu.py

128 lines
4.8 KiB
Python

import clang.cindex
import re
import shutil
from logger import LOG
# Regular expression to match #include "asm/..."
asm_pattern = re.compile(r'#\s*include\s*"asm/.*"')
# Include directories passed to clang
include_dirs = ['include','include/dolphin','src']
class FunctionChecker:
def __init__(self, file_path):
self.file_path = file_path
self.backup_file_path = None
self.index = clang.cindex.Index.create()
self.include_options = []
self.not_attempted = []
self.new_not_attempted = []
self.attempted = []
self.completed = []
for dir in include_dirs:
self.include_options += ['-I', dir]
def replace_static_asm(self):
pattern = r'\bstatic\s+asm\b'
self.backup_file_path = self.file_path + '.bak'
# Make a backup copy of the file
shutil.copy2(self.file_path, self.backup_file_path)
with open(self.file_path, 'r') as file:
lines = file.readlines()
replaced_lines = [re.sub(pattern, 'asm', line) for line in lines]
with open(self.file_path, 'w') as file:
file.writelines(replaced_lines)
def restore_backup(self):
shutil.move(self.backup_file_path, self.file_path)
def check_function(self,line):
# Check if there are any characters between { and }
if re.search(r'\{.+\}', line, re.DOTALL):
# Check if those characters are not '/* empty function */' or whitespaces or 'return True;' or 'return False;'
if not re.search(r'\{\s*(/\*\s*empty function\s*\*/)?\s*\}', line) and not re.search(r'\{\s*return (True|False)\s*;\s*\}', line, re.IGNORECASE):
return True
return False
def return_status(self) -> str:
unattempted = len(self.new_not_attempted)
attempted = len(self.attempted)
completed = len(self.completed)
if attempted > 0 or (unattempted > 0 and completed > 0):
return "in progress"
if unattempted > 0:
return "todo"
if completed > 0 or (completed == 0 and attempted == 0 and unattempted == 0):
return "done"
return "error"
def find_includes(self,node, nonmatching: bool):
if str(node.location.file) == self.file_path or str(node.location.file).endswith(".inc"):
# Check if the node is a function
if node.kind in (clang.cindex.CursorKind.FUNCTION_DECL,
clang.cindex.CursorKind.CXX_METHOD,
clang.cindex.CursorKind.CONSTRUCTOR,
clang.cindex.CursorKind.DESTRUCTOR,
clang.cindex.CursorKind.CONVERSION_FUNCTION):
# Get the function body
body = [token.spelling for token in node.get_tokens()]
body_str = ' '.join(body)
check_body_str = body_str.strip()
# Skip if the function body doesn't exist or is empty
if self.check_function(check_body_str):
LOG.debug(f"Checking function: {node.mangled_name}")
if asm_pattern.search(body_str):
if nonmatching:
self.new_not_attempted.append(node.mangled_name)
else:
self.not_attempted.append(node.mangled_name)
else:
if nonmatching and node.mangled_name not in self.not_attempted:
self.completed.append(node.mangled_name)
# Recurse for children of this node
for c in node.get_children():
self.find_includes(c, nonmatching)
def run(input_filename: str) -> str:
checker = FunctionChecker(input_filename)
try:
# tiny hack to get all functions to show up correctly in the AST
checker.replace_static_asm()
# first categorize unattempted functions
tu = checker.index.parse(checker.file_path, checker.include_options)
checker.find_includes(tu.cursor, False)
# enable nonmatching functions
checker.include_options += ['-DNONMATCHING']
# then categorize attempted/completed functions
tu = checker.index.parse(input_filename, checker.include_options)
checker.find_includes(tu.cursor, True)
checker.attempted = set(checker.not_attempted) - set(checker.new_not_attempted)
LOG.debug(f"Not Attempted: {len(set(checker.new_not_attempted))}")
LOG.debug(f"Attempted: {len(set(checker.attempted))}")
LOG.debug(f"Complete: {len(set(checker.completed))}")
LOG.debug(f"Total: {len(set(checker.new_not_attempted)) + len(set(checker.attempted)) + len(set(checker.completed))}")
return checker.return_status()
except Exception as e:
print("Exception:", e)
return e
finally:
checker.restore_backup()