Skip to content

Commit 75ded7f

Browse files
committed
[lldb][ObjC] Consult Objective-C runtime decl vendor when completing type
(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
1 parent d13bcb6 commit 75ded7f

File tree

7 files changed

+139
-11
lines changed

7 files changed

+139
-11
lines changed

lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.cpp

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,46 @@ Status ObjCLanguageRuntime::ObjCExceptionPrecondition::ConfigurePrecondition(
423423
return error;
424424
}
425425

426+
CompilerType ObjCLanguageRuntime::LookupInModulesVendor(ConstString class_name,
427+
Target &target) {
428+
assert(class_name);
429+
430+
auto *persistent_state = llvm::cast<ClangPersistentVariables>(
431+
target.GetPersistentExpressionStateForLanguage(lldb::eLanguageTypeC));
432+
if (!persistent_state)
433+
return {};
434+
435+
auto clang_modules_decl_vendor_sp =
436+
persistent_state->GetClangModulesDeclVendor();
437+
if (!clang_modules_decl_vendor_sp)
438+
return {};
439+
440+
auto types = clang_modules_decl_vendor_sp->FindTypes(
441+
class_name, /*max_matches*/ UINT32_MAX);
442+
if (types.empty())
443+
return {};
444+
445+
return types.front();
446+
}
447+
448+
CompilerType ObjCLanguageRuntime::LookupInRuntime(ConstString class_name) {
449+
auto *runtime_vendor = GetDeclVendor();
450+
if (!runtime_vendor)
451+
return {};
452+
453+
std::vector<CompilerDecl> compiler_decls;
454+
runtime_vendor->FindDecls(class_name, false, UINT32_MAX, compiler_decls);
455+
if (compiler_decls.empty())
456+
return {};
457+
458+
auto *ctx =
459+
llvm::dyn_cast<TypeSystemClang>(compiler_decls[0].GetTypeSystem());
460+
if (!ctx)
461+
return {};
462+
463+
return ctx->GetTypeForDecl(compiler_decls[0].GetOpaqueDecl());
464+
}
465+
426466
std::optional<CompilerType>
427467
ObjCLanguageRuntime::GetRuntimeType(CompilerType base_type) {
428468
CompilerType class_type;
@@ -442,18 +482,21 @@ ObjCLanguageRuntime::GetRuntimeType(CompilerType base_type) {
442482
if (!class_name)
443483
return std::nullopt;
444484

445-
TypeSP complete_objc_class_type_sp = LookupInCompleteClassCache(class_name);
446-
if (!complete_objc_class_type_sp)
447-
return std::nullopt;
448-
449-
CompilerType complete_class(
450-
complete_objc_class_type_sp->GetFullCompilerType());
451-
if (complete_class.GetCompleteType()) {
452-
if (is_pointer_type)
453-
return complete_class.GetPointerType();
454-
else
455-
return complete_class;
485+
if (TypeSP complete_objc_class_type_sp =
486+
LookupInCompleteClassCache(class_name)) {
487+
if (CompilerType complete_class =
488+
complete_objc_class_type_sp->GetFullCompilerType();
489+
complete_class.GetCompleteType())
490+
return is_pointer_type ? complete_class.GetPointerType() : complete_class;
456491
}
457492

493+
assert(m_process);
494+
if (CompilerType found =
495+
LookupInModulesVendor(class_name, m_process->GetTarget()))
496+
return is_pointer_type ? found.GetPointerType() : found;
497+
498+
if (CompilerType found = LookupInRuntime(class_name))
499+
return is_pointer_type ? found.GetPointerType() : found;
500+
458501
return std::nullopt;
459502
}

lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,10 @@ class ObjCLanguageRuntime : public LanguageRuntime {
465465

466466
ObjCLanguageRuntime(const ObjCLanguageRuntime &) = delete;
467467
const ObjCLanguageRuntime &operator=(const ObjCLanguageRuntime &) = delete;
468+
469+
private:
470+
CompilerType LookupInRuntime(ConstString class_name);
471+
CompilerType LookupInModulesVendor(ConstString class_name, Target &process);
468472
};
469473

470474
} // namespace lldb_private
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
OBJC_SOURCES := main.m lib.m
2+
LD_EXTRAS = -framework Foundation
3+
4+
include Makefile.rules
5+
6+
lib.o: CFLAGS = $(CFLAGS_NO_DEBUG)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import lldb
2+
from lldbsuite.test.decorators import *
3+
from lldbsuite.test.lldbtest import *
4+
from lldbsuite.test import lldbutil
5+
6+
7+
class TestIvarInFrameworkBase(TestBase):
8+
"""
9+
Tests whether LLDB's data inspection commands can correctly retrieve
10+
information about ivars from the Objective-C runtime.
11+
In this test-case we have a base class type for which we don't have access
12+
to the debug-info of the implementation (mimicking the scenario of subclassing
13+
a type from a system framework). LLDB won't be able to see the backing ivar for
14+
'fooProp' from just debug-info, but it will fall back on the runtime to get the
15+
necessary information.
16+
"""
17+
18+
def test_frame_var(self):
19+
self.build()
20+
lldbutil.run_to_source_breakpoint(self, "break here", lldb.SBFileSpec("main.m"))
21+
self.expect("frame variable *bar", substrs=["_fooProp = 10", "_barProp = 15"])
22+
23+
def test_expr(self):
24+
self.build()
25+
lldbutil.run_to_source_breakpoint(self, "break here", lldb.SBFileSpec("main.m"))
26+
self.expect_expr(
27+
"*bar",
28+
result_type="Bar",
29+
result_children=[
30+
ValueCheck(
31+
name="Foo",
32+
children=[
33+
ValueCheck(name="NSObject"),
34+
ValueCheck(name="_fooProp", value="10"),
35+
],
36+
),
37+
ValueCheck(name="_barProp", value="15"),
38+
],
39+
)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#import <Foundation/Foundation.h>
2+
3+
@interface Foo : NSObject
4+
@property int fooProp;
5+
- (id)init;
6+
@end
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#import "lib.h"
2+
3+
@implementation Foo
4+
- (id)init {
5+
self.fooProp = 10;
6+
return self;
7+
}
8+
@end
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#import "lib.h"
2+
#include <stdio.h>
3+
4+
@interface Bar : Foo
5+
@property int barProp;
6+
- (id)init;
7+
@end
8+
9+
@implementation Bar
10+
11+
- (id)init {
12+
self = [super init];
13+
self.barProp = 15;
14+
return self;
15+
}
16+
@end
17+
18+
int main() {
19+
Bar *bar = [Bar new];
20+
puts("break here");
21+
return 0;
22+
}

0 commit comments

Comments
 (0)