-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmain.py
273 lines (228 loc) · 11.8 KB
/
main.py
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
import os
from glob import glob
import argparse
import time
import datetime
from multiprocessing import Pool
# local import
from ast import ast
import result
AST_EXT_FILE = ".ast"
CSV_EXT_FILE = ".csv"
CPP_EXT_FILE = ("*.cc", "*.cpp")
# TODO support sigterm and close all thread
def parse_args():
_arg_parser = argparse.ArgumentParser(description="Python clang analyser c++")
_arg_parser.add_argument('-q', '--quiet', default=False, action='store_true',
help='Silence all verbose print.')
group = _arg_parser.add_argument_group("Debug")
group.add_argument('-d', '--debug', default=False, action='store_true',
help='Enable debug')
group.add_argument('--show_missing_header_file', default=False, action='store_true',
help='Show when Clang cannot find header file.')
group.add_argument('--remove_token', default=False, action='store_true',
help='We need token to retrieve the algorithm or condition statistic.')
group.add_argument('--exclude_decl_from_PCH', default=False, action='store_true',
help='Exclude local declarations from PCH in translation unit when parsing with Clang. '
'No difference when debug or on performance execution if not using PCH file.')
group.add_argument('--show_metric_time', default=False, action='store_true',
help='Show metric time for algorithm. Quiet has no impact on this argument.')
group = _arg_parser.add_argument_group("Parallelism")
group.add_argument('--disable_threading', default=False, action='store_true',
help='Disable multi-process execution.')
group.add_argument('-i', '--nb_cpu', default=None, type=int,
help='Default value is max cpu. Specify the count cpu usage.')
group = _arg_parser.add_argument_group("Compilation")
group.add_argument('-I', '--include_clang',
help='Add include file or directory. Separate each with \';\'. '
'Include can be absolute or relative from root_directory.')
group.add_argument('--find_include', default=False, action='store_true',
help='If active, search include file in root_directory.')
group = _arg_parser.add_argument_group("Configuration")
group.add_argument('--root_directory', required=True,
help='Path of root directory. This path is use to filter files from '
'this directory only and set the working_path is not specified.')
group.add_argument('--working_path',
help='Specify path of file or directory. Need to exist into root_directory. '
'If not specified, working_path will be root_directory.')
group.add_argument('--exclude_path', default="",
help='Exclude path of analysing.')
group.add_argument('--white_path', default="",
help='Path of white list. If not empty, only file from this path will be include.')
group.add_argument('--graph_path', default="graph",
help='Specify path of graph generation.')
group.add_argument('--translation_unit_dir',
help='Specify path of translation unit directory where to save AST file generate by Clang. '
'The parser will use the saving file if exist. '
'To force update AST files, we clean all. Use argument --clean_ast. '
'This feature improve parsing speed. '
'If AST file not exist for a specific source, it will generate it.')
group.add_argument('--clean_ast', default=False, action='store_true',
help='Clean ast files from --translation_unit_dir before create it.')
group = _arg_parser.add_argument_group("Statistic")
group.add_argument('--generate_csv_stat', default=False, action='store_true',
help='Generate statistic of each function and method. Write output in csv file. '
'The statistic is the variable number and c++ token like [if, while, return, ...]. '
'See --csv_stat_name to change the output name file.')
group.add_argument('--csv', default="result" + CSV_EXT_FILE,
help='Output csv name file of statistic generation. Add extension %s if not set.' % CSV_EXT_FILE)
group.add_argument('--append_csv', default=False, action='store_true',
help='Append result on csv file. Useful when run multiple execution.')
group = _arg_parser.add_argument_group("UML")
group.add_argument('--generate_uml', default=False, action='store_true',
help='Generate UML of relation between class.')
group = _arg_parser.add_argument_group("Control Flow")
group.add_argument('--generate_control_flow', default=False, action='store_true',
help='Generate control flow from main function.')
group.add_argument('--generate_dominator', default=False, action='store_true',
help='Generate dominator and post-dominator graph from control flow.')
group.add_argument('--show_detailed_cfg', default=False, action='store_true',
help='Show detailed information in cfg graph.')
return _arg_parser
def validate_parser(_arg_parser):
_parser = _arg_parser.parse_args()
_parser.ast_file = []
# validate argument
# root directory
if not os.path.isdir(_parser.root_directory):
_arg_parser.print_help()
raise ValueError("--root_directory '%s' is not a valid directory" % _parser.root_directory)
# force to clean path, like remove last /
_root_dir = _parser.root_directory = os.path.abspath(_parser.root_directory)
# working path
if not _parser.working_path:
_parser.working_path = _root_dir
elif _root_dir in _parser.working_path:
if not os.path.exists(_parser.working_path):
_arg_parser.print_help()
raise ValueError("--working_path '%s' is not a valid directory or file." % _parser.working_path)
else:
new_path = os.path.abspath(os.path.join(_root_dir, _parser.working_path))
if os.path.exists(new_path):
_parser.working_path = new_path
else:
val = _parser.working_path, _root_dir
raise ValueError("--working_path '%s' not exist in root_directory '%s'." % val)
# Functionality
if _parser.generate_csv_stat:
if _parser.csv[-len(CSV_EXT_FILE):] != CSV_EXT_FILE:
_parser.csv += CSV_EXT_FILE
# validate if can open it
try:
open(_parser.csv, mode="a").close()
except:
raise ValueError("--csv %s is wrong or maybe you don't access to write a file." % _parser.csv)
# translation unit path saving files
if _parser.translation_unit_dir:
if not os.path.isdir(_parser.translation_unit_dir):
os.makedirs(_parser.translation_unit_dir)
# use ast file if translation_unit_dir not contain ast file
_parser.ast_file = glob(os.path.join(_parser.translation_unit_dir, "*" + AST_EXT_FILE))
else:
# cannot clean ast file if no directory
_parser.clean_ast = False
# include files for clang
if _parser.include_clang:
_lst_include = [os.path.join(_root_dir, include) for include in _parser.include_clang.split(";")]
else:
_lst_include = []
# validate if all path exist
for include in _lst_include:
if not os.path.exists(include):
print("Warning, include '%s' not exist." % include)
# find include directory
if _parser.find_include:
key = "include"
lst_find = [os.path.join(dir_path, key) for dir_path, dir_names, _ in os.walk(_root_dir) if key in dir_names]
_lst_include.extend(lst_find)
# reorganize include argument for clang
_parser.lst_include_clang = ["-I" + include for include in _lst_include]
if _parser.debug and not _parser.quiet:
# don't show files from parser
print("Debug argument list - %s" % _parser)
# fill files to parse
if os.path.isdir(_parser.working_path):
_lst_file = []
for dir_path, _, _ in os.walk(_parser.working_path):
for a in CPP_EXT_FILE:
_lst_file.extend(glob(os.path.join(dir_path, a)))
elif os.path.isfile(_parser.working_path):
_lst_file = [_parser.working_path]
else:
raise Exception("Argument --working_path '%s' is not a file or a dir." % _parser.working_path)
# remove root_directory from file, for better visibility and filter with exclude dir
_pos_cut_path = len(_root_dir) + 1
lst_file = []
# TODO create absolute path with exclude path
lst_exclude_path = _parser.exclude_path.split(";") if _parser.exclude_path else []
lst_white_path = _parser.white_path.split(";") if _parser.white_path else []
for _file in _lst_file:
# ignore path
is_ignore = False
for exclude_path in lst_exclude_path:
if exclude_path in _file:
is_ignore = True
break
if is_ignore:
continue
# white path
for white_path in lst_white_path:
if white_path in _file:
break
else:
if lst_white_path:
continue
lst_file.append(_file[_pos_cut_path:])
_parser.files = lst_file
return _parser
class ClangParserGenerator:
def __init__(self, _lst_file):
self._lst_file = _lst_file
def generator(self):
for _file in self._lst_file:
yield ast.clang_parser(_file)
def start_clang_process(_parser):
if _parser.clean_ast:
for _file in _parser.ast_file:
os.remove(_file)
_parser.ast_file = []
lst_clang_parser_arg = [(f, _parser.root_directory, _parser.lst_include_clang, _parser) for f in _parser.files]
if _parser.disable_threading:
it = ClangParserGenerator(lst_clang_parser_arg).generator()
else:
it = Pool(processes=_parser.nb_cpu).imap_unordered(ast.clang_parser, lst_clang_parser_arg)
# execute and return result for all file
if _parser.quiet:
lst_result = [clang_obj_result for clang_obj_result in it]
else:
i_file = 0
count_file = len(lst_clang_parser_arg)
lst_result = []
for clang_obj_result in it:
i_file += 1
if not _parser.quiet:
print("(%s/%s) File %s" % (i_file, count_file, clang_obj_result[0]))
lst_result.append(clang_obj_result)
# compute ast completion
# clang_parser.class_completion(lst_result)
return lst_result
if __name__ == '__main__':
start_time = time.time()
start_clock = time.clock()
parser = validate_parser(parse_args())
lst_obj_ast = start_clang_process(parser)
if parser.generate_csv_stat:
result.generate_stat_csv.GenerateStatCsv(parser, lst_obj_ast).generate_stat()
if parser.generate_uml:
result.generate_uml.GenerateUml(parser, lst_obj_ast).generate_uml()
if parser.generate_control_flow:
result.generate_cfg.GenerateCfg(parser, lst_obj_ast).generate_cfg()
if parser.generate_dominator:
result.generate_dominator.GenerateDominator(parser, lst_obj_ast).generate_dominator()
result.generate_dominator.GenerateDominator(parser, lst_obj_ast, is_dominator=False).generate_dominator()
duration_time = datetime.timedelta(seconds=time.time() - start_time)
duration_clock = datetime.timedelta(seconds=time.clock() - start_clock)
if parser.show_metric_time or not parser.quiet:
print("Elapsed time was %s and elapsed clock was %s." % (duration_time, duration_clock))