xref: /llvm-project/lldb/test/API/commands/expression/diagnostics/TestExprDiagnostics.py (revision aca7a70cdada8aa13c7f4e24c431bfe47218a3fa)
1"""
2Test the diagnostics emitted by our embeded Clang instance that parses expressions.
3"""
4
5import lldb
6from lldbsuite.test.lldbtest import *
7from lldbsuite.test import lldbutil
8from lldbsuite.test.decorators import *
9
10
11class ExprDiagnosticsTestCase(TestBase):
12    def setUp(self):
13        # Call super's setUp().
14        TestBase.setUp(self)
15
16        self.main_source = "main.cpp"
17        self.main_source_spec = lldb.SBFileSpec(self.main_source)
18
19    def test_source_and_caret_printing(self):
20        """Test that the source and caret positions LLDB prints are correct"""
21        self.build()
22
23        (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
24            self, "// Break here", self.main_source_spec
25        )
26        frame = thread.GetFrameAtIndex(0)
27
28        # Test that source/caret are at the right position.
29        value = frame.EvaluateExpression("unknown_identifier")
30        self.assertFalse(value.GetError().Success())
31        # We should get a nice diagnostic with a caret pointing at the start of
32        # the identifier.
33        self.assertIn(
34            """
35    1 | unknown_identifier
36      | ^
37""",
38            value.GetError().GetCString(),
39        )
40        self.assertIn("<user expression 0>:1:1", value.GetError().GetCString())
41
42        # Same as above but with the identifier in the middle.
43        value = frame.EvaluateExpression("1 + unknown_identifier")
44        self.assertFalse(value.GetError().Success())
45        self.assertIn(
46            """
47    1 | 1 + unknown_identifier
48      |     ^
49""",
50            value.GetError().GetCString(),
51        )
52
53        # Multiline expressions.
54        value = frame.EvaluateExpression("int a = 0;\nfoobar +=1;\na")
55        self.assertFalse(value.GetError().Success())
56        # We should still get the right line information and caret position.
57        self.assertIn(
58            """
59    2 | foobar +=1;
60      | ^
61""",
62            value.GetError().GetCString(),
63        )
64
65        # It's the second line of the user expression.
66        self.assertIn("<user expression 2>:2:1", value.GetError().GetCString())
67
68        # Top-level expressions.
69        top_level_opts = lldb.SBExpressionOptions()
70        top_level_opts.SetTopLevel(True)
71
72        value = frame.EvaluateExpression("void foo(unknown_type x) {}", top_level_opts)
73        self.assertFalse(value.GetError().Success())
74        self.assertIn(
75            """
76    1 | void foo(unknown_type x) {}
77      |          ^
78""",
79            value.GetError().GetCString(),
80        )
81
82        # Top-level expressions might use a different wrapper code, but the file name should still
83        # be the same.
84        self.assertIn("<user expression 3>:1:10", value.GetError().GetCString())
85
86        # Multiline top-level expressions.
87        value = frame.EvaluateExpression("void x() {}\nvoid foo;", top_level_opts)
88        self.assertFalse(value.GetError().Success())
89        self.assertIn(
90            """
91    2 | void foo;
92      |      ^
93""",
94            value.GetError().GetCString(),
95        )
96
97        self.assertIn("<user expression 4>:2:6", value.GetError().GetCString())
98
99        # Test that we render Clang's 'notes' correctly.
100        value = frame.EvaluateExpression(
101            "struct SFoo{}; struct SFoo { int x; };", top_level_opts
102        )
103        self.assertFalse(value.GetError().Success())
104        self.assertIn(
105            "<user expression 5>:1:8: previous definition is here\n",
106            value.GetError().GetCString(),
107        )
108        self.assertIn(
109            """
110    1 | struct SFoo{}; struct SFoo { int x; };
111      |        ^
112""",
113            value.GetError().GetCString(),
114        )
115
116        # Declarations from the debug information currently have no debug information. It's not clear what
117        # we should do in this case, but we should at least not print anything that's wrong.
118        # In the future our declarations should have valid source locations.
119        value = frame.EvaluateExpression("struct FooBar { double x };", top_level_opts)
120        self.assertFalse(value.GetError().Success())
121        self.assertIn(
122            "error: <user expression 6>:1:8: redefinition of 'FooBar'\n",
123            value.GetError().GetCString(),
124        )
125        self.assertIn(
126            """
127    1 | struct FooBar { double x };
128      |        ^
129""",
130            value.GetError().GetCString(),
131        )
132
133        value = frame.EvaluateExpression("foo(1, 2)")
134        self.assertFalse(value.GetError().Success())
135        self.assertIn(
136            "error: <user expression 7>:1:1: no matching function for call to 'foo'\n",
137            value.GetError().GetCString(),
138        )
139        self.assertIn(
140            """
141    1 | foo(1, 2)
142      | ^~~
143note: candidate function not viable: requires single argument 'x', but 2 arguments were provided
144""",
145            value.GetError().GetCString(),
146        )
147
148        # Redefine something that we defined in a user-expression. We should use the previous expression file name
149        # for the original decl.
150        value = frame.EvaluateExpression("struct Redef { double x; };", top_level_opts)
151        value = frame.EvaluateExpression("struct Redef { float y; };", top_level_opts)
152        self.assertFalse(value.GetError().Success())
153        self.assertIn(
154            """error: <user expression 9>:1:8: redefinition of 'Redef'
155    1 | struct Redef { float y; };
156      |        ^
157<user expression 8>:1:8: previous definition is here
158    1 | struct Redef { double x; };
159      |        ^
160""",
161            value.GetError().GetCString(),
162        )
163
164    @add_test_categories(["objc"])
165    def test_source_locations_from_objc_modules(self):
166        self.build()
167
168        (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
169            self, "// Break here", self.main_source_spec
170        )
171        frame = thread.GetFrameAtIndex(0)
172
173        # Import foundation so that the Obj-C module is loaded (which contains source locations
174        # that can be used by LLDB).
175        self.runCmd("expr --language objective-c++ -- @import Foundation")
176        value = frame.EvaluateExpression("NSLog(1);")
177        self.assertFalse(value.GetError().Success())
178        # LLDB should print the source line that defines NSLog. To not rely on any
179        # header paths/line numbers or the actual formatting of the Foundation headers, only look
180        # for a few tokens in the output.
181        # File path should come from Foundation framework.
182        self.assertIn("/Foundation.framework/", value.GetError().GetCString())
183        # The NSLog definition source line should be printed. Return value and
184        # the first argument are probably stable enough that this test can check for them.
185        self.assertIn("void NSLog(NSString *format", value.GetError().GetCString())
186
187    def test_error_type(self):
188        """Test the error reporting in the API"""
189        self.build()
190
191        (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
192            self, "// Break here", self.main_source_spec
193        )
194        frame = thread.GetFrameAtIndex(0)
195        value = frame.EvaluateExpression('#error("I am error.")')
196        error = value.GetError()
197        self.assertEqual(error.GetType(), lldb.eErrorTypeExpression)
198        value = frame.FindVariable("f")
199        self.assertTrue(value.IsValid())
200        desc = value.GetObjectDescription()
201        self.assertEqual(desc, None)
202
203    def test_command_expr_sbdata(self):
204        """Test the structured diagnostics data"""
205        self.build()
206
207        (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
208            self, "// Break here", self.main_source_spec
209        )
210
211        def check_error(diags):
212            # Version.
213            version = diags.GetValueForKey("version")
214            self.assertEqual(version.GetIntegerValue(), 1)
215
216            details = diags.GetValueForKey("details")
217
218            # Detail 1/2: undeclared 'a'
219            diag = details.GetItemAtIndex(0)
220
221            severity = diag.GetValueForKey("severity")
222            message = diag.GetValueForKey("message")
223            rendered = diag.GetValueForKey("rendered")
224            sloc = diag.GetValueForKey("source_location")
225            filename = sloc.GetValueForKey("file")
226            hidden = sloc.GetValueForKey("hidden")
227            in_user_input = sloc.GetValueForKey("in_user_input")
228
229            self.assertEqual(str(severity), "error")
230            self.assertIn("undeclared identifier 'a'", str(message))
231            # The rendered string should contain the source file.
232            self.assertIn("user expression", str(rendered))
233            self.assertIn("user expression", str(filename))
234            self.assertFalse(hidden.GetBooleanValue())
235            self.assertTrue(in_user_input.GetBooleanValue())
236
237            # Detail 1/2: undeclared 'b'
238            diag = details.GetItemAtIndex(1)
239            message = diag.GetValueForKey("message")
240            self.assertIn("undeclared identifier 'b'", str(message))
241
242        # Test diagnostics in CommandReturnObject
243        interp = self.dbg.GetCommandInterpreter()
244        cro = lldb.SBCommandReturnObject()
245        interp.HandleCommand("expression -- a+b", cro)
246
247        diags = cro.GetErrorData()
248        check_error(diags)
249
250        # Test diagnostics in SBError
251        frame = thread.GetSelectedFrame()
252        value = frame.EvaluateExpression("a+b")
253        error = value.GetError()
254        self.assertTrue(error.Fail())
255        self.assertEqual(error.GetType(), lldb.eErrorTypeExpression)
256        data = error.GetErrorData()
257        version = data.GetValueForKey("version")
258        self.assertEqual(version.GetIntegerValue(), 1)
259        err_ty = data.GetValueForKey("type")
260        self.assertEqual(err_ty.GetIntegerValue(), lldb.eErrorTypeExpression)
261        diags = data.GetValueForKey("errors").GetItemAtIndex(0)
262        check_error(diags)
263