|  | # Given a path to llvm-objdump and a directory tree, spider the directory tree | 
|  | # dumping every object file encountered with correct options needed to demangle | 
|  | # symbols in the object file, and collect statistics about failed / crashed | 
|  | # demanglings.  Useful for stress testing the demangler against a large corpus | 
|  | # of inputs. | 
|  |  | 
|  | from __future__ import print_function | 
|  |  | 
|  | import argparse | 
|  | import functools | 
|  | import os | 
|  | import re | 
|  | import sys | 
|  | import subprocess | 
|  | import traceback | 
|  | from multiprocessing import Pool | 
|  | import multiprocessing | 
|  |  | 
|  | args = None | 
|  |  | 
|  |  | 
|  | def parse_line(line): | 
|  | question = line.find("?") | 
|  | if question == -1: | 
|  | return None, None | 
|  |  | 
|  | open_paren = line.find("(", question) | 
|  | if open_paren == -1: | 
|  | return None, None | 
|  | close_paren = line.rfind(")", open_paren) | 
|  | if open_paren == -1: | 
|  | return None, None | 
|  | mangled = line[question:open_paren] | 
|  | demangled = line[open_paren + 1 : close_paren] | 
|  | return mangled.strip(), demangled.strip() | 
|  |  | 
|  |  | 
|  | class Result(object): | 
|  | def __init__(self): | 
|  | self.crashed = [] | 
|  | self.file = None | 
|  | self.nsymbols = 0 | 
|  | self.errors = set() | 
|  | self.nfiles = 0 | 
|  |  | 
|  |  | 
|  | class MapContext(object): | 
|  | def __init__(self): | 
|  | self.rincomplete = None | 
|  | self.rcumulative = Result() | 
|  | self.pending_objs = [] | 
|  | self.npending = 0 | 
|  |  | 
|  |  | 
|  | def process_file(path, objdump): | 
|  | r = Result() | 
|  | r.file = path | 
|  |  | 
|  | popen_args = [objdump, "-t", "-demangle", path] | 
|  | p = subprocess.Popen(popen_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | 
|  | stdout, stderr = p.communicate() | 
|  | if p.returncode != 0: | 
|  | r.crashed = [r.file] | 
|  | return r | 
|  |  | 
|  | output = stdout.decode("utf-8") | 
|  |  | 
|  | for line in output.splitlines(): | 
|  | mangled, demangled = parse_line(line) | 
|  | if mangled is None: | 
|  | continue | 
|  | r.nsymbols += 1 | 
|  | if "invalid mangled name" in demangled: | 
|  | r.errors.add(mangled) | 
|  | return r | 
|  |  | 
|  |  | 
|  | def add_results(r1, r2): | 
|  | r1.crashed.extend(r2.crashed) | 
|  | r1.errors.update(r2.errors) | 
|  | r1.nsymbols += r2.nsymbols | 
|  | r1.nfiles += r2.nfiles | 
|  |  | 
|  |  | 
|  | def print_result_row(directory, result): | 
|  | print( | 
|  | "[{0} files, {1} crashes, {2} errors, {3} symbols]: '{4}'".format( | 
|  | result.nfiles, | 
|  | len(result.crashed), | 
|  | len(result.errors), | 
|  | result.nsymbols, | 
|  | directory, | 
|  | ) | 
|  | ) | 
|  |  | 
|  |  | 
|  | def process_one_chunk(pool, chunk_size, objdump, context): | 
|  | objs = [] | 
|  |  | 
|  | incomplete = False | 
|  | dir_results = {} | 
|  | ordered_dirs = [] | 
|  | while context.npending > 0 and len(objs) < chunk_size: | 
|  | this_dir = context.pending_objs[0][0] | 
|  | ordered_dirs.append(this_dir) | 
|  | re = Result() | 
|  | if context.rincomplete is not None: | 
|  | re = context.rincomplete | 
|  | context.rincomplete = None | 
|  |  | 
|  | dir_results[this_dir] = re | 
|  | re.file = this_dir | 
|  |  | 
|  | nneeded = chunk_size - len(objs) | 
|  | objs_this_dir = context.pending_objs[0][1] | 
|  | navail = len(objs_this_dir) | 
|  | ntaken = min(nneeded, navail) | 
|  | objs.extend(objs_this_dir[0:ntaken]) | 
|  | remaining_objs_this_dir = objs_this_dir[ntaken:] | 
|  | context.pending_objs[0] = (context.pending_objs[0][0], remaining_objs_this_dir) | 
|  | context.npending -= ntaken | 
|  | if ntaken == navail: | 
|  | context.pending_objs.pop(0) | 
|  | else: | 
|  | incomplete = True | 
|  |  | 
|  | re.nfiles += ntaken | 
|  |  | 
|  | assert len(objs) == chunk_size or context.npending == 0 | 
|  |  | 
|  | copier = functools.partial(process_file, objdump=objdump) | 
|  | mapped_results = list(pool.map(copier, objs)) | 
|  |  | 
|  | for mr in mapped_results: | 
|  | result_dir = os.path.dirname(mr.file) | 
|  | result_entry = dir_results[result_dir] | 
|  | add_results(result_entry, mr) | 
|  |  | 
|  | # It's only possible that a single item is incomplete, and it has to be the | 
|  | # last item. | 
|  | if incomplete: | 
|  | context.rincomplete = dir_results[ordered_dirs[-1]] | 
|  | ordered_dirs.pop() | 
|  |  | 
|  | # Now ordered_dirs contains a list of all directories which *did* complete. | 
|  | for c in ordered_dirs: | 
|  | re = dir_results[c] | 
|  | add_results(context.rcumulative, re) | 
|  | print_result_row(c, re) | 
|  |  | 
|  |  | 
|  | def process_pending_files(pool, chunk_size, objdump, context): | 
|  | while context.npending >= chunk_size: | 
|  | process_one_chunk(pool, chunk_size, objdump, context) | 
|  |  | 
|  |  | 
|  | def go(): | 
|  | global args | 
|  |  | 
|  | obj_dir = args.dir | 
|  | extensions = args.extensions.split(",") | 
|  | extensions = [x if x[0] == "." else "." + x for x in extensions] | 
|  |  | 
|  | pool_size = 48 | 
|  | pool = Pool(processes=pool_size) | 
|  |  | 
|  | try: | 
|  | nfiles = 0 | 
|  | context = MapContext() | 
|  |  | 
|  | for root, dirs, files in os.walk(obj_dir): | 
|  | root = os.path.normpath(root) | 
|  | pending = [] | 
|  | for f in files: | 
|  | file, ext = os.path.splitext(f) | 
|  | if not ext in extensions: | 
|  | continue | 
|  |  | 
|  | nfiles += 1 | 
|  | full_path = os.path.join(root, f) | 
|  | full_path = os.path.normpath(full_path) | 
|  | pending.append(full_path) | 
|  |  | 
|  | # If this directory had no object files, just print a default | 
|  | # status line and continue with the next dir | 
|  | if len(pending) == 0: | 
|  | print_result_row(root, Result()) | 
|  | continue | 
|  |  | 
|  | context.npending += len(pending) | 
|  | context.pending_objs.append((root, pending)) | 
|  | # Drain the tasks, `pool_size` at a time, until we have less than | 
|  | # `pool_size` tasks remaining. | 
|  | process_pending_files(pool, pool_size, args.objdump, context) | 
|  |  | 
|  | assert context.npending < pool_size | 
|  | process_one_chunk(pool, pool_size, args.objdump, context) | 
|  |  | 
|  | total = context.rcumulative | 
|  | nfailed = len(total.errors) | 
|  | nsuccess = total.nsymbols - nfailed | 
|  | ncrashed = len(total.crashed) | 
|  |  | 
|  | if nfailed > 0: | 
|  | print("Failures:") | 
|  | for m in sorted(total.errors): | 
|  | print("  " + m) | 
|  | if ncrashed > 0: | 
|  | print("Crashes:") | 
|  | for f in sorted(total.crashed): | 
|  | print("  " + f) | 
|  | print("Summary:") | 
|  | spct = float(nsuccess) / float(total.nsymbols) | 
|  | fpct = float(nfailed) / float(total.nsymbols) | 
|  | cpct = float(ncrashed) / float(nfiles) | 
|  | print("Processed {0} object files.".format(nfiles)) | 
|  | print( | 
|  | "{0}/{1} symbols successfully demangled ({2:.4%})".format( | 
|  | nsuccess, total.nsymbols, spct | 
|  | ) | 
|  | ) | 
|  | print("{0} symbols could not be demangled ({1:.4%})".format(nfailed, fpct)) | 
|  | print("{0} files crashed while demangling ({1:.4%})".format(ncrashed, cpct)) | 
|  |  | 
|  | except: | 
|  | traceback.print_exc() | 
|  |  | 
|  | pool.close() | 
|  | pool.join() | 
|  |  | 
|  |  | 
|  | if __name__ == "__main__": | 
|  | def_obj = "obj" if sys.platform == "win32" else "o" | 
|  |  | 
|  | parser = argparse.ArgumentParser( | 
|  | description="Demangle all symbols in a tree of object files, looking for failures." | 
|  | ) | 
|  | parser.add_argument( | 
|  | "dir", type=str, help="the root directory at which to start crawling" | 
|  | ) | 
|  | parser.add_argument( | 
|  | "--objdump", | 
|  | type=str, | 
|  | help="path to llvm-objdump.  If not specified " | 
|  | + "the tool is located as if by `which llvm-objdump`.", | 
|  | ) | 
|  | parser.add_argument( | 
|  | "--extensions", | 
|  | type=str, | 
|  | default=def_obj, | 
|  | help="comma separated list of extensions to demangle (e.g. `o,obj`).  " | 
|  | + "By default this will be `obj` on Windows and `o` otherwise.", | 
|  | ) | 
|  |  | 
|  | args = parser.parse_args() | 
|  |  | 
|  | multiprocessing.freeze_support() | 
|  | go() |