Skip to content

[lldb] Implement dynamic type resolution for embedded Swift classes #10204

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,12 @@ class TargetReflectionContext : public ReflectionContextInterface {
return llvm::createStringError("could not read type from metadata");
}

std::optional<swift::remote::RemoteAbsolutePointer>
ReadPointer(lldb::addr_t instance_address) override {
auto ptr = m_reflection_ctx.readPointer(instance_address);
return ptr;
}

std::optional<bool> IsValueInlinedInExistentialContainer(
swift::remote::RemoteAddress existential_address) override {
return m_reflection_ctx.isValueInlinedInExistentialContainer(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ class ReflectionContextInterface {
ReadTypeFromInstance(lldb::addr_t instance_address,
swift::reflection::DescriptorFinder *descriptor_finder,
bool skip_artificial_subclasses = false) = 0;
virtual std::optional<swift::remote::RemoteAbsolutePointer>
ReadPointer(lldb::addr_t instance_address) = 0;
virtual std::optional<bool> IsValueInlinedInExistentialContainer(
swift::remote::RemoteAddress existential_address) = 0;
virtual llvm::Expected<const swift::reflection::TypeRef &> ApplySubstitutions(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,10 @@ class SwiftLanguageRuntime : public LanguageRuntime {
TypeAndOrName &class_type_or_name, Address &address,
Value::ValueType &value_type, llvm::ArrayRef<uint8_t> &local_buffer);

/// Resolves the dynamic type of an embedded Swift class type.
CompilerType GetDynamicTypeAndAddress_EmbeddedClass(uint64_t instance_ptr,
CompilerType class_type);

/// Dynamic type resolution tends to want to generate scalar data -
/// but there are caveats Per original comment here "Our address is
/// the location of the dynamic type stored in memory. It isn't a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1962,6 +1962,41 @@ static bool IsPrivateNSClass(NodePointer node) {
return false;
}

CompilerType SwiftLanguageRuntime::GetDynamicTypeAndAddress_EmbeddedClass(
uint64_t instance_ptr, CompilerType class_type) {
ThreadSafeReflectionContext reflection_ctx = GetReflectionContext();
if (!reflection_ctx)
return {};
/// If this is an embedded Swift type, there is no metadata, and the
/// pointer points to the type's vtable. We can still resolve the type by
/// reading the vtable's symbol name.
auto pointer = reflection_ctx->ReadPointer(instance_ptr);
if (!pointer)
return {};
llvm::StringRef symbol_name;
if (pointer->isResolved()) {
// Find the symbol name at this address.
Address address;
address.SetLoadAddress(pointer->getOffset(), &GetProcess().GetTarget());
Symbol *symbol = address.CalculateSymbolContextSymbol();
if (!symbol)
return {};
Mangled mangled = symbol->GetMangled();
if (!mangled)
return {};
symbol_name = symbol->GetMangled().GetMangledName().GetStringRef();
} else {
symbol_name = pointer->getSymbol();
}
TypeSystemSwiftTypeRefSP ts = class_type.GetTypeSystem()
.dyn_cast_or_null<TypeSystemSwift>()
->GetTypeSystemSwiftTypeRef();
// The symbol name will be something like "type metadata for Class", extract
// "Class" from that name.
auto dynamic_type = ts->GetTypeFromTypeMetadataNode(symbol_name);
return dynamic_type;
}

bool SwiftLanguageRuntime::GetDynamicTypeAndAddress_Class(
ValueObject &in_value, CompilerType class_type,
lldb::DynamicValueType use_dynamic, TypeAndOrName &class_type_or_name,
Expand Down Expand Up @@ -2023,42 +2058,46 @@ bool SwiftLanguageRuntime::GetDynamicTypeAndAddress_Class(
if (!reflection_ctx)
return false;

const swift::reflection::TypeRef *typeref = nullptr;
{
auto typeref_or_err = reflection_ctx->ReadTypeFromInstance(
instance_ptr, ts->GetDescriptorFinder(), true);
if (typeref_or_err)
typeref = &*typeref_or_err;
else
LLDB_LOG_ERRORV(GetLog(LLDBLog::Types), typeref_or_err.takeError(),
"{0}");
}
CompilerType dynamic_type;
if (SwiftLanguageRuntime::GetManglingFlavor(
class_type.GetMangledTypeName()) ==
swift::Mangle::ManglingFlavor::Default) {

// If we couldn't find the typeref from the instance, the best we can do is
// use the static type. This is a valid use case when the binary doesn't
// contain any metadata (for example, embedded Swift).
if (!typeref) {
auto typeref_or_err = reflection_ctx->GetTypeRef(
class_type.GetMangledTypeName(), ts->GetDescriptorFinder());
if (typeref_or_err)
typeref = &*typeref_or_err;
else
LLDB_LOG_ERRORV(GetLog(LLDBLog::Types), typeref_or_err.takeError(),
"{0}");
}
const swift::reflection::TypeRef *typeref = nullptr;
{
auto typeref_or_err = reflection_ctx->ReadTypeFromInstance(
instance_ptr, ts->GetDescriptorFinder(), true);
if (typeref_or_err)
typeref = &*typeref_or_err;
else
LLDB_LOG_ERRORV(GetLog(LLDBLog::Types), typeref_or_err.takeError(),
"{0}");
}

if (!typeref) {
HEALTH_LOG("could not read typeref for type: {0} (instance_ptr = {0:x})",
class_type.GetMangledTypeName(), instance_ptr);
return false;
}
if (!typeref) {
HEALTH_LOG(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coming soon: we should to convert this to return llvm::Expected :-)

"could not read typeref for type: {0} (instance_ptr = {1:x})",
class_type.GetMangledTypeName(), instance_ptr);
return false;
}

auto flavor = SwiftLanguageRuntime::GetManglingFlavor(
class_type.GetMangledTypeName());
auto flavor = SwiftLanguageRuntime::GetManglingFlavor(
class_type.GetMangledTypeName());

swift::Demangle::Demangler dem;
swift::Demangle::NodePointer node = typeref->getDemangling(dem);
dynamic_type = ts->RemangleAsType(dem, node, flavor);
} else {
dynamic_type =
GetDynamicTypeAndAddress_EmbeddedClass(instance_ptr, class_type);
if (!dynamic_type) {
HEALTH_LOG("could not resolve dynamic type of embedded swift class: "
"{0} (instance_ptr = {1:x})",
class_type.GetMangledTypeName(), instance_ptr);
return false;
}
}

swift::Demangle::Demangler dem;
swift::Demangle::NodePointer node = typeref->getDemangling(dem);
CompilerType dynamic_type = ts->RemangleAsType(dem, node, flavor);
LLDB_LOG(log, "dynamic type of instance_ptr {0:x} is {1}", instance_ptr,
class_type.GetMangledTypeName());
class_type_or_name.SetCompilerType(dynamic_type);
Expand Down Expand Up @@ -2207,39 +2246,77 @@ bool SwiftLanguageRuntime::GetDynamicTypeAndAddress_Existential(
auto tr_ts = tss->GetTypeSystemSwiftTypeRef();
if (!tr_ts)
return false;
auto pair = reflection_ctx->ProjectExistentialAndUnwrapClass(
remote_existential, *existential_typeref,
tr_ts->GetDescriptorFinder());
if (use_local_buffer)
PopLocalBuffer();

if (!pair) {
if (log)
log->Printf("Runtime failed to get dynamic type of existential");
return false;
}

const swift::reflection::TypeRef *typeref;
swift::remote::RemoteAddress out_address(nullptr);
std::tie(typeref, out_address) = *pair;
auto ts = tss->GetTypeSystemSwiftTypeRef();
if (!ts)
return false;
swift::Demangle::Demangler dem;
swift::Demangle::NodePointer node = typeref->getDemangling(dem);
auto flavor = SwiftLanguageRuntime::GetManglingFlavor(
existential_type.GetMangledTypeName());
CompilerType dynamic_type;
uint64_t dynamic_address = 0;
if (flavor == swift::Mangle::ManglingFlavor::Default) {
auto pair = reflection_ctx->ProjectExistentialAndUnwrapClass(
remote_existential, *existential_typeref, tr_ts->GetDescriptorFinder());

if (!pair) {
if (log)
log->Printf("Runtime failed to get dynamic type of existential");
return false;
}

const swift::reflection::TypeRef *typeref;
swift::remote::RemoteAddress out_address(nullptr);
std::tie(typeref, out_address) = *pair;

auto ts = tss->GetTypeSystemSwiftTypeRef();
if (!ts)
return false;
swift::Demangle::Demangler dem;
swift::Demangle::NodePointer node = typeref->getDemangling(dem);
dynamic_type = ts->RemangleAsType(dem, node, flavor);
dynamic_address = out_address.getAddressData();
} else {
// In the embedded Swift case, the existential container just points to the
// instance.
auto reflection_ctx = GetReflectionContext();
if (!reflection_ctx)
return false;
auto maybe_addr_or_symbol =
reflection_ctx->ReadPointer(existential_address);
if (!maybe_addr_or_symbol)
return false;

uint64_t address = 0;
if (maybe_addr_or_symbol->isResolved()) {
address = maybe_addr_or_symbol->getOffset();
} else {
SymbolContextList sc_list;
auto &module_list = GetProcess().GetTarget().GetImages();
module_list.FindSymbolsWithNameAndType(
ConstString(maybe_addr_or_symbol->getSymbol()), eSymbolTypeAny,
sc_list);
if (sc_list.GetSize() != 1)
return false;

SymbolContext sc = sc_list[0];
Symbol *symbol = sc.symbol;
address = symbol->GetLoadAddress(&GetProcess().GetTarget());
}

dynamic_type =
GetDynamicTypeAndAddress_EmbeddedClass(address, existential_type);
if (!dynamic_type)
return false;
dynamic_address = maybe_addr_or_symbol->getOffset();
}
if (use_local_buffer)
PopLocalBuffer();

class_type_or_name.SetCompilerType(ts->RemangleAsType(dem, node, flavor));
address.SetRawAddress(out_address.getAddressData());
class_type_or_name.SetCompilerType(dynamic_type);
address.SetRawAddress(dynamic_address);

#ifndef NDEBUG
if (ModuleList::GetGlobalModuleListProperties()
.GetSwiftValidateTypeSystem()) {
auto reference_pair = GetDynamicTypeAndAddress_ExistentialRemoteAST(
in_value, existential_type, use_local_buffer, existential_address);
assert(pair.has_value() >= reference_pair.has_value() &&
"RemoteAST and runtime diverge");

if (reference_pair) {
CompilerType ref_type = std::get<CompilerType>(*reference_pair);
Expand Down
12 changes: 12 additions & 0 deletions lldb/source/Plugins/TypeSystem/Swift/TypeSystemSwiftTypeRef.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,18 @@ TypeSystemSwiftTypeRef::GetBaseName(swift::Demangle::NodePointer node) {
}
}

CompilerType TypeSystemSwiftTypeRef::GetTypeFromTypeMetadataNode(
llvm::StringRef mangled_name) {
Demangler dem;
NodePointer node = dem.demangleSymbol(mangled_name);
NodePointer type = swift_demangle::NodeAtPath(
node, {Node::Kind::Global, Node::Kind::TypeMetadata, Node::Kind::Type});
if (!type)
return {};
auto flavor = SwiftLanguageRuntime::GetManglingFlavor(mangled_name);
return RemangleAsType(dem, type, flavor);
}

TypeSP TypeSystemSwiftTypeRef::LookupClangType(StringRef name_ref) {
llvm::SmallVector<CompilerContext, 2> decl_context;
// Make up a decl context for non-nested types.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,10 @@ class TypeSystemSwiftTypeRef : public TypeSystemSwift {
/// Return the base name of the topmost nominal type.
static llvm::StringRef GetBaseName(swift::Demangle::NodePointer node);

/// Given a mangled name that mangles a "type metadata for Type", return a
/// CompilerType with that Type.
CompilerType GetTypeFromTypeMetadataNode(llvm::StringRef mangled_name);

/// Use API notes to determine the swiftified name of \p clang_decl.
std::string GetSwiftName(const clang::Decl *clang_decl,
TypeSystemClang &clang_typesystem) override;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
SWIFT_SOURCES := main.swift
SWIFT_EMBEDDED_MODE := 1

include Makefile.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import lldb
from lldbsuite.test.lldbtest import *
from lldbsuite.test.decorators import *
import lldbsuite.test.lldbutil as lldbutil


class TestSwiftEmbeddedClassTypeResolution(TestBase):
@skipUnlessDarwin
@swiftTest
def test(self):
self.build()
self.runCmd("setting set symbols.swift-enable-ast-context false")

target, process, thread, _ = lldbutil.run_to_source_breakpoint(
self, "break here", lldb.SBFileSpec("main.swift")
)
self.expect('frame variable s', substrs=['a.Sub', 'a.Super', 'superField = 42', 'subField = 100'])
self.expect('frame variable p', substrs=['a.Sub', 'a.Super', 'superField = 42', 'subField = 100'])
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
protocol P: AnyObject {

}

class Super: P {
let superField = 42
}

class Sub: Super {
let subField = 100
}

func f() {
let s: Super = Sub()
let p: P = Sub()
print("break here")
}

f()