src/etc/lldb_batchmode/check_lldb.py PYTHON 490 lines View on github.com → Search inside
1"""Contains the logic that compares variables to `INPUT_DATA` via the entrypoint2`check(var_name, breakpoint_idx, frame)`. These comparisons report errors to stdout, and then return3a `Result` indicating whether or not the variable matched.45Checks *do not* stop after the first encountered error. Some redundant information may be ommitted6(e.g. checking pretty printed type name if the synthetic isn't properly attached to the type).7"""89import sys10import traceback11from typing import Any, Callable1213import lldb1415from .common import (16    BLESS,17    INPUT_DATA,18    ArrayChild,19    ArrayLikeChildren,20    Child,21    Result,22    Variable,23    print_error,24    print_mismatch,25)26from .from_lldb import (27    BasicType,28    TypeClass,29    bless_variable,30    get_generics,31    type_from_lldb,32    variable_from_lldb,33)3435VARS_TESTED: list[dict[str, Result]] = []36"""Used to help ensure all expected variables were tested. Each element of the list corresponds to a37breakpoint, and contains a set of all of the variable names tested for that breakpoint."""383940def check(var_name: str, breakpoint_idx: int, frame: lldb.SBFrame) -> Result:41    """`lldb-repr` pseudo-command entrypoint. Checks the variable against `INPUT_DATA` for the given42    frame at the given breakpoint.43    """4445    if BLESS:46        print(f"blessing var {var_name}")47        bless_variable(INPUT_DATA, var_name, breakpoint_idx, frame)4849    # Even if we're blessing, we still want to run the variable through the test to make sure we're50    # not somehow saving invalid information5152    valobj: lldb.SBValue = frame.var(var_name)53    if not valobj.IsValid():54        print_error(var_name, "Unable to find variable")55        return Result.Mismatch5657    var = variable_from_lldb(valobj)5859    try:60        expected = INPUT_DATA.breakpoints[breakpoint_idx][var_name]61    except IndexError:62        print_error("INPUT_DATA", f"No data found for breakpoint #{breakpoint_idx}")63        return Result.Mismatch64    except KeyError:65        print_error(66            "INPUT_DATA",67            f"No data found for var '{var_name}' at breakpoint #{breakpoint_idx}",68        )69        return Result.Mismatch7071    result = var_matches(var, expected, valobj)72    # --bless outputs blank breakpoints for any breakpoints with no variables, so we need to account73    # for that here74    if len(VARS_TESTED) <= breakpoint_idx:75        VARS_TESTED.extend({} for _ in range(1 + breakpoint_idx - len(VARS_TESTED)))7677    VARS_TESTED[breakpoint_idx][var_name] = result7879    if result == Result.Ok:80        print(f"{var_name}: Ok")8182    return result838485TYPES_TESTED: dict[str, Result] = {}86"""Since types are unique and unchanging, we only need to test each type once. This also helps87ensure we have tested all types in `INPUT_DATA`88"""899091def type_matches(92    sbtype: lldb.SBType, sbtarget: lldb.SBTarget, provider_ok: bool = False93) -> Result:94    """Checks a type and all field/generic types (recursively) against the data contained in95    `INPUT_DATA`."""96    name: str = sbtype.GetName()97    error_source = f"type '{name}'"9899    if (r := TYPES_TESTED.get(name)) is not None:100        # The proper result was returned the first time the type was tested, so we can just pretend101        # everything we've already seen has succeeded.102        if not r:103            print_error(104                f"type '{name}'", f"mismatch (see prior output for type '{name}')"105            )106        return r107108    ty = type_from_lldb(sbtype, sbtarget)109110    expected = INPUT_DATA.types.get(name)111112    if expected is None:113        result = Result.Mismatch114        print_error(f"type '{name}'", "type not found in input data")115    else:116        basic_type_result = (117            Result.Ok if ty.basic_type == expected.basic_type else Result.Mismatch118        )119        if basic_type_result == Result.Mismatch:120            print_mismatch(121                error_source,122                "basic_type (lldb.eBasicType)",123                f"{ty.basic_type} ({BasicType(ty.basic_type)})",124                f"{expected.basic_type} ({BasicType(expected.basic_type)})",125            )126127        type_class_result = (128            Result.Ok if ty.type_class == expected.type_class else Result.Mismatch129        )130131        if type_class_result == Result.Mismatch:132            print_mismatch(133                error_source,134                "type_class (lldb.eTypeClass)",135                f"{ty.type_class} ({TypeClass(ty.type_class).name})",136                f"{expected.type_class} ({TypeClass(expected.type_class).name})",137            )138139        ty_result = ty.matches(expected, name, provider_ok)140141        result = type_class_result and ty_result142143    TYPES_TESTED[name] = result144145    fields: list[lldb.SBTypeMember] = sbtype.fields146    inner_types = [f.GetType() for f in fields]147    inner_types.extend(get_generics(sbtype, sbtarget))148149    for t in inner_types:150        result = type_matches(t, sbtarget) and result151152    return result153154155def tested_all_types() -> bool:156    """Returns true if all types in INPUT_DATA were tested this run."""157158    expected_types = set(INPUT_DATA.types)159    untested_types = expected_types.difference(TYPES_TESTED.keys())160161    if len(untested_types) != 0:162        print(163            f"[repr error] The following types were expected, but were not tested:\n\164  {untested_types}"165        )166167    return len(untested_types) == 0168169170def tested_all_variables() -> bool:171    expected_vars = [set(vars) for vars in INPUT_DATA.breakpoints]172    untested_vars = [173        expected.difference(tested.keys())174        for expected, tested in zip(expected_vars, VARS_TESTED)175    ]176177    tested_not_expected = [178        set(tested.keys()).difference(expected)179        for expected, tested in zip(expected_vars, VARS_TESTED)180    ]181182    result = True183184    for i, v in enumerate(untested_vars):185        if len(v) == 0:186            continue187188        result = False189        print(190            f"[repr error] The following variables were expected at breakpoint#{i}, but were not \191tested:\n  {v}"192        )193194    for i, v in enumerate(tested_not_expected):195        if len(v) == 0:196            continue197198        result = False199        print(200            f"[repr error] The following variables were tested, but do not exist in the input data \201at breakpoint#{i}:\n  {v}"202        )203204    return result205206207def var_matches(var: Variable, expected: Variable, valobj: lldb.SBValue) -> Result:208    # Happy path requires very little intercession from us. We keep these values on the stack209    # so we don't have to recalculate them if we need to do error handling210    summary_ok = var.summary == expected.summary211    synthetic_ok = var.synthetic == expected.synthetic212    pretty_type_name_ok = var.pretty_type_name == expected.pretty_type_name213    pretty_print_ok = var.pretty_print == expected.pretty_print214    format_ok = var.format == expected.format215216    type_ok = var.type == expected.type217218    if var.has_visualizer() or expected.has_visualizer():219        type_match_ok = type_matches(220            valobj.GetType(),221            valobj.GetTarget(),222            summary_ok223            & synthetic_ok224            & format_ok225            & pretty_type_name_ok226            & pretty_print_ok,227        )228    else:229        type_match_ok = Result.Ok230231    value_ok = var.value == expected.value232233    work_list = [valobj.GetChildAtIndex(i) for i in range(valobj.GetNumChildren())]234    target = valobj.GetTarget()235    child_types_ok = True236237    while len(work_list) != 0:238        obj = work_list.pop()239240        for i in range(obj.GetNumChildren()):241            child = obj.GetChildAtIndex(i)242            # We don't need to report an error for invalid children here. Invalid objects can't be243            # blessed, thus should never exist in INPUT_DATA. That means they will always report244            # as a mismatch in `children_match`245            if child.IsValid():246                work_list.append(child)247            else:248                child_types_ok = False249250        if var.has_visualizer() or expected.has_visualizer():251            child_types_ok &= type_matches(obj.GetType(), target) == Result.Ok252        else:253            type_match_ok = Result.Ok254255    children_ok = children_match(256        var.children, expected.children, valobj.GetName(), valobj257    )258259    if (260        type_ok261        and type_match_ok262        and pretty_type_name_ok263        and pretty_print_ok264        and value_ok265        and synthetic_ok266        and summary_ok267        and format_ok268        and children_ok269        and child_types_ok270    ):271        return Result.Ok272273    error_source = f"var '{valobj.GetName()}'"274275    # otherwise, we want to output exactly what doesn't match276    # and any additional helpful information277278    # We check the type first. If this has changed, it's relatively likely nothing else will work279    # properly280    if not type_ok:281        print_mismatch(282            error_source,283            "type (Type Name)",284            var.type,285            expected.type,286        )287288    # We check the summary next since it's the most user-visible output. We don't need to check289    # `pretty_print` if the summary provider doesn't match.290    if not summary_ok:291        print_mismatch(292            error_source, "summary (Summary Provider)", var.summary, expected.summary293        )294    elif not pretty_print_ok:295        print_mismatch(296            error_source,297            "pretty_print (Summary Output)",298            var.pretty_print,299            expected.pretty_print,300        )301302        # try the summary provider directly to see if it's throwing an exception303        if var.summary is not None:304            try:305                provider = get_provider(var.summary)306                _ = provider(valobj, {})307            except Exception as e:308                print_error(309                    error_source + " Summary",310                    "Error while running Summary \311provider:",312                )313                traceback.print_exception(type(e), e, e.__traceback__, file=sys.stdout)314315    # Next we check the value and formatter. These mostly affect primitives.316    if not value_ok:317        print_mismatch(error_source, "value", var.value, expected.value)318    if not format_ok:319        print_mismatch(error_source, "format", var.format, expected.format)320321    # Synthetic is checked next since children, pretty type name, and pretty print rely on it. If322    # the synthetic doesn't match, we can assume those won't match either.323    if not synthetic_ok:324        print_mismatch(325            error_source,326            "synthetic (Synthetic Provider)",327            var.synthetic,328            expected.synthetic,329        )330    else:331        if not pretty_type_name_ok:332            print_mismatch(333                error_source,334                f"pretty_type_name ({var.synthetic}.get_type_name)",335                var.pretty_type_name,336                expected.pretty_type_name,337            )338339        if not children_ok and var.synthetic is not None:340            # If the children don't match, we can check for more catastrophic failures using the341            # synthetic provider. All the per-children errors will have been printed in the342            # `children_match` check above.343            try:344                synth_provider = get_provider(var.synthetic)345346                # First we check for exceptions in the constructor and initialization347                synth: lldb.SBSyntheticValueProvider = synth_provider(348                    valobj.GetNonSyntheticValue(), {}349                )350                synth.update()351352                # If the `get_child_at_index` function doesn't exist, there's not much more we353                # can do354                if getattr(synth, "get_child_at_index", None) is not None:355                    # If all the children are invalid (e.g. because a template arg isn't356                    # resolving correctly, incorrect enum discriminant), we should dump the357                    # internal state of the synthetic358                    if not all(359                        synth.get_child_at_index(i).IsValid()360                        for i in range(synth.num_children())361                    ):362                        dump_synthetic_state(synth)363364            except Exception as e:365                print_error(366                    error_source + " Synthetic",367                    "Error while running Synthetic\368Provider:",369                )370                traceback.print_exception(type(e), e, e.__traceback__, file=sys.stdout)371372    return Result.Mismatch373374375def dump_synthetic_state(synth: Any):376    """Prints an object via builtin `vars()`. If `obj.__dict__` does not exist because the object is377    using `__slots__` intsead, the `__slots__` are converted into a dict and printed."""378    if (getattr(synth, "__dict__", None)) is not None:379        fields = vars(synth)380    elif (slots := getattr(synth, "__slots__", None)) is not None:381        fields = {name: getattr(synth, name, None) for name in slots}382    else:383        # Shouldn't be possible, but better safe than sorry384        print("Unable to print Synthetic Provider state")385        return386387    print(f"Synthetic Provider state:\n  {fields})")388389390def children_match(391    children: list[Child],392    expected: list[Child],393    path: str,394    valobj: lldb.SBValue,395) -> Result:396    """Recursively checks children against an expected value and prints errors for mismatches."""397398    result = Result.Ok if len(children) == len(expected) else Result.Mismatch399400    mismatches = []401    missing = []402    invalid_count = 0403404    for i in range(len(expected)):405        exp = expected[i]406407        if i >= len(children):408            missing.append(exp.name)409            continue410411        got = children[i]412413        if isinstance(children, ArrayLikeChildren):414            if isinstance(got, ArrayChild):415                got = Child(f"[{i}]", children.type, got.value, got.children)416            else:417                got = Child(f"[{i}]", children.type, got, [])418419        if isinstance(expected, ArrayLikeChildren):420            if isinstance(exp, ArrayChild):421                exp = Child(f"[{i}]", expected.type, exp.value, exp.children)422            else:423                exp = Child(f"[{i}]", expected.type, exp, [])424425        if got.name is None:426            result = Result.Mismatch427            invalid_count += 1428            mismatches.append(429                f"{exp.name}: {exp.type} = {exp.value} -> <Invalid SBValue>"430            )431        elif got.name != exp.name or got.type != exp.type or got.value != exp.value:432            result = Result.Mismatch433            mismatches.append(434                f"{exp.name}: {exp.type} = {exp.value} -> {got.name}: {got.type} = {got.value}"435            )436        # no point recursing into children if we've already mismatched437        elif exp.children is not None and len(exp.children) > 0:438            result &= children_match(439                got.children,440                exp.children,441                f"{path}.{exp.name}",442                valobj.GetChildAtIndex(i),443            )444445    if result == Result.Ok:446        return result447448    # If every single child is invalid, we can condense the output a lot by pointing to the449    # synthetic instead of printing a bunch of identical mismatches450    if invalid_count == len(children):451        print_error(452            path,453            f"All children of this object are invalid SBValue objects.\n    This is \454almost always caused by invalid state or logic in the SyntheticProvider.\n    This object's \455synthetic appears to be '{valobj.GetTypeSynthetic().GetData()}'",456        )457    elif len(mismatches) != 0:458        error_str = "\n    ".join(mismatches)459        print_error(460            path,461            f"The following children do not match (expected -> got):\n    {error_str}",462        )463    elif len(missing) != 0:464        error_str = ", ".join(missing)465        print_error(466            path,467            f"The following children were expected, but were not found:\n    {error_str}",468        )469    elif len(children) > len(expected):470        error_str = "\n    ".join(471            f"{got.name}: {got.type} = {got.value}" for got in children[len(expected) :]472        )473        print_error(474            path,475            f"The following children were found, but were not expected:\n    {error_str}",476        )477478    return result479480481def get_provider(provider_str: str) -> Callable[[lldb.SBValue, dict[Any, Any]], Any]:482    """Given a Varible.summary or Variable.Synthetic, imports the appropriate module and returns the483    matching Class/Function"""484    import importlib485486    [module, summary_name] = provider_str.split(".", 1)487    provider_module = importlib.import_module(module)488489    return getattr(provider_module, summary_name)

Code quality findings 16

Use logging module for better control and configurability
print-statement
print(f"blessing var {var_name}")
Use logging module for better control and configurability
print-statement
print(f"{var_name}: Ok")
Ensure functions have docstrings for documentation
missing-docstring
def type_matches(
Use logging module for better control and configurability
print-statement
print(
Ensure functions have docstrings for documentation
missing-docstring
def tested_all_variables() -> bool:
Use logging module for better control and configurability
print-statement
print(
Use logging module for better control and configurability
print-statement
print(
Ensure functions have docstrings for documentation
missing-docstring
def var_matches(var: Variable, expected: Variable, valobj: lldb.SBValue) -> Result:
Ensure try blocks have corresponding except or finally blocks
try-without-except
try:
Use logging module for better control and configurability
print-statement
print("Unable to print Synthetic Provider state")
Use logging module for better control and configurability
print-statement
print(f"Synthetic Provider state:\n {fields})")
Ensure functions have docstrings for documentation
missing-docstring
def children_match(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(children, ArrayLikeChildren):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(got, ArrayChild):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(expected, ArrayLikeChildren):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(exp, ArrayChild):

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.