From 61020d31c2f743bb1f6918e2413b6cafb1ddd2f8 Mon Sep 17 00:00:00 2001 From: Michael Buch Date: Fri, 10 Oct 2025 19:23:02 +0100 Subject: [PATCH 1/5] [lldb][Expression] Emit a 'Note' diagnostic that indicates the language used for expression evaluation (#161688) Depends on: * https://github.com/llvm/llvm-project/pull/162050 Since it's a 'Note' diagnostic it would only show up when expression evaluation actually failed. This helps with expression evaluation failure reports in mixed language environments where it's not quite clear what language the expression ran as. It may also reduce confusion around why the expression evaluator ran an expression in a language it wasn't asked to run (a softer alternative to what I attempted in https://github.com/llvm/llvm-project/pull/156648). Here are some example outputs: ``` # Without target (lldb) expr blah note: Falling back to default language. Ran expression as 'Objective C++'. # Stopped in target (lldb) expr blah note: Ran expression as 'C++14'. (lldb) expr -l objc -- blah note: Expression evaluation in pure Objective-C not supported. Ran expression as 'Objective C++'. (lldb) expr -l c -- blah note: Expression evaluation in pure C not supported. Ran expression as 'ISO C++'. (lldb) expr -l c++14 -- blah note: Ran expression as 'C++14' (lldb) expr -l c++20 -- blah note: Ran expression as 'C++20' (lldb) expr -l objective-c++ -- blah note: Ran expression as 'Objective C++' (lldb) expr -l D -- blah note: Expression evaluation in D not supported. Falling back to default language. Ran expression as 'Objective C++'. ``` I didn't put the diagnostic on the same line as the inline diagnostic for now because of implementation convenience, but if reviewers deem that a blocker I can take a stab at that again. Also, other language plugins (namely Swift), won't immediately benefit from this and will have to emit their own diagnistc. I played around with having a virtual API on `UserExpression` or `ExpressionParser` that will be called consistently, but by the time we're about to parse the expression we are already several frames deep into the plugin. Before (and at the beginning of) the generic `UserExpression::Parse` call we don't have enough information to notify which language we're going to parse in (at least for the C++ plugin). rdar://160297649 rdar://159669244 (cherry picked from commit e3620fe0685c656915977d55f822a82090041965) --- .../Clang/ClangExpressionParser.cpp | 40 ++++++++- .../Clang/ClangExpressionParser.h | 1 + .../Clang/ClangFunctionCaller.cpp | 4 +- .../Clang/ClangUserExpression.cpp | 4 +- .../Clang/ClangUtilityFunction.cpp | 2 +- .../diagnostics/TestExprDiagnostics.py | 20 ++++- .../interpreter/TestCommandInterpreterAPI.py | 50 ++++++++--- .../test/Shell/Expr/TestExprLanguageNote.test | 87 +++++++++++++++++++ 8 files changed, 185 insertions(+), 23 deletions(-) create mode 100644 lldb/test/Shell/Expr/TestExprLanguageNote.test diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp index 2af4544f498cf..8f88e4a2f1726 100644 --- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp +++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp @@ -74,6 +74,7 @@ #include "lldb/Core/Debugger.h" #include "lldb/Core/Disassembler.h" #include "lldb/Core/Module.h" +#include "lldb/Expression/DiagnosticManager.h" #include "lldb/Expression/IRExecutionUnit.h" #include "lldb/Expression/IRInterpreter.h" #include "lldb/Host/File.h" @@ -96,6 +97,7 @@ #include "Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h" #include "Plugins/Platform/MacOSX/PlatformDarwin.h" #include "lldb/Utility/XcodeSDK.h" +#include "lldb/lldb-enumerations.h" #include #include @@ -570,7 +572,8 @@ static void SetupTargetOpts(CompilerInstance &compiler, static void SetupLangOpts(CompilerInstance &compiler, ExecutionContextScope &exe_scope, - const Expression &expr) { + const Expression &expr, + DiagnosticManager &diagnostic_manager) { Log *log = GetLog(LLDBLog::Expressions); // If the expression is being evaluated in the context of an existing stack @@ -590,6 +593,9 @@ static void SetupLangOpts(CompilerInstance &compiler, : lldb::eLanguageTypeUnknown), lldb_private::Language::GetNameForLanguageType(language)); + lldb::LanguageType language_for_note = language; + std::string language_fallback_reason; + LangOptions &lang_opts = compiler.getLangOpts(); switch (language) { @@ -603,6 +609,10 @@ static void SetupLangOpts(CompilerInstance &compiler, // family language, because the expression parser uses features of C++ to // capture values. lang_opts.CPlusPlus = true; + + language_for_note = lldb::eLanguageTypeC_plus_plus; + language_fallback_reason = + "Expression evaluation in pure C not supported. "; break; case lldb::eLanguageTypeObjC: lang_opts.ObjC = true; @@ -610,6 +620,10 @@ static void SetupLangOpts(CompilerInstance &compiler, // to "ask for ObjC, get ObjC++" (see comment above). lang_opts.CPlusPlus = true; + language_for_note = lldb::eLanguageTypeObjC_plus_plus; + language_fallback_reason = + "Expression evaluation in pure Objective-C not supported. "; + // Clang now sets as default C++14 as the default standard (with // GNU extensions), so we do the same here to avoid mismatches that // cause compiler error when evaluating expressions (e.g. nullptr not found @@ -650,9 +664,27 @@ static void SetupLangOpts(CompilerInstance &compiler, lang_opts.CPlusPlus = true; lang_opts.CPlusPlus11 = true; compiler.getHeaderSearchOpts().UseLibcxx = true; + + language_for_note = lldb::eLanguageTypeObjC_plus_plus; + if (language != language_for_note) { + if (language != lldb::eLanguageTypeUnknown) + language_fallback_reason = llvm::formatv( + "Expression evaluation in {0} not supported. ", + lldb_private::Language::GetDisplayNameForLanguageType(language)); + + language_fallback_reason += + llvm::formatv("Falling back to default language. "); + } break; } + diagnostic_manager.AddDiagnostic( + llvm::formatv("{0}Ran expression as '{1}'.", language_fallback_reason, + lldb_private::Language::GetDisplayNameForLanguageType( + language_for_note)) + .str(), + lldb::Severity::eSeverityInfo, DiagnosticOrigin::eDiagnosticOriginLLDB); + lang_opts.Bool = true; lang_opts.WChar = true; lang_opts.Blocks = true; @@ -736,8 +768,8 @@ static void SetupImportStdModuleLangOpts(CompilerInstance &compiler, ClangExpressionParser::ClangExpressionParser( ExecutionContextScope *exe_scope, Expression &expr, - bool generate_debug_info, std::vector include_directories, - std::string filename) + bool generate_debug_info, DiagnosticManager &diagnostic_manager, + std::vector include_directories, std::string filename) : ExpressionParser(exe_scope, expr, generate_debug_info), m_compiler(), m_pp_callbacks(nullptr), m_include_directories(std::move(include_directories)), @@ -801,7 +833,7 @@ ClangExpressionParser::ClangExpressionParser( } // 4. Set language options. - SetupLangOpts(*m_compiler, *exe_scope, expr); + SetupLangOpts(*m_compiler, *exe_scope, expr, diagnostic_manager); auto *clang_expr = dyn_cast(&m_expr); if (clang_expr && clang_expr->DidImportCxxModules()) { LLDB_LOG(log, "Adding lang options for importing C++ modules"); diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h index 93e0b007dbcc8..734ad51c9646e 100644 --- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h +++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h @@ -65,6 +65,7 @@ class ClangExpressionParser : public ExpressionParser { /// diagnostics (i.e. errors, warnings or notes from Clang). ClangExpressionParser(ExecutionContextScope *exe_scope, Expression &expr, bool generate_debug_info, + DiagnosticManager &diagnostic_manager, std::vector include_directories = {}, std::string filename = ""); diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangFunctionCaller.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangFunctionCaller.cpp index e4a094f3aa512..d2db319afb7a0 100644 --- a/lldb/source/Plugins/ExpressionParser/Clang/ClangFunctionCaller.cpp +++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangFunctionCaller.cpp @@ -189,8 +189,8 @@ ClangFunctionCaller::CompileFunction(lldb::ThreadSP thread_to_use_sp, lldb::ProcessSP jit_process_sp(m_jit_process_wp.lock()); if (jit_process_sp) { const bool generate_debug_info = true; - auto *clang_parser = new ClangExpressionParser(jit_process_sp.get(), *this, - generate_debug_info); + auto *clang_parser = new ClangExpressionParser( + jit_process_sp.get(), *this, generate_debug_info, diagnostic_manager); num_errors = clang_parser->Parse(diagnostic_manager); m_parser.reset(clang_parser); } else { diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp index 6b743e29e21f6..e8d5ec3c7fd96 100644 --- a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp +++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp @@ -574,7 +574,7 @@ bool ClangUserExpression::TryParse( m_parser = std::make_unique( exe_ctx.GetBestExecutionContextScope(), *this, generate_debug_info, - m_include_directories, m_filename); + diagnostic_manager, m_include_directories, m_filename); unsigned num_errors = m_parser->Parse(diagnostic_manager); @@ -818,7 +818,7 @@ bool ClangUserExpression::Complete(ExecutionContext &exe_ctx, } ClangExpressionParser parser(exe_ctx.GetBestExecutionContextScope(), *this, - false); + false, diagnostic_manager); // We have to find the source code location where the user text is inside // the transformed expression code. When creating the transformed text, we diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangUtilityFunction.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangUtilityFunction.cpp index 1f44200c4cff8..e6983066a12fa 100644 --- a/lldb/source/Plugins/ExpressionParser/Clang/ClangUtilityFunction.cpp +++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangUtilityFunction.cpp @@ -120,7 +120,7 @@ bool ClangUtilityFunction::Install(DiagnosticManager &diagnostic_manager, const bool generate_debug_info = true; ClangExpressionParser parser(exe_ctx.GetBestExecutionContextScope(), *this, - generate_debug_info); + generate_debug_info, diagnostic_manager); unsigned num_errors = parser.Parse(diagnostic_manager); diff --git a/lldb/test/API/commands/expression/diagnostics/TestExprDiagnostics.py b/lldb/test/API/commands/expression/diagnostics/TestExprDiagnostics.py index 0cc505aedc4be..ec208f2c32503 100644 --- a/lldb/test/API/commands/expression/diagnostics/TestExprDiagnostics.py +++ b/lldb/test/API/commands/expression/diagnostics/TestExprDiagnostics.py @@ -215,8 +215,22 @@ def check_error(diags): details = diags.GetValueForKey("details") - # Detail 1/2: undeclared 'a' + # Detail 1/3: note: requested expression language diag = details.GetItemAtIndex(0) + self.assertEqual(str(diag.GetValueForKey("severity")), "note") + self.assertEqual( + str(diag.GetValueForKey("message")), "Ran expression as 'C++11'." + ) + self.assertEqual( + str(diag.GetValueForKey("rendered")), "Ran expression as 'C++11'." + ) + self.assertEqual(str(diag.GetValueForKey("source_location")), "") + self.assertEqual(str(diag.GetValueForKey("file")), "") + self.assertFalse(diag.GetValueForKey("hidden").GetBooleanValue()) + self.assertFalse(diag.GetValueForKey("in_user_input").GetBooleanValue()) + + # Detail 2/3: undeclared 'a' + diag = details.GetItemAtIndex(1) severity = diag.GetValueForKey("severity") message = diag.GetValueForKey("message") @@ -234,8 +248,8 @@ def check_error(diags): self.assertFalse(hidden.GetBooleanValue()) self.assertTrue(in_user_input.GetBooleanValue()) - # Detail 1/2: undeclared 'b' - diag = details.GetItemAtIndex(1) + # Detail 3/3: undeclared 'b' + diag = details.GetItemAtIndex(2) message = diag.GetValueForKey("message") self.assertIn("undeclared identifier 'b'", str(message)) diff --git a/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py b/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py index 1029bdc3096d0..01ed11a5a1121 100644 --- a/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py +++ b/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py @@ -1,4 +1,4 @@ -"""Test the SBCommandInterpreter APIs.""" +"""tESt the SBCommandInterpreter APIs.""" import json import lldb @@ -156,13 +156,15 @@ def test_get_transcript(self): self.assertEqual(transcript[0]["error"], "") # (lldb) an-unknown-command - self.assertEqual(transcript[1], + self.assertEqual( + transcript[1], { "command": "an-unknown-command", # Unresolved commands don't have "commandName"/"commandArguments" "output": "", "error": "error: 'an-unknown-command' is not a valid command.\n", - }) + }, + ) # (lldb) br s -f main.c -l self.assertEqual(transcript[2]["command"], "br s -f main.c -l %d" % self.line) @@ -175,14 +177,17 @@ def test_get_transcript(self): self.assertEqual(transcript[2]["error"], "") # (lldb) p a - self.assertEqual(transcript[3], + self.assertEqual( + transcript[3], { "command": "p a", "commandName": "dwim-print", "commandArguments": "-- a", "output": "", - "error": "error: :1:1: use of undeclared identifier 'a'\n 1 | a\n | ^\n", - }) + "error": "note: Falling back to default language. Ran expression as 'Objective C++'.\n" + "error: :1:1: use of undeclared identifier 'a'\n 1 | a\n | ^\n", + }, + ) # (lldb) statistics dump self.assertEqual(transcript[4]["command"], "statistics dump") @@ -203,7 +208,10 @@ def test_save_transcript_setting_default(self): self.assertTrue(ci, VALID_COMMAND_INTERPRETER) # The setting's default value should be "false" - self.runCmd("settings show interpreter.save-transcript", "interpreter.save-transcript (boolean) = false\n") + self.runCmd( + "settings show interpreter.save-transcript", + "interpreter.save-transcript (boolean) = false\n", + ) def test_save_transcript_setting_off(self): ci = self.dbg.GetCommandInterpreter() @@ -250,17 +258,37 @@ def test_get_transcript_returns_copy(self): structured_data_1 = ci.GetTranscript() self.assertTrue(structured_data_1.IsValid()) self.assertEqual(structured_data_1.GetSize(), 1) - self.assertEqual(structured_data_1.GetItemAtIndex(0).GetValueForKey("command").GetStringValue(100), "version") + self.assertEqual( + structured_data_1.GetItemAtIndex(0) + .GetValueForKey("command") + .GetStringValue(100), + "version", + ) # Run some more commands and get the transcript as structured data again self.runCmd("help") structured_data_2 = ci.GetTranscript() self.assertTrue(structured_data_2.IsValid()) self.assertEqual(structured_data_2.GetSize(), 2) - self.assertEqual(structured_data_2.GetItemAtIndex(0).GetValueForKey("command").GetStringValue(100), "version") - self.assertEqual(structured_data_2.GetItemAtIndex(1).GetValueForKey("command").GetStringValue(100), "help") + self.assertEqual( + structured_data_2.GetItemAtIndex(0) + .GetValueForKey("command") + .GetStringValue(100), + "version", + ) + self.assertEqual( + structured_data_2.GetItemAtIndex(1) + .GetValueForKey("command") + .GetStringValue(100), + "help", + ) # Now, the first structured data should remain unchanged self.assertTrue(structured_data_1.IsValid()) self.assertEqual(structured_data_1.GetSize(), 1) - self.assertEqual(structured_data_1.GetItemAtIndex(0).GetValueForKey("command").GetStringValue(100), "version") + self.assertEqual( + structured_data_1.GetItemAtIndex(0) + .GetValueForKey("command") + .GetStringValue(100), + "version", + ) diff --git a/lldb/test/Shell/Expr/TestExprLanguageNote.test b/lldb/test/Shell/Expr/TestExprLanguageNote.test new file mode 100644 index 0000000000000..f3dc5928fcca9 --- /dev/null +++ b/lldb/test/Shell/Expr/TestExprLanguageNote.test @@ -0,0 +1,87 @@ +# RUN: split-file %s %t +# RUN: %clang_host -g %t/main.cpp -o %t.out +# +# RUN: %lldb -x -b -o "settings set interpreter.stop-command-source-on-error false" \ +# RUN: -s %t/no-target.input 2>&1 | FileCheck %s --check-prefix=CHECK-NO-TARGET +# +# RUN: %lldb %t.out -x -b -o "settings set interpreter.stop-command-source-on-error false" \ +# RUN: -s %t/with-target.input 2>&1 | FileCheck %s --check-prefix=CHECK-TARGET + +#--- main.cpp + +int main() { + int x = 10; + __builtin_debugtrap(); +} + +#--- with-target.input + +expr blah + +# CHECK-TARGET: (lldb) expr +# CHECK-TARGET: note: Falling back to default language. Ran expression as 'Objective C++'. + +run + +expr blah + +# CHECK-TARGET: (lldb) expr +# CHECK-TARGET: note: Ran expression as 'C++14'. + +expr -l objc -- blah + +# CHECK-TARGET: (lldb) expr +# CHECK-TARGET: note: Expression evaluation in pure Objective-C not supported. Ran expression as 'Objective C++'. + +expr -l c -- blah + +# CHECK-TARGET: (lldb) expr +# CHECK-TARGET: note: Expression evaluation in pure C not supported. Ran expression as 'ISO C++'. + +expr -l c++14 -- blah + +# CHECK-TARGET: (lldb) expr +# CHECK-TARGET: note: Ran expression as 'C++14' + +expr -l c++20 -- blah + +# CHECK-TARGET: (lldb) expr +# CHECK-TARGET: note: Ran expression as 'C++20' + +expr -l objective-c++ -- blah + +# CHECK-TARGET: (lldb) expr +# CHECK-TARGET: note: Ran expression as 'Objective C++' + +# D uses TypeSystemClang but running expressions in it isn't supported. Test that we warn about this. +expr -l D -- blah + +# CHECK-TARGET: (lldb) expr +# CHECK-TARGET: note: Expression evaluation in D not supported. Falling back to default language. Ran expression as 'Objective C++'. + +expr -l c++17 -- x = 5 + +# CHECK-TARGET: (lldb) expr +# CHECK-TARGET-NOT: note: + +expr x = 5 + +# CHECK-TARGET: (lldb) expr +# CHECK-TARGET-NOT: note: + +#--- no-target.input + +expr blah + +# CHECK-NO-TARGET: (lldb) expr +# CHECK-NO-TARGET: note: Falling back to default language. Ran expression as 'Objective C++'. + +expr -l c++ -- 1 + 1 + +# CHECK-NO-TARGET: (lldb) expr +# CHECK-NO-TARGET-NOT: note: + +expr 1 + 1 + +# CHECK-NO-TARGET: (lldb) expr +# CHECK-NO-TARGET-NOT: note: From 2788430db3c82b9b64430dbdf22d5a4d531b04f0 Mon Sep 17 00:00:00 2001 From: Michael Buch Date: Wed, 8 Oct 2025 18:33:25 +0100 Subject: [PATCH 2/5] [llvm][Dwarf] Add LanguageDescription API that accounts for version (#162048) Currently `llvm::dwarf::LanguageDescription` returns a stringified `DW_LNAME`. It would be useful to have an API that returns the language name for a particular `DW_LNAME_`/version pair. LLDB's use case is that it wants to emit diagnostics with human readable descriptions of the language we got from debug-info (see https://github.com/llvm/llvm-project/pull/161688). We could maintain a side-table in LLDB but thought this might be generally useful and should live next to the existing `LanguageDescription` API. (cherry picked from commit 030d8e66498d6818300b1a8678861a016bd37e10) --- llvm/include/llvm/BinaryFormat/Dwarf.h | 7 ++ llvm/lib/BinaryFormat/Dwarf.cpp | 111 ++++++++++++++++++++++ llvm/unittests/BinaryFormat/DwarfTest.cpp | 73 ++++++++++++++ 3 files changed, 191 insertions(+) diff --git a/llvm/include/llvm/BinaryFormat/Dwarf.h b/llvm/include/llvm/BinaryFormat/Dwarf.h index 231b7ac17d75f..7aaff7a781a5a 100644 --- a/llvm/include/llvm/BinaryFormat/Dwarf.h +++ b/llvm/include/llvm/BinaryFormat/Dwarf.h @@ -499,8 +499,15 @@ toDW_LNAME(SourceLanguage language) { return {}; } +/// Returns a version-independent language name. LLVM_ABI llvm::StringRef LanguageDescription(SourceLanguageName name); +/// Returns a language name corresponding to the specified version. +/// If the version is not recognized for the specified language, returns +/// the version-independent name. +LLVM_ABI llvm::StringRef LanguageDescription(SourceLanguageName Name, + uint32_t Version); + inline bool isCPlusPlus(SourceLanguage S) { bool result = false; // Deliberately enumerate all the language options so we get a warning when diff --git a/llvm/lib/BinaryFormat/Dwarf.cpp b/llvm/lib/BinaryFormat/Dwarf.cpp index 0d17dc175fed9..e08678b655209 100644 --- a/llvm/lib/BinaryFormat/Dwarf.cpp +++ b/llvm/lib/BinaryFormat/Dwarf.cpp @@ -472,6 +472,117 @@ StringRef llvm::dwarf::LanguageDescription(dwarf::SourceLanguageName lname) { return "Unknown"; } +StringRef llvm::dwarf::LanguageDescription(dwarf::SourceLanguageName Name, + uint32_t Version) { + switch (Name) { + // YYYY + case DW_LNAME_Ada: { + if (Version <= 1983) + return "Ada 83"; + if (Version <= 1995) + return "Ada 95"; + if (Version <= 2005) + return "Ada 2005"; + if (Version <= 2012) + return "Ada 2012"; + } break; + + case DW_LNAME_Cobol: { + if (Version <= 1974) + return "COBOL-74"; + if (Version <= 1985) + return "COBOL-85"; + } break; + + case DW_LNAME_Fortran: { + if (Version <= 1977) + return "FORTRAN 77"; + if (Version <= 1990) + return "FORTRAN 90"; + if (Version <= 1995) + return "Fortran 95"; + if (Version <= 2003) + return "Fortran 2003"; + if (Version <= 2008) + return "Fortran 2008"; + if (Version <= 2018) + return "Fortran 2018"; + } break; + + // YYYYMM + case DW_LNAME_C: { + if (Version == 0) + break; + if (Version <= 198912) + return "C89"; + if (Version <= 199901) + return "C99"; + if (Version <= 201112) + return "C11"; + if (Version <= 201710) + return "C17"; + } break; + + case DW_LNAME_C_plus_plus: { + if (Version == 0) + break; + if (Version <= 199711) + return "C++98"; + if (Version <= 200310) + return "C++03"; + if (Version <= 201103) + return "C++11"; + if (Version <= 201402) + return "C++14"; + if (Version <= 201703) + return "C++17"; + if (Version <= 202002) + return "C++20"; + } break; + + case DW_LNAME_ObjC_plus_plus: + case DW_LNAME_ObjC: + case DW_LNAME_Move: + case DW_LNAME_SYCL: + case DW_LNAME_BLISS: + case DW_LNAME_Crystal: + case DW_LNAME_D: + case DW_LNAME_Dylan: + case DW_LNAME_Go: + case DW_LNAME_Haskell: + case DW_LNAME_HLSL: + case DW_LNAME_Java: + case DW_LNAME_Julia: + case DW_LNAME_Kotlin: + case DW_LNAME_Modula2: + case DW_LNAME_Modula3: + case DW_LNAME_OCaml: + case DW_LNAME_OpenCL_C: + case DW_LNAME_Pascal: + case DW_LNAME_PLI: + case DW_LNAME_Python: + case DW_LNAME_RenderScript: + case DW_LNAME_Rust: + case DW_LNAME_Swift: + case DW_LNAME_UPC: + case DW_LNAME_Zig: + case DW_LNAME_Assembly: + case DW_LNAME_C_sharp: + case DW_LNAME_Mojo: + case DW_LNAME_GLSL: + case DW_LNAME_GLSL_ES: + case DW_LNAME_OpenCL_CPP: + case DW_LNAME_CPP_for_OpenCL: + case DW_LNAME_Ruby: + case DW_LNAME_Hylo: + case DW_LNAME_Metal: + break; + } + + // Fallback to un-versioned name. + return LanguageDescription(Name); +} + StringRef llvm::dwarf::CaseString(unsigned Case) { switch (Case) { case DW_ID_case_sensitive: diff --git a/llvm/unittests/BinaryFormat/DwarfTest.cpp b/llvm/unittests/BinaryFormat/DwarfTest.cpp index 684e59fa2785c..f4519f61adf85 100644 --- a/llvm/unittests/BinaryFormat/DwarfTest.cpp +++ b/llvm/unittests/BinaryFormat/DwarfTest.cpp @@ -219,4 +219,77 @@ TEST(DwarfTest, lname) { EXPECT_EQ(roundtrip(DW_LANG_##NAME), DW_LANG_##NAME); #include "llvm/BinaryFormat/Dwarf.def" } + +TEST(DwarfTest, lname_getSourceLanguageName) { + // Some basics. + EXPECT_EQ(getSourceLanguageName("DW_LNAME_Ada"), DW_LNAME_Ada); + EXPECT_EQ(getSourceLanguageName("DW_LNAME_Metal"), DW_LNAME_Metal); + + // Test invalid input. + EXPECT_EQ(getSourceLanguageName(""), 0U); + EXPECT_EQ(getSourceLanguageName("blah"), 0U); + EXPECT_EQ(getSourceLanguageName("DW_LNAME__something_unlikely"), 0U); + EXPECT_EQ(getSourceLanguageName("DW_LANG_C"), 0U); + + // Test that we cover all DW_LNAME_ names. +#define xstr(X) #X +#define HANDLE_DW_LNAME(ID, NAME, DESC, LOWER_BOUND) \ + EXPECT_EQ(getSourceLanguageName(xstr(DW_LNAME_##NAME)), DW_LNAME_##NAME); +#include "llvm/BinaryFormat/Dwarf.def" +} + +TEST(DwarfTest, lname_SourceLanguageNameString) { + // Some basics. + EXPECT_EQ(SourceLanguageNameString(DW_LNAME_C_plus_plus), + "DW_LNAME_C_plus_plus"); + EXPECT_EQ(SourceLanguageNameString(DW_LNAME_CPP_for_OpenCL), + "DW_LNAME_CPP_for_OpenCL"); + + // Test invalid input. + EXPECT_EQ(SourceLanguageNameString(static_cast(0)), ""); + + // Test that we cover all DW_LNAME_ names. +#define xstr(X) #X +#define HANDLE_DW_LNAME(ID, NAME, DESC, LOWER_BOUND) \ + EXPECT_EQ(SourceLanguageNameString(DW_LNAME_##NAME), xstr(DW_LNAME_##NAME)); +#include "llvm/BinaryFormat/Dwarf.def" +} + +TEST(DWARFDebugInfo, TestLanguageDescription_Versioned) { + // Tests for the llvm::dwarf::LanguageDescription API that + // takes a name *and* a version. + + // Unknown language. + EXPECT_EQ( + llvm::dwarf::LanguageDescription(static_cast(0)), + "Unknown"); + + EXPECT_EQ( + llvm::dwarf::LanguageDescription(static_cast(0), 0), + "Unknown"); + + // Test that specifying an invalid version falls back to a valid language name + // regardless. + EXPECT_EQ(llvm::dwarf::LanguageDescription(DW_LNAME_ObjC, 0), "Objective C"); + EXPECT_EQ(llvm::dwarf::LanguageDescription(DW_LNAME_Julia, 0), "Julia"); + + // Check some versions. + EXPECT_EQ(llvm::dwarf::LanguageDescription(DW_LNAME_C_plus_plus, 199711), + "C++98"); + EXPECT_EQ(llvm::dwarf::LanguageDescription(DW_LNAME_C_plus_plus, 201402), + "C++14"); + + // Versions round up. + EXPECT_EQ(llvm::dwarf::LanguageDescription(DW_LNAME_C_plus_plus, 201400), + "C++14"); + + // Version 0 for C and C++ is an unversioned name. + EXPECT_EQ(llvm::dwarf::LanguageDescription(DW_LNAME_C, 0), "C (K&R and ISO)"); + EXPECT_EQ(llvm::dwarf::LanguageDescription(DW_LNAME_C_plus_plus, 0), + "ISO C++"); + + // Version 0 for other versioned languages may not be the unversioned name. + EXPECT_EQ(llvm::dwarf::LanguageDescription(DW_LNAME_Fortran, 0), + "FORTRAN 77"); +} } // end namespace From e182da404b821f810277a25721119e41c9df19dd Mon Sep 17 00:00:00 2001 From: Michael Buch Date: Fri, 3 Oct 2025 23:23:08 +0100 Subject: [PATCH 3/5] [lldb][Language] Add Language::GetDisplayNameForLanguageType API (#161803) The intention for this API is to be used when presenting language names to the user, e.g., in expression evaluation diagnostics or LLDB errors. Most uses of `GetNameForLanguageType` can be probably replaced with `GetDisplayNameForLanguageType`, but that's out of scope of this PR. This uses `llvm::dwarf::LanguageDescription` under the hood, so we would lose the version numbers in the names. If we deem those to be important, we could switch to an implementation that hardcodes a list of user-friendly names with version numbers included. The intention is to use it from https://github.com/llvm/llvm-project/pull/161688 Depends on https://github.com/llvm/llvm-project/pull/161804 (cherry picked from commit 2c3724419c04c3b6d918eb4c2eec00a4372d2937) --- lldb/include/lldb/Target/Language.h | 7 +++++++ lldb/source/Target/Language.cpp | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/lldb/include/lldb/Target/Language.h b/lldb/include/lldb/Target/Language.h index 87da1a84fe2eb..062c584d5ee2e 100644 --- a/lldb/include/lldb/Target/Language.h +++ b/lldb/include/lldb/Target/Language.h @@ -412,8 +412,15 @@ class Language : public PluginInterface { GetLanguageTypeFromString(const char *string) = delete; static lldb::LanguageType GetLanguageTypeFromString(llvm::StringRef string); + /// Returns the internal LLDB name for the specified language. When presenting + /// the language name to users, use \ref GetDisplayNameForLanguageType + /// instead. static const char *GetNameForLanguageType(lldb::LanguageType language); + /// Returns a user-friendly name for the specified language. + static llvm::StringRef + GetDisplayNameForLanguageType(lldb::LanguageType language); + static void PrintAllLanguages(Stream &s, const char *prefix, const char *suffix); diff --git a/lldb/source/Target/Language.cpp b/lldb/source/Target/Language.cpp index 7911f9481030f..11928028fa200 100644 --- a/lldb/source/Target/Language.cpp +++ b/lldb/source/Target/Language.cpp @@ -272,6 +272,10 @@ const char *Language::GetNameForLanguageType(LanguageType language) { return language_names[eLanguageTypeUnknown].name; } +llvm::StringRef Language::GetDisplayNameForLanguageType(LanguageType language) { + return SourceLanguage(language).GetDescription(); +} + void Language::PrintSupportedLanguagesForExpressions(Stream &s, llvm::StringRef prefix, llvm::StringRef suffix) { From f99c7a44100abda906e076877f042d9c196d04b2 Mon Sep 17 00:00:00 2001 From: Michael Buch Date: Fri, 3 Oct 2025 23:15:38 +0100 Subject: [PATCH 4/5] [lldb][Language] Simplify SourceLanguage::GetDescription (#161804) Currently we don't benefit from the user-friendly names that `LanguageDescription` returns because we would always use `Language::GetNameForLanguageType`. I'm not aware of a situation where `GetDescription` should prefer the non-human readable form of the name with. This patch removes the call to `GetNameForLanguageType`. `LanguageDescription` already handles languages that it doesn't know about. For those it would return `Unknown`. The LLDB language types should all be available via DWARF. If there are languages that don't map cleanly between `lldb::LanguageType` and `DW_LANG`, then we should add explicit support for that in the `SourceLanguage::SourceLanguage` constructor. (cherry picked from commit 7f51a2a47d2e706d04855b0e41690ebafa2b3238) --- lldb/source/Target/Language.cpp | 26 ++++++++--- lldb/unittests/Target/CMakeLists.txt | 1 + lldb/unittests/Target/Language.cpp | 69 ++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 6 deletions(-) create mode 100644 lldb/unittests/Target/Language.cpp diff --git a/lldb/source/Target/Language.cpp b/lldb/source/Target/Language.cpp index 11928028fa200..5d359b5ef01d1 100644 --- a/lldb/source/Target/Language.cpp +++ b/lldb/source/Target/Language.cpp @@ -548,9 +548,26 @@ Language::Language() = default; // Destructor Language::~Language() = default; +static std::optional +ToDwarfSourceLanguage(lldb::LanguageType language_type) { + if (language_type < lldb::eLanguageTypeLastStandardLanguage) + return static_cast(language_type); + + switch (language_type) { + case eLanguageTypeMipsAssembler: + return llvm::dwarf::DW_LANG_Mips_Assembler; + default: + return std::nullopt; + } +} + SourceLanguage::SourceLanguage(lldb::LanguageType language_type) { - auto lname = - llvm::dwarf::toDW_LNAME((llvm::dwarf::SourceLanguage)language_type); + std::optional dwarf_lang = + ToDwarfSourceLanguage(language_type); + if (!dwarf_lang) + return; + + auto lname = llvm::dwarf::toDW_LNAME(*dwarf_lang); if (!lname) return; name = lname->first; @@ -565,11 +582,8 @@ lldb::LanguageType SourceLanguage::AsLanguageType() const { } llvm::StringRef SourceLanguage::GetDescription() const { - LanguageType type = AsLanguageType(); - if (type) - return Language::GetNameForLanguageType(type); return llvm::dwarf::LanguageDescription( - (llvm::dwarf::SourceLanguageName)name); + static_cast(name)); } bool SourceLanguage::IsC() const { return name == llvm::dwarf::DW_LNAME_C; } diff --git a/lldb/unittests/Target/CMakeLists.txt b/lldb/unittests/Target/CMakeLists.txt index 3169339ec699f..0c79675a3d890 100644 --- a/lldb/unittests/Target/CMakeLists.txt +++ b/lldb/unittests/Target/CMakeLists.txt @@ -2,6 +2,7 @@ add_lldb_unittest(TargetTests ABITest.cpp DynamicRegisterInfoTest.cpp ExecutionContextTest.cpp + Language.cpp LocateModuleCallbackTest.cpp MemoryRegionInfoTest.cpp MemoryTest.cpp diff --git a/lldb/unittests/Target/Language.cpp b/lldb/unittests/Target/Language.cpp new file mode 100644 index 0000000000000..a00fda78d569a --- /dev/null +++ b/lldb/unittests/Target/Language.cpp @@ -0,0 +1,69 @@ +//===-- LanguageTest.cpp --------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lldb/Target/Language.h" +#include "lldb/lldb-enumerations.h" +#include "gtest/gtest.h" + +using namespace lldb_private; +using namespace lldb; + +namespace { +class LanguageTest : public ::testing::Test {}; +} // namespace + +TEST_F(LanguageTest, SourceLanguage_GetDescription) { + for (uint32_t i = 1; i < lldb::eNumLanguageTypes; ++i) { + // 0x29 is unassigned + if (i == 0x29) + continue; + + auto lang_type = static_cast(i); + if (lang_type == lldb::eLanguageTypeLastStandardLanguage) + continue; + + SourceLanguage lang(lang_type); + + // eLanguageTypeHIP is not implemented as a DW_LNAME because of a conflict. + if (lang_type == lldb::eLanguageTypeHIP) + EXPECT_FALSE(lang); + else + EXPECT_TRUE(lang); + } + + EXPECT_EQ(SourceLanguage(eLanguageTypeC_plus_plus).GetDescription(), + "ISO C++"); + EXPECT_EQ(SourceLanguage(eLanguageTypeC_plus_plus_17).GetDescription(), + "ISO C++"); + EXPECT_EQ(SourceLanguage(eLanguageTypeC_plus_plus_20).GetDescription(), + "ISO C++"); + + EXPECT_EQ(SourceLanguage(eLanguageTypeObjC).GetDescription(), "Objective C"); + EXPECT_EQ(SourceLanguage(eLanguageTypeMipsAssembler).GetDescription(), + "Assembly"); + + auto next_vendor_language = + static_cast(eLanguageTypeMipsAssembler + 1); + if (next_vendor_language < eNumLanguageTypes) + EXPECT_NE(SourceLanguage(next_vendor_language).GetDescription(), "Unknown"); + + EXPECT_EQ(SourceLanguage(eLanguageTypeUnknown).GetDescription(), "Unknown"); +} + +TEST_F(LanguageTest, SourceLanguage_AsLanguageType) { + EXPECT_EQ(SourceLanguage(eLanguageTypeC_plus_plus).AsLanguageType(), + eLanguageTypeC_plus_plus); + EXPECT_EQ(SourceLanguage(eLanguageTypeC_plus_plus_03).AsLanguageType(), + eLanguageTypeC_plus_plus_03); + + // Vendor-specific language code. + EXPECT_EQ(SourceLanguage(eLanguageTypeMipsAssembler).AsLanguageType(), + eLanguageTypeAssembly); + EXPECT_EQ(SourceLanguage(eLanguageTypeUnknown).AsLanguageType(), + eLanguageTypeUnknown); +} From 43776ef9a33c506d8ee3ea5f4f1661bfca3533e3 Mon Sep 17 00:00:00 2001 From: Michael Buch Date: Sat, 11 Oct 2025 13:00:03 +0100 Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=8D=92=20[lldb][test]=20Fix=20languag?= =?UTF-8?q?e=20note=20tests=20on=20Windows?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The LLDB test-suite compiles the tests on Windows with 'C++14' by default: https://github.com/llvm/llvm-project/blob/3bfb5b0e7ccbcb9f127f5b9c958e6499ba9c0523/lldb/packages/Python/lldbsuite/test/make/Makefile.rules#L357-L360 This fixes Windows buildbot failures. --- .../expression/diagnostics/TestExprDiagnostics.py | 8 ++++---- lldb/test/Shell/Expr/TestExprLanguageNote.test | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lldb/test/API/commands/expression/diagnostics/TestExprDiagnostics.py b/lldb/test/API/commands/expression/diagnostics/TestExprDiagnostics.py index ec208f2c32503..bd468f272d15d 100644 --- a/lldb/test/API/commands/expression/diagnostics/TestExprDiagnostics.py +++ b/lldb/test/API/commands/expression/diagnostics/TestExprDiagnostics.py @@ -218,11 +218,11 @@ def check_error(diags): # Detail 1/3: note: requested expression language diag = details.GetItemAtIndex(0) self.assertEqual(str(diag.GetValueForKey("severity")), "note") - self.assertEqual( - str(diag.GetValueForKey("message")), "Ran expression as 'C++11'." + self.assertIn( + "Ran expression as 'C++", str(diag.GetValueForKey("message")) ) - self.assertEqual( - str(diag.GetValueForKey("rendered")), "Ran expression as 'C++11'." + self.assertIn( + "Ran expression as 'C++", str(diag.GetValueForKey("rendered")) ) self.assertEqual(str(diag.GetValueForKey("source_location")), "") self.assertEqual(str(diag.GetValueForKey("file")), "") diff --git a/lldb/test/Shell/Expr/TestExprLanguageNote.test b/lldb/test/Shell/Expr/TestExprLanguageNote.test index f3dc5928fcca9..b4387bf9121ae 100644 --- a/lldb/test/Shell/Expr/TestExprLanguageNote.test +++ b/lldb/test/Shell/Expr/TestExprLanguageNote.test @@ -26,7 +26,7 @@ run expr blah # CHECK-TARGET: (lldb) expr -# CHECK-TARGET: note: Ran expression as 'C++14'. +# CHECK-TARGET: note: Ran expression as 'C++{{.*}}' expr -l objc -- blah