Skip to content

Commit 61020d3

Browse files
committed
[lldb][Expression] Emit a 'Note' diagnostic that indicates the language used for expression evaluation (llvm#161688)
Depends on: * llvm#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 llvm#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 e3620fe)
1 parent 4293e64 commit 61020d3

File tree

8 files changed

+185
-23
lines changed

8 files changed

+185
-23
lines changed

lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
#include "lldb/Core/Debugger.h"
7575
#include "lldb/Core/Disassembler.h"
7676
#include "lldb/Core/Module.h"
77+
#include "lldb/Expression/DiagnosticManager.h"
7778
#include "lldb/Expression/IRExecutionUnit.h"
7879
#include "lldb/Expression/IRInterpreter.h"
7980
#include "lldb/Host/File.h"
@@ -96,6 +97,7 @@
9697
#include "Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h"
9798
#include "Plugins/Platform/MacOSX/PlatformDarwin.h"
9899
#include "lldb/Utility/XcodeSDK.h"
100+
#include "lldb/lldb-enumerations.h"
99101

100102
#include <cctype>
101103
#include <memory>
@@ -570,7 +572,8 @@ static void SetupTargetOpts(CompilerInstance &compiler,
570572

571573
static void SetupLangOpts(CompilerInstance &compiler,
572574
ExecutionContextScope &exe_scope,
573-
const Expression &expr) {
575+
const Expression &expr,
576+
DiagnosticManager &diagnostic_manager) {
574577
Log *log = GetLog(LLDBLog::Expressions);
575578

576579
// If the expression is being evaluated in the context of an existing stack
@@ -590,6 +593,9 @@ static void SetupLangOpts(CompilerInstance &compiler,
590593
: lldb::eLanguageTypeUnknown),
591594
lldb_private::Language::GetNameForLanguageType(language));
592595

596+
lldb::LanguageType language_for_note = language;
597+
std::string language_fallback_reason;
598+
593599
LangOptions &lang_opts = compiler.getLangOpts();
594600

595601
switch (language) {
@@ -603,13 +609,21 @@ static void SetupLangOpts(CompilerInstance &compiler,
603609
// family language, because the expression parser uses features of C++ to
604610
// capture values.
605611
lang_opts.CPlusPlus = true;
612+
613+
language_for_note = lldb::eLanguageTypeC_plus_plus;
614+
language_fallback_reason =
615+
"Expression evaluation in pure C not supported. ";
606616
break;
607617
case lldb::eLanguageTypeObjC:
608618
lang_opts.ObjC = true;
609619
// FIXME: the following language option is a temporary workaround,
610620
// to "ask for ObjC, get ObjC++" (see comment above).
611621
lang_opts.CPlusPlus = true;
612622

623+
language_for_note = lldb::eLanguageTypeObjC_plus_plus;
624+
language_fallback_reason =
625+
"Expression evaluation in pure Objective-C not supported. ";
626+
613627
// Clang now sets as default C++14 as the default standard (with
614628
// GNU extensions), so we do the same here to avoid mismatches that
615629
// cause compiler error when evaluating expressions (e.g. nullptr not found
@@ -650,9 +664,27 @@ static void SetupLangOpts(CompilerInstance &compiler,
650664
lang_opts.CPlusPlus = true;
651665
lang_opts.CPlusPlus11 = true;
652666
compiler.getHeaderSearchOpts().UseLibcxx = true;
667+
668+
language_for_note = lldb::eLanguageTypeObjC_plus_plus;
669+
if (language != language_for_note) {
670+
if (language != lldb::eLanguageTypeUnknown)
671+
language_fallback_reason = llvm::formatv(
672+
"Expression evaluation in {0} not supported. ",
673+
lldb_private::Language::GetDisplayNameForLanguageType(language));
674+
675+
language_fallback_reason +=
676+
llvm::formatv("Falling back to default language. ");
677+
}
653678
break;
654679
}
655680

681+
diagnostic_manager.AddDiagnostic(
682+
llvm::formatv("{0}Ran expression as '{1}'.", language_fallback_reason,
683+
lldb_private::Language::GetDisplayNameForLanguageType(
684+
language_for_note))
685+
.str(),
686+
lldb::Severity::eSeverityInfo, DiagnosticOrigin::eDiagnosticOriginLLDB);
687+
656688
lang_opts.Bool = true;
657689
lang_opts.WChar = true;
658690
lang_opts.Blocks = true;
@@ -736,8 +768,8 @@ static void SetupImportStdModuleLangOpts(CompilerInstance &compiler,
736768

737769
ClangExpressionParser::ClangExpressionParser(
738770
ExecutionContextScope *exe_scope, Expression &expr,
739-
bool generate_debug_info, std::vector<std::string> include_directories,
740-
std::string filename)
771+
bool generate_debug_info, DiagnosticManager &diagnostic_manager,
772+
std::vector<std::string> include_directories, std::string filename)
741773
: ExpressionParser(exe_scope, expr, generate_debug_info), m_compiler(),
742774
m_pp_callbacks(nullptr),
743775
m_include_directories(std::move(include_directories)),
@@ -801,7 +833,7 @@ ClangExpressionParser::ClangExpressionParser(
801833
}
802834

803835
// 4. Set language options.
804-
SetupLangOpts(*m_compiler, *exe_scope, expr);
836+
SetupLangOpts(*m_compiler, *exe_scope, expr, diagnostic_manager);
805837
auto *clang_expr = dyn_cast<ClangUserExpression>(&m_expr);
806838
if (clang_expr && clang_expr->DidImportCxxModules()) {
807839
LLDB_LOG(log, "Adding lang options for importing C++ modules");

lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ class ClangExpressionParser : public ExpressionParser {
6565
/// diagnostics (i.e. errors, warnings or notes from Clang).
6666
ClangExpressionParser(ExecutionContextScope *exe_scope, Expression &expr,
6767
bool generate_debug_info,
68+
DiagnosticManager &diagnostic_manager,
6869
std::vector<std::string> include_directories = {},
6970
std::string filename = "<clang expression>");
7071

lldb/source/Plugins/ExpressionParser/Clang/ClangFunctionCaller.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,8 @@ ClangFunctionCaller::CompileFunction(lldb::ThreadSP thread_to_use_sp,
189189
lldb::ProcessSP jit_process_sp(m_jit_process_wp.lock());
190190
if (jit_process_sp) {
191191
const bool generate_debug_info = true;
192-
auto *clang_parser = new ClangExpressionParser(jit_process_sp.get(), *this,
193-
generate_debug_info);
192+
auto *clang_parser = new ClangExpressionParser(
193+
jit_process_sp.get(), *this, generate_debug_info, diagnostic_manager);
194194
num_errors = clang_parser->Parse(diagnostic_manager);
195195
m_parser.reset(clang_parser);
196196
} else {

lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -574,7 +574,7 @@ bool ClangUserExpression::TryParse(
574574

575575
m_parser = std::make_unique<ClangExpressionParser>(
576576
exe_ctx.GetBestExecutionContextScope(), *this, generate_debug_info,
577-
m_include_directories, m_filename);
577+
diagnostic_manager, m_include_directories, m_filename);
578578

579579
unsigned num_errors = m_parser->Parse(diagnostic_manager);
580580

@@ -818,7 +818,7 @@ bool ClangUserExpression::Complete(ExecutionContext &exe_ctx,
818818
}
819819

820820
ClangExpressionParser parser(exe_ctx.GetBestExecutionContextScope(), *this,
821-
false);
821+
false, diagnostic_manager);
822822

823823
// We have to find the source code location where the user text is inside
824824
// the transformed expression code. When creating the transformed text, we

lldb/source/Plugins/ExpressionParser/Clang/ClangUtilityFunction.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ bool ClangUtilityFunction::Install(DiagnosticManager &diagnostic_manager,
120120

121121
const bool generate_debug_info = true;
122122
ClangExpressionParser parser(exe_ctx.GetBestExecutionContextScope(), *this,
123-
generate_debug_info);
123+
generate_debug_info, diagnostic_manager);
124124

125125
unsigned num_errors = parser.Parse(diagnostic_manager);
126126

lldb/test/API/commands/expression/diagnostics/TestExprDiagnostics.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -215,8 +215,22 @@ def check_error(diags):
215215

216216
details = diags.GetValueForKey("details")
217217

218-
# Detail 1/2: undeclared 'a'
218+
# Detail 1/3: note: requested expression language
219219
diag = details.GetItemAtIndex(0)
220+
self.assertEqual(str(diag.GetValueForKey("severity")), "note")
221+
self.assertEqual(
222+
str(diag.GetValueForKey("message")), "Ran expression as 'C++11'."
223+
)
224+
self.assertEqual(
225+
str(diag.GetValueForKey("rendered")), "Ran expression as 'C++11'."
226+
)
227+
self.assertEqual(str(diag.GetValueForKey("source_location")), "")
228+
self.assertEqual(str(diag.GetValueForKey("file")), "")
229+
self.assertFalse(diag.GetValueForKey("hidden").GetBooleanValue())
230+
self.assertFalse(diag.GetValueForKey("in_user_input").GetBooleanValue())
231+
232+
# Detail 2/3: undeclared 'a'
233+
diag = details.GetItemAtIndex(1)
220234

221235
severity = diag.GetValueForKey("severity")
222236
message = diag.GetValueForKey("message")
@@ -234,8 +248,8 @@ def check_error(diags):
234248
self.assertFalse(hidden.GetBooleanValue())
235249
self.assertTrue(in_user_input.GetBooleanValue())
236250

237-
# Detail 1/2: undeclared 'b'
238-
diag = details.GetItemAtIndex(1)
251+
# Detail 3/3: undeclared 'b'
252+
diag = details.GetItemAtIndex(2)
239253
message = diag.GetValueForKey("message")
240254
self.assertIn("undeclared identifier 'b'", str(message))
241255

lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Test the SBCommandInterpreter APIs."""
1+
"""tESt the SBCommandInterpreter APIs."""
22

33
import json
44
import lldb
@@ -156,13 +156,15 @@ def test_get_transcript(self):
156156
self.assertEqual(transcript[0]["error"], "")
157157

158158
# (lldb) an-unknown-command
159-
self.assertEqual(transcript[1],
159+
self.assertEqual(
160+
transcript[1],
160161
{
161162
"command": "an-unknown-command",
162163
# Unresolved commands don't have "commandName"/"commandArguments"
163164
"output": "",
164165
"error": "error: 'an-unknown-command' is not a valid command.\n",
165-
})
166+
},
167+
)
166168

167169
# (lldb) br s -f main.c -l <line>
168170
self.assertEqual(transcript[2]["command"], "br s -f main.c -l %d" % self.line)
@@ -175,14 +177,17 @@ def test_get_transcript(self):
175177
self.assertEqual(transcript[2]["error"], "")
176178

177179
# (lldb) p a
178-
self.assertEqual(transcript[3],
180+
self.assertEqual(
181+
transcript[3],
179182
{
180183
"command": "p a",
181184
"commandName": "dwim-print",
182185
"commandArguments": "-- a",
183186
"output": "",
184-
"error": "error: <user expression 0>:1:1: use of undeclared identifier 'a'\n 1 | a\n | ^\n",
185-
})
187+
"error": "note: Falling back to default language. Ran expression as 'Objective C++'.\n"
188+
"error: <user expression 0>:1:1: use of undeclared identifier 'a'\n 1 | a\n | ^\n",
189+
},
190+
)
186191

187192
# (lldb) statistics dump
188193
self.assertEqual(transcript[4]["command"], "statistics dump")
@@ -203,7 +208,10 @@ def test_save_transcript_setting_default(self):
203208
self.assertTrue(ci, VALID_COMMAND_INTERPRETER)
204209

205210
# The setting's default value should be "false"
206-
self.runCmd("settings show interpreter.save-transcript", "interpreter.save-transcript (boolean) = false\n")
211+
self.runCmd(
212+
"settings show interpreter.save-transcript",
213+
"interpreter.save-transcript (boolean) = false\n",
214+
)
207215

208216
def test_save_transcript_setting_off(self):
209217
ci = self.dbg.GetCommandInterpreter()
@@ -250,17 +258,37 @@ def test_get_transcript_returns_copy(self):
250258
structured_data_1 = ci.GetTranscript()
251259
self.assertTrue(structured_data_1.IsValid())
252260
self.assertEqual(structured_data_1.GetSize(), 1)
253-
self.assertEqual(structured_data_1.GetItemAtIndex(0).GetValueForKey("command").GetStringValue(100), "version")
261+
self.assertEqual(
262+
structured_data_1.GetItemAtIndex(0)
263+
.GetValueForKey("command")
264+
.GetStringValue(100),
265+
"version",
266+
)
254267

255268
# Run some more commands and get the transcript as structured data again
256269
self.runCmd("help")
257270
structured_data_2 = ci.GetTranscript()
258271
self.assertTrue(structured_data_2.IsValid())
259272
self.assertEqual(structured_data_2.GetSize(), 2)
260-
self.assertEqual(structured_data_2.GetItemAtIndex(0).GetValueForKey("command").GetStringValue(100), "version")
261-
self.assertEqual(structured_data_2.GetItemAtIndex(1).GetValueForKey("command").GetStringValue(100), "help")
273+
self.assertEqual(
274+
structured_data_2.GetItemAtIndex(0)
275+
.GetValueForKey("command")
276+
.GetStringValue(100),
277+
"version",
278+
)
279+
self.assertEqual(
280+
structured_data_2.GetItemAtIndex(1)
281+
.GetValueForKey("command")
282+
.GetStringValue(100),
283+
"help",
284+
)
262285

263286
# Now, the first structured data should remain unchanged
264287
self.assertTrue(structured_data_1.IsValid())
265288
self.assertEqual(structured_data_1.GetSize(), 1)
266-
self.assertEqual(structured_data_1.GetItemAtIndex(0).GetValueForKey("command").GetStringValue(100), "version")
289+
self.assertEqual(
290+
structured_data_1.GetItemAtIndex(0)
291+
.GetValueForKey("command")
292+
.GetStringValue(100),
293+
"version",
294+
)
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# RUN: split-file %s %t
2+
# RUN: %clang_host -g %t/main.cpp -o %t.out
3+
#
4+
# RUN: %lldb -x -b -o "settings set interpreter.stop-command-source-on-error false" \
5+
# RUN: -s %t/no-target.input 2>&1 | FileCheck %s --check-prefix=CHECK-NO-TARGET
6+
#
7+
# RUN: %lldb %t.out -x -b -o "settings set interpreter.stop-command-source-on-error false" \
8+
# RUN: -s %t/with-target.input 2>&1 | FileCheck %s --check-prefix=CHECK-TARGET
9+
10+
#--- main.cpp
11+
12+
int main() {
13+
int x = 10;
14+
__builtin_debugtrap();
15+
}
16+
17+
#--- with-target.input
18+
19+
expr blah
20+
21+
# CHECK-TARGET: (lldb) expr
22+
# CHECK-TARGET: note: Falling back to default language. Ran expression as 'Objective C++'.
23+
24+
run
25+
26+
expr blah
27+
28+
# CHECK-TARGET: (lldb) expr
29+
# CHECK-TARGET: note: Ran expression as 'C++14'.
30+
31+
expr -l objc -- blah
32+
33+
# CHECK-TARGET: (lldb) expr
34+
# CHECK-TARGET: note: Expression evaluation in pure Objective-C not supported. Ran expression as 'Objective C++'.
35+
36+
expr -l c -- blah
37+
38+
# CHECK-TARGET: (lldb) expr
39+
# CHECK-TARGET: note: Expression evaluation in pure C not supported. Ran expression as 'ISO C++'.
40+
41+
expr -l c++14 -- blah
42+
43+
# CHECK-TARGET: (lldb) expr
44+
# CHECK-TARGET: note: Ran expression as 'C++14'
45+
46+
expr -l c++20 -- blah
47+
48+
# CHECK-TARGET: (lldb) expr
49+
# CHECK-TARGET: note: Ran expression as 'C++20'
50+
51+
expr -l objective-c++ -- blah
52+
53+
# CHECK-TARGET: (lldb) expr
54+
# CHECK-TARGET: note: Ran expression as 'Objective C++'
55+
56+
# D uses TypeSystemClang but running expressions in it isn't supported. Test that we warn about this.
57+
expr -l D -- blah
58+
59+
# CHECK-TARGET: (lldb) expr
60+
# CHECK-TARGET: note: Expression evaluation in D not supported. Falling back to default language. Ran expression as 'Objective C++'.
61+
62+
expr -l c++17 -- x = 5
63+
64+
# CHECK-TARGET: (lldb) expr
65+
# CHECK-TARGET-NOT: note:
66+
67+
expr x = 5
68+
69+
# CHECK-TARGET: (lldb) expr
70+
# CHECK-TARGET-NOT: note:
71+
72+
#--- no-target.input
73+
74+
expr blah
75+
76+
# CHECK-NO-TARGET: (lldb) expr
77+
# CHECK-NO-TARGET: note: Falling back to default language. Ran expression as 'Objective C++'.
78+
79+
expr -l c++ -- 1 + 1
80+
81+
# CHECK-NO-TARGET: (lldb) expr
82+
# CHECK-NO-TARGET-NOT: note:
83+
84+
expr 1 + 1
85+
86+
# CHECK-NO-TARGET: (lldb) expr
87+
# CHECK-NO-TARGET-NOT: note:

0 commit comments

Comments
 (0)