275 lines · python
1"""2Test the diagnostics emitted by our embeded Clang instance that parses expressions.3"""4 5import lldb6from lldbsuite.test.lldbtest import *7from lldbsuite.test import lldbutil8from 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_spec25 )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 of32 # the identifier.33 self.assertIn(34 """35 1 | unknown_identifier36 | ^~~~~~~~~~~~~~~~~~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_identifier48 | ^~~~~~~~~~~~~~~~~~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 still83 # 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_opts102 )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 what117 # 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 provided144""",145 value.GetError().GetCString(),146 )147 148 # Redefine something that we defined in a user-expression. We should use the previous expression file name149 # 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 here158 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_spec170 )171 frame = thread.GetFrameAtIndex(0)172 173 # Import foundation so that the Obj-C module is loaded (which contains source locations174 # 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 any179 # header paths/line numbers or the actual formatting of the Foundation headers, only look180 # 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 and184 # 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_spec193 )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_spec209 )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/3: note: requested expression language219 diag = details.GetItemAtIndex(0)220 self.assertEqual(str(diag.GetValueForKey("severity")), "note")221 self.assertIn("Ran expression as 'C++", str(diag.GetValueForKey("message")))222 self.assertIn(223 "Ran expression as 'C++", str(diag.GetValueForKey("rendered"))224 )225 self.assertEqual(str(diag.GetValueForKey("source_location")), "")226 self.assertEqual(str(diag.GetValueForKey("file")), "")227 self.assertFalse(diag.GetValueForKey("hidden").GetBooleanValue())228 self.assertFalse(diag.GetValueForKey("in_user_input").GetBooleanValue())229 230 # Detail 2/3: undeclared 'a'231 diag = details.GetItemAtIndex(1)232 233 severity = diag.GetValueForKey("severity")234 message = diag.GetValueForKey("message")235 rendered = diag.GetValueForKey("rendered")236 sloc = diag.GetValueForKey("source_location")237 filename = sloc.GetValueForKey("file")238 hidden = sloc.GetValueForKey("hidden")239 in_user_input = sloc.GetValueForKey("in_user_input")240 241 self.assertEqual(str(severity), "error")242 self.assertIn("undeclared identifier 'a'", str(message))243 # The rendered string should contain the source file.244 self.assertIn("user expression", str(rendered))245 self.assertIn("user expression", str(filename))246 self.assertFalse(hidden.GetBooleanValue())247 self.assertTrue(in_user_input.GetBooleanValue())248 249 # Detail 3/3: undeclared 'b'250 diag = details.GetItemAtIndex(2)251 message = diag.GetValueForKey("message")252 self.assertIn("undeclared identifier 'b'", str(message))253 254 # Test diagnostics in CommandReturnObject255 interp = self.dbg.GetCommandInterpreter()256 cro = lldb.SBCommandReturnObject()257 interp.HandleCommand("expression -- a+b", cro)258 259 diags = cro.GetErrorData()260 check_error(diags)261 262 # Test diagnostics in SBError263 frame = thread.GetSelectedFrame()264 value = frame.EvaluateExpression("a+b")265 error = value.GetError()266 self.assertTrue(error.Fail())267 self.assertEqual(error.GetType(), lldb.eErrorTypeExpression)268 data = error.GetErrorData()269 version = data.GetValueForKey("version")270 self.assertEqual(version.GetIntegerValue(), 1)271 err_ty = data.GetValueForKey("type")272 self.assertEqual(err_ty.GetIntegerValue(), lldb.eErrorTypeExpression)273 diags = data.GetValueForKey("errors").GetItemAtIndex(0)274 check_error(diags)275