327 lines
11 KiB
Python
327 lines
11 KiB
Python
|
import argparse
|
||
|
import os
|
||
|
import sys
|
||
|
import json
|
||
|
import ccsyspath
|
||
|
import time
|
||
|
import clang.cindex
|
||
|
from clang.cindex import Cursor
|
||
|
from clang.cindex import CursorKind
|
||
|
from pathlib import Path
|
||
|
from clang.cindex import TranslationUnit
|
||
|
|
||
|
|
||
|
def get_args():
|
||
|
'''
|
||
|
Get and parse the parameters from the console
|
||
|
'''
|
||
|
parse = argparse.ArgumentParser(prog='python3.8 compare.py')
|
||
|
parse.add_argument('-l',
|
||
|
type=str,
|
||
|
required=True,
|
||
|
help='input lib file path')
|
||
|
parse.add_argument('-i',
|
||
|
type=str,
|
||
|
required=True,
|
||
|
help='input head file path')
|
||
|
parse.add_argument('-m', type=str, help='input macros file path')
|
||
|
parse.add_argument('-b', type=str, help='input blacklist file path')
|
||
|
parse.add_argument('-o', type=str, help='result file output path')
|
||
|
args = parse.parse_args()
|
||
|
paramers_value = vars(args)
|
||
|
global INPUT_HEAD_PATH
|
||
|
INPUT_HEAD_PATH = paramers_value['i']
|
||
|
global INPUT_LIB_PATH
|
||
|
INPUT_LIB_PATH = paramers_value['l']
|
||
|
global INPUT_MACROS_PATH
|
||
|
INPUT_MACROS_PATH = paramers_value['m']
|
||
|
INPUT_MACROS_PATH = '' if INPUT_MACROS_PATH == None else INPUT_MACROS_PATH
|
||
|
global INPUT_BLACK_PATH
|
||
|
INPUT_BLACK_PATH = paramers_value['b']
|
||
|
INPUT_BLACK_PATH = '' if INPUT_BLACK_PATH == None else INPUT_BLACK_PATH
|
||
|
global OUTPUT_RESULT_PATH
|
||
|
OUTPUT_RESULT_PATH = paramers_value['o']
|
||
|
OUTPUT_RESULT_PATH = '' if OUTPUT_RESULT_PATH == None else OUTPUT_RESULT_PATH
|
||
|
check_parameters()
|
||
|
|
||
|
|
||
|
def check_parameters():
|
||
|
'''
|
||
|
Check whether the obtained parameters are correct
|
||
|
'''
|
||
|
my_file = Path(INPUT_LIB_PATH)
|
||
|
if not my_file.is_file():
|
||
|
print('please input correct lib file path')
|
||
|
exit()
|
||
|
my_file = Path(INPUT_HEAD_PATH)
|
||
|
if not my_file.is_dir():
|
||
|
print('please input correct head file path')
|
||
|
exit()
|
||
|
global INPUT_BLACK_PATH
|
||
|
if not INPUT_BLACK_PATH == '':
|
||
|
my_file = Path(INPUT_BLACK_PATH)
|
||
|
if not my_file.is_file():
|
||
|
print('warring:input correct blacklist file path is error')
|
||
|
INPUT_BLACK_PATH = ''
|
||
|
global INPUT_MACROS_PATH
|
||
|
if not INPUT_MACROS_PATH == '':
|
||
|
my_file = Path(INPUT_MACROS_PATH)
|
||
|
if not my_file.is_file():
|
||
|
print('warring:input correct macros file path is error')
|
||
|
INPUT_MACROS_PATH = ''
|
||
|
global OUTPUT_RESULT_PATH
|
||
|
if not OUTPUT_RESULT_PATH == '':
|
||
|
my_file = Path(OUTPUT_RESULT_PATH)
|
||
|
if not my_file.is_dir():
|
||
|
print('warring:input correct output file path is error')
|
||
|
OUTPUT_RESULT_PATH = ''
|
||
|
|
||
|
|
||
|
def os_popen(stmt):
|
||
|
'''
|
||
|
Get the result of execution according to command parameter
|
||
|
'''
|
||
|
re = os.popen(stmt).readlines()
|
||
|
result = []
|
||
|
for i in range(0, len(re)):
|
||
|
res = re[i].strip('\n')
|
||
|
result.append(res)
|
||
|
return result
|
||
|
|
||
|
|
||
|
def get_info_from_file(file_path):
|
||
|
'''
|
||
|
Get the list of contents in the file based on the path parameter
|
||
|
'''
|
||
|
temp_file_list = []
|
||
|
with open(file_path) as file_object:
|
||
|
for line in file_object:
|
||
|
temp_file_list.append(line.rstrip())
|
||
|
return temp_file_list
|
||
|
|
||
|
|
||
|
def get_lib_strs():
|
||
|
'''
|
||
|
Get a list of library file symbols
|
||
|
'''
|
||
|
temp_lib_name_list = os_popen(
|
||
|
'nm -D ' + INPUT_LIB_PATH +
|
||
|
'| grep -Ev " U " | grep -Ev " W " | grep -Ev " D " | grep -Ev " V " | grep -Ev " w " | awk \'{print $3}\' | xargs c++filt'
|
||
|
)
|
||
|
if len(temp_lib_name_list) == 0:
|
||
|
print("canot find lib file error")
|
||
|
exit()
|
||
|
global LIB_FILE_NAME_LIST
|
||
|
for i in temp_lib_name_list:
|
||
|
LIB_FILE_NAME_LIST.append(sub_str_name(i))
|
||
|
LIB_FILE_NAME_LIST = list(set(LIB_FILE_NAME_LIST))
|
||
|
LIB_FILE_NAME_LIST.sort()
|
||
|
|
||
|
|
||
|
def get_permission_num(permissions):
|
||
|
'''
|
||
|
Get the number of arguments of c++ function
|
||
|
'''
|
||
|
if not permissions.find('()') == -1:
|
||
|
return 0
|
||
|
else:
|
||
|
count_premission = 1
|
||
|
current_sym_num = 0
|
||
|
for i in permissions:
|
||
|
if i == '<':
|
||
|
current_sym_num = current_sym_num + 1
|
||
|
if i == '>':
|
||
|
current_sym_num = current_sym_num - 1
|
||
|
if i == ',' and current_sym_num == 0:
|
||
|
count_premission = count_premission + 1
|
||
|
return count_premission
|
||
|
|
||
|
|
||
|
def sub_str_name(iteam):
|
||
|
'''
|
||
|
Handling redundant information in library file symbol table
|
||
|
'''
|
||
|
if not iteam.find('non-virtual thunk to ') == -1:
|
||
|
iteam = iteam.replace('non-virtual thunk to ', '')
|
||
|
if not iteam.find('virtual thunk to ') == -1:
|
||
|
iteam = iteam.replace('virtual thunk to ', '')
|
||
|
if iteam.find('(') == -1:
|
||
|
return iteam
|
||
|
else:
|
||
|
return iteam[:iteam.index('(')] + '@' + str(get_permission_num(iteam))
|
||
|
|
||
|
|
||
|
def get_head_strs():
|
||
|
'''
|
||
|
Get a list of header file symbols
|
||
|
'''
|
||
|
head_file_name_list = os_popen('find ' + INPUT_HEAD_PATH + ' -name "*.h"')
|
||
|
if len(head_file_name_list) == 0:
|
||
|
print('canot find head file error')
|
||
|
exit()
|
||
|
compile_args = ['-x', 'c++']
|
||
|
marcros_list = []
|
||
|
if not INPUT_MACROS_PATH == '':
|
||
|
temp_macros = get_info_from_file(INPUT_MACROS_PATH)
|
||
|
for i in temp_macros:
|
||
|
marcros_list.append('-D' + i)
|
||
|
for i in head_file_name_list:
|
||
|
global CURRENT_FILE_TYPE
|
||
|
CURRENT_FILE_TYPE = 0
|
||
|
index = clang.cindex.Index.create()
|
||
|
parser = index.parse(i, args=compile_args)
|
||
|
cursor = parser.cursor
|
||
|
traverse(cursor)
|
||
|
if CURRENT_FILE_TYPE == 0:
|
||
|
index_temp = clang.cindex.Index.create()
|
||
|
syspath = ccsyspath.system_include_paths('clang')
|
||
|
incargs = [b'-I' + inc for inc in syspath]
|
||
|
incargs.append('-I'+INPUT_HEAD_PATH)
|
||
|
parser_temp = index_temp.parse(i, args=marcros_list + incargs)
|
||
|
cursor_temp = parser_temp.cursor
|
||
|
traverse_c(cursor_temp, 0)
|
||
|
else:
|
||
|
index_temp = clang.cindex.Index.create()
|
||
|
syspath = ccsyspath.system_include_paths('clang++')
|
||
|
incargs = [b'-I' + inc for inc in syspath]
|
||
|
incargs.append('-I'+INPUT_HEAD_PATH)
|
||
|
parser_temp = index_temp.parse(i,
|
||
|
args=marcros_list + compile_args +
|
||
|
incargs)
|
||
|
cursor_temp = parser_temp.cursor
|
||
|
traverse_cpp(cursor_temp, '')
|
||
|
global HEAD_FILE_NAME_LIST
|
||
|
HEAD_FILE_NAME_LIST = list(set(HEAD_FILE_NAME_LIST))
|
||
|
HEAD_FILE_NAME_LIST.sort()
|
||
|
|
||
|
|
||
|
def traverse_c(node, depth):
|
||
|
'''
|
||
|
Recursively obtain the symbol list from the c language header file and store it in HEAD_FILE_NAME_LIST
|
||
|
'''
|
||
|
global HEAD_FILE_NAME_LIST
|
||
|
if node.kind == CursorKind.FUNCTION_DECL:
|
||
|
if is_has_extern_in_node(node):
|
||
|
HEAD_FILE_NAME_LIST.append(node.spelling)
|
||
|
if node.kind == CursorKind.VAR_DECL and depth == 1:
|
||
|
if is_has_extern_in_node(node):
|
||
|
HEAD_FILE_NAME_LIST.append(node.spelling)
|
||
|
for n in node.get_children():
|
||
|
traverse_c(n, depth + 1)
|
||
|
|
||
|
|
||
|
def check_cpp_namespace(depth):
|
||
|
if not depth.find('std') == -1:
|
||
|
return False
|
||
|
if not depth.find('__gnu_cxx') == -1:
|
||
|
return False
|
||
|
if not depth.find('__cxxabiv1') == -1:
|
||
|
return False
|
||
|
if not depth.find('__pthread_cleanup_class') == -1:
|
||
|
return False
|
||
|
return True
|
||
|
|
||
|
|
||
|
def traverse_cpp(node, depth):
|
||
|
'''
|
||
|
Recursively obtain the symbol list from the c++ language header file and store it in HEAD_FILE_NAME_LIST
|
||
|
'''
|
||
|
global HEAD_FILE_NAME_LIST
|
||
|
if node.kind == CursorKind.NAMESPACE or node.kind == CursorKind.CLASS_DECL:
|
||
|
if depth == '':
|
||
|
depth = node.spelling
|
||
|
else:
|
||
|
depth = depth + '::' + node.spelling
|
||
|
if node.kind == CursorKind.CXX_METHOD and check_cpp_namespace(
|
||
|
depth) and not depth == '' and not node.is_pure_virtual_method():
|
||
|
HEAD_FILE_NAME_LIST.append(depth + '::' + node.spelling + '@' +
|
||
|
str(get_permission_num(node.displayname)))
|
||
|
if node.kind == CursorKind.FUNCTION_TEMPLATE and check_cpp_namespace(
|
||
|
depth) and not depth == '':
|
||
|
HEAD_FILE_NAME_LIST.append(depth + '::' + node.spelling + '@' +
|
||
|
str(get_permission_num(node.displayname)))
|
||
|
if node.kind == CursorKind.DESTRUCTOR and check_cpp_namespace(depth):
|
||
|
HEAD_FILE_NAME_LIST.append(depth + '::' + node.spelling + '@0')
|
||
|
if node.kind == CursorKind.VAR_DECL and check_cpp_namespace(
|
||
|
depth) and not depth == '':
|
||
|
HEAD_FILE_NAME_LIST.append(depth + '::' + node.spelling)
|
||
|
if node.kind == CursorKind.CONSTRUCTOR and check_cpp_namespace(depth):
|
||
|
HEAD_FILE_NAME_LIST.append(depth + '::' + node.spelling + '@' +
|
||
|
str(get_permission_num(node.displayname)))
|
||
|
for n in node.get_children():
|
||
|
traverse_cpp(n, depth)
|
||
|
|
||
|
|
||
|
def is_has_extern_in_node(node):
|
||
|
for i in node.get_tokens():
|
||
|
if i.spelling == 'extern' or i.spelling == '__inline':
|
||
|
return False
|
||
|
return True
|
||
|
|
||
|
|
||
|
def traverse(node):
|
||
|
'''
|
||
|
Determine the type of the file parameter
|
||
|
'''
|
||
|
global CURRENT_FILE_TYPE
|
||
|
if node.kind == CursorKind.CLASS_DECL:
|
||
|
CURRENT_FILE_TYPE = 1
|
||
|
for n in node.get_children():
|
||
|
traverse(n)
|
||
|
|
||
|
|
||
|
def get_compare_result():
|
||
|
'''
|
||
|
Compare the symbol lists of header and library files and generate a file that stores the results of the comparison
|
||
|
'''
|
||
|
only_lib_have = list(
|
||
|
set(LIB_FILE_NAME_LIST).difference(set(HEAD_FILE_NAME_LIST)))
|
||
|
only_head_have = list(
|
||
|
set(HEAD_FILE_NAME_LIST).difference(set(LIB_FILE_NAME_LIST)))
|
||
|
not_compare_list = []
|
||
|
if not INPUT_BLACK_PATH == '':
|
||
|
not_compare_list = not_compare_list + get_info_from_file(
|
||
|
INPUT_BLACK_PATH)
|
||
|
only_lib_have = list(set(only_lib_have).difference(set(not_compare_list)))
|
||
|
only_head_have = list(
|
||
|
set(only_head_have).difference(set(not_compare_list)))
|
||
|
only_lib_have.sort()
|
||
|
only_head_have.sort()
|
||
|
result = {}
|
||
|
result['head_file_path'] = INPUT_HEAD_PATH
|
||
|
result['lib_file_path'] = INPUT_LIB_PATH
|
||
|
result['only_in_head_file'] = only_head_have
|
||
|
result['only_in_lib_file'] = only_lib_have
|
||
|
result['head_file_symble_list_num:lib_file_symble_list_nmu'] = str(
|
||
|
len(HEAD_FILE_NAME_LIST)) + ':' + str(len(LIB_FILE_NAME_LIST))
|
||
|
seconds = time.time()
|
||
|
time_str = time.strftime('%Y-%m-%d-%H:%M:%S', time.localtime(seconds))
|
||
|
out_difference_path = OUTPUT_RESULT_PATH + 'compare_result_' + time_str + '.json'
|
||
|
out_lib_path = OUTPUT_RESULT_PATH + 'lib_file_list_' + time_str + '.txt'
|
||
|
out_head_path = OUTPUT_RESULT_PATH + 'head_file_list_' + time_str + '.txt'
|
||
|
with open(out_difference_path, 'w') as file_obj:
|
||
|
json.dump(result, file_obj, indent=4, separators=(',', ':'))
|
||
|
file_obj.close()
|
||
|
f = open(out_lib_path, 'w')
|
||
|
for line in LIB_FILE_NAME_LIST:
|
||
|
f.write(line + '\n')
|
||
|
f.close()
|
||
|
f = open(out_head_path, 'w')
|
||
|
for line in HEAD_FILE_NAME_LIST:
|
||
|
f.write(line + '\n')
|
||
|
f.close()
|
||
|
|
||
|
|
||
|
INPUT_LIB_PATH = '' #The path to the entered library file
|
||
|
INPUT_HEAD_PATH = '' #The path to the entered header file
|
||
|
INPUT_BLACK_PATH = '' #The path to the entered blacklist file
|
||
|
INPUT_MACROS_PATH = '' #The path to the entered macro definition file
|
||
|
OUTPUT_RESULT_PATH = '' #The output path of the result file
|
||
|
HEAD_FILE_NAME_LIST = [] #header file symbol list
|
||
|
LIB_FILE_NAME_LIST = [] #library file symbol list
|
||
|
CURRENT_FILE_TYPE = 0 #current file type
|
||
|
get_args()
|
||
|
get_head_strs()
|
||
|
get_lib_strs()
|
||
|
get_compare_result()
|