"""
Test the diagnostics emitted by our embeded Clang instance that parses expressions.
"""
import lldb
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
from lldbsuite.test.decorators import *
class ExprDiagnosticsTestCase(TestBase):
def setUp(self):
# Call super's setUp().
TestBase.setUp(self)
self.main_source = "main.cpp"
self.main_source_spec = lldb.SBFileSpec(self.main_source)
def test_source_and_caret_printing(self):
"""Test that the source and caret positions LLDB prints are correct"""
self.build()
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
self, "// Break here", self.main_source_spec
)
frame = thread.GetFrameAtIndex(0)
# Test that source/caret are at the right position.
value = frame.EvaluateExpression("unknown_identifier")
self.assertFalse(value.GetError().Success())
# We should get a nice diagnostic with a caret pointing at the start of
# the identifier.
self.assertIn(
"""
1 | unknown_identifier
| ^
""",
value.GetError().GetCString(),
)
self.assertIn("<user expression 0>:1:1", value.GetError().GetCString())
# Same as above but with the identifier in the middle.
value = frame.EvaluateExpression("1 + unknown_identifier")
self.assertFalse(value.GetError().Success())
self.assertIn(
"""
1 | 1 + unknown_identifier
| ^
""",
value.GetError().GetCString(),
)
# Multiline expressions.
value = frame.EvaluateExpression("int a = 0;\nfoobar +=1;\na")
self.assertFalse(value.GetError().Success())
# We should still get the right line information and caret position.
self.assertIn(
"""
2 | foobar +=1;
| ^
""",
value.GetError().GetCString(),
)
# It's the second line of the user expression.
self.assertIn("<user expression 2>:2:1", value.GetError().GetCString())
# Top-level expressions.
top_level_opts = lldb.SBExpressionOptions()
top_level_opts.SetTopLevel(True)
value = frame.EvaluateExpression("void foo(unknown_type x) {}", top_level_opts)
self.assertFalse(value.GetError().Success())
self.assertIn(
"""
1 | void foo(unknown_type x) {}
| ^
""",
value.GetError().GetCString(),
)
# Top-level expressions might use a different wrapper code, but the file name should still
# be the same.
self.assertIn("<user expression 3>:1:10", value.GetError().GetCString())
# Multiline top-level expressions.
value = frame.EvaluateExpression("void x() {}\nvoid foo;", top_level_opts)
self.assertFalse(value.GetError().Success())
self.assertIn(
"""
2 | void foo;
| ^
""",
value.GetError().GetCString(),
)
self.assertIn("<user expression 4>:2:6", value.GetError().GetCString())
# Test that we render Clang's 'notes' correctly.
value = frame.EvaluateExpression(
"struct SFoo{}; struct SFoo { int x; };", top_level_opts
)
self.assertFalse(value.GetError().Success())
self.assertIn(
"<user expression 5>:1:8: previous definition is here\n",
value.GetError().GetCString(),
)
self.assertIn(
"""
1 | struct SFoo{}; struct SFoo { int x; };
| ^
""",
value.GetError().GetCString(),
)
# Declarations from the debug information currently have no debug information. It's not clear what
# we should do in this case, but we should at least not print anything that's wrong.
# In the future our declarations should have valid source locations.
value = frame.EvaluateExpression("struct FooBar { double x };", top_level_opts)
self.assertFalse(value.GetError().Success())
self.assertIn(
"error: <user expression 6>:1:8: redefinition of 'FooBar'\n",
value.GetError().GetCString(),
)
self.assertIn(
"""
1 | struct FooBar { double x };
| ^
""",
value.GetError().GetCString(),
)
value = frame.EvaluateExpression("foo(1, 2)")
self.assertFalse(value.GetError().Success())
self.assertIn(
"error: <user expression 7>:1:1: no matching function for call to 'foo'\n",
value.GetError().GetCString(),
)
self.assertIn(
"""
1 | foo(1, 2)
| ^~~
note: candidate function not viable: requires single argument 'x', but 2 arguments were provided
""",
value.GetError().GetCString(),
)
# Redefine something that we defined in a user-expression. We should use the previous expression file name
# for the original decl.
value = frame.EvaluateExpression("struct Redef { double x; };", top_level_opts)
value = frame.EvaluateExpression("struct Redef { float y; };", top_level_opts)
self.assertFalse(value.GetError().Success())
self.assertIn(
"""error: <user expression 9>:1:8: redefinition of 'Redef'
1 | struct Redef { float y; };
| ^
<user expression 8>:1:8: previous definition is here
1 | struct Redef { double x; };
| ^
""",
value.GetError().GetCString(),
)
@add_test_categories(["objc"])
def test_source_locations_from_objc_modules(self):
self.build()
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
self, "// Break here", self.main_source_spec
)
frame = thread.GetFrameAtIndex(0)
# Import foundation so that the Obj-C module is loaded (which contains source locations
# that can be used by LLDB).
self.runCmd("expr --language objective-c++ -- @import Foundation")
value = frame.EvaluateExpression("NSLog(1);")
self.assertFalse(value.GetError().Success())
# LLDB should print the source line that defines NSLog. To not rely on any
# header paths/line numbers or the actual formatting of the Foundation headers, only look
# for a few tokens in the output.
# File path should come from Foundation framework.
self.assertIn("/Foundation.framework/", value.GetError().GetCString())
# The NSLog definition source line should be printed. Return value and
# the first argument are probably stable enough that this test can check for them.
self.assertIn("void NSLog(NSString *format", value.GetError().GetCString())
def test_command_expr_formatting(self):
"""Test that the source and caret positions LLDB prints are correct"""
self.build()
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
self, "// Break here", self.main_source_spec
)
frame = thread.GetFrameAtIndex(0)
self.expect("settings set show-inline-diagnostics true")
def check(input_ref):
self.expect(input_ref[0], error=True, substrs=input_ref[1:])
check(
[
"expression -- a+b",
" ^ ^",
" │ ╰─ error: use of undeclared identifier 'b'",
" ╰─ error: use of undeclared identifier 'a'",
]
)
check(
[
"expr -- a",
" ^",
" ╰─ error: use of undeclared identifier 'a'",
]
)
check(
[
"expr -i 0 -o 0 -- a",
" ^",
" ╰─ error: use of undeclared identifier 'a'",
]
)
self.expect(
"expression --top-level -- template<typename T> T FOO(T x) { return x/2;}"
)
check(
[
'expression -- FOO("")',
" ^",
" ╰─ note: in instantiation of function template specialization 'FOO<const char *>' requested here",
"error: <user expression",
"invalid operands to binary expression",
]
)
check(["expression --\na\n+\nb", "error: <user", "a", "error: <user", "b"])