Files
clang-p2996/lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py
John Harrison 4ea1994a03 [lldb-dap] Adjusting how repl-mode auto determines commands vs variable expressions. (#78005)
The previous logic for determining if an expression was a command or
variable expression in the repl would incorrectly identify the context
in many common cases where a local variable name partially overlaps with
the repl input.

For example:
```
int foo() {
  int var = 1; // break point, evaluating "p var", previously emitted a warning
}
```

Instead of checking potentially multiple conflicting values against the
expression input, I updated the heuristic to only consider the first
term. This is much more reliable at eliminating false positives when the
input does not actually hide a local variable.

Additionally, I updated the warning on conflicts to occur anytime the
conflict is detected since the specific conflict can change based on the
current input. This also includes additional details on how users can
change the behavior.

Example Debug Console output from
lldb/test/API/tools/lldb-dap/evaluate/main.cpp:11 breakpoint 3.

```
lldb-dap> var + 3
Warning: Expression 'var' is both an LLDB command and variable. It will be evaluated as a variable. To evaluate the expression as an LLDB command, use '`' as a prefix.
45

lldb-dap> var + 1
Warning: Expression 'var' is both an LLDB command and variable. It will be evaluated as a variable. To evaluate the expression as an LLDB command, use '`' as a prefix.
43
```
2024-01-17 09:00:22 -08:00

219 lines
8.4 KiB
Python

"""
Test lldb-dap completions request
"""
import re
import lldbdap_testcase
import dap_server
from lldbsuite.test import lldbutil
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
class TestDAP_evaluate(lldbdap_testcase.DAPTestCaseBase):
def assertEvaluate(self, expression, regex):
self.assertRegexpMatches(
self.dap_server.request_evaluate(expression, context=self.context)["body"][
"result"
],
regex,
)
def assertEvaluateFailure(self, expression):
self.assertNotIn(
"result",
self.dap_server.request_evaluate(expression, context=self.context)["body"],
)
def isResultExpandedDescription(self):
return self.context == "repl" or self.context == "hover"
def isExpressionParsedExpected(self):
return self.context != "hover"
def run_test_evaluate_expressions(
self, context=None, enableAutoVariableSummaries=False
):
"""
Tests the evaluate expression request at different breakpoints
"""
self.context = context
program = self.getBuildArtifact("a.out")
self.build_and_launch(
program, enableAutoVariableSummaries=enableAutoVariableSummaries
)
source = "main.cpp"
self.set_source_breakpoints(
source,
[
line_number(source, "// breakpoint 1"),
line_number(source, "// breakpoint 2"),
line_number(source, "// breakpoint 3"),
line_number(source, "// breakpoint 4"),
line_number(source, "// breakpoint 5"),
line_number(source, "// breakpoint 6"),
line_number(source, "// breakpoint 7"),
],
)
self.continue_to_next_stop()
# Expressions at breakpoint 1, which is in main
self.assertEvaluate("var1", "20")
self.assertEvaluate("var2", "21")
self.assertEvaluate("static_int", "42")
self.assertEvaluate("non_static_int", "43")
self.assertEvaluate("struct1.foo", "15")
self.assertEvaluate("struct2->foo", "16")
if self.isResultExpandedDescription():
self.assertEvaluate(
"struct1",
r"\(my_struct\) (struct1|\$\d+) = \(foo = 15\)",
)
self.assertEvaluate("struct2", r"\(my_struct \*\) (struct2|\$\d+) = 0x.*")
self.assertEvaluate(
"struct3", r"\(my_struct \*\) (struct3|\$\d+) = nullptr"
)
else:
self.assertEvaluate(
"struct1",
re.escape("{foo:15}")
if enableAutoVariableSummaries
else "my_struct @ 0x",
)
self.assertEvaluate(
"struct2", "0x.* {foo:16}" if enableAutoVariableSummaries else "0x.*"
)
self.assertEvaluate("struct3", "0x.*0")
if context == "repl":
# In the repl context expressions may be interpreted as lldb
# commands since no variables have the same name as the command.
self.assertEvaluate("var", r"\(lldb\) var\n.*")
else:
self.assertEvaluateFailure("var") # local variable of a_function
self.assertEvaluateFailure("my_struct") # type name
self.assertEvaluateFailure("int") # type name
self.assertEvaluateFailure("foo") # member of my_struct
if self.isExpressionParsedExpected():
self.assertEvaluate("a_function", "0x.*a.out`a_function.*")
self.assertEvaluate("a_function(1)", "1")
self.assertEvaluate("var2 + struct1.foo", "36")
self.assertEvaluate("foo_func", "0x.*a.out`foo_func.*")
self.assertEvaluate("foo_var", "44")
else:
self.assertEvaluateFailure("a_function")
self.assertEvaluateFailure("a_function(1)")
self.assertEvaluateFailure("var2 + struct1.foo")
self.assertEvaluateFailure("foo_func")
self.assertEvaluateFailure("foo_var")
# Expressions at breakpoint 2, which is an anonymous block
self.continue_to_next_stop()
self.assertEvaluate("var1", "20")
self.assertEvaluate("var2", "2") # different variable with the same name
self.assertEvaluate("static_int", "42")
self.assertEvaluate(
"non_static_int", "10"
) # different variable with the same name
if self.isResultExpandedDescription():
self.assertEvaluate(
"struct1",
r"\(my_struct\) (struct1|\$\d+) = \(foo = 15\)",
)
else:
self.assertEvaluate(
"struct1",
re.escape("{foo:15}")
if enableAutoVariableSummaries
else "my_struct @ 0x",
)
self.assertEvaluate("struct1.foo", "15")
self.assertEvaluate("struct2->foo", "16")
if self.isExpressionParsedExpected():
self.assertEvaluate("a_function", "0x.*a.out`a_function.*")
self.assertEvaluate("a_function(1)", "1")
self.assertEvaluate("var2 + struct1.foo", "17")
self.assertEvaluate("foo_func", "0x.*a.out`foo_func.*")
self.assertEvaluate("foo_var", "44")
else:
self.assertEvaluateFailure("a_function")
self.assertEvaluateFailure("a_function(1)")
self.assertEvaluateFailure("var2 + struct1.foo")
self.assertEvaluateFailure("foo_func")
self.assertEvaluateFailure("foo_var")
# Expressions at breakpoint 3, which is inside a_function
self.continue_to_next_stop()
self.assertEvaluate("var", "42")
self.assertEvaluate("static_int", "42")
self.assertEvaluate("non_static_int", "43")
self.assertEvaluateFailure("var1")
self.assertEvaluateFailure("var2")
self.assertEvaluateFailure("struct1")
self.assertEvaluateFailure("struct1.foo")
self.assertEvaluateFailure("struct2->foo")
self.assertEvaluateFailure("var2 + struct1.foo")
if self.isExpressionParsedExpected():
self.assertEvaluate("a_function", "0x.*a.out`a_function.*")
self.assertEvaluate("a_function(1)", "1")
self.assertEvaluate("var + 1", "43")
self.assertEvaluate("foo_func", "0x.*a.out`foo_func.*")
self.assertEvaluate("foo_var", "44")
else:
self.assertEvaluateFailure("a_function")
self.assertEvaluateFailure("a_function(1)")
self.assertEvaluateFailure("var + 1")
self.assertEvaluateFailure("foo_func")
self.assertEvaluateFailure("foo_var")
# Now we check that values are updated after stepping
self.continue_to_next_stop()
self.assertEvaluate("my_vec", "size=2")
self.continue_to_next_stop()
self.assertEvaluate("my_vec", "size=3")
self.assertEvaluate("my_map", "size=2")
self.continue_to_next_stop()
self.assertEvaluate("my_map", "size=3")
self.assertEvaluate("my_bool_vec", "size=1")
self.continue_to_next_stop()
self.assertEvaluate("my_bool_vec", "size=2")
@skipIfWindows
@skipIfRemote
def test_generic_evaluate_expressions(self):
# Tests context-less expression evaluations
self.run_test_evaluate_expressions(enableAutoVariableSummaries=False)
@skipIfWindows
@skipIfRemote
def test_repl_evaluate_expressions(self):
# Tests expression evaluations that are triggered from the Debug Console
self.run_test_evaluate_expressions("repl", enableAutoVariableSummaries=False)
@skipIfWindows
@skipIfRemote
def test_watch_evaluate_expressions(self):
# Tests expression evaluations that are triggered from a watch expression
self.run_test_evaluate_expressions("watch", enableAutoVariableSummaries=True)
@skipIfWindows
@skipIfRemote
def test_hover_evaluate_expressions(self):
# Tests expression evaluations that are triggered when hovering on the editor
self.run_test_evaluate_expressions("hover", enableAutoVariableSummaries=False)
@skipIfWindows
@skipIfRemote
def test_variable_evaluate_expressions(self):
# Tests expression evaluations that are triggered in the variable explorer
self.run_test_evaluate_expressions("variable", enableAutoVariableSummaries=True)