summaryrefslogtreecommitdiff
path: root/tools/classify_tu.py
blob: 915b5dd1a424bc8f171da86141c6f20aea9ac635 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
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()