diff --git a/lldb/include/lldb/Core/Debugger.h b/lldb/include/lldb/Core/Debugger.h index 42b9f27014f3d..e964c481a90b9 100644 --- a/lldb/include/lldb/Core/Debugger.h +++ b/lldb/include/lldb/Core/Debugger.h @@ -316,6 +316,8 @@ class Debugger : public std::enable_shared_from_this, llvm::StringRef GetAutosuggestionAnsiSuffix() const; + bool GetShowDontUsePoHint() const; + bool GetUseSourceCache() const; bool SetUseSourceCache(bool use_source_cache); diff --git a/lldb/source/Commands/CommandObjectDWIMPrint.cpp b/lldb/source/Commands/CommandObjectDWIMPrint.cpp index e15e23770f74b..4220bcef34076 100644 --- a/lldb/source/Commands/CommandObjectDWIMPrint.cpp +++ b/lldb/source/Commands/CommandObjectDWIMPrint.cpp @@ -27,6 +27,8 @@ #include "llvm/ADT/StringRef.h" #include "llvm/Support/FormatVariadic.h" +#include + using namespace llvm; using namespace lldb; using namespace lldb_private; @@ -109,6 +111,37 @@ bool CommandObjectDWIMPrint::DoExecute(StringRef command, language = frame->GuessLanguage(); // END SWIFT + // Add a hint if object description was requested, but no description + // function was implemented. + auto maybe_add_hint = [&](llvm::StringRef output) { + // Identify the default output of object description for Swift and + // Objective-C + // ". The regex is: + // - Start with "<". + // - Followed by 1 or more non-whitespace characters. + // - Followed by ": 0x". + // - Followed by 5 or more hex digits. + // - Followed by ">". + // - End with zero or more whitespace characters. + const std::regex swift_class_regex("^<\\S+: 0x[[:xdigit:]]{5,}>\\s*$"); + + if (GetDebugger().GetShowDontUsePoHint() && target_ptr && + (language == lldb::eLanguageTypeSwift || + language == lldb::eLanguageTypeObjC) && + std::regex_match(output.data(), swift_class_regex)) { + + static bool note_shown = false; + if (note_shown) + return; + + result.GetOutputStream() + << "note: object description requested, but type doesn't implement " + "a custom object description. Consider using \"p\" instead of " + "\"po\" (this note will only be shown once per debug session).\n"; + note_shown = true; + } + }; + // First, try `expr` as the name of a frame variable. if (frame) { auto valobj_sp = frame->FindVariable(ConstString(expr)); @@ -126,7 +159,15 @@ bool CommandObjectDWIMPrint::DoExecute(StringRef command, flags, expr); } - valobj_sp->Dump(result.GetOutputStream(), dump_options); + if (is_po) { + StreamString temp_result_stream; + valobj_sp->Dump(temp_result_stream, dump_options); + llvm::StringRef output = temp_result_stream.GetString(); + maybe_add_hint(output); + result.GetOutputStream() << output; + } else { + valobj_sp->Dump(result.GetOutputStream(), dump_options); + } result.SetStatus(eReturnStatusSuccessFinishResult); return true; } @@ -185,8 +226,17 @@ bool CommandObjectDWIMPrint::DoExecute(StringRef command, expr); } - if (valobj_sp->GetError().GetError() != UserExpression::kNoResult) - valobj_sp->Dump(result.GetOutputStream(), dump_options); + if (valobj_sp->GetError().GetError() != UserExpression::kNoResult) { + if (is_po) { + StreamString temp_result_stream; + valobj_sp->Dump(temp_result_stream, dump_options); + llvm::StringRef output = temp_result_stream.GetString(); + maybe_add_hint(output); + result.GetOutputStream() << output; + } else { + valobj_sp->Dump(result.GetOutputStream(), dump_options); + } + } if (suppress_result) if (auto result_var_sp = diff --git a/lldb/source/Core/CoreProperties.td b/lldb/source/Core/CoreProperties.td index 7a9438085bc66..5e2020f50e512 100644 --- a/lldb/source/Core/CoreProperties.td +++ b/lldb/source/Core/CoreProperties.td @@ -225,6 +225,10 @@ let Definition = "debugger" in { Global, DefaultStringValue<"${ansi.normal}">, Desc<"When displaying suggestion in a color-enabled terminal, use the ANSI terminal code specified in this format immediately after the suggestion.">; + def ShowDontUsePoHint: Property<"show-dont-use-po-hint", "Boolean">, + Global, + DefaultTrue, + Desc<"If true, and object description was requested for a type that does not implement it, LLDB will print a hint telling the user to consider using p instead.">; def DWIMPrintVerbosity: Property<"dwim-print-verbosity", "Enum">, Global, DefaultEnumValue<"eDWIMPrintVerbosityNone">, diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp index 3ccc0a55f6700..73fdf665ed4ad 100644 --- a/lldb/source/Core/Debugger.cpp +++ b/lldb/source/Core/Debugger.cpp @@ -423,6 +423,12 @@ llvm::StringRef Debugger::GetAutosuggestionAnsiSuffix() const { return m_collection_sp->GetPropertyAtIndexAsString(nullptr, idx, ""); } +bool Debugger::GetShowDontUsePoHint() const { + const uint32_t idx = ePropertyShowDontUsePoHint; + return m_collection_sp->GetPropertyAtIndexAsBoolean(nullptr, + idx, g_debugger_properties[idx].default_uint_value != 0); +} + bool Debugger::GetUseSourceCache() const { const uint32_t idx = ePropertyUseSourceCache; return m_collection_sp->GetPropertyAtIndexAsBoolean( diff --git a/lldb/test/API/lang/objc/objc-po-hint/Makefile b/lldb/test/API/lang/objc/objc-po-hint/Makefile new file mode 100644 index 0000000000000..ad28ecfeb5d12 --- /dev/null +++ b/lldb/test/API/lang/objc/objc-po-hint/Makefile @@ -0,0 +1,4 @@ +OBJC_SOURCES := main.m +LD_EXTRAS = -lobjc -framework Foundation + +include Makefile.rules diff --git a/lldb/test/API/lang/objc/objc-po-hint/TestObjcPoHint.py b/lldb/test/API/lang/objc/objc-po-hint/TestObjcPoHint.py new file mode 100644 index 0000000000000..5d65180f9626b --- /dev/null +++ b/lldb/test/API/lang/objc/objc-po-hint/TestObjcPoHint.py @@ -0,0 +1,49 @@ +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class TestObjcPoHint(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + def test_show_po_hint(self): + ### Test that the po hint is shown once with the DWIM print command + self.build() + _, _, _, _ = lldbutil.run_to_source_breakpoint( + self, "Set breakpoint here", lldb.SBFileSpec("main.m") + ) + # Make sure the hint is printed the first time + self.expect( + "dwim-print -O -- foo", + substrs=[ + "note: object description requested, but type doesn't implement " + 'a custom object description. Consider using "p" instead of "po"', + " + +@interface Foo : NSObject {} + +-(id) init; + +@end + +@implementation Foo + +-(id) init +{ + return self = [super init]; +} +@end + +int main() +{ + Foo *foo = [Foo new]; + NSLog(@"a"); // Set breakpoint here. + return 0; +}