[Lldb-commits] [lldb] e3620fe - [lldb][Expression] Emit a 'Note' diagnostic that indicates the language used for expression evaluation (#161688)
    via lldb-commits 
    lldb-commits at lists.llvm.org
       
    Fri Oct 10 11:23:06 PDT 2025
    
    
  
Author: Michael Buch
Date: 2025-10-10T19:23:02+01:00
New Revision: e3620fe0685c656915977d55f822a82090041965
URL: https://github.com/llvm/llvm-project/commit/e3620fe0685c656915977d55f822a82090041965
DIFF: https://github.com/llvm/llvm-project/commit/e3620fe0685c656915977d55f822a82090041965.diff
LOG: [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
Added: 
    lldb/test/Shell/Expr/TestExprLanguageNote.test
Modified: 
    lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
    lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h
    lldb/source/Plugins/ExpressionParser/Clang/ClangFunctionCaller.cpp
    lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp
    lldb/source/Plugins/ExpressionParser/Clang/ClangUtilityFunction.cpp
    lldb/test/API/commands/expression/diagnostics/TestExprDiagnostics.py
    lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py
Removed: 
    
################################################################################
diff  --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
index 3c49c911108a3..6b121c934dfbb 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 <cctype>
 #include <memory>
@@ -527,7 +529,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
@@ -547,6 +550,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) {
@@ -560,6 +566,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;
@@ -567,6 +577,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
@@ -607,9 +621,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;
@@ -687,8 +719,8 @@ static void SetupImportStdModuleLangOpts(CompilerInstance &compiler,
 
 ClangExpressionParser::ClangExpressionParser(
     ExecutionContextScope *exe_scope, Expression &expr,
-    bool generate_debug_info, std::vector<std::string> include_directories,
-    std::string filename)
+    bool generate_debug_info, DiagnosticManager &diagnostic_manager,
+    std::vector<std::string> 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)),
@@ -754,7 +786,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<ClangUserExpression>(&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<std::string> include_directories = {},
                         std::string filename = "<clang expression>");
 
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<ClangExpressionParser>(
       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 <line>
         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: <user expression 0>: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: <user expression 0>: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:
        
    
    
More information about the lldb-commits
mailing list