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