blob: 67194a09ec4ce2599ded6784c45ee48efe939fcc [file] [edit]
from __future__ import annotations
from typing import List
import lldb
from lldb_providers import *
from rust_types import (
ENUM_DISR_FIELD_NAME,
ENUM_LLDB_ENCODED_VARIANTS,
RustType,
classify_union,
is_tuple_fields,
)
####################################################################################################
# This file contains lookup functions that associate rust types with their synthetic/summary
# providers.
#
# LLDB caches the results of the the commands in `lldb_commands`, but that caching is "shallow". It
# purely associates the type with the function given, whether it is a regular function or a class
# constructor. If the function makes decisions about what type of SyntheticProvider to return, that
# processing is done **each time a value of that type is encountered**.
#
# To reiterate, inspecting a `vec![T; 100_000]` will call `T`'s lookup function/constructor 100,000
# times. This can lead to significant delays in value visualization if the lookup logic is complex.
#
# As such, lookup functions should be kept as minimal as possible. LLDB technically expects a
# SyntheticProvider class constructor. If you can provide just a class constructor, that should be
# preferred. If extra processing must be done, try to keep it as minimal and as targeted as possible
# (see: `classify_hashmap()` vs `classify_hashset()`).
####################################################################################################
# BACKCOMPAT: rust 1.35
def is_hashbrown_hashmap(hash_map: lldb.SBValue) -> bool:
return len(hash_map.type.fields) == 1
def classify_hashmap(valobj: lldb.SBValue, _dict: LLDBOpaque) -> object:
if is_hashbrown_hashmap(valobj):
return StdHashMapSyntheticProvider(valobj, _dict)
else:
return StdOldHashMapSyntheticProvider(valobj, _dict)
def classify_hashset(valobj: lldb.SBValue, _dict: LLDBOpaque) -> object:
hash_map = valobj.GetChildAtIndex(0)
if is_hashbrown_hashmap(hash_map):
return StdHashMapSyntheticProvider(valobj, _dict, show_values=False)
else:
return StdOldHashMapSyntheticProvider(hash_map, _dict, show_values=False)
def arc_synthetic(valobj: lldb.SBValue, _dict: LLDBOpaque) -> object:
return StdRcSyntheticProvider(valobj, _dict, is_atomic=True)
def classify_rust_type(type: lldb.SBType, is_msvc: bool) -> RustType:
if type.IsPointerType():
return RustType.Indirection
# there is a bit of code duplication here because we don't want to check all of the standard
# library regexes since LLDB handles that for us
type_class = type.GetTypeClass()
if type_class == lldb.eTypeClassStruct:
fields: List[lldb.SBTypeMember] = type.fields
if len(fields) == 0:
return RustType.Empty
# <<variant>> is emitted by GDB while LLDB(18.1+) emits "$variants$"
if (
fields[0].name == ENUM_DISR_FIELD_NAME
or fields[0].name == ENUM_LLDB_ENCODED_VARIANTS
):
return RustType.Enum
if is_tuple_fields(fields):
return RustType.Tuple
return RustType.Struct
if type_class == lldb.eTypeClassUnion:
# If we're debugging msvc, sum-type enums should have been caught by the regex in lldb
# commands since they all start with "enum2$<"
if is_msvc:
return RustType.Union
return classify_union(type.fields)
return RustType.Other
def synthetic_lookup(valobj: lldb.SBValue, _dict: LLDBOpaque) -> object:
"""Returns the synthetic provider for the given value"""
# small hack to check for the DWARF debug info section, since SBTarget.triple and
# SBProcess.triple report lldb's target rather than the executable's. SBProcessInfo.triple
# returns a triple without the ABI. It is also possible for any of those functions to return a
# None object.
# Instead, we look for the GNU `.debug_info` section, as MSVC does not have one with the same
# name
# FIXME: I don't know if this works when the DWARF lives in a separate file
# (see: https://gcc.gnu.org/wiki/DebugFissionDWP). Splitting the DWARF is very uncommon afaik so
# it should be okay for the time being.
is_msvc = not valobj.GetFrame().GetModule().FindSection(".debug_info").IsValid()
rust_type = classify_rust_type(valobj.GetType(), is_msvc)
if rust_type == RustType.Struct or rust_type == RustType.Union:
return StructSyntheticProvider(valobj, _dict)
if rust_type == RustType.StructVariant:
return StructSyntheticProvider(valobj, _dict, is_variant=True)
if rust_type == RustType.Tuple:
return TupleSyntheticProvider(valobj, _dict)
if rust_type == RustType.TupleVariant:
return TupleSyntheticProvider(valobj, _dict, is_variant=True)
if rust_type == RustType.Empty:
return EmptySyntheticProvider(valobj, _dict)
if rust_type == RustType.RegularEnum:
discriminant = valobj.GetChildAtIndex(0).GetChildAtIndex(0).GetValueAsUnsigned()
return synthetic_lookup(valobj.GetChildAtIndex(discriminant), _dict)
if rust_type == RustType.SingletonEnum:
return synthetic_lookup(valobj.GetChildAtIndex(0), _dict)
if rust_type == RustType.Enum:
# this little trick lets us treat `synthetic_lookup` as a "recognizer function" for the enum
# summary providers, reducing the number of lookups we have to do. This is a huge time save
# because there's no way (via type name) to recognize sum-type enums on `*-gnu` targets. The
# alternative would be to shove every single type through `summary_lookup`, which is
# incredibly wasteful. Once these scripts are updated for LLDB 19.0 and we can use
# `--recognizer-function`, this hack will only be needed for backwards compatibility.
summary: lldb.SBTypeSummary = valobj.GetTypeSummary()
if (
summary.summary_data is None
or summary.summary_data.strip()
!= "lldb_lookup.ClangEncodedEnumSummaryProvider(valobj,internal_dict)"
):
rust_category: lldb.SBTypeCategory = lldb.debugger.GetCategory("Rust")
rust_category.AddTypeSummary(
lldb.SBTypeNameSpecifier(valobj.GetTypeName()),
lldb.SBTypeSummary().CreateWithFunctionName(
"lldb_lookup.ClangEncodedEnumSummaryProvider"
),
)
return ClangEncodedEnumProvider(valobj, _dict)
if rust_type == RustType.Indirection:
return IndirectionSyntheticProvider(valobj, _dict)
return DefaultSyntheticProvider(valobj, _dict)