Skip to content

Commit 1442efe

Browse files
committed
[lldb] Print better diagnostics for user expressions and modules
Summary: Currently our expression evaluators only prints very basic errors that are not very useful when writing complex expressions. For example, in the expression below the user made a type error, but it's not clear from the diagnostic what went wrong: ``` (lldb) expr printf("Modulos are:", foobar%mo1, foobar%mo2, foobar%mo3) error: invalid operands to binary expression ('int' and 'double') ``` This patch enables full Clang diagnostics in our expression evaluator. After this patch the diagnostics for the expression look like this: ``` (lldb) expr printf("Modulos are:", foobar%mo1, foobar%mo2, foobar%mo3) error: <user expression 1>:1:54: invalid operands to binary expression ('int' and 'float') printf("Modulos are:", foobar%mo1, foobar%mo2, foobar%mo3) ~~~~~~^~~~ ``` To make this possible, we now emulate a user expression file within our diagnostics. This prevents that the user is exposed to our internal wrapper code we inject. Note that the diagnostics that refer to declarations from the debug information (e.g. 'note' diagnostics pointing to a called function) will not be improved by this as they don't have any source locations associated with them, so caret or line printing isn't possible. We instead just suppress these diagnostics as we already do with warnings as they would otherwise just be a context message without any context (and the original diagnostic in the user expression should be enough to explain the issue). Fixes rdar://24306342 Reviewers: JDevlieghere, aprantl, shafik, #lldb Reviewed By: JDevlieghere, #lldb Subscribers: usaxena95, davide, jingham, aprantl, arphaman, kadircet, lldb-commits Tags: #lldb Differential Revision: https://reviews.llvm.org/D65646 llvm-svn: 372203
1 parent 377aaa2 commit 1442efe

File tree

14 files changed

+236
-71
lines changed

14 files changed

+236
-71
lines changed

clang/include/clang/Basic/DiagnosticOptions.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ DIAGOPT(Pedantic, 1, 0) /// -pedantic
4949
DIAGOPT(PedanticErrors, 1, 0) /// -pedantic-errors
5050
DIAGOPT(ShowColumn, 1, 1) /// Show column number on diagnostics.
5151
DIAGOPT(ShowLocation, 1, 1) /// Show source location information.
52+
DIAGOPT(ShowLevel, 1, 1) /// Show diagnostic level.
5253
DIAGOPT(AbsolutePath, 1, 0) /// Use absolute paths.
5354
DIAGOPT(ShowCarets, 1, 1) /// Show carets in diagnostics.
5455
DIAGOPT(ShowFixits, 1, 1) /// Show fixit information.

clang/lib/Frontend/TextDiagnostic.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -683,8 +683,9 @@ void TextDiagnostic::emitDiagnosticMessage(
683683
if (DiagOpts->ShowColors)
684684
OS.resetColor();
685685

686-
printDiagnosticLevel(OS, Level, DiagOpts->ShowColors,
687-
DiagOpts->CLFallbackMode);
686+
if (DiagOpts->ShowLevel)
687+
printDiagnosticLevel(OS, Level, DiagOpts->ShowColors,
688+
DiagOpts->CLFallbackMode);
688689
printDiagnosticMessage(OS,
689690
/*IsSupplemental*/ Level == DiagnosticsEngine::Note,
690691
Message, OS.tell() - StartOfLocationInfo,
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
CXX_SOURCES := main.cpp
2+
3+
include Makefile.rules
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
"""
2+
Test the diagnostics emitted by our embeded Clang instance that parses expressions.
3+
"""
4+
5+
import lldb
6+
from lldbsuite.test.lldbtest import *
7+
from lldbsuite.test import lldbutil
8+
from lldbsuite.test.decorators import *
9+
10+
class ExprDiagnosticsTestCase(TestBase):
11+
12+
mydir = TestBase.compute_mydir(__file__)
13+
14+
def setUp(self):
15+
# Call super's setUp().
16+
TestBase.setUp(self)
17+
18+
self.main_source = "main.cpp"
19+
self.main_source_spec = lldb.SBFileSpec(self.main_source)
20+
21+
def test_source_and_caret_printing(self):
22+
"""Test that the source and caret positions LLDB prints are correct"""
23+
self.build()
24+
25+
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(self,
26+
'// Break here', self.main_source_spec)
27+
frame = thread.GetFrameAtIndex(0)
28+
29+
# Test that source/caret are at the right position.
30+
value = frame.EvaluateExpression("unknown_identifier")
31+
self.assertFalse(value.GetError().Success())
32+
# We should get a nice diagnostic with a caret pointing at the start of
33+
# the identifier.
34+
self.assertIn("\nunknown_identifier\n^\n", value.GetError().GetCString())
35+
self.assertIn("<user expression 0>:1:1", value.GetError().GetCString())
36+
37+
# Same as above but with the identifier in the middle.
38+
value = frame.EvaluateExpression("1 + unknown_identifier ")
39+
self.assertFalse(value.GetError().Success())
40+
self.assertIn("\n1 + unknown_identifier", value.GetError().GetCString())
41+
self.assertIn("\n ^\n", value.GetError().GetCString())
42+
43+
# Multiline expressions.
44+
value = frame.EvaluateExpression("int a = 0;\nfoobar +=1;\na")
45+
self.assertFalse(value.GetError().Success())
46+
# We should still get the right line information and caret position.
47+
self.assertIn("\nfoobar +=1;\n^\n", value.GetError().GetCString())
48+
# It's the second line of the user expression.
49+
self.assertIn("<user expression 2>:2:1", value.GetError().GetCString())
50+
51+
# Top-level expressions.
52+
top_level_opts = lldb.SBExpressionOptions();
53+
top_level_opts.SetTopLevel(True)
54+
55+
value = frame.EvaluateExpression("void foo(unknown_type x) {}", top_level_opts)
56+
self.assertFalse(value.GetError().Success())
57+
self.assertIn("\nvoid foo(unknown_type x) {}\n ^\n", value.GetError().GetCString())
58+
# Top-level expressions might use a different wrapper code, but the file name should still
59+
# be the same.
60+
self.assertIn("<user expression 3>:1:10", value.GetError().GetCString())
61+
62+
# Multiline top-level expressions.
63+
value = frame.EvaluateExpression("void x() {}\nvoid foo(unknown_type x) {}", top_level_opts)
64+
self.assertFalse(value.GetError().Success())
65+
self.assertIn("\nvoid foo(unknown_type x) {}\n ^\n", value.GetError().GetCString())
66+
self.assertIn("<user expression 4>:2:10", value.GetError().GetCString())
67+
68+
# Test that we render Clang's 'notes' correctly.
69+
value = frame.EvaluateExpression("struct SFoo{}; struct SFoo { int x; };", top_level_opts)
70+
self.assertFalse(value.GetError().Success())
71+
self.assertIn("<user expression 5>:1:8: previous definition is here\nstruct SFoo{}; struct SFoo { int x; };\n ^\n", value.GetError().GetCString())
72+
73+
# Declarations from the debug information currently have no debug information. It's not clear what
74+
# we should do in this case, but we should at least not print anything that's wrong.
75+
# In the future our declarations should have valid source locations.
76+
value = frame.EvaluateExpression("struct FooBar { double x };", top_level_opts)
77+
self.assertFalse(value.GetError().Success())
78+
self.assertEqual("error: <user expression 6>:1:8: redefinition of 'FooBar'\nstruct FooBar { double x };\n ^\n", value.GetError().GetCString())
79+
80+
value = frame.EvaluateExpression("foo(1, 2)")
81+
self.assertFalse(value.GetError().Success())
82+
self.assertEqual("error: <user expression 7>:1:1: no matching function for call to 'foo'\nfoo(1, 2)\n^~~\nnote: candidate function not viable: requires single argument 'x', but 2 arguments were provided\n\n", value.GetError().GetCString())
83+
84+
# Redefine something that we defined in a user-expression. We should use the previous expression file name
85+
# for the original decl.
86+
value = frame.EvaluateExpression("struct Redef { double x; };", top_level_opts)
87+
value = frame.EvaluateExpression("struct Redef { float y; };", top_level_opts)
88+
self.assertFalse(value.GetError().Success())
89+
self.assertIn("error: <user expression 9>:1:8: redefinition of 'Redef'\nstruct Redef { float y; };\n ^\n<user expression 8>:1:8: previous definition is here\nstruct Redef { double x; };\n ^", value.GetError().GetCString())
90+
91+
@skipUnlessDarwin
92+
def test_source_locations_from_objc_modules(self):
93+
self.build()
94+
95+
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(self,
96+
'// Break here', self.main_source_spec)
97+
frame = thread.GetFrameAtIndex(0)
98+
99+
# Import foundation so that the Obj-C module is loaded (which contains source locations
100+
# that can be used by LLDB).
101+
self.runCmd("expr @import Foundation")
102+
value = frame.EvaluateExpression("NSLog(1);")
103+
self.assertFalse(value.GetError().Success())
104+
print(value.GetError().GetCString())
105+
# LLDB should print the source line that defines NSLog. To not rely on any
106+
# header paths/line numbers or the actual formatting of the Foundation headers, only look
107+
# for a few tokens in the output.
108+
# File path should come from Foundation framework.
109+
self.assertIn("/Foundation.framework/", value.GetError().GetCString())
110+
# The NSLog definition source line should be printed. Return value and
111+
# the first argument are probably stable enough that this test can check for them.
112+
self.assertIn("void NSLog(NSString *format", value.GetError().GetCString())
113+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
void foo(int x) {}
2+
3+
struct FooBar {
4+
int i;
5+
};
6+
7+
int main() {
8+
FooBar f;
9+
foo(1);
10+
return 0; // Break here
11+
}

lldb/packages/Python/lldbsuite/test/lang/objc/foundation/TestObjCMethods.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ def test_data_type_and_expr(self):
191191
"expression self->non_existent_member",
192192
COMMAND_FAILED_AS_EXPECTED,
193193
error=True,
194-
startstr="error: 'MyString' does not have a member named 'non_existent_member'")
194+
substrs=["error:", "'MyString' does not have a member named 'non_existent_member'"])
195195

196196
# Use expression parser.
197197
self.runCmd("expression self->str")

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class ClangDiagnostic : public Diagnostic {
2929
return diag->getKind() == eDiagnosticOriginClang;
3030
}
3131

32-
ClangDiagnostic(const char *message, DiagnosticSeverity severity,
32+
ClangDiagnostic(llvm::StringRef message, DiagnosticSeverity severity,
3333
uint32_t compiler_id)
3434
: Diagnostic(message, severity, eDiagnosticOriginClang, compiler_id) {}
3535

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

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -137,25 +137,27 @@ class ClangExpressionParser::LLDBPreprocessorCallbacks : public PPCallbacks {
137137

138138
class ClangDiagnosticManagerAdapter : public clang::DiagnosticConsumer {
139139
public:
140-
ClangDiagnosticManagerAdapter()
141-
: m_passthrough(new clang::TextDiagnosticBuffer) {}
142-
143-
ClangDiagnosticManagerAdapter(
144-
const std::shared_ptr<clang::TextDiagnosticBuffer> &passthrough)
145-
: m_passthrough(passthrough) {}
140+
ClangDiagnosticManagerAdapter(DiagnosticOptions &opts) {
141+
DiagnosticOptions *m_options = new DiagnosticOptions(opts);
142+
m_options->ShowPresumedLoc = true;
143+
m_options->ShowLevel = false;
144+
m_os.reset(new llvm::raw_string_ostream(m_output));
145+
m_passthrough.reset(
146+
new clang::TextDiagnosticPrinter(*m_os, m_options, false));
147+
}
146148

147149
void ResetManager(DiagnosticManager *manager = nullptr) {
148150
m_manager = manager;
149151
}
150152

151153
void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
152154
const clang::Diagnostic &Info) override {
153-
if (m_manager) {
154-
llvm::SmallVector<char, 32> diag_str;
155-
Info.FormatDiagnostic(diag_str);
156-
diag_str.push_back('\0');
157-
const char *data = diag_str.data();
155+
// Render diagnostic message to m_output.
156+
m_output.clear();
157+
m_passthrough->HandleDiagnostic(DiagLevel, Info);
158+
m_os->flush();
158159

160+
if (m_manager) {
159161
lldb_private::DiagnosticSeverity severity;
160162
bool make_new_diagnostic = true;
161163

@@ -172,12 +174,16 @@ class ClangDiagnosticManagerAdapter : public clang::DiagnosticConsumer {
172174
severity = eDiagnosticSeverityRemark;
173175
break;
174176
case DiagnosticsEngine::Level::Note:
175-
m_manager->AppendMessageToDiagnostic(data);
177+
m_manager->AppendMessageToDiagnostic(m_output);
176178
make_new_diagnostic = false;
177179
}
178180
if (make_new_diagnostic) {
181+
// ClangDiagnostic messages are expected to have no whitespace/newlines
182+
// around them.
183+
std::string stripped_output = llvm::StringRef(m_output).trim();
184+
179185
ClangDiagnostic *new_diagnostic =
180-
new ClangDiagnostic(data, severity, Info.getID());
186+
new ClangDiagnostic(stripped_output, severity, Info.getID());
181187
m_manager->AddDiagnostic(new_diagnostic);
182188

183189
// Don't store away warning fixits, since the compiler doesn't have
@@ -194,23 +200,17 @@ class ClangDiagnosticManagerAdapter : public clang::DiagnosticConsumer {
194200
}
195201
}
196202
}
197-
198-
m_passthrough->HandleDiagnostic(DiagLevel, Info);
199-
}
200-
201-
void FlushDiagnostics(DiagnosticsEngine &Diags) {
202-
m_passthrough->FlushDiagnostics(Diags);
203-
}
204-
205-
DiagnosticConsumer *clone(DiagnosticsEngine &Diags) const {
206-
return new ClangDiagnosticManagerAdapter(m_passthrough);
207203
}
208204

209-
clang::TextDiagnosticBuffer *GetPassthrough() { return m_passthrough.get(); }
205+
clang::TextDiagnosticPrinter *GetPassthrough() { return m_passthrough.get(); }
210206

211207
private:
212208
DiagnosticManager *m_manager = nullptr;
213-
std::shared_ptr<clang::TextDiagnosticBuffer> m_passthrough;
209+
std::shared_ptr<clang::TextDiagnosticPrinter> m_passthrough;
210+
/// Output stream of m_passthrough.
211+
std::shared_ptr<llvm::raw_string_ostream> m_os;
212+
/// Output string filled by m_os.
213+
std::string m_output;
214214
};
215215

216216
static void SetupModuleHeaderPaths(CompilerInstance *compiler,
@@ -258,12 +258,11 @@ static void SetupModuleHeaderPaths(CompilerInstance *compiler,
258258
// Implementation of ClangExpressionParser
259259
//===----------------------------------------------------------------------===//
260260

261-
ClangExpressionParser::ClangExpressionParser(
262-
ExecutionContextScope *exe_scope, Expression &expr,
263-
bool generate_debug_info, std::vector<std::string> include_directories)
261+
ClangExpressionParser::ClangExpressionParser(ExecutionContextScope *exe_scope, Expression &expr,
262+
bool generate_debug_info, std::vector<std::string> include_directories, std::string filename)
264263
: ExpressionParser(exe_scope, expr, generate_debug_info), m_compiler(),
265264
m_pp_callbacks(nullptr),
266-
m_include_directories(std::move(include_directories)) {
265+
m_include_directories(std::move(include_directories)), m_filename(std::move(filename)) {
267266
Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS));
268267

269268
// We can't compile expressions without a target. So if the exe_scope is
@@ -557,7 +556,9 @@ ClangExpressionParser::ClangExpressionParser(
557556

558557
// 6. Set up the diagnostic buffer for reporting errors
559558

560-
m_compiler->getDiagnostics().setClient(new ClangDiagnosticManagerAdapter);
559+
auto diag_mgr = new ClangDiagnosticManagerAdapter(
560+
m_compiler->getDiagnostics().getDiagnosticOptions());
561+
m_compiler->getDiagnostics().setClient(diag_mgr);
561562

562563
// 7. Set up the source management objects inside the compiler
563564
m_compiler->createFileManager();
@@ -869,8 +870,7 @@ ClangExpressionParser::ParseInternal(DiagnosticManager &diagnostic_manager,
869870
ClangDiagnosticManagerAdapter *adapter =
870871
static_cast<ClangDiagnosticManagerAdapter *>(
871872
m_compiler->getDiagnostics().getClient());
872-
clang::TextDiagnosticBuffer *diag_buf = adapter->GetPassthrough();
873-
diag_buf->FlushDiagnostics(m_compiler->getDiagnostics());
873+
auto diag_buf = adapter->GetPassthrough();
874874

875875
adapter->ResetManager(&diagnostic_manager);
876876

@@ -921,7 +921,7 @@ ClangExpressionParser::ParseInternal(DiagnosticManager &diagnostic_manager,
921921

922922
if (!created_main_file) {
923923
std::unique_ptr<MemoryBuffer> memory_buffer =
924-
MemoryBuffer::getMemBufferCopy(expr_text, "<lldb-expr>");
924+
MemoryBuffer::getMemBufferCopy(expr_text, m_filename);
925925
source_mgr.setMainFileID(source_mgr.createFileID(std::move(memory_buffer)));
926926
}
927927

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,14 @@ class ClangExpressionParser : public ExpressionParser {
5353
/// @param[in] include_directories
5454
/// List of include directories that should be used when parsing the
5555
/// expression.
56+
///
57+
/// @param[in] filename
58+
/// Name of the source file that should be used when rendering
59+
/// diagnostics (i.e. errors, warnings or notes from Clang).
5660
ClangExpressionParser(ExecutionContextScope *exe_scope, Expression &expr,
5761
bool generate_debug_info,
58-
std::vector<std::string> include_directories = {});
62+
std::vector<std::string> include_directories = {},
63+
std::string filename = "<clang expression>");
5964

6065
/// Destructor
6166
~ClangExpressionParser() override;
@@ -178,6 +183,8 @@ class ClangExpressionParser : public ExpressionParser {
178183
std::unique_ptr<ClangASTContext> m_ast_context;
179184

180185
std::vector<std::string> m_include_directories;
186+
/// File name used for the user expression.
187+
std::string m_filename;
181188
};
182189
}
183190

0 commit comments

Comments
 (0)