brintos

brintos / llvm-project-archived public Read only

0
0
Text · 23.1 KiB · cdca2ec Raw
712 lines · python
1#!/usr/bin/env python2#3# Debugify summary for the original debug info testing.4#5 6from __future__ import print_function7import argparse8import os9import re10import sys11from json import loads12from collections import defaultdict13from collections import OrderedDict14 15class DILocBug:16    def __init__(self, origin, action, bb_name, fn_name, instr):17        self.origin = origin18        self.action = action19        self.bb_name = bb_name20        self.fn_name = fn_name21        self.instr = instr22 23    def key(self):24        return self.action + self.bb_name + self.fn_name + self.instr25 26    def reduced_key(self, bug_pass):27        if self.origin is not None:28            # If we have the origin stacktrace available, we can use it to efficiently deduplicate identical errors. We29            # just need to remove the pointer values from the string first, so that we can deduplicate across files.30            origin_no_addr = re.sub(r"0x[0-9a-fA-F]+", "", self.origin)31            return origin_no_addr32        return bug_pass + self.instr33 34    def to_dict(self):35        result = {36            "instr": self.instr,37            "fn_name": self.fn_name,38            "bb_name": self.bb_name,39            "action": self.action,40        }41        if self.origin:42            result["origin"] = self.origin43        return result44 45 46class DISPBug:47    def __init__(self, action, fn_name):48        self.action = action49        self.fn_name = fn_name50 51    def key(self):52        return self.action + self.fn_name53 54    def reduced_key(self, bug_pass):55        return bug_pass + self.fn_name56 57    def to_dict(self):58        return {59            "fn_name": self.fn_name,60            "action": self.action,61        }62 63 64class DIVarBug:65    def __init__(self, action, name, fn_name):66        self.action = action67        self.name = name68        self.fn_name = fn_name69 70    def key(self):71        return self.action + self.name + self.fn_name72 73    def reduced_key(self, bug_pass):74        return bug_pass + self.name75 76    def to_dict(self):77        return {78            "fn_name": self.fn_name,79            "name": self.name,80            "action": self.action,81        }82 83 84def print_bugs_yaml(name, bugs_dict, indent=2):85    def get_bug_line(indent_level: int, text: str, margin_mark: bool = False):86        if margin_mark:87            return "- ".rjust(indent_level * indent) + text88        return " " * indent * indent_level + text89 90    print(f"{name}:")91    for bugs_file, bugs_pass_dict in sorted(iter(bugs_dict.items())):92        print(get_bug_line(1, f"{bugs_file}:"))93        for bugs_pass, bugs_list in sorted(iter(bugs_pass_dict.items())):94            print(get_bug_line(2, f"{bugs_pass}:"))95            for bug in bugs_list:96                bug_dict = bug.to_dict()97                first_line = True98                # First item needs a '-' in the margin.99                for key, val in sorted(iter(bug_dict.items())):100                    if "\n" in val:101                        # Output block text for any multiline string.102                        print(get_bug_line(3, f"{key}: |", first_line))103                        for line in val.splitlines():104                            print(get_bug_line(4, line))105                    else:106                        print(get_bug_line(3, f"{key}: {val}", first_line))107                    first_line = False108 109# Report the bugs in form of html.110def generate_html_report(111    di_location_bugs,112    di_subprogram_bugs,113    di_var_bugs,114    di_location_bugs_summary,115    di_sp_bugs_summary,116    di_var_bugs_summary,117    html_file,118):119    fileout = open(html_file, "w")120 121    html_header = """ <html>122  <head>123  <style>124  table, th, td {125    border: 1px solid black;126  }127  table.center {128    margin-left: auto;129    margin-right: auto;130  }131  </style>132  </head>133  <body>134  """135 136    # Create the table for Location bugs.137    table_title_di_loc = "Location Bugs found by the Debugify"138 139    table_di_loc = """<table>140  <caption><b>{}</b></caption>141  <tr>142  """.format(143        table_title_di_loc144    )145 146    # If any DILocation bug has an origin stack trace, we emit an extra column in the table, which we must therefore147    # determine up-front.148    has_origin_col = any(149        x.origin is not None150        for per_file_bugs in di_location_bugs.values()151        for per_pass_bugs in per_file_bugs.values()152        for x in per_pass_bugs153    )154 155    header_di_loc = [156        "File",157        "LLVM Pass Name",158        "LLVM IR Instruction",159        "Function Name",160        "Basic Block Name",161        "Action",162    ]163    if has_origin_col:164        header_di_loc.append("Origin")165 166    for column in header_di_loc:167        table_di_loc += "    <th>{0}</th>\n".format(column.strip())168    table_di_loc += "  </tr>\n"169 170    at_least_one_bug_found = False171 172    # Handle loction bugs.173    for file, per_file_bugs in di_location_bugs.items():174        for llvm_pass, per_pass_bugs in per_file_bugs.items():175            # No location bugs for the pass.176            if len(per_pass_bugs) == 0:177                continue178            at_least_one_bug_found = True179            row = []180            table_di_loc += "  </tr>\n"181            # Get the bugs info.182            for x in per_pass_bugs:183                row.append("    <tr>\n")184                row.append(file)185                row.append(llvm_pass)186                row.append(x.instr)187                row.append(x.fn_name)188                row.append(x.bb_name)189                row.append(x.action)190                if has_origin_col:191                    if x.origin is not None:192                        row.append(193                            f"<details><summary>View Origin StackTrace</summary><pre>{x.origin}</pre></details>"194                        )195                    else:196                        row.append("")197                row.append("    </tr>\n")198            # Dump the bugs info into the table.199            for column in row:200                # The same file-pass pair can have multiple bugs.201                if column == "    <tr>\n" or column == "    </tr>\n":202                    table_di_loc += column203                    continue204                table_di_loc += "    <td>{0}</td>\n".format(column.strip())205            table_di_loc += "  <tr>\n"206 207    if not at_least_one_bug_found:208        table_di_loc += """  <tr>209        <td colspan='7'> No bugs found </td>210      </tr>211    """212    table_di_loc += "</table>\n"213 214    # Create the summary table for the loc bugs.215    table_title_di_loc_sum = "Summary of Location Bugs"216    table_di_loc_sum = """<table>217  <caption><b>{}</b></caption>218  <tr>219  """.format(220        table_title_di_loc_sum221    )222 223    header_di_loc_sum = ["LLVM Pass Name", "Number of bugs"]224 225    for column in header_di_loc_sum:226        table_di_loc_sum += "    <th>{0}</th>\n".format(column.strip())227    table_di_loc_sum += "  </tr>\n"228 229    # Print the summary.230    row = []231    for llvm_pass, num in sorted(di_location_bugs_summary.items()):232        row.append("    <tr>\n")233        row.append(llvm_pass)234        row.append(str(num))235        row.append("    </tr>\n")236    for column in row:237        if column == "    <tr>\n" or column == "    </tr>\n":238            table_di_loc_sum += column239            continue240        table_di_loc_sum += "    <td>{0}</td>\n".format(column.strip())241    table_di_loc_sum += "  <tr>\n"242 243    if not at_least_one_bug_found:244        table_di_loc_sum += """<tr>245        <td colspan='2'> No bugs found </td>246      </tr>247    """248    table_di_loc_sum += "</table>\n"249 250    # Create the table for SP bugs.251    table_title_di_sp = "SP Bugs found by the Debugify"252    table_di_sp = """<table>253  <caption><b>{}</b></caption>254  <tr>255  """.format(256        table_title_di_sp257    )258 259    header_di_sp = ["File", "LLVM Pass Name", "Function Name", "Action"]260 261    for column in header_di_sp:262        table_di_sp += "    <th>{0}</th>\n".format(column.strip())263    table_di_sp += "  </tr>\n"264 265    at_least_one_bug_found = False266 267    # Handle fn bugs.268    for file, per_file_bugs in di_subprogram_bugs.items():269        for llvm_pass, per_pass_bugs in per_file_bugs.items():270            # No SP bugs for the pass.271            if len(per_pass_bugs) == 0:272                continue273            at_least_one_bug_found = True274            row = []275            table_di_sp += "  </tr>\n"276            # Get the bugs info.277            for x in per_pass_bugs:278                row.append("    <tr>\n")279                row.append(file)280                row.append(llvm_pass)281                row.append(x.fn_name)282                row.append(x.action)283                row.append("    </tr>\n")284            # Dump the bugs info into the table.285            for column in row:286                # The same file-pass pair can have multiple bugs.287                if column == "    <tr>\n" or column == "    </tr>\n":288                    table_di_sp += column289                    continue290                table_di_sp += "    <td>{0}</td>\n".format(column.strip())291            table_di_sp += "  <tr>\n"292 293    if not at_least_one_bug_found:294        table_di_sp += """<tr>295        <td colspan='4'> No bugs found </td>296      </tr>297    """298    table_di_sp += "</table>\n"299 300    # Create the summary table for the sp bugs.301    table_title_di_sp_sum = "Summary of SP Bugs"302    table_di_sp_sum = """<table>303  <caption><b>{}</b></caption>304  <tr>305  """.format(306        table_title_di_sp_sum307    )308 309    header_di_sp_sum = ["LLVM Pass Name", "Number of bugs"]310 311    for column in header_di_sp_sum:312        table_di_sp_sum += "    <th>{0}</th>\n".format(column.strip())313    table_di_sp_sum += "  </tr>\n"314 315    # Print the summary.316    row = []317    for llvm_pass, num in sorted(di_sp_bugs_summary.items()):318        row.append("    <tr>\n")319        row.append(llvm_pass)320        row.append(str(num))321        row.append("    </tr>\n")322    for column in row:323        if column == "    <tr>\n" or column == "    </tr>\n":324            table_di_sp_sum += column325            continue326        table_di_sp_sum += "    <td>{0}</td>\n".format(column.strip())327    table_di_sp_sum += "  <tr>\n"328 329    if not at_least_one_bug_found:330        table_di_sp_sum += """<tr>331        <td colspan='2'> No bugs found </td>332      </tr>333    """334    table_di_sp_sum += "</table>\n"335 336    # Create the table for Variable bugs.337    table_title_di_var = "Variable Location Bugs found by the Debugify"338    table_di_var = """<table>339  <caption><b>{}</b></caption>340  <tr>341  """.format(342        table_title_di_var343    )344 345    header_di_var = ["File", "LLVM Pass Name", "Variable", "Function", "Action"]346 347    for column in header_di_var:348        table_di_var += "    <th>{0}</th>\n".format(column.strip())349    table_di_var += "  </tr>\n"350 351    at_least_one_bug_found = False352 353    # Handle var bugs.354    for file, per_file_bugs in di_var_bugs.items():355        for llvm_pass, per_pass_bugs in per_file_bugs.items():356            # No SP bugs for the pass.357            if len(per_pass_bugs) == 0:358                continue359            at_least_one_bug_found = True360            row = []361            table_di_var += "  </tr>\n"362            # Get the bugs info.363            for x in per_pass_bugs:364                row.append("    <tr>\n")365                row.append(file)366                row.append(llvm_pass)367                row.append(x.name)368                row.append(x.fn_name)369                row.append(x.action)370                row.append("    </tr>\n")371            # Dump the bugs info into the table.372            for column in row:373                # The same file-pass pair can have multiple bugs.374                if column == "    <tr>\n" or column == "    </tr>\n":375                    table_di_var += column376                    continue377                table_di_var += "    <td>{0}</td>\n".format(column.strip())378            table_di_var += "  <tr>\n"379 380    if not at_least_one_bug_found:381        table_di_var += """<tr>382        <td colspan='4'> No bugs found </td>383      </tr>384    """385    table_di_var += "</table>\n"386 387    # Create the summary table for the sp bugs.388    table_title_di_var_sum = "Summary of Variable Location Bugs"389    table_di_var_sum = """<table>390  <caption><b>{}</b></caption>391  <tr>392  """.format(393        table_title_di_var_sum394    )395 396    header_di_var_sum = ["LLVM Pass Name", "Number of bugs"]397 398    for column in header_di_var_sum:399        table_di_var_sum += "    <th>{0}</th>\n".format(column.strip())400    table_di_var_sum += "  </tr>\n"401 402    # Print the summary.403    row = []404    for llvm_pass, num in sorted(di_var_bugs_summary.items()):405        row.append("    <tr>\n")406        row.append(llvm_pass)407        row.append(str(num))408        row.append("    </tr>\n")409    for column in row:410        if column == "    <tr>\n" or column == "    </tr>\n":411            table_di_var_sum += column412            continue413        table_di_var_sum += "    <td>{0}</td>\n".format(column.strip())414    table_di_var_sum += "  <tr>\n"415 416    if not at_least_one_bug_found:417        table_di_var_sum += """<tr>418        <td colspan='2'> No bugs found </td>419      </tr>420    """421    table_di_var_sum += "</table>\n"422 423    # Finish the html page.424    html_footer = """</body>425  </html>"""426 427    new_line = "<br>\n"428 429    fileout.writelines(html_header)430    fileout.writelines(table_di_loc)431    fileout.writelines(new_line)432    fileout.writelines(table_di_loc_sum)433    fileout.writelines(new_line)434    fileout.writelines(new_line)435    fileout.writelines(table_di_sp)436    fileout.writelines(new_line)437    fileout.writelines(table_di_sp_sum)438    fileout.writelines(new_line)439    fileout.writelines(new_line)440    fileout.writelines(table_di_var)441    fileout.writelines(new_line)442    fileout.writelines(table_di_var_sum)443    fileout.writelines(html_footer)444    fileout.close()445 446    print("The " + html_file + " generated.")447 448 449# Read the JSON file in chunks.450def get_json_chunk(file, start, size):451    json_parsed = None452    di_checker_data = []453    skipped_lines = 0454    line = 0455 456    # The file contains json object per line.457    # An example of the line (formatted json):458    # {459    #  "file": "simple.c",460    #  "pass": "Deduce function attributes in RPO",461    #  "bugs": [462    #    [463    #      {464    #        "action": "drop",465    #        "metadata": "DISubprogram",466    #        "name": "fn2"467    #      },468    #      {469    #        "action": "drop",470    #        "metadata": "DISubprogram",471    #        "name": "fn1"472    #      }473    #    ]474    #  ]475    # }476    with open(file) as json_objects_file:477        for json_object_line in json_objects_file:478            line += 1479            if line < start:480                continue481            if line >= start + size:482                break483            try:484                json_object = loads(json_object_line)485            except:486                skipped_lines += 1487            else:488                di_checker_data.append(json_object)489 490    return (di_checker_data, skipped_lines, line)491 492 493# Parse the program arguments.494def parse_program_args(parser):495    parser.add_argument("file_name", type=str, help="json file to process")496    parser.add_argument(497        "--reduce",498        action="store_true",499        help="create reduced report by deduplicating bugs within and across files",500    )501 502    report_type_group = parser.add_mutually_exclusive_group(required=True)503    report_type_group.add_argument(504        "--report-html-file", type=str, help="output HTML file for the generated report"505    )506    report_type_group.add_argument(507        "--acceptance-test",508        action="store_true",509        help="if set, produce terminal-friendly output and return 0 iff the input file is empty or does not exist",510    )511 512    return parser.parse_args()513 514 515def Main():516    parser = argparse.ArgumentParser()517    opts = parse_program_args(parser)518 519    if opts.report_html_file is not None and not opts.report_html_file.endswith(520        ".html"521    ):522        print("error: The output file must be '.html'.")523        sys.exit(1)524 525    if opts.acceptance_test:526        if os.path.isdir(opts.file_name):527            print(f"error: Directory passed as input file: '{opts.file_name}'")528            sys.exit(1)529        if not os.path.exists(opts.file_name):530            # We treat an empty input file as a success, as debugify will generate an output file iff any errors are531            # found, meaning we expect 0 errors to mean that the expected file does not exist.532            print(f"No errors detected for: {opts.file_name}")533            sys.exit(0)534 535    # Use the defaultdict in order to make multidim dicts.536    di_location_bugs = defaultdict(lambda: defaultdict(list))537    di_subprogram_bugs = defaultdict(lambda: defaultdict(list))538    di_variable_bugs = defaultdict(lambda: defaultdict(list))539 540    # Use the ordered dict to make a summary.541    di_location_bugs_summary = OrderedDict()542    di_sp_bugs_summary = OrderedDict()543    di_var_bugs_summary = OrderedDict()544 545    # If we are using --reduce, use these sets to deduplicate similar bugs within and across files.546    di_loc_reduced_set = set()547    di_sp_reduced_set = set()548    di_var_reduced_set = set()549 550    start_line = 0551    chunk_size = 1000000552    end_line = chunk_size - 1553    skipped_lines = 0554    skipped_bugs = 0555    # Process each chunk of 1 million JSON lines.556    while True:557        if start_line > end_line:558            break559        (debug_info_bugs, skipped, end_line) = get_json_chunk(560            opts.file_name, start_line, chunk_size561        )562        start_line += chunk_size563        skipped_lines += skipped564 565        # Map the bugs into the file-pass pairs.566        for bugs_per_pass in debug_info_bugs:567            try:568                bugs_file = bugs_per_pass["file"]569                bugs_pass = bugs_per_pass["pass"]570                bugs = bugs_per_pass["bugs"][0]571            except:572                skipped_lines += 1573                continue574 575            di_loc_bugs = di_location_bugs.get("bugs_file", {}).get("bugs_pass", [])576            di_sp_bugs = di_subprogram_bugs.get("bugs_file", {}).get("bugs_pass", [])577            di_var_bugs = di_variable_bugs.get("bugs_file", {}).get("bugs_pass", [])578 579            # Omit duplicated bugs.580            di_loc_set = set()581            di_sp_set = set()582            di_var_set = set()583            for bug in bugs:584                try:585                    bugs_metadata = bug["metadata"]586                except:587                    skipped_bugs += 1588                    continue589 590                if bugs_metadata == "DILocation":591                    try:592                        origin = bug.get("origin")593                        action = bug["action"]594                        bb_name = bug["bb-name"]595                        fn_name = bug["fn-name"]596                        instr = bug["instr"]597                    except:598                        skipped_bugs += 1599                        continue600                    di_loc_bug = DILocBug(origin, action, bb_name, fn_name, instr)601                    if not di_loc_bug.key() in di_loc_set:602                        di_loc_set.add(di_loc_bug.key())603                        if opts.reduce:604                            reduced_key = di_loc_bug.reduced_key(bugs_pass)605                            if not reduced_key in di_loc_reduced_set:606                                di_loc_reduced_set.add(reduced_key)607                                di_loc_bugs.append(di_loc_bug)608                        else:609                            di_loc_bugs.append(di_loc_bug)610 611                    # Fill the summary dict.612                    if bugs_pass in di_location_bugs_summary:613                        di_location_bugs_summary[bugs_pass] += 1614                    else:615                        di_location_bugs_summary[bugs_pass] = 1616                elif bugs_metadata == "DISubprogram":617                    try:618                        action = bug["action"]619                        name = bug["name"]620                    except:621                        skipped_bugs += 1622                        continue623                    di_sp_bug = DISPBug(action, name)624                    if not di_sp_bug.key() in di_sp_set:625                        di_sp_set.add(di_sp_bug.key())626                        if opts.reduce:627                            reduced_key = di_sp_bug.reduced_key(bugs_pass)628                            if not reduced_key in di_sp_reduced_set:629                                di_sp_reduced_set.add(reduced_key)630                                di_sp_bugs.append(di_sp_bug)631                        else:632                            di_sp_bugs.append(di_sp_bug)633 634                    # Fill the summary dict.635                    if bugs_pass in di_sp_bugs_summary:636                        di_sp_bugs_summary[bugs_pass] += 1637                    else:638                        di_sp_bugs_summary[bugs_pass] = 1639                elif bugs_metadata == "dbg-var-intrinsic":640                    try:641                        action = bug["action"]642                        fn_name = bug["fn-name"]643                        name = bug["name"]644                    except:645                        skipped_bugs += 1646                        continue647                    di_var_bug = DIVarBug(action, name, fn_name)648                    if not di_var_bug.key() in di_var_set:649                        di_var_set.add(di_var_bug.key())650                        if opts.reduce:651                            reduced_key = di_var_bug.reduced_key(bugs_pass)652                            if not reduced_key in di_var_reduced_set:653                                di_var_reduced_set.add(reduced_key)654                                di_var_bugs.append(di_var_bug)655                        else:656                            di_var_bugs.append(di_var_bug)657 658                    # Fill the summary dict.659                    if bugs_pass in di_var_bugs_summary:660                        di_var_bugs_summary[bugs_pass] += 1661                    else:662                        di_var_bugs_summary[bugs_pass] = 1663                else:664                    # Unsupported metadata.665                    skipped_bugs += 1666                    continue667 668            if di_loc_bugs:669                di_location_bugs[bugs_file][bugs_pass] = di_loc_bugs670            if di_sp_bugs:671                di_subprogram_bugs[bugs_file][bugs_pass] = di_sp_bugs672            if di_var_bugs:673                di_variable_bugs[bugs_file][bugs_pass] = di_var_bugs674 675    if opts.report_html_file is not None:676        generate_html_report(677            di_location_bugs,678            di_subprogram_bugs,679            di_variable_bugs,680            di_location_bugs_summary,681            di_sp_bugs_summary,682            di_var_bugs_summary,683            opts.report_html_file,684        )685    else:686        # Pretty(ish) print the detected bugs, but check if any exist first so that we don't print an empty dict.687        if di_location_bugs:688            print_bugs_yaml("DILocation Bugs", di_location_bugs)689        if di_subprogram_bugs:690            print_bugs_yaml("DISubprogram Bugs", di_subprogram_bugs)691        if di_variable_bugs:692            print_bugs_yaml("DIVariable Bugs", di_variable_bugs)693 694    if opts.acceptance_test:695        if any((di_location_bugs, di_subprogram_bugs, di_variable_bugs)):696            # Add a newline gap after printing at least one error.697            print()698            print(f"Errors detected for: {opts.file_name}")699            sys.exit(1)700        else:701            print(f"No errors detected for: {opts.file_name}")702 703    if skipped_lines > 0:704        print("Skipped lines: " + str(skipped_lines))705    if skipped_bugs > 0:706        print("Skipped bugs: " + str(skipped_bugs))707 708 709if __name__ == "__main__":710    Main()711    sys.exit(0)712