[lldb][ObjC] Consult Objective-C runtime decl vendor when completing type (#164011)

(Note, this upstreams code that has been deployed on Apple's Swift LLDB
for many years at this point).

When a `ValueObject` computes its "complete type"
(`MaybeCalculateCompleteType`), it gives the language runtimes a chance
to override the type known to it. The current implementation of
`ObjCLanguageRuntime::GetRuntimeType`, however, didn't consult the
`AppleObjCDeclVendor` to look for types.

As demonstrated in the attached test, when we don't have debug-info for
a base class type (most commonly happens when inheriting from system
framework types) we would not be able to deduce ivars of that type.
However, the runtime knows about the ivars, so we should be able to
retrieve them.

There's still a couple of caveats for future follow-up/investigation:
1. `frame var` isn't able to access such backing ivars explicitly (even
if they do exist)
2. When compiling with `-gmodules`, LLDB gets confused about what is
correct source of information for these decls is.

rdar://162069497
This commit is contained in:
Michael Buch 2025-10-17 23:27:57 +01:00 committed by GitHub
parent 9522f989b0
commit bf2d84db8e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 139 additions and 11 deletions

View File

@ -423,6 +423,46 @@ Status ObjCLanguageRuntime::ObjCExceptionPrecondition::ConfigurePrecondition(
return error;
}
CompilerType ObjCLanguageRuntime::LookupInModulesVendor(ConstString class_name,
Target &target) {
assert(class_name);
auto *persistent_state = llvm::cast<ClangPersistentVariables>(
target.GetPersistentExpressionStateForLanguage(lldb::eLanguageTypeC));
if (!persistent_state)
return {};
auto clang_modules_decl_vendor_sp =
persistent_state->GetClangModulesDeclVendor();
if (!clang_modules_decl_vendor_sp)
return {};
auto types = clang_modules_decl_vendor_sp->FindTypes(
class_name, /*max_matches*/ UINT32_MAX);
if (types.empty())
return {};
return types.front();
}
CompilerType ObjCLanguageRuntime::LookupInRuntime(ConstString class_name) {
auto *runtime_vendor = GetDeclVendor();
if (!runtime_vendor)
return {};
std::vector<CompilerDecl> compiler_decls;
runtime_vendor->FindDecls(class_name, false, UINT32_MAX, compiler_decls);
if (compiler_decls.empty())
return {};
auto *ctx =
llvm::dyn_cast<TypeSystemClang>(compiler_decls[0].GetTypeSystem());
if (!ctx)
return {};
return ctx->GetTypeForDecl(compiler_decls[0].GetOpaqueDecl());
}
std::optional<CompilerType>
ObjCLanguageRuntime::GetRuntimeType(CompilerType base_type) {
CompilerType class_type;
@ -442,18 +482,21 @@ ObjCLanguageRuntime::GetRuntimeType(CompilerType base_type) {
if (!class_name)
return std::nullopt;
TypeSP complete_objc_class_type_sp = LookupInCompleteClassCache(class_name);
if (!complete_objc_class_type_sp)
return std::nullopt;
CompilerType complete_class(
complete_objc_class_type_sp->GetFullCompilerType());
if (complete_class.GetCompleteType()) {
if (is_pointer_type)
return complete_class.GetPointerType();
else
return complete_class;
if (TypeSP complete_objc_class_type_sp =
LookupInCompleteClassCache(class_name)) {
if (CompilerType complete_class =
complete_objc_class_type_sp->GetFullCompilerType();
complete_class.GetCompleteType())
return is_pointer_type ? complete_class.GetPointerType() : complete_class;
}
assert(m_process);
if (CompilerType found =
LookupInModulesVendor(class_name, m_process->GetTarget()))
return is_pointer_type ? found.GetPointerType() : found;
if (CompilerType found = LookupInRuntime(class_name))
return is_pointer_type ? found.GetPointerType() : found;
return std::nullopt;
}

View File

@ -465,6 +465,10 @@ protected:
ObjCLanguageRuntime(const ObjCLanguageRuntime &) = delete;
const ObjCLanguageRuntime &operator=(const ObjCLanguageRuntime &) = delete;
private:
CompilerType LookupInRuntime(ConstString class_name);
CompilerType LookupInModulesVendor(ConstString class_name, Target &process);
};
} // namespace lldb_private

View File

@ -0,0 +1,6 @@
OBJC_SOURCES := main.m lib.m
LD_EXTRAS = -framework Foundation
include Makefile.rules
lib.o: CFLAGS = $(CFLAGS_NO_DEBUG)

View File

@ -0,0 +1,39 @@
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
class TestIvarInFrameworkBase(TestBase):
"""
Tests whether LLDB's data inspection commands can correctly retrieve
information about ivars from the Objective-C runtime.
In this test-case we have a base class type for which we don't have access
to the debug-info of the implementation (mimicking the scenario of subclassing
a type from a system framework). LLDB won't be able to see the backing ivar for
'fooProp' from just debug-info, but it will fall back on the runtime to get the
necessary information.
"""
def test_frame_var(self):
self.build()
lldbutil.run_to_source_breakpoint(self, "break here", lldb.SBFileSpec("main.m"))
self.expect("frame variable *bar", substrs=["_fooProp = 10", "_barProp = 15"])
def test_expr(self):
self.build()
lldbutil.run_to_source_breakpoint(self, "break here", lldb.SBFileSpec("main.m"))
self.expect_expr(
"*bar",
result_type="Bar",
result_children=[
ValueCheck(
name="Foo",
children=[
ValueCheck(name="NSObject"),
ValueCheck(name="_fooProp", value="10"),
],
),
ValueCheck(name="_barProp", value="15"),
],
)

View File

@ -0,0 +1,6 @@
#import <Foundation/Foundation.h>
@interface Foo : NSObject
@property int fooProp;
- (id)init;
@end

View File

@ -0,0 +1,8 @@
#import "lib.h"
@implementation Foo
- (id)init {
self.fooProp = 10;
return self;
}
@end

View File

@ -0,0 +1,22 @@
#import "lib.h"
#include <stdio.h>
@interface Bar : Foo
@property int barProp;
- (id)init;
@end
@implementation Bar
- (id)init {
self = [super init];
self.barProp = 15;
return self;
}
@end
int main() {
Bar *bar = [Bar new];
puts("break here");
return 0;
}