diff --git a/lldb/test/API/tools/lldb-dap/variables/objc/Makefile b/lldb/test/API/tools/lldb-dap/variables/objc/Makefile new file mode 100644 index 0000000000000..71d7ec417633f --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/variables/objc/Makefile @@ -0,0 +1,9 @@ +OBJC_SOURCES := main.m + +CFLAGS_EXTRAS := -w -fobjc-arc + +USE_SYSTEM_STDLIB := 1 + +LD_EXTRAS := -framework Foundation + +include Makefile.rules diff --git a/lldb/test/API/tools/lldb-dap/variables/objc/TestDAP_variables_objc.py b/lldb/test/API/tools/lldb-dap/variables/objc/TestDAP_variables_objc.py new file mode 100644 index 0000000000000..9dcbed50987b9 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/variables/objc/TestDAP_variables_objc.py @@ -0,0 +1,31 @@ +""" +Test 'variables' requests for obj-c types. +""" + +import lldbdap_testcase +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * + + +class TestDAP_variables_objc(lldbdap_testcase.DAPTestCaseBase): + @skipUnlessDarwin + def test_objc_description(self): + """Test that we can get the description of an Objective-C object.""" + program = self.getBuildArtifact("a.out") + self.build_and_launch( + program, + ) + source = "main.m" + breakpoint_ids = self.set_source_breakpoints( + source, [line_number(source, "// breakpoint")] + ) + self.continue_to_breakpoints(breakpoint_ids) + + greeter_var = self.dap_server.get_local_variable(name="greeter") + self.assertIsNotNone(greeter_var, "greeter variable should not be None") + self.assertEqual(greeter_var["type"], "Greeter *") + self.assertEqual(greeter_var["evaluateName"], "greeter") + self.assertRegexpMatches( + greeter_var["value"], r"" + ) + self.continue_to_exit() diff --git a/lldb/test/API/tools/lldb-dap/variables/objc/main.m b/lldb/test/API/tools/lldb-dap/variables/objc/main.m new file mode 100644 index 0000000000000..aeafa0019af8c --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/variables/objc/main.m @@ -0,0 +1,45 @@ +#import + +@interface Greeter : NSObject + +@property(nonatomic, strong) NSString *name; + +- (void)greet:(NSString *)other; + +@end + +@implementation Greeter + +- (instancetype)initWithName:(NSString *)name { + if ((self = [super init])) { + _name = [name copy]; + } + return self; +} + +- (void)greet:(NSString *)other { + NSLog(@"Hello %@, from %@", other, _name); +} + +- (NSString *)description { + return + [NSString stringWithFormat:@"", (void *)self, _name]; +} + +- (NSString *)debugDescription { + return [NSString stringWithFormat:@"", + (void *)self, _name]; +} + +@end + +int main(int argc, char *argv[]) { + Greeter *greeter = [[Greeter alloc] initWithName:@"Bob"]; + if (argc > 1) { + [greeter greet:@(argv[1])]; + } else { + [greeter greet:@"World"]; + } + + return 0; // breakpoint +} diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp index 08e65ab835a57..a42b4af05d782 100644 --- a/lldb/tools/lldb-dap/JSONUtils.cpp +++ b/lldb/tools/lldb-dap/JSONUtils.cpp @@ -836,6 +836,7 @@ VariableDescription::VariableDescription(lldb::SBValue v, os_display_value << ""; } else { value = llvm::StringRef(v.GetValue()).str(); + object_description = llvm::StringRef(v.GetObjectDescription()).str(); summary = llvm::StringRef(v.GetSummary()).str(); if (summary.empty() && auto_variable_summaries) auto_summary = TryCreateAutoSummary(v); @@ -843,7 +844,10 @@ VariableDescription::VariableDescription(lldb::SBValue v, std::optional effective_summary = !summary.empty() ? summary : auto_summary; - if (!value.empty()) { + if (!object_description.empty() && + (!effective_summary || effective_summary->empty())) { + os_display_value << object_description; + } else if (!value.empty()) { os_display_value << value; if (effective_summary) os_display_value << " " << *effective_summary; @@ -901,18 +905,18 @@ llvm::json::Object VariableDescription::GetVariableExtensionsJSON() { std::string VariableDescription::GetResult(llvm::StringRef context) { // In repl context, the results can be displayed as multiple lines so more // detailed descriptions can be returned. - if (context != "repl") + if (context != "repl" || !v.IsValid()) return display_value; - if (!v.IsValid()) - return display_value; + // First, try the SBValue::GetObjectDescription(), which may call into + // language runtime specific formatters (see ValueObjectPrinter). + if (!object_description.empty()) + return object_description; - // Try the SBValue::GetDescription(), which may call into language runtime - // specific formatters (see ValueObjectPrinter). + // Fallback to the default description for the value. lldb::SBStream stream; v.GetDescription(stream); - llvm::StringRef description = stream.GetData(); - return description.trim().str(); + return llvm::StringRef(stream.GetData()).trim().str(); } bool ValuePointsToCode(lldb::SBValue v) { diff --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h index fd9a06931ebff..0a660d04d02d8 100644 --- a/lldb/tools/lldb-dap/JSONUtils.h +++ b/lldb/tools/lldb-dap/JSONUtils.h @@ -329,6 +329,9 @@ struct VariableDescription { std::string evaluate_name; // The output of SBValue.GetValue() if it doesn't fail. It might be empty. std::string value; + // The output of SBValue.GetObjectDescription() if it doesn't fail. It might + // be empty. + std::string object_description; // The summary string of this variable. It might be empty. std::string summary; // The auto summary if using `enableAutoVariableSummaries`.