blob: 6c17a9ff336d2a53c97a31a5506f2b220b39971f [file]
"""
Test lldb-dap evaluate request
"""
import re
from typing import Optional, Union
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import line_number
from lldbsuite.test.tools.lldb_dap import DAPTestCaseBase
from lldbsuite.test.tools.lldb_dap.session_helpers import ExpectEval, FrameContext
from lldbsuite.test.tools.lldb_dap.types import EvaluateContext, LaunchArgs, ValueFormat
@skipIfWasm # no expression evaluation
class TestDAP_evaluate(DAPTestCaseBase):
# The frame that `assert_eval*` functions calls evaluate in.
_eval_frame: Optional[FrameContext] = None
def set_evaluation_frame(self, frame: Optional[FrameContext]):
"""Set the frame that `assert_eval*` functions calls evaluate in."""
self._eval_frame = frame
def run_evaluate_expressions(
self,
context: Optional[EvaluateContext] = None,
enableAutoVariableSummaries: bool = False,
):
self.set_evaluation_frame(None)
is_result_expanded = context == "repl"
is_result_brief = context == "clipboard"
is_result_summary = (
not is_result_expanded
and not is_result_brief
and enableAutoVariableSummaries
)
context_parses_expressions = context != "hover"
session = self.build_and_create_session()
def assert_eval(
expression: str, matches: Union[str, re.Pattern], *, as_hex=False, **expects
):
fmt = ValueFormat(hex=True) if as_hex else None
if eval_frame := self._eval_frame:
body = eval_frame.evaluate(expression, context=context, format=fmt)
else:
body = session.evaluate(expression, context=context, format=fmt)
expects.setdefault("has_mem_ref", True)
session.verify_evaluate(body, ExpectEval(matches=matches, **expects))
def assert_eval_fails(expression: str):
frame_id = self._eval_frame.id if self._eval_frame else None
session.do_evaluate(expression, frameId=frame_id, context=context).error(
f"expected {expression!r} to fail using {context=!r} in {frame_id=!r}"
)
def assert_eval_fails_frameless(expression: str):
session.do_evaluate(expression, frameId=None, context=context).error(
f"expected {expression!r} to fail using {context=!r} with no frame"
)
source = "main.cpp"
program = self.getBuildArtifact("a.out")
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"),
]
launch = LaunchArgs(
program, enableAutoVariableSummaries=enableAutoVariableSummaries
)
with session.configure(launch) as cfg:
bp_ids = session.resolve_source_breakpoints(source, breakpoint_lines)
bp1, bp2, bp3, bp4, bp5, bp6, bp7, bp8 = bp_ids
# Expression at breakpoint 1: In main.
stop_event = session.verify_stopped_on_breakpoint(bp1, after=cfg.process_event)
main_frames = session.thread_context_from(stop_event).frames(levels=2)
main_frame, caller_frame = main_frames[0], main_frames[1]
self.set_evaluation_frame(main_frame)
assert_eval("var1", "20", type="int")
# In repl context, an empty expression repeats the previous expression.
if context == "repl":
assert_eval("", "20")
else:
assert_eval_fails("")
assert_eval("var2", "21", type="int")
if context == "repl":
assert_eval("", "21", type="int")
assert_eval("", "21", type="int")
# Verify hex and decimal formatting.
assert_eval("static_int", "0x0000002a", type="int", as_hex=True)
assert_eval("static_int", "42", type="int")
assert_eval("non_static_int", "0x0000002b", type="int", as_hex=True)
assert_eval("non_static_int", "43", type="int")
assert_eval("struct1.foo", "0x0000000f", type="int", as_hex=True)
assert_eval("struct1.foo", "15", type="int")
assert_eval("struct2->foo", "0x00000010", type="int", as_hex=True)
assert_eval("struct2->foo", "16", type="int")
if is_result_expanded:
struct1_match = r"\(my_struct\) (struct1|\$\d+) = \(foo = 15\)"
elif is_result_brief:
struct1_match = r"\(foo = 15\)"
elif is_result_summary:
struct1_match = r"\{foo:15\}"
else:
struct1_match = "my_struct"
assert_eval("struct1", struct1_match, type="my_struct", has_var_ref=True)
if is_result_expanded:
struct2_match = r"\(my_struct \*\) (struct2|\$\d+) = 0x.*"
elif is_result_summary:
struct2_match = r"0x.* \{foo:16\}"
else:
struct2_match = r"0x.*"
assert_eval("struct2", struct2_match, type="my_struct *", has_var_ref=True)
if is_result_expanded:
struct3_match = r"\(my_struct \*\) (struct3|\$\d+) = nullptr"
elif is_result_brief:
struct3_match = "nullptr"
else:
struct3_match = r"0x.*0"
assert_eval("struct3", struct3_match, type="my_struct *", has_var_ref=True)
if context in ("repl", None):
# In repl or unknown context expressions may be interpreted as lldb
# commands since no variables have the same name as the command.
eval_body = main_frame.evaluate("list")
session.verify_evaluate(eval_body, matches=r".*", has_mem_ref=False)
# Changing the frame should not make a difference.
eval_body = caller_frame.evaluate("version")
session.verify_evaluate(eval_body, matches=r".*lldb.+", has_mem_ref=False)
else:
assert_eval_fails("list") # local variable of a_function.
assert_eval_fails("version")
# Identifiers and variables not in scope should fail.
assert_eval_fails("my_struct") # struct name.
assert_eval_fails("int") # type name.
assert_eval_fails("foo") # member variable of my_struct.
if context_parses_expressions:
assert_eval(
"a_function",
r"0x.*a.out`a_function.*",
type="int (*)(int)",
has_var_ref=True,
has_mem_ref=False,
has_loc_ref=True,
)
assert_eval("a_function(1)", "1", type="int", has_mem_ref=False)
assert_eval("var2 + struct1.foo", "36", has_mem_ref=False)
assert_eval(
"foo_func",
r"0x.*a.out`foo_func.*",
type="int (*)()",
has_var_ref=True,
has_mem_ref=False,
has_loc_ref=True,
)
else:
assert_eval_fails("a_function")
assert_eval_fails("a_function(1)")
assert_eval_fails("var2 + struct1.foo")
assert_eval_fails("foo_func")
assert_eval_fails("(float) var2")
# foo_var is a global variable and should evaluate.
assert_eval("foo_var", "44")
# Expressions at breakpoint 2: In an anonymous block.
stop_event = session.continue_to_breakpoint(bp2)
self.set_evaluation_frame(session.top_frame_from(stop_event))
assert_eval("var1", "20")
assert_eval("var2", "2") # shadowed variable.
assert_eval("static_int", "42")
assert_eval("non_static_int", "10") # shadowed variable.
assert_eval("struct1", struct1_match, type="my_struct", has_var_ref=True)
assert_eval("struct1.foo", "15")
assert_eval("struct2->foo", "16")
if context_parses_expressions:
assert_eval(
"a_function",
r"0x.*a.out`a_function.*",
type="int (*)(int)",
has_var_ref=True,
has_mem_ref=False,
has_loc_ref=True,
)
assert_eval("a_function(1)", "1", has_mem_ref=False)
assert_eval("var2 + struct1.foo", "17", has_mem_ref=False)
assert_eval(
"foo_func",
r"0x.*a.out`foo_func.*",
has_var_ref=True,
has_mem_ref=False,
)
else:
assert_eval_fails("a_function")
assert_eval_fails("a_function(1)")
assert_eval_fails("var2 + struct1.foo")
assert_eval_fails("foo_func")
assert_eval("foo_var", "44")
# Expressions at breakpoint 3: In a_function.
stop_event = session.continue_to_breakpoint(bp3)
a_function_frames = session.thread_context_from(stop_event).frames(levels=2)
a_frame, a_function_parent_frame = a_function_frames[0], a_function_frames[1]
self.set_evaluation_frame(a_frame)
assert_eval("list", "42")
assert_eval("static_int", "42")
assert_eval("non_static_int", "43")
# Verify variable from a different frame.
eval_body = a_function_parent_frame.evaluate("var1", context=context)
session.verify_evaluate(eval_body, matches="20")
if context_parses_expressions:
# Access a global variable with no frame at all (frameId omitted).
eval_body = session.evaluate("static_int", context=context)
session.verify_evaluate(eval_body, matches="42", type="int")
assert_eval_fails_frameless("var1") # local, not global.
assert_eval_fails_frameless("totally_bogus_expr_xyz")
if context in ("repl", None):
# `help` shares its name with a real LLDB command (see main.cpp).
# With no frame, the global should win and emit an ambiguity warning.
event_before_help_eval = session.last_event()
eval_body = session.evaluate("help", context=context)
session.verify_evaluate(eval_body, matches="99", type="int")
session.collect_console(
until="Expression 'help' is both an LLDB command and variable",
after=event_before_help_eval,
)
# `script print(...)` output goes to the console only, not the
# evaluate response.
marker = "lldb_dap_test_script_print_marker"
event_before_script_eval = session.last_event()
eval_body = session.evaluate(
f"script print('{marker}')", context=context
)
session.verify_evaluate(eval_body, result="")
session.collect_console(until=marker, after=event_before_script_eval)
# A valid frame should add local lookup without preventing the same
# global from taking precedence over the LLDB command.
eval_body = a_frame.evaluate("help", context=context)
session.verify_evaluate(eval_body, matches="99", type="int")
# Use a uniquely named alias with deterministic output to verify that a
# command with no matching variable is still run as an LLDB command.
session.evaluate(
"command alias lldb_dap_test_command expression -- 12345",
context=context,
)
eval_body = session.evaluate("lldb_dap_test_command", context=context)
session.verify_evaluate(eval_body, matches="12345", has_mem_ref=False)
# In a_function's own frame these names are out of scope.
assert_eval_fails("var1")
assert_eval_fails("var2")
assert_eval_fails("struct1")
assert_eval_fails("struct1.foo")
assert_eval_fails("struct2->foo")
assert_eval_fails("var2 + struct1.foo")
if context_parses_expressions:
assert_eval(
"a_function",
r"0x.*a.out`a_function.*",
has_var_ref=True,
has_mem_ref=False,
has_loc_ref=True,
)
assert_eval("a_function(1)", "1", has_mem_ref=False)
assert_eval("list + 1", "43", has_mem_ref=False)
assert_eval(
"foo_func",
r"0x.*a.out`foo_func.*",
has_var_ref=True,
has_mem_ref=False,
)
else:
assert_eval_fails("a_function")
assert_eval_fails("a_function(1)")
assert_eval_fails("list + 1")
assert_eval_fails("foo_func")
assert_eval("foo_var", "44")
# Expressions at breakpoints 4-7.
# Now we check that values are updated after stepping.
stop_event = session.continue_to_breakpoint(bp4)
self.set_evaluation_frame(session.top_frame_from(stop_event))
if is_result_expanded:
my_vec_match = (
r"\(std::vector<int>\) \$\d+ = size=2 \{\n \[0\] = 1\n \[1\] = 2\n\}"
)
elif is_result_brief:
my_vec_match = r"size=2 \{\n \[0\] = 1\n \[1\] = 2\n\}"
else:
my_vec_match = "size=2"
assert_eval("my_vec", my_vec_match, has_var_ref=True)
stop_event = session.continue_to_breakpoint(bp5)
self.set_evaluation_frame(session.top_frame_from(stop_event))
if is_result_expanded:
my_vec_match = r"\(std::vector<int>\) \$\d+ = size=3 \{\n \[0\] = 1\n \[1\] = 2\n \[2\] = 3\n\}"
elif is_result_brief:
my_vec_match = r"size=3 \{\n \[0\] = 1\n \[1\] = 2\n \[2\] = 3\n\}"
else:
my_vec_match = "size=3"
assert_eval("my_vec", my_vec_match, has_var_ref=True)
if is_result_expanded:
my_map_match = r"\(std::map<int, int>\) \$\d+ = size=2 \{\n \[0\] = \(first = 1, second = 2\)\n \[1\] = \(first = 2, second = 3\)\n\}"
elif is_result_brief:
my_map_match = r"size=2 \{\n \[0\] = \(first = 1, second = 2\)\n \[1\] = \(first = 2, second = 3\)\n\}"
else:
my_map_match = "size=2"
assert_eval("my_map", my_map_match, has_var_ref=True)
stop_event = session.continue_to_breakpoint(bp6)
self.set_evaluation_frame(session.top_frame_from(stop_event))
assert_eval("my_map", "size=3", has_var_ref=True)
if is_result_expanded:
my_bool_match = r"\(std::vector<bool>\) \$\d+ = size=1 {\n \[0\] = true\n}"
elif is_result_brief:
my_bool_match = r"size=1 \{\n \[0\] = true\n\}"
else:
my_bool_match = "size=1"
assert_eval("my_bool_vec", my_bool_match, has_var_ref=True)
stop_event = session.continue_to_breakpoint(bp7)
self.set_evaluation_frame(session.top_frame_from(stop_event))
if is_result_expanded:
my_bool_match = r"\(std::vector<bool>\) \$\d+ = size=2 \{\n \[0\] = true\n \[1\] = false\n\}"
elif is_result_brief:
my_bool_match = r"size=2 \{\n \[0\] = true\n \[1\] = false\n\}"
else:
my_bool_match = "size=2"
assert_eval("my_bool_vec", my_bool_match, has_var_ref=True)
# Expressions at breakpoint 8.
stop_event = session.continue_to_breakpoint(bp8)
self.set_evaluation_frame(session.top_frame_from(stop_event))
if context == "repl":
# In repl, empty expressions repeat the previous lldb command,
# so each call reads the next byte of my_ints.
assert_eval("memory read -c 1 &my_ints", r".* 05 .*\n", has_mem_ref=False)
assert_eval("", r".* 0a .*\n", has_mem_ref=False)
assert_eval("", r".* 0f .*\n", has_mem_ref=False)
assert_eval("", r".* 14 .*\n", has_mem_ref=False)
assert_eval("", r".* 19 .*\n", has_mem_ref=False)
if is_result_expanded:
my_longs_match = (
r"\(long\[3\]\) \$\d+ = \(\[0\] = 5, \[1\] = 6, \[2\] = 7\)"
)
elif is_result_brief:
my_longs_match = r"\(\[0\] = 5, \[1\] = 6, \[2\] = 7\)"
elif is_result_summary:
my_longs_match = r"\{5, 6, 7\}"
else:
my_longs_match = r"long\[3\]"
assert_eval("my_longs", my_longs_match, has_var_ref=True)
session.continue_to_exit()
@skipIfWindows
def test_generic_evaluate_expressions(self):
# Tests context-less expression evaluations.
self.run_evaluate_expressions(enableAutoVariableSummaries=False)
@skipIfWindows
def test_repl_evaluate_expressions(self):
# Tests expression evaluations that are triggered from the Debug Console.
self.run_evaluate_expressions("repl", enableAutoVariableSummaries=False)
@skipIfWindows
def test_watch_evaluate_expressions(self):
# Tests expression evaluations that are triggered from a watch expression.
self.run_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_evaluate_expressions("hover", enableAutoVariableSummaries=False)
@skipIfWindows
def test_variable_evaluate_expressions(self):
# Tests expression evaluations that are triggered in the variable explorer.
self.run_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_evaluate_expressions("clipboard", enableAutoVariableSummaries=False)