llvm/llvm/test/DebugInfo/Inputs/check-fake-use.py

#!/usr/bin/python3

# Parsing dwarfdump's output to determine whether the location list for the
# parameter "b" covers all of the function. The script searches for information
# in the input file to determine the [prologue, epilogue) range for the
# function, the location list range for "b", and checks that the latter covers
# the entirety of the former.
import re
import sys

DebugInfoPattern = r"\.debug_info contents:"
DebugLinePattern = r"\.debug_line contents:"
ProloguePattern = r"^\s*0x([0-9a-f]+)\s.+prologue_end"
EpiloguePattern = r"^\s*0x([0-9a-f]+)\s.+epilogue_begin"
FormalPattern = r"^0x[0-9a-f]+:\s+DW_TAG_formal_parameter"
LocationPattern = r"DW_AT_location\s+\[DW_FORM_([a-z_]+)\](?:.*0x([a-f0-9]+))"
DebugLocPattern = r'\[0x([a-f0-9]+),\s+0x([a-f0-9]+)\) ".text": (.+)$'

SeenDebugInfo = False
SeenDebugLine = False
LocationRanges = None
PrologueEnd = None
EpilogueBegin = None

# The dwarfdump output should contain the DW_AT_location for "b" first, then the
# line table which should contain prologue_end and epilogue_begin entries.
with open(sys.argv[1], "r") as dwarf_dump_file:
    dwarf_iter = iter(dwarf_dump_file)
    for line in dwarf_iter:
        if not SeenDebugInfo and re.match(DebugInfoPattern, line):
            SeenDebugInfo = True
        if not SeenDebugLine and re.match(DebugLinePattern, line):
            SeenDebugLine = True
        # Get the range of DW_AT_location for "b".
        if LocationRanges is None:
            if match := re.match(FormalPattern, line):
                # Go until we either find DW_AT_location or reach the end of this entry.
                location_match = None
                while location_match is None:
                    if (line := next(dwarf_iter, "")) == "\n":
                        raise RuntimeError(
                            ".debug_info output is missing DW_AT_location for 'b'"
                        )
                    location_match = re.search(LocationPattern, line)
                # Variable has whole-scope location, represented by an empty tuple.
                if location_match.group(1) == "exprloc":
                    LocationRanges = ()
                    continue
                if location_match.group(1) != "sec_offset":
                    raise RuntimeError(
                        f"Unhandled form for DW_AT_location: DW_FORM_{location_match.group(1)}"
                    )
                # Variable has location range list.
                if (
                    debug_loc_match := re.search(DebugLocPattern, next(dwarf_iter, ""))
                ) is None:
                    raise RuntimeError(f"Invalid location range list for 'b'")
                LocationRanges = (
                    int(debug_loc_match.group(1), 16),
                    int(debug_loc_match.group(2), 16),
                )
                while (
                    debug_loc_match := re.search(DebugLocPattern, next(dwarf_iter, ""))
                ) is not None:
                    match_loc_start = int(debug_loc_match.group(1), 16)
                    match_loc_end = int(debug_loc_match.group(2), 16)
                    match_expr = debug_loc_match.group(3)
                    if match_loc_start != LocationRanges[1]:
                        raise RuntimeError(
                            f"Location list for 'b' is discontinuous from [0x{LocationRanges[1]:x}, 0x{match_loc_start:x})"
                        )
                    if "stack_value" in match_expr:
                        raise RuntimeError(
                            f"Location list for 'b' contains a stack_value expression: {match_expr}"
                        )
                    LocationRanges = (LocationRanges[0], match_loc_end)
        # Get the prologue_end address.
        elif PrologueEnd is None:
            if match := re.match(ProloguePattern, line):
                PrologueEnd = int(match.group(1), 16)
        # Get the epilogue_begin address.
        elif EpilogueBegin is None:
            if match := re.match(EpiloguePattern, line):
                EpilogueBegin = int(match.group(1), 16)
                break

if not SeenDebugInfo:
    raise RuntimeError(".debug_info section not found.")
if not SeenDebugLine:
    raise RuntimeError(".debug_line section not found.")

if LocationRanges is None:
    raise RuntimeError(".debug_info output is missing parameter 'b'")
if PrologueEnd is None:
    raise RuntimeError(".debug_line output is missing prologue_end")
if EpilogueBegin is None:
    raise RuntimeError(".debug_line output is missing epilogue_begin")

if len(LocationRanges) == 2 and (
    LocationRanges[0] > PrologueEnd or LocationRanges[1] < EpilogueBegin
):
    raise RuntimeError(
        f"""Location list for 'b' does not cover the whole function:")
    Prologue to Epilogue = [0x{PrologueEnd:x}, 0x{EpilogueBegin:x})
    Location range = [0x{LocationRanges[0]:x}, 0x{LocationRanges[1]:x})
"""
    )