Adding more supported operators to DIL breaks tests in `DWIMPrint` and `lldb-dap`, which shouldn't be simply adjusted for new DIL capabilities. They act as a check for the boundaries of what subset of expressions `DWIMPrint` and `lldb-dap` expect to be evaluated when using `GetValueForVariableExpressionPath` function. With this patch, the caller can now pick a mode that limits the expressions DIL can evaluate, which ensures the expected preexisting behavior. More operators can now be safely added to DIL, which can still be evaluated by DIL when using `frame var` command or the API call with Full mode selected (or not specified at all). DIL will only attempt evaluating expressions that contain operations allowed by a selected mode: - Simple: identifiers, operators: '.' - Legacy: identifiers, integers, operators: '.', '->', '*', '&', '[]' - Full: everything supported by DIL
502 lines
19 KiB
Python
502 lines
19 KiB
Python
"""
|
|
Test lldb-dap evaluate request
|
|
"""
|
|
|
|
import re
|
|
|
|
import lldbdap_testcase
|
|
from lldbsuite.test.decorators import skipIfWindows
|
|
from lldbsuite.test.lldbtest import line_number
|
|
from typing import TypedDict, Optional
|
|
|
|
|
|
class EvaluateResponseBody(TypedDict, total=False):
|
|
result: str
|
|
variablesReference: int
|
|
type: Optional[str]
|
|
memoryReference: Optional[str]
|
|
valueLocationReference: Optional[int]
|
|
|
|
|
|
class TestDAP_evaluate(lldbdap_testcase.DAPTestCaseBase):
|
|
def assertEvaluate(
|
|
self,
|
|
expression,
|
|
result: str,
|
|
want_type="",
|
|
want_varref=False,
|
|
want_memref=True,
|
|
want_locref=False,
|
|
frame_index: Optional[int] = 0,
|
|
is_hex=None,
|
|
):
|
|
resp = self.dap_server.request_evaluate(
|
|
expression, context=self.context, is_hex=is_hex, frameIndex=frame_index
|
|
)
|
|
self.assertTrue(
|
|
resp["success"],
|
|
f"Failed to evaluate expression {expression!r} in frame {frame_index}",
|
|
)
|
|
body: EvaluateResponseBody = resp["body"]
|
|
self.assertRegex(
|
|
body["result"],
|
|
result,
|
|
f"Unexpected 'result' for expression {expression!r} in response body {body}",
|
|
)
|
|
if want_varref:
|
|
self.assertNotEqual(
|
|
body["variablesReference"],
|
|
0,
|
|
f"Unexpected 'variablesReference' for expression {expression!r} in response body {body}",
|
|
)
|
|
else:
|
|
self.assertEqual(
|
|
body["variablesReference"],
|
|
0,
|
|
f"Unexpected 'variablesReference' for expression {expression!r} in response body {body}",
|
|
)
|
|
if want_type:
|
|
self.assertEqual(
|
|
body["type"],
|
|
want_type,
|
|
f"Unexpected 'type' for expression {expression!r} in response body {body}",
|
|
)
|
|
if want_memref:
|
|
self.assertIn(
|
|
"memoryReference",
|
|
body,
|
|
f"Unexpected 'memoryReference' for expression {expression!r} in response body {body}",
|
|
)
|
|
if want_locref:
|
|
self.assertIn(
|
|
"valueLocationReference",
|
|
body,
|
|
f"Unexpected 'valueLocationReference' for expression {expression!r} in response body {body}",
|
|
)
|
|
|
|
def assertEvaluateFailure(self, expression):
|
|
response = self.dap_server.request_evaluate(expression, context=self.context)
|
|
self.assertFalse(
|
|
response["success"],
|
|
f"Expression:'{expression}' should fail in {self.context} context, got {response!r}",
|
|
)
|
|
self.assertNotIn(
|
|
"result",
|
|
response["body"],
|
|
)
|
|
|
|
def isResultExpandedDescription(self):
|
|
return self.context == "repl"
|
|
|
|
def isResultShortDescription(self):
|
|
return self.context == "clipboard"
|
|
|
|
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"
|
|
breakpoint_lines = [
|
|
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"),
|
|
line_number(source, "// breakpoint 8"),
|
|
]
|
|
breakpoint_ids = self.set_source_breakpoints(source, breakpoint_lines)
|
|
|
|
self.assertEqual(
|
|
len(breakpoint_ids),
|
|
len(breakpoint_lines),
|
|
"Did not resolve all the breakpoints.",
|
|
)
|
|
breakpoint_1 = breakpoint_ids[0]
|
|
breakpoint_2 = breakpoint_ids[1]
|
|
breakpoint_3 = breakpoint_ids[2]
|
|
breakpoint_4 = breakpoint_ids[3]
|
|
breakpoint_5 = breakpoint_ids[4]
|
|
breakpoint_6 = breakpoint_ids[5]
|
|
breakpoint_7 = breakpoint_ids[6]
|
|
breakpoint_8 = breakpoint_ids[7]
|
|
self.continue_to_breakpoint(breakpoint_1)
|
|
|
|
# Expressions at breakpoint 1, which is in main
|
|
self.assertEvaluate("var1", "20", want_type="int")
|
|
# Empty expression should equate to the previous expression.
|
|
if context == "repl":
|
|
self.assertEvaluate("", "20")
|
|
else:
|
|
self.assertEvaluateFailure("")
|
|
self.assertEvaluate("var2", "21", want_type="int")
|
|
if context == "repl":
|
|
self.assertEvaluate("", "21", want_type="int")
|
|
self.assertEvaluate("", "21", want_type="int")
|
|
self.assertEvaluate("static_int", "0x0000002a", want_type="int", is_hex=True)
|
|
self.assertEvaluate(
|
|
"non_static_int", "0x0000002b", want_type="int", is_hex=True
|
|
)
|
|
self.assertEvaluate("struct1.foo", "0x0000000f", want_type="int", is_hex=True)
|
|
self.assertEvaluate("struct2->foo", "0x00000010", want_type="int", is_hex=True)
|
|
self.assertEvaluate("static_int", "42", want_type="int")
|
|
self.assertEvaluate("non_static_int", "43", want_type="int")
|
|
self.assertEvaluate("struct1.foo", "15", want_type="int")
|
|
self.assertEvaluate("struct2->foo", "16", want_type="int")
|
|
|
|
if self.isResultExpandedDescription():
|
|
self.assertEvaluate(
|
|
"struct1",
|
|
r"\(my_struct\) (struct1|\$\d+) = \(foo = 15\)",
|
|
want_type="my_struct",
|
|
want_varref=True,
|
|
)
|
|
self.assertEvaluate(
|
|
"struct2",
|
|
r"\(my_struct \*\) (struct2|\$\d+) = 0x.*",
|
|
want_type="my_struct *",
|
|
want_varref=True,
|
|
)
|
|
self.assertEvaluate(
|
|
"struct3",
|
|
r"\(my_struct \*\) (struct3|\$\d+) = nullptr",
|
|
want_type="my_struct *",
|
|
want_varref=True,
|
|
)
|
|
elif self.isResultShortDescription():
|
|
self.assertEvaluate(
|
|
"struct1",
|
|
"(foo = 15)",
|
|
want_type="my_struct",
|
|
want_varref=True,
|
|
)
|
|
self.assertEvaluate(
|
|
"struct2",
|
|
r"0x.*",
|
|
want_type="my_struct *",
|
|
want_varref=True,
|
|
)
|
|
self.assertEvaluate(
|
|
"struct3",
|
|
"nullptr",
|
|
want_type="my_struct *",
|
|
want_varref=True,
|
|
)
|
|
else:
|
|
self.assertEvaluate(
|
|
"struct1",
|
|
(re.escape("{foo:15}") if enableAutoVariableSummaries else "my_struct"),
|
|
want_varref=True,
|
|
)
|
|
self.assertEvaluate(
|
|
"struct2",
|
|
"0x.* {foo:16}" if enableAutoVariableSummaries else "0x.*",
|
|
want_varref=True,
|
|
want_type="my_struct *",
|
|
)
|
|
self.assertEvaluate(
|
|
"struct3", "0x.*0", want_varref=True, want_type="my_struct *"
|
|
)
|
|
|
|
if context == "repl" or context is None:
|
|
# In repl or unknown context expressions may be interpreted as lldb
|
|
# commands since no variables have the same name as the command.
|
|
self.assertEvaluate("list", r".*", want_memref=False)
|
|
# Changing the frame index should not make a difference
|
|
self.assertEvaluate(
|
|
"version", r".*lldb.+", want_memref=False, frame_index=1
|
|
)
|
|
|
|
else:
|
|
self.assertEvaluateFailure("list") # 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.*",
|
|
want_type="int (*)(int)",
|
|
want_varref=True,
|
|
want_memref=False,
|
|
want_locref=True,
|
|
)
|
|
self.assertEvaluate(
|
|
"a_function(1)", "1", want_memref=False, want_type="int"
|
|
)
|
|
self.assertEvaluate("var2 + struct1.foo", "36", want_memref=False)
|
|
self.assertEvaluate(
|
|
"foo_func",
|
|
"0x.*a.out`foo_func.*",
|
|
want_type="int (*)()",
|
|
want_varref=True,
|
|
want_memref=False,
|
|
want_locref=True,
|
|
)
|
|
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("(float) var2")
|
|
self.assertEvaluate("foo_var", "44")
|
|
|
|
# Expressions at breakpoint 2, which is an anonymous block
|
|
self.continue_to_breakpoint(breakpoint_2)
|
|
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\)",
|
|
want_type="my_struct",
|
|
want_varref=True,
|
|
)
|
|
elif self.isResultShortDescription():
|
|
self.assertEvaluate(
|
|
"struct1",
|
|
"(foo = 15)",
|
|
want_type="my_struct",
|
|
want_varref=True,
|
|
)
|
|
else:
|
|
self.assertEvaluate(
|
|
"struct1",
|
|
(re.escape("{foo:15}") if enableAutoVariableSummaries else "my_struct"),
|
|
want_type="my_struct",
|
|
want_varref=True,
|
|
)
|
|
self.assertEvaluate("struct1.foo", "15")
|
|
self.assertEvaluate("struct2->foo", "16")
|
|
|
|
if self.isExpressionParsedExpected():
|
|
self.assertEvaluate(
|
|
"a_function",
|
|
"0x.*a.out`a_function.*",
|
|
want_type="int (*)(int)",
|
|
want_varref=True,
|
|
want_memref=False,
|
|
want_locref=True,
|
|
)
|
|
self.assertEvaluate("a_function(1)", "1", want_memref=False)
|
|
self.assertEvaluate("var2 + struct1.foo", "17", want_memref=False)
|
|
self.assertEvaluate(
|
|
"foo_func", "0x.*a.out`foo_func.*", want_varref=True, want_memref=False
|
|
)
|
|
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.assertEvaluate("foo_var", "44")
|
|
|
|
# Expressions at breakpoint 3, which is inside a_function
|
|
self.continue_to_breakpoint(breakpoint_3)
|
|
self.assertEvaluate("list", "42")
|
|
self.assertEvaluate("static_int", "42")
|
|
self.assertEvaluate("non_static_int", "43")
|
|
# variable from a different frame
|
|
self.assertEvaluate("var1", "20", frame_index=1)
|
|
|
|
if self.isExpressionParsedExpected():
|
|
# access global variable without a frame
|
|
# Run in variable mode to avoid interpreting it as a command
|
|
res = self.dap_server.request_evaluate(
|
|
"`lldb-dap repl-mode variable", context="repl"
|
|
)
|
|
self.assertTrue(res["success"])
|
|
self.assertEvaluate("static_int", "42", frame_index=None, want_memref=False)
|
|
res = self.dap_server.request_evaluate(
|
|
"`lldb-dap repl-mode auto", context="repl"
|
|
)
|
|
self.assertTrue(res["success"])
|
|
|
|
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.*",
|
|
want_varref=True,
|
|
want_memref=False,
|
|
want_locref=True,
|
|
)
|
|
self.assertEvaluate("a_function(1)", "1", want_memref=False)
|
|
self.assertEvaluate("list + 1", "43", want_memref=False)
|
|
self.assertEvaluate(
|
|
"foo_func", "0x.*a.out`foo_func.*", want_varref=True, want_memref=False
|
|
)
|
|
self.assertEvaluate("foo_var", "44")
|
|
else:
|
|
self.assertEvaluateFailure("a_function")
|
|
self.assertEvaluateFailure("a_function(1)")
|
|
self.assertEvaluateFailure("list + 1")
|
|
self.assertEvaluateFailure("foo_func")
|
|
self.assertEvaluate("foo_var", "44")
|
|
|
|
# Now we check that values are updated after stepping
|
|
self.continue_to_breakpoint(breakpoint_4)
|
|
if self.isResultExpandedDescription():
|
|
self.assertEvaluate(
|
|
"my_vec",
|
|
r"\(std::vector<int>\) \$\d+ = size=2 {\n \[0\] = 1\n \[1\] = 2\n}",
|
|
want_varref=True,
|
|
)
|
|
elif self.isResultShortDescription():
|
|
self.assertEvaluate(
|
|
"my_vec", r"size=2 {\n \[0\] = 1\n \[1\] = 2\n}", want_varref=True
|
|
)
|
|
else:
|
|
self.assertEvaluate("my_vec", "size=2", want_varref=True)
|
|
self.continue_to_breakpoint(breakpoint_5)
|
|
if self.isResultExpandedDescription():
|
|
self.assertEvaluate(
|
|
"my_vec",
|
|
r"\(std::vector<int>\) \$\d+ = size=3 {\n \[0\] = 1\n \[1\] = 2\n \[2\] = 3\n}",
|
|
want_varref=True,
|
|
)
|
|
elif self.isResultShortDescription():
|
|
self.assertEvaluate(
|
|
"my_vec",
|
|
r"size=3 {\n \[0\] = 1\n \[1\] = 2\n \[2\] = 3\n}",
|
|
want_varref=True,
|
|
)
|
|
else:
|
|
self.assertEvaluate("my_vec", "size=3", want_varref=True)
|
|
|
|
if self.isResultExpandedDescription():
|
|
self.assertEvaluate(
|
|
"my_map",
|
|
r"\(std::map<int, int>\) \$\d+ = size=2 {\n \[0\] = \(first = 1, second = 2\)\n \[1\] = \(first = 2, second = 3\)\n}",
|
|
want_varref=True,
|
|
)
|
|
elif self.isResultShortDescription():
|
|
self.assertEvaluate(
|
|
"my_map",
|
|
r"size=2 {\n \[0\] = \(first = 1, second = 2\)\n \[1\] = \(first = 2, second = 3\)\n}",
|
|
want_varref=True,
|
|
)
|
|
else:
|
|
self.assertEvaluate("my_map", "size=2", want_varref=True)
|
|
self.continue_to_breakpoint(breakpoint_6)
|
|
self.assertEvaluate("my_map", "size=3", want_varref=True)
|
|
|
|
if self.isResultExpandedDescription():
|
|
self.assertEvaluate(
|
|
"my_bool_vec",
|
|
r"\(std::vector<bool>\) \$\d+ = size=1 {\n \[0\] = true\n}",
|
|
want_varref=True,
|
|
)
|
|
elif self.isResultShortDescription():
|
|
self.assertEvaluate(
|
|
"my_bool_vec", r"size=1 {\n \[0\] = true\n}", want_varref=True
|
|
)
|
|
else:
|
|
self.assertEvaluate("my_bool_vec", "size=1", want_varref=True)
|
|
self.continue_to_breakpoint(breakpoint_7)
|
|
if self.isResultExpandedDescription():
|
|
self.assertEvaluate(
|
|
"my_bool_vec",
|
|
r"\(std::vector<bool>\) \$\d+ = size=2 {\n \[0\] = true\n \[1\] = false\n}",
|
|
want_varref=True,
|
|
)
|
|
elif self.isResultShortDescription():
|
|
self.assertEvaluate(
|
|
"my_bool_vec",
|
|
r"size=2 {\n \[0\] = true\n \[1\] = false\n}",
|
|
want_varref=True,
|
|
)
|
|
else:
|
|
self.assertEvaluate("my_bool_vec", "size=2", want_varref=True)
|
|
|
|
self.continue_to_breakpoint(breakpoint_8)
|
|
# Test memory read, especially with 'empty' repeat commands.
|
|
if context == "repl":
|
|
self.assertEvaluate(
|
|
"memory read -c 1 &my_ints", ".* 05 .*\n", want_memref=False
|
|
)
|
|
self.assertEvaluate("", ".* 0a .*\n", want_memref=False)
|
|
self.assertEvaluate("", ".* 0f .*\n", want_memref=False)
|
|
self.assertEvaluate("", ".* 14 .*\n", want_memref=False)
|
|
self.assertEvaluate("", ".* 19 .*\n", want_memref=False)
|
|
|
|
if self.isResultExpandedDescription():
|
|
self.assertEvaluate(
|
|
"my_longs",
|
|
r"\(long\[3\]\) \$\d+ = \(\[0\] = 5, \[1\] = 6, \[2\] = 7\)",
|
|
want_varref=True,
|
|
)
|
|
elif self.isResultShortDescription():
|
|
self.assertEvaluate(
|
|
"my_longs",
|
|
r"\(\[0\] = 5, \[1\] = 6, \[2\] = 7\)",
|
|
want_varref=True,
|
|
)
|
|
else:
|
|
self.assertEvaluate(
|
|
"my_longs",
|
|
"{5, 6, 7}" if enableAutoVariableSummaries else r"long\[3\]",
|
|
want_varref=True,
|
|
)
|
|
|
|
self.continue_to_exit()
|
|
|
|
@skipIfWindows
|
|
def test_generic_evaluate_expressions(self):
|
|
# Tests context-less expression evaluations
|
|
self.run_test_evaluate_expressions(enableAutoVariableSummaries=False)
|
|
|
|
@skipIfWindows
|
|
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
|
|
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
|
|
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
|
|
def test_variable_evaluate_expressions(self):
|
|
# Tests expression evaluations that are triggered in the variable explorer
|
|
self.run_test_evaluate_expressions(
|
|
"variables", enableAutoVariableSummaries=True
|
|
)
|
|
|
|
@skipIfWindows
|
|
def test_clipboard_evaluate_expressions(self):
|
|
# Tests expression evaluations that are triggered when value copied in editor
|
|
self.run_test_evaluate_expressions(
|
|
"clipboard", enableAutoVariableSummaries=False
|
|
)
|