EvaluateRequests handler now uses the target's context if no valid frameId is provided, enabling evaluation of global variables without requiring a valid stack frame. In repl mode it now uses the last `successful` variable or command expression, if the provided user's expression is empty. Try to evaluate the expression if the evaluation context is `Unknown`
501 lines
19 KiB
Python
501 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.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
|
|
)
|