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()
 |