239 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			239 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env python3
 | |
| 
 | |
| import os, sys, argparse, re, difflib, json
 | |
| 
 | |
| def jsonKeys2int(x):
 | |
|     try:
 | |
|         if isinstance(x, dict):
 | |
|             return {int(k):v for k,v in x.items()}
 | |
|         return x
 | |
|     except ValueError:
 | |
|         return x
 | |
| 
 | |
| def symtypes_parse(path, data = None):
 | |
|     if not data:
 | |
|         data = {
 | |
|                 'children': { 0 : []},
 | |
|                 'parents': { 0 : []},
 | |
|                 'strtab': ["(root)"],
 | |
|                 'index' : {},
 | |
|                 'file' : { }
 | |
|         }
 | |
| 
 | |
|     bpath = os.path.basename(path)
 | |
|     data["file"] = {}
 | |
|     data["file"][bpath] = { 0 : "" }
 | |
| 
 | |
|     with open(path, 'r') as fp_ref:
 | |
|         for line in fp_ref.readlines():
 | |
|             lsplit = line.split(' ')
 | |
|             root = lsplit.pop(0)
 | |
|             children = list(filter(lambda x: len(x) > 2 and (x[1] == '#' or x == "UNKNOWN"), lsplit))
 | |
|             if root in data["index"]:
 | |
|                 idx = data["index"][root]
 | |
|                 if idx in data["children"] and len(data["children"][idx]) > 0:
 | |
|                     continue
 | |
|             index = data_add(data, root, 0, bpath, line)
 | |
|             for child in children:
 | |
|                 child_index = data_add(data, child, index, "", "")
 | |
|     return data
 | |
| 
 | |
| def data_add(data, ident, parent, bpath, line):
 | |
|     index = data['strtab'].index(ident) if ident in data['strtab'] else -1
 | |
|     if index == -1:
 | |
|         index = len(data['strtab'])
 | |
|         data['strtab'].append(ident)
 | |
|         data['index'][ident] = index
 | |
|     data['children'][parent].append(index)
 | |
|     if index not in data['children']:
 | |
|         data['children'][index] = []
 | |
|     if index in data['parents']:
 | |
|         if parent not in data['parents'][index]:
 | |
|             data['parents'][index].append(parent)
 | |
|     else:
 | |
|         data['parents'][index] = [parent]
 | |
|     if bpath and line:
 | |
|         data['file'][bpath][index] = line
 | |
|     return index
 | |
| 
 | |
| #def symtypes_dfs(data_a, source, sink, trace, inverse = False):
 | |
| # print(' > '.join(list(map(lambda i: data_a['strtab'][i], path + [e]))))
 | |
| def symtypes_dfs(data, start, inverse=False, full=False):
 | |
|     start_i = data["index"][start]
 | |
|     stack = [(start_i,[start_i])]
 | |
|     visited = set()
 | |
|     paths = []
 | |
|     while stack:
 | |
|         (node, path) = stack.pop()
 | |
|         if full and node in path[:-1]:
 | |
|             continue
 | |
|         if not full:
 | |
|             if node in visited:
 | |
|                 continue
 | |
|             visited.add(node)
 | |
|         paths.append(path)
 | |
|         if not inverse:
 | |
|             for child in reversed(data["children"][node]):
 | |
|                 stack.append((child, path + [child]))
 | |
|         else:
 | |
|             for child in reversed(data["parents"][node]):
 | |
|                 if child == 0:
 | |
|                     continue
 | |
|                 stack.append((child, path + [child]))
 | |
| 
 | |
|     return visited, paths
 | |
| 
 | |
| def st_open(path):
 | |
|     if not path:
 | |
|         raise ValueError("Blank blank.")
 | |
|     if not os.path.exists(path):
 | |
|         raise OSError(f"Path {path} does not exist.")
 | |
|     with open(path, "r") as f:
 | |
|         try:
 | |
|             return json.load(f, object_hook=jsonKeys2int)
 | |
|         except ValueError:
 | |
|             pass
 | |
|     return symtypes_parse(path)
 | |
| 
 | |
| def st_write(path, data):
 | |
|     with open(path, "w+") as f:
 | |
|         if "file" in data:
 | |
|             del data["file"]
 | |
|         json.dump(data, f)
 | |
| 
 | |
| def index(symtype, output):
 | |
|     data = st_open(symtype)
 | |
|     if output:
 | |
|         st_write(output, data)
 | |
|     return data
 | |
| 
 | |
| def st_print(node):
 | |
|     if node[1] != '#':
 | |
|         return node
 | |
|     if node[0] == 's':
 | |
|         return "struct " + node[2:]
 | |
|     if node[0] == 't':
 | |
|         return "typedef " + node[2:]
 | |
|     if node[0] == 'E':
 | |
|         return "enum const " + node[2:]
 | |
|     if node[0] == 'e':
 | |
|         return "enum " + node[2:]
 | |
|     if node[0] == 'u':
 | |
|         return "union " + node[2:]
 | |
|     return node
 | |
| 
 | |
| def im(file, dump_list, dump_path, dump_tree, start, inverse, silent):
 | |
|     data = index(file, None)
 | |
| 
 | |
|     if start not in data["index"]:
 | |
|         if not silent:
 | |
|             print(f"Node {start} not found in file {file}. Exitting.")
 | |
|         sys.exit(1)
 | |
| 
 | |
|     nodes, paths = symtypes_dfs(data, start, inverse)
 | |
| 
 | |
|     if dump_list:
 | |
|         for node in map(lambda i: data['strtab'][i], nodes):
 | |
|             print(f"{st_print(node)} (symtype node: {node})")
 | |
| 
 | |
|     if not dump_path and not dump_tree:
 | |
|         return
 | |
| 
 | |
|     for path in paths:
 | |
|         if dump_tree:
 | |
|             print((len(path)-1)*"  " + " - " + f"{st_print(data['strtab'][path[-1]])} (symtype node: {data['strtab'][path[-1]]})");
 | |
|             continue
 | |
|         if dump_path:
 | |
|             print(list(map(lambda i: data["strtab"][i], path)))
 | |
| 
 | |
| def diff(ref, new, start):
 | |
|     data_ref = index(ref, None)
 | |
|     data_new = index(new, None)
 | |
| 
 | |
|     nodes_ref, _ = symtypes_dfs(data_ref, start)
 | |
|     nodes_new, _ = symtypes_dfs(data_new, start)
 | |
| 
 | |
|     nodes_ref_lbl = set(map(lambda i: data_ref['strtab'][i], nodes_ref))
 | |
|     nodes_new_lbl = set(map(lambda i: data_new['strtab'][i], nodes_new))
 | |
|     nodes_all = nodes_ref_lbl | nodes_new_lbl
 | |
|     nodes_13 = nodes_all - nodes_ref_lbl
 | |
|     nodes_23 = nodes_all - nodes_new_lbl
 | |
| 
 | |
|     if nodes_23:
 | |
|         print("The following nodes were encountered only in reference symtypes:")
 | |
|         print("\t" + "\n\t".join(nodes_23))
 | |
| 
 | |
|     if nodes_13:
 | |
|         print("The following nodes were encountered only in new symtypes:")
 | |
|         print("\t" + "\n\t".join(nodes_13))
 | |
| 
 | |
|     bpath_a = os.path.basename(ref)
 | |
|     bpath_b = os.path.basename(new)
 | |
|     for node in nodes_all - (nodes_13 | nodes_23):
 | |
|         idx_a = data_ref['index'][node]
 | |
|         idx_b = data_new['index'][node]
 | |
|         r = set(map(lambda i: data_ref['strtab'][i], data_ref['children'][idx_a]))
 | |
|         n = set(map(lambda i: data_new['strtab'][i], data_new['children'][idx_b]))
 | |
| 
 | |
|         if r == n:
 | |
|             continue
 | |
| 
 | |
|         i = ["\t"+data_ref['file'][bpath_a][idx_a]], \
 | |
|             ["\t"+data_new['file'][bpath_b][idx_b]]
 | |
| 
 | |
|         if i[0] != i[1]:
 | |
|             print(f"Possible breakage detected for {st_print(node)} (symtype node: {node}) ...")
 | |
|             if len(n) == 1 and "UNKNOWN" in n:
 | |
|                 print("\treplaced by UNKNOWN. Please inspect changes to #include directives")
 | |
|             if len(r) == 1 and "UNKNOWN" in r:
 | |
|                 print("\tUNKNOWN got replaced. Please inspect changes to #include directives")
 | |
|             diff = difflib.ndiff(i[0], i[1])
 | |
|             print(''.join(diff), end="")
 | |
| 
 | |
| 
 | |
| if __name__ == "__main__":
 | |
|     parser = argparse.ArgumentParser()
 | |
| 
 | |
|     subparsers = parser.add_subparsers(help='Modes of operation.',
 | |
|             dest="mode")
 | |
|     parser_index = subparsers.add_parser('index',
 | |
|             help='Calculate symtypes index.')
 | |
|     parser_image = subparsers.add_parser('image',
 | |
|             help='Show type/symbol dependencies.')
 | |
|     parser_preimage = subparsers.add_parser('preimage',
 | |
|             help='Show type/symbol preimage.')
 | |
|     parser_df = subparsers.add_parser('diff',
 | |
|             help='Calculate simple symtype diff.')
 | |
| 
 | |
|     parser_index.add_argument('-o', '--output', type=str, required=True,
 | |
|             help='Output index file.')
 | |
|     parser_index.add_argument('symtype', type=str)
 | |
| 
 | |
|     for p in [ parser_image, parser_preimage ]:
 | |
|         p.add_argument('-i', '--index', action='store_true',
 | |
|                 help="Input is an index file.")
 | |
|         p.add_argument('-S', '--silent', action='store_true')
 | |
|         p.add_argument('-l', '--ls', action='store_true',
 | |
|                 help="List dependent nodes.")
 | |
|         p.add_argument('-p', '--path', action='store_true',
 | |
|                 help="List paths to dependent nodes.")
 | |
|         p.add_argument('-t', '--tree', action='store_true',
 | |
|                 help="Dump tree.")
 | |
|         p.add_argument('-s', '--start', type=str, nargs='?',
 | |
|                 help="Start symtype entry/entries.")
 | |
|         p.add_argument('symtype', type=str)
 | |
| 
 | |
|     parser_df.add_argument('reference', type=str)
 | |
|     parser_df.add_argument('new', type=str)
 | |
|     parser_df.add_argument('-s', '--start', type=str, nargs='?',
 | |
|                 help="Start symtype entry/entries.")
 | |
| 
 | |
|     args = parser.parse_args()
 | |
| 
 | |
|     if args.mode == "index":
 | |
|         index(args.symtype, args.output)
 | |
|     elif args.mode == "image" or args.mode == "preimage":
 | |
|         im(args.symtype, args.ls, args.path, args.tree, args.start, args.mode == "preimage", args.silent)
 | |
|     elif args.mode == "diff":
 | |
|         diff(args.reference, args.new, args.start)
 |