diff --git a/src/etc/lldb_batchmode/check_lldb.py b/src/etc/lldb_batchmode/check_lldb.py new file mode 100644 index 0000000000000..ec51fe3153c1d --- /dev/null +++ b/src/etc/lldb_batchmode/check_lldb.py @@ -0,0 +1,467 @@ +"""Contains the logic that compares variables to `INPUT_DATA` via the entrypoint +`check(var_name, breakpoint_idx, frame)`. These comparisons report errors to stdout, and then return +a `Result` indicating whether or not the variable matched. + +Checks *do not* stop after the first encountered error. Some redundant information may be ommitted +(e.g. checking pretty printed type name if the synthetic isn't properly attached to the type). +""" + +from typing import Any, Callable +import traceback +import sys + +import lldb +from .common import ( + BLESS, + INPUT_DATA, + Child, + Variable, + Result, + print_error, + print_mismatch, +) +from .from_lldb import ( + BasicType, + TypeClass, + bless_variable, + variable_from_lldb, + type_from_lldb, + get_generics, +) + + +VARS_TESTED: list[dict[str, Result]] = [] +"""Used to help ensure all expected variables were tested. Each element of the list corresponds to a +breakpoint, and contains a set of all of the variable names tested for that breakpoint.""" + + +def check(var_name: str, breakpoint_idx: int, frame: lldb.SBFrame) -> Result: + """`lldb-repr` pseudo-command entrypoint. Checks the variable against `INPUT_DATA` for the given + frame at the given breakpoint. + """ + + if BLESS: + print(f"blessing var {var_name}") + bless_variable(INPUT_DATA, var_name, breakpoint_idx, frame) + + # Even if we're blessing, we still want to run the variable through the test to make sure we're + # not somehow saving invalid information + + valobj: lldb.SBValue = frame.var(var_name) + if not valobj.IsValid(): + print_error(var_name, "Unable to find variable") + return Result.Mismatch + + var = variable_from_lldb(valobj) + + try: + expected = INPUT_DATA.breakpoints[breakpoint_idx][var_name] + except IndexError: + print_error("INPUT_DATA", f"No data found for breakpoint #{breakpoint_idx}") + return Result.Mismatch + except KeyError: + print_error( + "INPUT_DATA", + f"No data found for var '{var_name}' at breakpoint #{breakpoint_idx}", + ) + return Result.Mismatch + + result = var_matches(var, expected, valobj) + # --bless outputs blank breakpoints for any breakpoints with no variables, so we need to account + # for that here + if len(VARS_TESTED) <= breakpoint_idx: + VARS_TESTED.extend({} for _ in range(1 + breakpoint_idx - len(VARS_TESTED))) + + VARS_TESTED[breakpoint_idx][var_name] = result + + if result == Result.Ok: + print(f"{var_name}: Ok") + + return result + + +TYPES_TESTED: dict[str, Result] = {} +"""Since types are unique and unchanging, we only need to test each type once. This also helps +ensure we have tested all types in `INPUT_DATA` +""" + + +def type_matches( + sbtype: lldb.SBType, sbtarget: lldb.SBTarget, provider_ok: bool = False +) -> Result: + """Checks a type and all field/generic types (recursively) against the data contained in + `INPUT_DATA`.""" + name: str = sbtype.GetName() + error_source = f"type '{name}'" + + if (r := TYPES_TESTED.get(name)) is not None: + # The proper result was returned the first time the type was tested, so we can just pretend + # everything we've already seen has succeeded. + if not r: + print_error( + f"type '{name}'", f"mismatch (see prior output for type '{name}')" + ) + return r + + ty = type_from_lldb(sbtype, sbtarget) + + expected = INPUT_DATA.types.get(name) + + if expected is None: + result = Result.Mismatch + print_error(f"type '{name}'", "type not found in input data") + else: + basic_type_result = ( + Result.Ok if ty.basic_type == expected.basic_type else Result.Mismatch + ) + if basic_type_result == Result.Mismatch: + print_mismatch( + error_source, + "basic_type (lldb.eBasicType)", + f"{ty.basic_type} ({BasicType(ty.basic_type)})", + f"{expected.basic_type} ({BasicType(expected.basic_type)})", + ) + + type_class_result = ( + Result.Ok if ty.type_class == expected.type_class else Result.Mismatch + ) + + if type_class_result == Result.Mismatch: + print_mismatch( + error_source, + "type_class (lldb.eTypeClass)", + f"{ty.type_class} ({TypeClass(ty.type_class).name})", + f"{expected.type_class} ({TypeClass(expected.type_class).name})", + ) + + ty_result = ty.matches(expected, name, provider_ok) + + result = basic_type_result and type_class_result and ty_result + + TYPES_TESTED[name] = result + + fields: list[lldb.SBTypeMember] = sbtype.fields + inner_types = [f.GetType() for f in fields] + inner_types.extend(get_generics(sbtype, sbtarget)) + + for t in inner_types: + result = type_matches(t, sbtarget) and result + + return result + + +def tested_all_types() -> bool: + """Returns true if all types in INPUT_DATA were tested this run.""" + + expected_types = set(k for k in INPUT_DATA.types) + untested_types = expected_types.difference(TYPES_TESTED.keys()) + + if len(untested_types) != 0: + print( + f"[repr error] The following types were expected, but were not tested:\n\ + {untested_types}" + ) + + return len(untested_types) == 0 + + +def tested_all_variables() -> bool: + expected_vars = [set(k for k in vars) for vars in INPUT_DATA.breakpoints] + untested_vars = [ + expected.difference(tested.keys()) + for expected, tested in zip(expected_vars, VARS_TESTED) + ] + + tested_not_expected = [ + set(tested.keys()).difference(expected) + for expected, tested in zip(expected_vars, VARS_TESTED) + ] + + result = True + + for i, v in enumerate(untested_vars): + if len(v) == 0: + continue + + result = False + print( + f"[repr error] The following variables were expected at breakpoint#{i}, but were not \ +tested:\n {v}" + ) + + for i, v in enumerate(tested_not_expected): + if len(v) == 0: + continue + + result = False + print( + f"[repr error] The following variables were tested, but do not exist in the input data \ +at breakpoint#{i}:\n {v}" + ) + + return result + + +def var_matches(var: Variable, expected: Variable, valobj: lldb.SBValue) -> Result: + # Happy path requires very little intercession from us. We keep these values on the stack + # so we don't have to recalculate them if we need to do error handling + summary_ok = var.summary == expected.summary + synthetic_ok = var.synthetic == expected.synthetic + pretty_type_name_ok = var.pretty_type_name == expected.pretty_type_name + pretty_print_ok = var.pretty_print == expected.pretty_print + format_ok = var.format == expected.format + + type_ok = var.type == expected.type + type_match_ok = type_matches( + valobj.GetType(), + valobj.GetTarget(), + summary_ok & synthetic_ok & format_ok & pretty_type_name_ok & pretty_print_ok, + ) + + value_ok = var.value == expected.value + + work_list = [valobj.GetChildAtIndex(i) for i in range(valobj.GetNumChildren())] + target = valobj.GetTarget() + child_types_ok = True + + while len(work_list) != 0: + obj = work_list.pop() + + for i in range(obj.GetNumChildren()): + child = obj.GetChildAtIndex(i) + # We don't need to report an error for invalid children here. Invalid objects can't be + # blessed, thus should never exist in INPUT_DATA. That means they will always report + # as a mismatch in `children_match` + if child.IsValid(): + work_list.append(child) + else: + child_types_ok = False + + child_types_ok &= type_matches(obj.GetType(), target) == Result.Ok + + children_ok = children_match( + var.children, expected.children, valobj.GetName(), valobj + ) + + if ( + type_ok + and type_match_ok + and pretty_type_name_ok + and pretty_print_ok + and value_ok + and synthetic_ok + and summary_ok + and format_ok + and children_ok + and child_types_ok + ): + return Result.Ok + + error_source = f"var '{valobj.GetName()}'" + + # otherwise, we want to output exactly what doesn't match + # and any additional helpful information + + # We check the type first. If this has changed, it's relatively likely nothing else will work + # properly + if not type_ok: + print_mismatch( + error_source, + "type (Type Name)", + var.type, + expected.type, + ) + + # We check the summary next since it's the most user-visible output. We don't need to check + # `pretty_print` if the summary provider doesn't match. + if not summary_ok: + print_mismatch( + error_source, "summary (Summary Provider)", var.summary, expected.summary + ) + elif not pretty_print_ok: + print_mismatch( + error_source, + "pretty_print (Summary Output)", + var.pretty_print, + expected.pretty_print, + ) + + # try the summary provider directly to see if it's throwing an exception + if var.summary is not None: + try: + provider = get_provider(var.summary) + _ = provider(valobj, {}) + except Exception as e: + print_error( + error_source + " Summary", + "Error while running Summary \ +provider:", + ) + traceback.print_exception(type(e), e, e.__traceback__, file=sys.stdout) + + # Next we check the value and formatter. These mostly affect primitives. + if not value_ok: + print_mismatch(error_source, "value", var.value, expected.value) + if not format_ok: + print_mismatch(error_source, "format", var.format, expected.format) + + # Synthetic is checked next since children, pretty type name, and pretty print rely on it. If + # the synthetic doesn't match, we can assume those won't match either. + if not synthetic_ok: + print_mismatch( + error_source, + "synthetic (Synthetic Provider)", + var.synthetic, + expected.synthetic, + ) + else: + if not pretty_type_name_ok: + print_mismatch( + error_source, + f"pretty_type_name ({var.synthetic}.get_type_name)", + var.pretty_type_name, + expected.pretty_type_name, + ) + + if not children_ok: + # If the children don't match, we can check for more catastrophic failures using the + # synthetic provider. All the per-children errors will have been printed in the + # `children_match` check above. + if var.synthetic is not None: + try: + synth_provider = get_provider(var.synthetic) + + # First we check for exceptions in the constructor and initialization + synth: lldb.SBSyntheticValueProvider = synth_provider( + valobj.GetNonSyntheticValue(), {} + ) + synth.update() + + # If the `get_child_at_index` function doesn't exist, there's not much more we + # can do + if getattr(synth, "get_child_at_index", None) is not None: + # If all the children are invalid (e.g. because a template arg isn't + # resolving correctly, incorrect enum discriminant), we should dump the + # internal state of the synthetic + if not all( + synth.get_child_at_index(i).IsValid() + for i in range(synth.num_children()) + ): + dump_synthetic_state(synth) + + except Exception as e: + print_error( + error_source + " Synthetic", + "Error while running Synthetic\ +Provider:", + ) + traceback.print_exception( + type(e), e, e.__traceback__, file=sys.stdout + ) + + return Result.Mismatch + + +def dump_synthetic_state(synth: Any): + """Prints an object via builtin `vars()`. If `obj.__dict__` does not exist because the object is + using `__slots__` intsead, the `__slots__` are converted into a dict and printed.""" + if (getattr(synth, "__dict__", None)) is not None: + fields = vars(synth) + elif (slots := getattr(synth, "__slots__", None)) is not None: + fields = {name: getattr(synth, name, None) for name in slots} + else: + # Shouldn't be possible, but better safe than sorry + print("Unable to print Synthetic Provider state") + return + + print(f"Synthetic Provider state:\n {fields})") + + +def children_match( + children: list[Child], + expected: list[Child], + path: str, + valobj: lldb.SBValue, +) -> Result: + """Recursively checks children against an expected value and prints errors for mismatches.""" + + result = Result.Ok if len(children) == len(expected) else Result.Mismatch + + mismatches = [] + missing = [] + invalid_count = 0 + + for i in range(len(expected)): + exp = expected[i] + + if i >= len(children): + missing.append(exp.name) + continue + + got = children[i] + + if got.name is None: + result = Result.Mismatch + invalid_count += 1 + mismatches.append( + f"{exp.name}: {exp.type} = {exp.value} -> " + ) + elif got.name != exp.name or got.type != exp.type or got.value != exp.value: + result = Result.Mismatch + mismatches.append( + f"{exp.name}: {exp.type} = {exp.value} -> {got.name}: {got.type} = {got.value}" + ) + # no point recursing into children if we've already mismatched + elif len(exp.children) != 0: + result &= children_match( + got.children, + exp.children, + f"{path}.{exp.name}", + valobj.GetChildAtIndex(i), + ) + + if result == Result.Ok: + return result + + # If every single child is invalid, we can condense the output a lot by pointing to the + # synthetic instead of printing a bunch of identical mismatches + if invalid_count == len(children): + print_error( + path, + f"All children of this object are invalid SBValue objects.\n This is \ +almost always caused by invalid state or logic in the SyntheticProvider.\n This object's \ +synthetic appears to be '{valobj.GetTypeSynthetic().GetData()}'", + ) + elif len(mismatches) != 0: + error_str = "\n ".join(mismatches) + print_error( + path, + f"The following children do not match (expected -> got):\n {error_str}", + ) + elif len(missing) != 0: + error_str = ", ".join(missing) + print_error( + path, + f"The following children were expected, but were not found:\n {error_str}", + ) + elif len(children) > len(expected): + error_str = "\n ".join( + f"{got.name}: {got.type} = {got.value}" for got in children[len(expected) :] + ) + print_error( + path, + f"The following children were found, but were not expected:\n {error_str}", + ) + + return result + + +def get_provider(provider_str: str) -> Callable[[lldb.SBValue, dict[Any, Any]], Any]: + """Given a Varible.summary or Variable.Synthetic, imports the appropriate module and returns the + matching Class/Function""" + import importlib + + [module, summary_name] = provider_str.split(".", 1) + provider_module = importlib.import_module(module) + + return getattr(provider_module, summary_name) diff --git a/src/etc/lldb_batchmode/common.py b/src/etc/lldb_batchmode/common.py index 6dfcf2ad79f39..438c4cb0cb4f9 100644 --- a/src/etc/lldb_batchmode/common.py +++ b/src/etc/lldb_batchmode/common.py @@ -1,23 +1,57 @@ """Contains the class definitions outlining the schema of the test data. For LLDB conversion from/into these types, see `./from_lldb.py`""" -import enum import json import os +from enum import Enum from dataclasses import asdict, dataclass, field, fields, is_dataclass -from types import NoneType -from typing import Any, Optional, get_origin, Final +from typing import Any, Optional, Union, get_origin, Final +from pprint import pformat char = str -Primitive = int | float | bool | char +Primitive = Union[int, float, bool, char] ByteSize = int # see: default json decoder docs https://docs.python.org/3/library/json.html#json.JSONDecoder # The types we're dealing with can only be: int, str, float, list, dict, bool, and None -JsonType = int | str | float | list["JsonType"] | bool | None | dict[str, "JsonType"] +JsonType = Union[int, str, float, list["JsonType"], bool, None, dict[str, "JsonType"]] -class Target(enum.Enum): +class Result(Enum): + Ok = True + Mismatch = False + + def __and__(self, other: "Result") -> "Result": + return Result(self.value & other.value) + + def __bool__(self) -> bool: + return self.value + + +ANSI_RED = "\033[91m" +ANSI_END = "\033[0m" + + +def print_error(error_source: str, message: str): + print(f"{ANSI_RED} [repr error: {error_source}]{ANSI_END} {message}") + + +def format_mismatch(label: str, got: Optional[Any], expected: Optional[Any]) -> str: + if got is None and expected is not None: + return f"{label} not found, expected: {expected}" + elif expected is not None and got is None: + return f"{label} '{got}' found when none was expected." + else: + return f"{label} does not match.\n Expected: {expected}\n Got: {got}" + + +def print_mismatch( + error_source: str, label: str, got: Optional[Any], expected: Optional[Any] +): + print_error(error_source, format_mismatch(label, got, expected)) + + +class Target(Enum): """Due to the differences between PDB and DWARF debug info, we cannot guarantee their output will be identical. Since LLDB can handle both, we need to conditionally select the correct test data to use. @@ -36,6 +70,7 @@ class Target(enum.Enum): def get_target() -> Target: # set by compiletest when launching LLDB t: str = os.environ["LLDB_BATCHMODE_TARGET_TRIPLE"] + if t.endswith("windows-msvc"): return Target.WindowsMsvc if t.endswith("windows-gnu") or t.endswith("windows-gnullvm"): @@ -48,6 +83,7 @@ def get_target() -> Target: """Global constant set by `compiletest` that determines whether or not we are blessing the test data.""" + TARGET: Final[Target] = get_target() """Global constant set by `compiletest`. Determines which target the tests were run for, thus which set of test input we check.""" @@ -61,7 +97,7 @@ def annot_to_ty(annot: str) -> type[Any]: "int": int, "float": float, "bool": bool, - "None": NoneType, + "None": type(None), "list": list, "dict": dict, "str": str, @@ -137,7 +173,7 @@ def from_dict(ty: type[Any], data: JsonType): return data -@dataclass(slots=True) +@dataclass(frozen=True) class Field: name: str type: str @@ -147,7 +183,7 @@ class Field: offset: ByteSize -@dataclass(slots=True) +@dataclass class Type: size: ByteSize # When GDB support is added to the test framework, basic_type and type_class will probably be @@ -161,7 +197,13 @@ class Type: recognizer functions.""" fields: list[Field] - """Stored as a list due to our reliance on `SBType.GetFieldAtIndex()`""" + """Stored as a list due to our reliance on `SBType.GetFieldAtIndex()` + + Note: LLDB **does not** reorder the fields of a type based on their offset. For example, + `GetFieldAtIndex(0).GetByteOffset()` may return `8`. Instead, the order of the fields is a + direct reflection of their ordering in the debug info (which, as far as I know, is the same as + their declaration order in the source code). + """ generic_params: list[str] """Stored as a list due to our reliance on `SBType.GetTemplateArgumentType()` and the sequential @@ -171,8 +213,150 @@ class Type: # values, so it's not super urgent. # static_fields: list[StaticField] + def matches( + self, expected: "Type", type_name: str, provider_ok: bool = False + ) -> Result: + result = Result.Ok + error_source = f"type '{type_name}'" + # FIXME handle 32 bit targets + if self.size != expected.size: + result = Result.Mismatch + print_mismatch(error_source, "size", self.size, expected.size) + + if self.fields != expected.fields: + result = Result.Mismatch + self.print_field_errors(expected, error_source) + + if self.generic_params != expected.generic_params: + result = Result.Mismatch + print_mismatch( + error_source, + "generic_params", + self.generic_params, + expected.generic_params, + ) + + if result == Result.Mismatch and provider_ok: + print_error( + error_source, + "It appears these changes do not affect the type's providers. Consider rerunning \ +with the `--bless` option", + ) + + return result + + def print_field_errors(self, expected: "Type", error_source: str): + """Extra processing for better error messages. The following common cases are covered: + * New/Missing fields + * Source code rearranged fields + * Rustc rearranged fields + * Renamed fields + + If none of the common cases are encountered, we just generically print any mismatched + fields. + """ + + # FIXME these checks aren't exactly the most efficient they could be. Luckily, the happy + # path skips this function entirely, so passing tests are still fast. These checks could + # probably all be done in 2ish total iters over each list, but optimization isn't a huge + # concern at the moment. + + got_set = set(self.fields) + expected_set = set(expected.fields) -@dataclass(slots=True) + if len(self.fields) != len(expected.fields): + new_fields = got_set.difference(expected_set) + + missing_fields = expected_set.difference(got_set) + + if len(missing_fields) != 0: + print_error( + error_source, + f"The following field(s) appear to have been removed from the type:\n\ +{missing_fields}", + ) + + if len(new_fields) != 0: + print_error( + error_source, + f"The following field(s) appear to have been added to the type:\n\ +{new_fields}", + ) + + # are all of the same fields present, regardless of order? If so, they were rearranged + # in the source code, but the compiler kept the same ordering. + elif got_set == expected_set: + print_error( + error_source, + f"Field(s) appear to have been rearranged:\n Expected:\n\ +{pformat(self.fields, indent=6)}\n Got:\n{pformat(expected.fields, indent=6)}", + ) + else: + # we know for sure that both sets of fields are the same length, but some parts of one + # or more fields don't match + types_match = True + offsets_match = True + names_match = True + mismatches: list[tuple[Field, Field]] = [] + + for g, e in zip(self.fields, expected.fields): + if g.type != e.type: + types_match = False + mismatches.append((g, e)) + if g.offset != e.offset: + offsets_match = False + mismatches.append((g, e)) + if g.name != e.name: + names_match = False + mismatches.append((g, e)) + + # If the types and offsets are the same but the names aren't, we know fields have + # been renamed. + if types_match and offsets_match: + renames = "\n ".join( + map(lambda m: f"{m[1].name} -> {m[0].name}", mismatches) + ) + print_error( + error_source, + f"The following field(s) appear to have been renamed (expected -> got):\n\ + {renames}", + ) + + # If the types and names are the same, but the offsets are different, we know that rustc + # has decided to order the fields differently, despite the source code not changing + elif types_match and names_match: + reordered = "\n ".join( + map( + lambda m: ( + f"{m[1].name} offset: +{m[1].offset} -> {m[0].name} offset: \n\ ++{m[0].offset}" + ), + mismatches, + ) + ) + + print_error( + error_source, + f"The following field(s) appear to have been reordered by rustc (expected -> \ +got):\n {reordered}", + ) + + else: + mm_string = "\n ".join( + map( + lambda m: (f"{m[1]} -> {m[0]}"), + mismatches, + ) + ) + + print_error( + error_source, + f"The following field(s) do not match (expected -> got):\n\ + {mm_string}", + ) + + +@dataclass class Child: """Similar to `Variable`, but carries less information since we primarily test top-level values (and assume values of these child types have been tested thoroughly elsewhere). @@ -196,7 +380,7 @@ class Child: """ -@dataclass(slots=True) +@dataclass class Variable: type: str """The fully qualified name of the variable's type. Full type information should be looked up @@ -230,19 +414,18 @@ class Variable: children are the result of the provider's `get_child_at_index` function""" -@dataclass(slots=True) +@dataclass class BlessMetadata: """ - Contains additional context about the tools at the time the test data was generated + Contains additional context about the tools at the time the test data was generated. """ python_version: str = "" debugger_version: str = "" - # FIXME (todo) - # feature_flags: str + feature_flags: str = "" -@dataclass(slots=True) +@dataclass class TargetData: """ Top-level container for all test data. @@ -287,6 +470,9 @@ def initialize() -> "TargetData": generated for this test yet, consider using the `--bless` option." ) + if BLESS: + return result + with open(path, "r") as f: try: result = from_dict(TargetData, json.load(f)) @@ -324,5 +510,14 @@ def save_blessing(self, metadata: BlessMetadata): x = json.dumps(asdict(self), indent=" ") _ = json.loads(x) + # ensure the necessary directories exist first + import pathlib + + os.makedirs(pathlib.Path(path).parent, exist_ok=True) + with open(path, "w") as f: f.write(x) + f.write("\n") + + +INPUT_DATA: TargetData = TargetData.initialize() diff --git a/src/etc/lldb_batchmode/from_lldb.py b/src/etc/lldb_batchmode/from_lldb.py index 2c786466859c8..987617b683de6 100644 --- a/src/etc/lldb_batchmode/from_lldb.py +++ b/src/etc/lldb_batchmode/from_lldb.py @@ -9,11 +9,14 @@ """ from struct import unpack, calcsize +from enum import Enum, IntFlag +from typing import Optional, Union import lldb import lldb_lookup from .common import ( + BLESS, TARGET, Child, Field, @@ -23,6 +26,47 @@ Variable, ) +HAS_FLOAT128: bool = getattr(lldb, "eBasicTypeFloat128", None) is not None + +# We use the following lists to dynamically create the enums at run-time (they're used to print +# more meaningful error messages when basic_type and type_class don't match). +# It takes a few hundred microseconds at runtime to generate these lists, but it means we never have +# to upkeep version-specific flags. Since the underlying integers are what are stored and tested +# against, these don't affect (and are not affected by) the test data. +_lldb_type_classes = { + k.removeprefix("eTypeClass"): v + for k, v in lldb.__dict__.items() + if k.startswith("eTypeClass") +} +_lldb_basic_types = { + k.removeprefix("eBasicType"): v + for k, v in lldb.__dict__.items() + if k.startswith("eBasicType") +} + + +# We specify boundary=KEEP to tell python that values that aren't directly specified should still +# be formatted as if they're members of TypeClass (rather than throwing an exception) +class TypeClass(IntFlag): + """Direct mapping of `lldb.eTypeClass` bitflags for convenience. Used to print a more meaningful + error message when Type.type_class does not match. + """ + + # Enums create their members based on locals. We can access and modify the locals dict just like + # any other. As gross as it is, this is canonical, per Python's own tutorial. + # See: https://docs.python.org/3/howto/enum.html#timeperiod + # The alternative is using the functional syntax, but that doesn't allow us to set boundary=KEEP + vars().update(_lldb_type_classes) + + +class BasicType(Enum): + """Direct mapping of `lldb.eBasicType` enumerations for convenience. Used to print a more + meaningful error message when Type.basic_type does not match. + """ + + vars().update(_lldb_basic_types) + + _UNSIGNED_INT_TYPES = { lldb.eBasicTypeUnsignedChar, lldb.eBasicTypeUnsignedShort, @@ -36,12 +80,11 @@ lldb.eBasicTypeHalf, lldb.eBasicTypeFloat, lldb.eBasicTypeDouble, - # FIXME: lldb added support for Float128 in 22.1, but python has no native - # support for it (even through `ctypes` or other alternatives). The best we - # can probably manage is comparing the raw bytes and/or trusting LLDB's output. - lldb.eBasicTypeFloat128, } +if HAS_FLOAT128: + _FLOAT_TYPES.add(lldb.eBasicTypeFloat128) + _SIZE_TO_FLOAT_FMT = { 2: "e", 4: "f", @@ -82,7 +125,7 @@ def type_unpack_fmt(kind: int, size: int) -> str: return fmt -def decode_primitive(valobj: lldb.SBValue) -> int | float | bool | str: +def decode_primitive(valobj: lldb.SBValue) -> Union[int, float, bool, str]: data: lldb.SBData = valobj.GetData() type: lldb.SBType = valobj.GetType().GetCanonicalType() @@ -119,7 +162,7 @@ def decode_primitive(valobj: lldb.SBValue) -> int | float | bool | str: return got -def get_summary_or_value(valobj: lldb.SBValue) -> str | None: +def get_summary_or_value(valobj: lldb.SBValue) -> Optional[str]: """`SBValue.GetSummary` only prints summaries from summary providers. It returns `None` if there is no summary provider, rather than printing the default representation of the value. Often we want any printable representation at all, so this function falls back to `SBValue.GetValue`. @@ -134,6 +177,9 @@ def get_summary_or_value(valobj: lldb.SBValue) -> str | None: def field_from_lldb(field: lldb.SBTypeMember) -> Field: + if BLESS and not field.IsValid(): + raise Exception("Cannot bless invalid SBTypeMember object") + return Field(field.GetName(), field.GetType().GetName(), field.GetOffsetInBytes()) @@ -175,6 +221,9 @@ def get_generics(ty: lldb.SBType, sbtarget: lldb.SBTarget) -> list[lldb.SBType]: def type_from_lldb(ty: lldb.SBType, sbtarget: lldb.SBTarget) -> Type: + if BLESS and not ty.IsValid(): + raise Exception("Cannot bless invalid SBType object") + generic_types = get_generics(ty, sbtarget) generics = [g.GetName() for g in generic_types] @@ -188,6 +237,9 @@ def type_from_lldb(ty: lldb.SBType, sbtarget: lldb.SBTarget) -> Type: def child_from_lldb(child: lldb.SBValue) -> Child: + if BLESS and not child.IsValid(): + raise Exception("Cannot bless invalid child") + sbtype: lldb.SBType = child.GetType() if not sbtype.IsPointerType() and sbtype.GetBasicType() != lldb.eBasicTypeInvalid: @@ -203,6 +255,9 @@ def child_from_lldb(child: lldb.SBValue) -> Child: def variable_from_lldb(var: lldb.SBValue) -> Variable: + if BLESS and not var.IsValid(): + raise Exception("Cannot bless invalid SBValue object") + sbtype = var.GetType() type_name = sbtype.GetName() @@ -221,12 +276,16 @@ def variable_from_lldb(var: lldb.SBValue) -> Variable: value = None if (synth := var.GetTypeSynthetic()).IsValid(): - synthetic = synth.GetData().strip() + synthetic = synth.GetData() + if synthetic is not None: + synthetic = synthetic.strip() else: synthetic = None if (summ := var.GetTypeSummary()).IsValid(): - summary = summ.GetData().strip() + summary = summ.GetData() + if summary is not None: + summary = summary.strip() else: summary = None @@ -266,18 +325,29 @@ def bless_variable( # FIXME (todo) error handling raise Exception(f"") + # HACK it's obviously not ideal to output empty breakpoints, but it will be somewhat rare for it + # to happen (you would need a breakpoint with repr -> breakpoint without repr -> breakpoint + # with repr). In the more common case (e.g. 1 breakpoint, sequential breakpoints all with repr + # commands), this saves a lot more space than converting all TargetData.breakpoints to + # `dict[int,...]` if len(target_data.breakpoints) <= breakpoint_idx: - target_data.breakpoints.append({}) + target_data.breakpoints.extend( + {} for i in range(1 + breakpoint_idx - len(target_data.breakpoints)) + ) var_data = variable_from_lldb(valobj) target_data.breakpoints[breakpoint_idx][var_name] = var_data - bless_type(target_data, valobj.GetType(), valobj.GetTarget()) - # We also need to bless the types of the valobj's children, as they may not appear in the type # or fields. - for i in range(valobj.GetNumChildren()): - bless_type(target_data, valobj.GetChildAtIndex(i).GetType(), valobj.GetTarget()) + target = valobj.GetTarget() + + work_list = [valobj] + while len(work_list) != 0: + obj = work_list.pop() + work_list.extend([obj.GetChildAtIndex(i) for i in range(obj.GetNumChildren())]) + + bless_type(target_data, obj.GetType(), target) def bless_type(target_data: TargetData, type: lldb.SBType, sbtarget: lldb.SBTarget): @@ -289,9 +359,15 @@ def bless_type(target_data: TargetData, type: lldb.SBType, sbtarget: lldb.SBTarg # If the type already exists in the type map, we don't need to process any further. We do # need to check that the type data is actually identical to its mapping before moving on. # It shouldn't ever be different, but better safe than sorry. - assert target_data.types[t_name] == t_data + import pprint + + assert ( + target_data.types[t_name] == t_data + ), f"old: {pprint.pformat(target_data.types[t_name])}\nnew: {pprint.pformat(t_data)}" return + print(f"blessing type: {t_name}") + # We need to add this type first just in case the type contains itself. target_data.types[t_name] = t_data diff --git a/src/etc/lldb_batchmode/runner.py b/src/etc/lldb_batchmode/runner.py index 8b226b3349cb1..cd0879f63cee6 100644 --- a/src/etc/lldb_batchmode/runner.py +++ b/src/etc/lldb_batchmode/runner.py @@ -20,6 +20,8 @@ import threading import re import time +import traceback + try: import thread @@ -68,7 +70,7 @@ def breakpoint_callback(frame, bp_loc, dict): registered_breakpoints = set() -def execute_command(command_interpreter, command): +def execute_command(command_interpreter: lldb.SBCommandInterpreter, command: str): """Executes a single CLI command""" global new_breakpoints global registered_breakpoints @@ -183,6 +185,15 @@ def get_env_arg(name): return value +def dispatch_repr(var_name: str, breakpoint_index: int, frame: lldb.SBFrame) -> bool: + # We save importing the check until we actually see a repr command. This prevents us from trying + # to load input data from tests that don't use `repr` commands. + from .check_lldb import check + from .common import Result + + return check(var_name, breakpoint_index, frame) == Result.Ok + + #################################################################################################### # ~main #################################################################################################### @@ -194,6 +205,7 @@ def main(): print("LLDB batch-mode script") print("----------------------") + print(f"Python version: {sys.version}") print("Debugger commands script is '%s'." % script_path) print("Target executable is '%s'." % target_path) print("Current working directory is '%s'" % os.getcwd()) @@ -201,8 +213,12 @@ def main(): # Start the timeout watchdog start_watchdog() - # Create a new debugger instance - debugger = lldb.SBDebugger.Create() + # This is the debugger instance of the lldb executable that imported and ran this python script. + # There is some weird behavior around LLDB reassigning, clearing, or not updating their own + # references (like `lldb.debugger`) while a python function is actively running (i.e. if control + # is not given back to the REPL). To prevent LLDB from changing things out from under us, we + # store this reference locally. + debugger = lldb.debugger # When we step or continue, don't return from the function until the process # stops. We do this by setting the async mode to false. @@ -210,17 +226,14 @@ def main(): # Create a target from a file and arch print("Creating a target for '%s'" % target_path) - target_error = lldb.SBError() - target = debugger.CreateTarget(target_path, None, None, True, target_error) - if not target: + target: lldb.SBTarget = debugger.CreateTargetWithFileAndTargetTriple( + target_path, lldb.SBPlatform.GetHostPlatform().GetTriple() + ) + + if not target or not target.IsValid(): print( - "Could not create debugging target '" - + target_path - + "': " - + str(target_error) - + ". Aborting.", - file=sys.stderr, + "Could not create debugging target '" + target_path + ". Aborting.", ) sys.exit(1) @@ -229,6 +242,10 @@ def main(): command_interpreter = debugger.GetCommandInterpreter() + repr_cmd_run = False + breakpoint_index = 0 + all_ok = True + try: script_file = open(script_path, "r") @@ -239,16 +256,73 @@ def main(): or command == "r" or re.match(r"^process\s+launch.*", command) ): - # Before starting to run the program, let the thread sleep a bit, so all - # breakpoint added events can be processed - time.sleep(0.5) - if command != "": + print(f"(lldb) {command}") + process: lldb.SBProcess = target.LaunchSimple(None, None, None) + if ( + process.GetSelectedThread().GetStopReason() + == lldb.eStopReasonBreakpoint + and breakpoint_index is None + ): + breakpoint_index = 0 + continue + if command == "continue" or command == "c": + print(f"(lldb) {command}") + process.Continue() + if ( + process.GetSelectedThread().GetStopReason() + == lldb.eStopReasonBreakpoint + ): + breakpoint_index += 1 + continue + if command == "quit" or command == "exit": + print(f"(lldb) {command}") + break + if command.startswith("repr "): + repr_cmd_run = True + var_name = command.split(" ", 1)[1] + + p = target.GetProcess() + frame = p.GetSelectedThread().GetSelectedFrame() + + print(command) + all_ok &= dispatch_repr(var_name, breakpoint_index, frame) + elif command != "": execute_command(command_interpreter, command) except IOError as e: - print("Could not read debugging script '%s'." % script_path, file=sys.stderr) - print(e, file=sys.stderr) - print("Aborting.", file=sys.stderr) - sys.exit(1) + print("Could not read debugging script '%s'." % script_path) + traceback.print_exception(type(e), e, e.__traceback__, file=sys.stdout) + print("Aborting.") + # Returning status codes using `sys.exit` doesn't work since we're in an LLDB managed python + # instance. This command sets the exit code but *does not* kill LLDB, the debugee process, + # or the SBDebugger object. + debugger.HandleCommand("quit 1") + except Exception as e: + traceback.print_exception(e, file=sys.stdout) + debugger.HandleCommand("quit 1") + else: # Executes if the `try` block throws no exceptions. + if repr_cmd_run: + # We save importing these until we actually see a repr command. This prevents us + # from trying to load input data from tests that don't use `repr` commands. + from .check_lldb import tested_all_types, tested_all_variables + from .common import BLESS, BlessMetadata, INPUT_DATA + + # `bless` should resolve any errors from mismatched test data, so any errors that reach + # this point are either from the `bless` not working properly, or some other issue with + # the test itself. In either case, we probably don't want to update the test data until + # those are resolved. + # Only runs if the test contains a repr command, as we don't want to create an input + # file for a test that won't ever use it. + + if not tested_all_types() or not tested_all_variables(): + debugger.HandleCommand("quit 1") + elif BLESS: + from lldb_providers import FEATURE_FLAGS + + INPUT_DATA.save_blessing( + BlessMetadata( + sys.version, debugger.GetVersionString(), str(FEATURE_FLAGS) + ) + ) finally: script_file.close() diff --git a/src/etc/lldb_lookup.py b/src/etc/lldb_lookup.py index 3533d149e7494..ce6aeb6576e02 100644 --- a/src/etc/lldb_lookup.py +++ b/src/etc/lldb_lookup.py @@ -111,15 +111,6 @@ def __lldb_init_module(debugger: lldb.SBDebugger, _dict: LLDBOpaque): # RUST_CATEGORY.AddLanguage(lldb.eLanguageTypeRust) - global FEATURE_FLAGS - # Most feature checks should be possible via simple "does this API exist at all" checks. - if getattr(lldb.SBType, "GetStaticFieldWithName", None) is not None: - FEATURE_FLAGS |= LLDBFeature.StaticFields - if getattr(lldb, "eFormatterMatchCallback", None) is not None: - FEATURE_FLAGS |= LLDBFeature.TypeRecognizers - if getattr(lldb, "eBasicTypeFloat128", None) is not None: - FEATURE_FLAGS |= LLDBFeature.Float128 - register_providers_compatibility() diff --git a/src/etc/lldb_providers.py b/src/etc/lldb_providers.py index f6c2ab8f2b8c7..fed6aad24caaf 100644 --- a/src/etc/lldb_providers.py +++ b/src/etc/lldb_providers.py @@ -81,7 +81,23 @@ class LLDBFeature(Flag): a formatter, and handlers in `TypeSystemClang`""" -FEATURE_FLAGS: LLDBFeature = LLDBFeature(0) +def detect_features() -> LLDBFeature: + import lldb + + features = LLDBFeature(0) + + # Most feature checks should be possible via simple "does this API exist at all" checks. + if getattr(lldb.SBType, "GetStaticFieldWithName", None) is not None: + features |= LLDBFeature.StaticFields + if getattr(lldb, "eFormatterMatchCallback", None) is not None: + features |= LLDBFeature.TypeRecognizers + if getattr(lldb, "eBasicTypeFloat128", None) is not None: + features |= LLDBFeature.Float128 + + return features + + +FEATURE_FLAGS: LLDBFeature = detect_features() class LLDBOpaque: diff --git a/src/tools/compiletest/src/directives/directive_names.rs b/src/tools/compiletest/src/directives/directive_names.rs index aa942ca48fe02..b73e782252148 100644 --- a/src/tools/compiletest/src/directives/directive_names.rs +++ b/src/tools/compiletest/src/directives/directive_names.rs @@ -154,6 +154,7 @@ pub(crate) const KNOWN_DIRECTIVE_NAMES: &[&str] = &[ "known-bug", "lldb-check", "lldb-command", + "lldb-repr", "llvm-cov-flags", "max-llvm-major-version", "min-apple-lldb-version", diff --git a/src/tools/compiletest/src/runtest/debugger.rs b/src/tools/compiletest/src/runtest/debugger.rs index a28e38a7db5d7..fac633d4606ab 100644 --- a/src/tools/compiletest/src/runtest/debugger.rs +++ b/src/tools/compiletest/src/runtest/debugger.rs @@ -27,9 +27,6 @@ impl DebuggerCommands { debugger_prefix: &str, test_revision: Option<&str>, ) -> Result { - let command_directive = format!("{debugger_prefix}-command"); - let check_directive = format!("{debugger_prefix}-check"); - let mut breakpoint_lines = vec![]; let mut commands = vec![]; let mut check_lines = vec![]; @@ -51,15 +48,22 @@ impl DebuggerCommands { continue; } - if directive.name == command_directive - && let Some(command) = directive.value_after_colon() - { - commands.push(command.to_string()); - } - if directive.name == check_directive - && let Some(pattern) = directive.value_after_colon() - { - check_lines.push((line_number, pattern.to_string())); + let Some(directive_kind) = directive.name.strip_prefix(debugger_prefix) else { + continue; + }; + + match (directive_kind, directive.value_after_colon()) { + ("-command", Some(command)) => commands.push(command.to_string()), + ("-check", Some(pattern)) => check_lines.push((line_number, pattern.to_string())), + ("-repr", Some(var_name)) => { + // pseudo-command intercepted by `lldb_batchmode` to run custom variable + // inspection logic. + commands.push(format!("repr {}", var_name.trim())); + // Artificially output by `lldb_batchmode` to confirm that the inspection logic + // encountered no errors. + check_lines.push((line_number, format!("{var_name}: Ok"))); + } + _ => continue, } } diff --git a/src/tools/compiletest/src/runtest/debuginfo.rs b/src/tools/compiletest/src/runtest/debuginfo.rs index 574d7618703d2..992291d918320 100644 --- a/src/tools/compiletest/src/runtest/debuginfo.rs +++ b/src/tools/compiletest/src/runtest/debuginfo.rs @@ -469,11 +469,22 @@ impl TestCx<'_> { // make sure `PATH` points to all the dlls necessary to run the debugee let path = prepend_to_path(&self.config.target_run_lib_path); + // Output the file path of the input data for `lldb-repr` commands + let lldb_input_data_path = self.config.src_root.join(format!( + "{}/lldb_input/{}.json", + self.testpaths.file.parent().unwrap(), + get_target_file_name(&self.config.target) + )); + let mut cmd = ArgFileCommand::new(lldb); - cmd.arg("--one-line") + cmd.arg("--batch") // --batch executes our script from --one-line and kills lldb afterwards + .arg("--one-line") .arg("script --language python -- import lldb_batchmode; lldb_batchmode.main()") .env("LLDB_BATCHMODE_TARGET_PATH", test_executable) .env("LLDB_BATCHMODE_SCRIPT_PATH", debugger_script) + .env("LLDB_BATCHMODE_INPUT_DATA_PATH", lldb_input_data_path) + .env("LLDB_BATCHMODE_BLESS_TEST_DATA", if self.config.bless { "1" } else { "0" }) + .env("LLDB_BATCHMODE_TARGET_TRIPLE", &self.config.target) .env("PYTHONUNBUFFERED", "1") // Help debugging #78665 .env("PYTHONPATH", pythonpath) .env("PATH", path); @@ -512,3 +523,15 @@ fn prepend_to_path(some_path: &Utf8Path) -> String { some_path.to_string() } } + +/// Converts the given target name into the appropriate input file name based on the +/// targets defined in `lldb_batchmode.common.Target` +fn get_target_file_name(target_name: &str) -> &'static str { + if target_name.ends_with("windows-msvc") { + "windows_msvc" + } else if target_name.ends_with("windows-gnu") || target_name.ends_with("windows-gnullvm") { + "windows_gnu" + } else { + "non_windows" + } +} diff --git a/tests/debuginfo/basic-types/lldb_input/non_windows.json b/tests/debuginfo/basic-types/lldb_input/non_windows.json new file mode 100644 index 0000000000000..5ba00f1ead20e --- /dev/null +++ b/tests/debuginfo/basic-types/lldb_input/non_windows.json @@ -0,0 +1,237 @@ +{ + "bless_metadata": { + "python_version": "3.14.5 (main, May 11 2026, 00:00:00) [GCC 16.1.1 20260501 (Red Hat 16.1.1-1)]", + "debugger_version": "lldb version 22.1.8", + "feature_flags": "LLDBFeature.StaticFields|TypeRecognizers|Float128" + }, + "types": { + "bool": { + "size": 1, + "basic_type": 21, + "type_class": 4, + "fields": [], + "generic_params": [] + }, + "long": { + "size": 8, + "basic_type": 15, + "type_class": 4, + "fields": [], + "generic_params": [] + }, + "char32_t": { + "size": 4, + "basic_type": 9, + "type_class": 4, + "fields": [], + "generic_params": [] + }, + "char": { + "size": 1, + "basic_type": 3, + "type_class": 4, + "fields": [], + "generic_params": [] + }, + "short": { + "size": 2, + "basic_type": 11, + "type_class": 4, + "fields": [], + "generic_params": [] + }, + "int": { + "size": 4, + "basic_type": 13, + "type_class": 4, + "fields": [], + "generic_params": [] + }, + "unsigned long": { + "size": 8, + "basic_type": 16, + "type_class": 4, + "fields": [], + "generic_params": [] + }, + "unsigned char": { + "size": 1, + "basic_type": 4, + "type_class": 4, + "fields": [], + "generic_params": [] + }, + "unsigned short": { + "size": 2, + "basic_type": 12, + "type_class": 4, + "fields": [], + "generic_params": [] + }, + "unsigned int": { + "size": 4, + "basic_type": 14, + "type_class": 4, + "fields": [], + "generic_params": [] + }, + "float": { + "size": 4, + "basic_type": 23, + "type_class": 4, + "fields": [], + "generic_params": [] + }, + "double": { + "size": 8, + "basic_type": 24, + "type_class": 4, + "fields": [], + "generic_params": [] + } + }, + "breakpoints": [ + { + "b": { + "type": "bool", + "pretty_type_name": null, + "pretty_print": "false", + "value": false, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "i": { + "type": "long", + "pretty_type_name": null, + "pretty_print": "-1", + "value": -1, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "c": { + "type": "char32_t", + "pretty_type_name": null, + "pretty_print": "U+0x00000061 U'a'", + "value": "a", + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "i8": { + "type": "char", + "pretty_type_name": null, + "pretty_print": "'D'", + "value": 68, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "i16": { + "type": "short", + "pretty_type_name": null, + "pretty_print": "-16", + "value": -16, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "i32": { + "type": "int", + "pretty_type_name": null, + "pretty_print": "-32", + "value": -32, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "i64": { + "type": "long", + "pretty_type_name": null, + "pretty_print": "-64", + "value": -64, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "u": { + "type": "unsigned long", + "pretty_type_name": null, + "pretty_print": "1", + "value": 1, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "u8": { + "type": "unsigned char", + "pretty_type_name": null, + "pretty_print": "'d'", + "value": 100, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "u16": { + "type": "unsigned short", + "pretty_type_name": null, + "pretty_print": "16", + "value": 16, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "u32": { + "type": "unsigned int", + "pretty_type_name": null, + "pretty_print": "32", + "value": 32, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "u64": { + "type": "unsigned long", + "pretty_type_name": null, + "pretty_print": "64", + "value": 64, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "f32": { + "type": "float", + "pretty_type_name": null, + "pretty_print": "2.5", + "value": 2.5, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "f64": { + "type": "double", + "pretty_type_name": null, + "pretty_print": "3.5", + "value": 3.5, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + } + } + ] +} diff --git a/tests/debuginfo/basic-types/lldb_input/windows_gnu.json b/tests/debuginfo/basic-types/lldb_input/windows_gnu.json new file mode 100644 index 0000000000000..ed2ea1d7eec68 --- /dev/null +++ b/tests/debuginfo/basic-types/lldb_input/windows_gnu.json @@ -0,0 +1,237 @@ +{ + "bless_metadata": { + "python_version": "3.11.9 (tags/v3.11.9:de54cf5, Apr 2 2024, 10:12:12) [MSC v.1938 64 bit (AMD64)]", + "debugger_version": "lldb version 22.1.2 (https://github.com/llvm/llvm-project revision 1ab49a973e210e97d61e5db6557180dcb92c3e98)\n clang revision 1ab49a973e210e97d61e5db6557180dcb92c3e98\n llvm revision 1ab49a973e210e97d61e5db6557180dcb92c3e98", + "feature_flags": "LLDBFeature.StaticFields|TypeRecognizers|Float128" + }, + "types": { + "bool": { + "size": 1, + "basic_type": 21, + "type_class": 4, + "fields": [], + "generic_params": [] + }, + "long long": { + "size": 8, + "basic_type": 17, + "type_class": 4, + "fields": [], + "generic_params": [] + }, + "char32_t": { + "size": 4, + "basic_type": 9, + "type_class": 4, + "fields": [], + "generic_params": [] + }, + "char": { + "size": 1, + "basic_type": 3, + "type_class": 4, + "fields": [], + "generic_params": [] + }, + "short": { + "size": 2, + "basic_type": 11, + "type_class": 4, + "fields": [], + "generic_params": [] + }, + "int": { + "size": 4, + "basic_type": 13, + "type_class": 4, + "fields": [], + "generic_params": [] + }, + "unsigned long long": { + "size": 8, + "basic_type": 18, + "type_class": 4, + "fields": [], + "generic_params": [] + }, + "unsigned char": { + "size": 1, + "basic_type": 4, + "type_class": 4, + "fields": [], + "generic_params": [] + }, + "unsigned short": { + "size": 2, + "basic_type": 12, + "type_class": 4, + "fields": [], + "generic_params": [] + }, + "unsigned int": { + "size": 4, + "basic_type": 14, + "type_class": 4, + "fields": [], + "generic_params": [] + }, + "float": { + "size": 4, + "basic_type": 23, + "type_class": 4, + "fields": [], + "generic_params": [] + }, + "double": { + "size": 8, + "basic_type": 24, + "type_class": 4, + "fields": [], + "generic_params": [] + } + }, + "breakpoints": [ + { + "b": { + "type": "bool", + "pretty_type_name": null, + "pretty_print": "false", + "value": false, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "i": { + "type": "long long", + "pretty_type_name": null, + "pretty_print": "-1", + "value": -1, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "c": { + "type": "char32_t", + "pretty_type_name": null, + "pretty_print": "U+0x00000061 U'a'", + "value": "a", + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "i8": { + "type": "char", + "pretty_type_name": null, + "pretty_print": "'D'", + "value": 68, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "i16": { + "type": "short", + "pretty_type_name": null, + "pretty_print": "-16", + "value": -16, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "i32": { + "type": "int", + "pretty_type_name": null, + "pretty_print": "-32", + "value": -32, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "i64": { + "type": "long long", + "pretty_type_name": null, + "pretty_print": "-64", + "value": -64, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "u": { + "type": "unsigned long long", + "pretty_type_name": null, + "pretty_print": "1", + "value": 1, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "u8": { + "type": "unsigned char", + "pretty_type_name": null, + "pretty_print": "'d'", + "value": 100, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "u16": { + "type": "unsigned short", + "pretty_type_name": null, + "pretty_print": "16", + "value": 16, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "u32": { + "type": "unsigned int", + "pretty_type_name": null, + "pretty_print": "32", + "value": 32, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "u64": { + "type": "unsigned long long", + "pretty_type_name": null, + "pretty_print": "64", + "value": 64, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "f32": { + "type": "float", + "pretty_type_name": null, + "pretty_print": "2.5", + "value": 2.5, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "f64": { + "type": "double", + "pretty_type_name": null, + "pretty_print": "3.5", + "value": 3.5, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + } + } + ] +} diff --git a/tests/debuginfo/basic-types/lldb_input/windows_msvc.json b/tests/debuginfo/basic-types/lldb_input/windows_msvc.json new file mode 100644 index 0000000000000..04507093fc2c9 --- /dev/null +++ b/tests/debuginfo/basic-types/lldb_input/windows_msvc.json @@ -0,0 +1,237 @@ +{ + "bless_metadata": { + "python_version": "3.11.9 (tags/v3.11.9:de54cf5, Apr 2 2024, 10:12:12) [MSC v.1938 64 bit (AMD64)]", + "debugger_version": "lldb version 22.1.2 (https://github.com/llvm/llvm-project revision 1ab49a973e210e97d61e5db6557180dcb92c3e98)\n clang revision 1ab49a973e210e97d61e5db6557180dcb92c3e98\n llvm revision 1ab49a973e210e97d61e5db6557180dcb92c3e98", + "feature_flags": "LLDBFeature.StaticFields|TypeRecognizers|Float128" + }, + "types": { + "bool": { + "size": 1, + "basic_type": 21, + "type_class": 4, + "fields": [], + "generic_params": [] + }, + "long long": { + "size": 8, + "basic_type": 17, + "type_class": 4, + "fields": [], + "generic_params": [] + }, + "char32_t": { + "size": 4, + "basic_type": 9, + "type_class": 4, + "fields": [], + "generic_params": [] + }, + "signed char": { + "size": 1, + "basic_type": 3, + "type_class": 4, + "fields": [], + "generic_params": [] + }, + "short": { + "size": 2, + "basic_type": 11, + "type_class": 4, + "fields": [], + "generic_params": [] + }, + "int": { + "size": 4, + "basic_type": 13, + "type_class": 4, + "fields": [], + "generic_params": [] + }, + "unsigned long long": { + "size": 8, + "basic_type": 18, + "type_class": 4, + "fields": [], + "generic_params": [] + }, + "unsigned char": { + "size": 1, + "basic_type": 4, + "type_class": 4, + "fields": [], + "generic_params": [] + }, + "unsigned short": { + "size": 2, + "basic_type": 12, + "type_class": 4, + "fields": [], + "generic_params": [] + }, + "unsigned int": { + "size": 4, + "basic_type": 14, + "type_class": 4, + "fields": [], + "generic_params": [] + }, + "float": { + "size": 4, + "basic_type": 23, + "type_class": 4, + "fields": [], + "generic_params": [] + }, + "double": { + "size": 8, + "basic_type": 24, + "type_class": 4, + "fields": [], + "generic_params": [] + } + }, + "breakpoints": [ + { + "b": { + "type": "bool", + "pretty_type_name": null, + "pretty_print": "false", + "value": false, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "i": { + "type": "long long", + "pretty_type_name": null, + "pretty_print": "-1", + "value": -1, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "c": { + "type": "char32_t", + "pretty_type_name": null, + "pretty_print": "U+0x00000061 U'a'", + "value": "a", + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "i8": { + "type": "signed char", + "pretty_type_name": null, + "pretty_print": "'D'", + "value": 68, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "i16": { + "type": "short", + "pretty_type_name": null, + "pretty_print": "-16", + "value": -16, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "i32": { + "type": "int", + "pretty_type_name": null, + "pretty_print": "-32", + "value": -32, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "i64": { + "type": "long long", + "pretty_type_name": null, + "pretty_print": "-64", + "value": -64, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "u": { + "type": "unsigned long long", + "pretty_type_name": null, + "pretty_print": "1", + "value": 1, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "u8": { + "type": "unsigned char", + "pretty_type_name": null, + "pretty_print": "'d'", + "value": 100, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "u16": { + "type": "unsigned short", + "pretty_type_name": null, + "pretty_print": "16", + "value": 16, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "u32": { + "type": "unsigned int", + "pretty_type_name": null, + "pretty_print": "32", + "value": 32, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "u64": { + "type": "unsigned long long", + "pretty_type_name": null, + "pretty_print": "64", + "value": 64, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "f32": { + "type": "float", + "pretty_type_name": null, + "pretty_print": "2.5", + "value": 2.5, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + }, + "f64": { + "type": "double", + "pretty_type_name": null, + "pretty_print": "3.5", + "value": 3.5, + "synthetic": null, + "summary": null, + "format": null, + "children": [] + } + } + ] +} diff --git a/tests/debuginfo/basic-types.rs b/tests/debuginfo/basic-types/main.rs similarity index 84% rename from tests/debuginfo/basic-types.rs rename to tests/debuginfo/basic-types/main.rs index eea5c68d886f7..00c2685d4b3b9 100644 --- a/tests/debuginfo/basic-types.rs +++ b/tests/debuginfo/basic-types/main.rs @@ -7,6 +7,10 @@ //@ compile-flags:-g //@ disable-gdb-pretty-printers //@ ignore-backends: gcc +//@ min-llvm-lldb-version: 22.1.0 + +// This version corresponds to swift 6.2.3/lldb 19.1.5 +//@ min-apple-lldb-version: 1703.0.236.21 // === GDB TESTS =================================================================================== @@ -47,33 +51,20 @@ // === LLDB TESTS ================================================================================== //@ lldb-command:run -//@ lldb-command:v b -//@ lldb-check:[...] false -//@ lldb-command:v i -//@ lldb-check:[...] -1 - -//@ lldb-command:v i8 -//@ lldb-check:[...] 'D' -//@ lldb-command:v i16 -//@ lldb-check:[...] -16 -//@ lldb-command:v i32 -//@ lldb-check:[...] -32 -//@ lldb-command:v i64 -//@ lldb-check:[...] -64 -//@ lldb-command:v u -//@ lldb-check:[...] 1 -//@ lldb-command:v u8 -//@ lldb-check:[...] 'd' -//@ lldb-command:v u16 -//@ lldb-check:[...] 16 -//@ lldb-command:v u32 -//@ lldb-check:[...] 32 -//@ lldb-command:v u64 -//@ lldb-check:[...] 64 -//@ lldb-command:v f32 -//@ lldb-check:[...] 2.5 -//@ lldb-command:v f64 -//@ lldb-check:[...] 3.5 +//@ lldb-repr:b +//@ lldb-repr:i +//@ lldb-repr:c +//@ lldb-repr:i8 +//@ lldb-repr:i16 +//@ lldb-repr:i32 +//@ lldb-repr:i64 +//@ lldb-repr:u +//@ lldb-repr:u8 +//@ lldb-repr:u16 +//@ lldb-repr:u32 +//@ lldb-repr:u64 +//@ lldb-repr:f32 +//@ lldb-repr:f64 // === CDB TESTS ===================================================================================