Skip to content

Commit e3620fe

Browse files
authored
[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
1 parent 4188e18 commit e3620fe

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>
@@ -527,7 +529,8 @@ static void SetupTargetOpts(CompilerInstance &compiler,
527529

528530
static void SetupLangOpts(CompilerInstance &compiler,
529531
ExecutionContextScope &exe_scope,
530-
const Expression &expr) {
532+
const Expression &expr,
533+
DiagnosticManager &diagnostic_manager) {
531534
Log *log = GetLog(LLDBLog::Expressions);
532535

533536
// If the expression is being evaluated in the context of an existing stack
@@ -547,6 +550,9 @@ static void SetupLangOpts(CompilerInstance &compiler,
547550
: lldb::eLanguageTypeUnknown),
548551
lldb_private::Language::GetNameForLanguageType(language));
549552

553+
lldb::LanguageType language_for_note = language;
554+
std::string language_fallback_reason;
555+
550556
LangOptions &lang_opts = compiler.getLangOpts();
551557

552558
switch (language) {
@@ -560,13 +566,21 @@ static void SetupLangOpts(CompilerInstance &compiler,
560566
// family language, because the expression parser uses features of C++ to
561567
// capture values.
562568
lang_opts.CPlusPlus = true;
569+
570+
language_for_note = lldb::eLanguageTypeC_plus_plus;
571+
language_fallback_reason =
572+
"Expression evaluation in pure C not supported. ";
563573
break;
564574
case lldb::eLanguageTypeObjC:
565575
lang_opts.ObjC = true;
566576
// FIXME: the following language option is a temporary workaround,
567577
// to "ask for ObjC, get ObjC++" (see comment above).
568578
lang_opts.CPlusPlus = true;
569579

580+
language_for_note = lldb::eLanguageTypeObjC_plus_plus;
581+
language_fallback_reason =
582+
"Expression evaluation in pure Objective-C not supported. ";
583+
570584
// Clang now sets as default C++14 as the default standard (with
571585
// GNU extensions), so we do the same here to avoid mismatches that
572586
// cause compiler error when evaluating expressions (e.g. nullptr not found
@@ -607,9 +621,27 @@ static void SetupLangOpts(CompilerInstance &compiler,
607621
lang_opts.CPlusPlus = true;
608622
lang_opts.CPlusPlus11 = true;
609623
compiler.getHeaderSearchOpts().UseLibcxx = true;
624+
625+
language_for_note = lldb::eLanguageTypeObjC_plus_plus;
626+
if (language != language_for_note) {
627+
if (language != lldb::eLanguageTypeUnknown)
628+
language_fallback_reason = llvm::formatv(
629+
"Expression evaluation in {0} not supported. ",
630+
lldb_private::Language::GetDisplayNameForLanguageType(language));
631+
632+
language_fallback_reason +=
633+
llvm::formatv("Falling back to default language. ");
634+
}
610635
break;
611636
}
612637

638+
diagnostic_manager.AddDiagnostic(
639+
llvm::formatv("{0}Ran expression as '{1}'.", language_fallback_reason,
640+
lldb_private::Language::GetDisplayNameForLanguageType(
641+
language_for_note))
642+
.str(),
643+
lldb::Severity::eSeverityInfo, DiagnosticOrigin::eDiagnosticOriginLLDB);
644+
613645
lang_opts.Bool = true;
614646
lang_opts.WChar = true;
615647
lang_opts.Blocks = true;
@@ -687,8 +719,8 @@ static void SetupImportStdModuleLangOpts(CompilerInstance &compiler,
687719

688720
ClangExpressionParser::ClangExpressionParser(
689721
ExecutionContextScope *exe_scope, Expression &expr,
690-
bool generate_debug_info, std::vector<std::string> include_directories,
691-
std::string filename)
722+
bool generate_debug_info, DiagnosticManager &diagnostic_manager,
723+
std::vector<std::string> include_directories, std::string filename)
692724
: ExpressionParser(exe_scope, expr, generate_debug_info), m_compiler(),
693725
m_pp_callbacks(nullptr),
694726
m_include_directories(std::move(include_directories)),
@@ -754,7 +786,7 @@ ClangExpressionParser::ClangExpressionParser(
754786
}
755787

756788
// 4. Set language options.
757-
SetupLangOpts(*m_compiler, *exe_scope, expr);
789+
SetupLangOpts(*m_compiler, *exe_scope, expr, diagnostic_manager);
758790
auto *clang_expr = dyn_cast<ClangUserExpression>(&m_expr);
759791
if (clang_expr && clang_expr->DidImportCxxModules()) {
760792
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)