This patch relands https://github.com/llvm/llvm-project/pull/70639 It was reverted because under certain conditions we triggered an assertion in `DIBuilder`. Specifically, in the original patch we called `EmitGlobalVariable` at the end of `CGDebugInfo::finalize`, after all the temporary `DIType`s have been uniqued. With limited debug-info such temporary nodes would be created more frequently, leaving us with non-uniqued nodes by the time we got to `DIBuilder::finalize`; this violated its pre-condition and caused assertions to trigger. To fix this, the latest iteration of the patch moves `EmitGlobalVariable` to the beginning of `CGDebugInfo::finalize`. Now, when we create a temporary `DIType` node as a result of emitting a variable definition, it will get uniqued in time. A test-case was added for this scenario. We also now don't emit a linkage name for non-locationed constants since LLDB doesn't make use of it anyway. Original commit message: """ When an LLDB user asks for the value of a static data member, LLDB starts by searching the Names accelerator table for the corresponding variable definition DIE. For static data members with out-of-class definitions that works fine, because those get represented as global variables with a location and making them eligible to be added to the Names table. However, in-class definitions won’t get indexed because we usually don't emit global variables for them. So in DWARF we end up with a single `DW_TAG_member` that usually holds the constant initializer. But we don't get a corresponding CU-level `DW_TAG_variable` like we do for out-of-class definitions. To make it more convenient for debuggers to get to the value of inline static data members, this patch makes sure we emit definitions for static variables with constant initializers the same way we do for other static variables. This also aligns Clang closer to GCC, which produces CU-level definitions for inline statics and also emits these into `.debug_pubnames`. The implementation keeps track of newly created static data members. Then in `CGDebugInfo::finalize`, we emit a global `DW_TAG_variable` with a `DW_AT_const_value` for any of those declarations that didn't end up with a definition in the `DeclCache`. The newly emitted `DW_TAG_variable` will look as follows: ``` 0x0000007b: DW_TAG_structure_type DW_AT_calling_convention (DW_CC_pass_by_value) DW_AT_name ("Foo") ... 0x0000008d: DW_TAG_member DW_AT_name ("i") DW_AT_type (0x00000062 "const int") DW_AT_external (true) DW_AT_declaration (true) DW_AT_const_value (4) Newly added vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv 0x0000009a: DW_TAG_variable DW_AT_specification (0x0000008d "i") DW_AT_const_value (4) DW_AT_linkage_name ("_ZN2t2IiE1iIfEE") ``` This patch also drops the `DW_AT_const_value` off of the declaration since we now always have it on the definition. This ensures that the `DWARFParallelLinker` can type-merge class with static members where we couldn't attach the constant on the declaration in some CUs. """ Dependent changes: * https://github.com/llvm/llvm-project/pull/71800
662 lines
27 KiB
Python
662 lines
27 KiB
Python
"""
|
|
Test lldb-dap setBreakpoints request
|
|
"""
|
|
|
|
import os
|
|
|
|
import lldbdap_testcase
|
|
import dap_server
|
|
from lldbsuite.test import lldbutil
|
|
from lldbsuite.test.decorators import *
|
|
from lldbsuite.test.lldbtest import *
|
|
|
|
|
|
def make_buffer_verify_dict(start_idx, count, offset=0):
|
|
verify_dict = {}
|
|
for i in range(start_idx, start_idx + count):
|
|
verify_dict["[%i]" % (i)] = {"type": "int", "value": str(i + offset)}
|
|
return verify_dict
|
|
|
|
|
|
class TestDAP_variables(lldbdap_testcase.DAPTestCaseBase):
|
|
def verify_values(self, verify_dict, actual, varref_dict=None, expression=None):
|
|
if "equals" in verify_dict:
|
|
verify = verify_dict["equals"]
|
|
for key in verify:
|
|
verify_value = verify[key]
|
|
actual_value = actual[key]
|
|
self.assertEqual(
|
|
verify_value,
|
|
actual_value,
|
|
'"%s" keys don\'t match (%s != %s) from:\n%s'
|
|
% (key, actual_value, verify_value, actual),
|
|
)
|
|
if "startswith" in verify_dict:
|
|
verify = verify_dict["startswith"]
|
|
for key in verify:
|
|
verify_value = verify[key]
|
|
actual_value = actual[key]
|
|
startswith = actual_value.startswith(verify_value)
|
|
self.assertTrue(
|
|
startswith,
|
|
('"%s" value "%s" doesn\'t start with' ' "%s")')
|
|
% (key, actual_value, verify_value),
|
|
)
|
|
if "contains" in verify_dict:
|
|
verify = verify_dict["contains"]
|
|
for key in verify:
|
|
contains_array = verify[key]
|
|
actual_value = actual[key]
|
|
self.assertTrue(isinstance(contains_array, list))
|
|
for verify_value in contains_array:
|
|
self.assertIn(verify_value, actual_value)
|
|
if "missing" in verify_dict:
|
|
missing = verify_dict["missing"]
|
|
for key in missing:
|
|
self.assertTrue(
|
|
key not in actual, 'key "%s" is not expected in %s' % (key, actual)
|
|
)
|
|
hasVariablesReference = "variablesReference" in actual
|
|
varRef = None
|
|
if hasVariablesReference:
|
|
# Remember variable references in case we want to test further
|
|
# by using the evaluate name.
|
|
varRef = actual["variablesReference"]
|
|
if varRef != 0 and varref_dict is not None:
|
|
if expression is None:
|
|
evaluateName = actual["evaluateName"]
|
|
else:
|
|
evaluateName = expression
|
|
varref_dict[evaluateName] = varRef
|
|
if (
|
|
"hasVariablesReference" in verify_dict
|
|
and verify_dict["hasVariablesReference"]
|
|
):
|
|
self.assertTrue(hasVariablesReference, "verify variable reference")
|
|
if "children" in verify_dict:
|
|
self.assertTrue(
|
|
hasVariablesReference and varRef is not None and varRef != 0,
|
|
("children verify values specified for " "variable without children"),
|
|
)
|
|
|
|
response = self.dap_server.request_variables(varRef)
|
|
self.verify_variables(
|
|
verify_dict["children"], response["body"]["variables"], varref_dict
|
|
)
|
|
|
|
def verify_variables(self, verify_dict, variables, varref_dict=None):
|
|
for variable in variables:
|
|
name = variable["name"]
|
|
if not name.startswith("std::"):
|
|
self.assertIn(
|
|
name, verify_dict, 'variable "%s" in verify dictionary' % (name)
|
|
)
|
|
self.verify_values(verify_dict[name], variable, varref_dict)
|
|
|
|
def darwin_dwarf_missing_obj(self, initCommands):
|
|
self.build(debug_info="dwarf")
|
|
program = self.getBuildArtifact("a.out")
|
|
main_obj = self.getBuildArtifact("main.o")
|
|
self.assertTrue(os.path.exists(main_obj))
|
|
# Delete the main.o file that contains the debug info so we force an
|
|
# error when we run to main and try to get variables
|
|
os.unlink(main_obj)
|
|
|
|
self.create_debug_adaptor()
|
|
self.assertTrue(os.path.exists(program), "executable must exist")
|
|
|
|
self.launch(program=program, initCommands=initCommands)
|
|
|
|
functions = ["main"]
|
|
breakpoint_ids = self.set_function_breakpoints(functions)
|
|
self.assertEquals(len(breakpoint_ids), len(functions), "expect one breakpoint")
|
|
self.continue_to_breakpoints(breakpoint_ids)
|
|
|
|
locals = self.dap_server.get_local_variables()
|
|
|
|
verify_locals = {
|
|
"<error>": {
|
|
"equals": {"type": "const char *"},
|
|
"contains": {
|
|
"value": [
|
|
"debug map object file ",
|
|
'main.o" containing debug info does not exist, debug info will not be loaded',
|
|
]
|
|
},
|
|
},
|
|
}
|
|
varref_dict = {}
|
|
self.verify_variables(verify_locals, locals, varref_dict)
|
|
|
|
def do_test_scopes_variables_setVariable_evaluate(
|
|
self, enableAutoVariableSummaries: bool
|
|
):
|
|
"""
|
|
Tests the "scopes", "variables", "setVariable", and "evaluate"
|
|
packets.
|
|
"""
|
|
program = self.getBuildArtifact("a.out")
|
|
self.build_and_launch(
|
|
program, enableAutoVariableSummaries=enableAutoVariableSummaries
|
|
)
|
|
source = "main.cpp"
|
|
breakpoint1_line = line_number(source, "// breakpoint 1")
|
|
lines = [breakpoint1_line]
|
|
# Set breakpoint in the thread function so we can step the threads
|
|
breakpoint_ids = self.set_source_breakpoints(source, lines)
|
|
self.assertEqual(
|
|
len(breakpoint_ids), len(lines), "expect correct number of breakpoints"
|
|
)
|
|
self.continue_to_breakpoints(breakpoint_ids)
|
|
locals = self.dap_server.get_local_variables()
|
|
globals = self.dap_server.get_global_variables()
|
|
buffer_children = make_buffer_verify_dict(0, 32)
|
|
verify_locals = {
|
|
"argc": {"equals": {"type": "int", "value": "1"}},
|
|
"argv": {
|
|
"equals": {"type": "const char **"},
|
|
"startswith": {"value": "0x"},
|
|
"hasVariablesReference": True,
|
|
},
|
|
"pt": {
|
|
"equals": {"type": "PointType"},
|
|
"hasVariablesReference": True,
|
|
"children": {
|
|
"x": {"equals": {"type": "int", "value": "11"}},
|
|
"y": {"equals": {"type": "int", "value": "22"}},
|
|
"buffer": {"children": buffer_children},
|
|
},
|
|
},
|
|
"x": {"equals": {"type": "int"}},
|
|
}
|
|
verify_globals = {
|
|
"s_local": {"equals": {"type": "float", "value": "2.25"}},
|
|
"::g_global": {"equals": {"type": "int", "value": "123"}},
|
|
"s_global": {"equals": {"type": "int", "value": "234"}},
|
|
}
|
|
varref_dict = {}
|
|
self.verify_variables(verify_locals, locals, varref_dict)
|
|
self.verify_variables(verify_globals, globals, varref_dict)
|
|
# pprint.PrettyPrinter(indent=4).pprint(varref_dict)
|
|
# We need to test the functionality of the "variables" request as it
|
|
# has optional parameters like "start" and "count" to limit the number
|
|
# of variables that are fetched
|
|
varRef = varref_dict["pt.buffer"]
|
|
response = self.dap_server.request_variables(varRef)
|
|
self.verify_variables(buffer_children, response["body"]["variables"])
|
|
# Verify setting start=0 in the arguments still gets all children
|
|
response = self.dap_server.request_variables(varRef, start=0)
|
|
self.verify_variables(buffer_children, response["body"]["variables"])
|
|
# Verify setting count=0 in the arguments still gets all children.
|
|
# If count is zero, it means to get all children.
|
|
response = self.dap_server.request_variables(varRef, count=0)
|
|
self.verify_variables(buffer_children, response["body"]["variables"])
|
|
# Verify setting count to a value that is too large in the arguments
|
|
# still gets all children, and no more
|
|
response = self.dap_server.request_variables(varRef, count=1000)
|
|
self.verify_variables(buffer_children, response["body"]["variables"])
|
|
# Verify setting the start index and count gets only the children we
|
|
# want
|
|
response = self.dap_server.request_variables(varRef, start=5, count=5)
|
|
self.verify_variables(
|
|
make_buffer_verify_dict(5, 5), response["body"]["variables"]
|
|
)
|
|
# Verify setting the start index to a value that is out of range
|
|
# results in an empty list
|
|
response = self.dap_server.request_variables(varRef, start=32, count=1)
|
|
self.assertEqual(
|
|
len(response["body"]["variables"]),
|
|
0,
|
|
"verify we get no variable back for invalid start",
|
|
)
|
|
|
|
# Test evaluate
|
|
expressions = {
|
|
"pt.x": {
|
|
"equals": {"result": "11", "type": "int"},
|
|
"hasVariablesReference": False,
|
|
},
|
|
"pt.buffer[2]": {
|
|
"equals": {"result": "2", "type": "int"},
|
|
"hasVariablesReference": False,
|
|
},
|
|
"pt": {
|
|
"equals": {"type": "PointType"},
|
|
"startswith": {
|
|
"result": "{x:11, y:22}"
|
|
if enableAutoVariableSummaries
|
|
else "PointType @ 0x"
|
|
},
|
|
"hasVariablesReference": True,
|
|
},
|
|
"pt.buffer": {
|
|
"equals": {"type": "int[32]"},
|
|
"startswith": {
|
|
"result": "{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...}"
|
|
if enableAutoVariableSummaries
|
|
else "int[32] @ 0x"
|
|
},
|
|
"hasVariablesReference": True,
|
|
},
|
|
"argv": {
|
|
"equals": {"type": "const char **"},
|
|
"startswith": {"result": "0x"},
|
|
"hasVariablesReference": True,
|
|
},
|
|
"argv[0]": {
|
|
"equals": {"type": "const char *"},
|
|
"startswith": {"result": "0x"},
|
|
"hasVariablesReference": True,
|
|
},
|
|
"2+3": {
|
|
"equals": {"result": "5", "type": "int"},
|
|
"hasVariablesReference": False,
|
|
},
|
|
}
|
|
for expression in expressions:
|
|
response = self.dap_server.request_evaluate(expression)
|
|
self.verify_values(expressions[expression], response["body"])
|
|
|
|
# Test setting variables
|
|
self.set_local("argc", 123)
|
|
argc = self.get_local_as_int("argc")
|
|
self.assertEqual(argc, 123, "verify argc was set to 123 (123 != %i)" % (argc))
|
|
|
|
self.set_local("argv", 0x1234)
|
|
argv = self.get_local_as_int("argv")
|
|
self.assertEqual(
|
|
argv, 0x1234, "verify argv was set to 0x1234 (0x1234 != %#x)" % (argv)
|
|
)
|
|
|
|
# Set a variable value whose name is synthetic, like a variable index
|
|
# and verify the value by reading it
|
|
self.dap_server.request_setVariable(varRef, "[0]", 100)
|
|
response = self.dap_server.request_variables(varRef, start=0, count=1)
|
|
self.verify_variables(
|
|
make_buffer_verify_dict(0, 1, 100), response["body"]["variables"]
|
|
)
|
|
|
|
# Set a variable value whose name is a real child value, like "pt.x"
|
|
# and verify the value by reading it
|
|
varRef = varref_dict["pt"]
|
|
self.dap_server.request_setVariable(varRef, "x", 111)
|
|
response = self.dap_server.request_variables(varRef, start=0, count=1)
|
|
value = response["body"]["variables"][0]["value"]
|
|
self.assertEqual(
|
|
value, "111", "verify pt.x got set to 111 (111 != %s)" % (value)
|
|
)
|
|
|
|
# We check shadowed variables and that a new get_local_variables request
|
|
# gets the right data
|
|
breakpoint2_line = line_number(source, "// breakpoint 2")
|
|
lines = [breakpoint2_line]
|
|
breakpoint_ids = self.set_source_breakpoints(source, lines)
|
|
self.assertEqual(
|
|
len(breakpoint_ids), len(lines), "expect correct number of breakpoints"
|
|
)
|
|
self.continue_to_breakpoints(breakpoint_ids)
|
|
|
|
verify_locals["argc"]["equals"]["value"] = "123"
|
|
verify_locals["pt"]["children"]["x"]["equals"]["value"] = "111"
|
|
verify_locals["x @ main.cpp:17"] = {"equals": {"type": "int", "value": "89"}}
|
|
verify_locals["x @ main.cpp:19"] = {"equals": {"type": "int", "value": "42"}}
|
|
verify_locals["x @ main.cpp:21"] = {"equals": {"type": "int", "value": "72"}}
|
|
|
|
self.verify_variables(verify_locals, self.dap_server.get_local_variables())
|
|
|
|
# Now we verify that we correctly change the name of a variable with and without differentiator suffix
|
|
self.assertFalse(self.dap_server.request_setVariable(1, "x2", 9)["success"])
|
|
self.assertFalse(
|
|
self.dap_server.request_setVariable(1, "x @ main.cpp:0", 9)["success"]
|
|
)
|
|
|
|
self.assertTrue(
|
|
self.dap_server.request_setVariable(1, "x @ main.cpp:17", 17)["success"]
|
|
)
|
|
self.assertTrue(
|
|
self.dap_server.request_setVariable(1, "x @ main.cpp:19", 19)["success"]
|
|
)
|
|
self.assertTrue(
|
|
self.dap_server.request_setVariable(1, "x @ main.cpp:21", 21)["success"]
|
|
)
|
|
|
|
# The following should have no effect
|
|
self.assertFalse(
|
|
self.dap_server.request_setVariable(1, "x @ main.cpp:21", "invalid")[
|
|
"success"
|
|
]
|
|
)
|
|
|
|
verify_locals["x @ main.cpp:17"]["equals"]["value"] = "17"
|
|
verify_locals["x @ main.cpp:19"]["equals"]["value"] = "19"
|
|
verify_locals["x @ main.cpp:21"]["equals"]["value"] = "21"
|
|
|
|
self.verify_variables(verify_locals, self.dap_server.get_local_variables())
|
|
|
|
# The plain x variable shold refer to the innermost x
|
|
self.assertTrue(self.dap_server.request_setVariable(1, "x", 22)["success"])
|
|
verify_locals["x @ main.cpp:21"]["equals"]["value"] = "22"
|
|
|
|
self.verify_variables(verify_locals, self.dap_server.get_local_variables())
|
|
|
|
# In breakpoint 3, there should be no shadowed variables
|
|
breakpoint3_line = line_number(source, "// breakpoint 3")
|
|
lines = [breakpoint3_line]
|
|
breakpoint_ids = self.set_source_breakpoints(source, lines)
|
|
self.assertEqual(
|
|
len(breakpoint_ids), len(lines), "expect correct number of breakpoints"
|
|
)
|
|
self.continue_to_breakpoints(breakpoint_ids)
|
|
|
|
locals = self.dap_server.get_local_variables()
|
|
names = [var["name"] for var in locals]
|
|
# The first shadowed x shouldn't have a suffix anymore
|
|
verify_locals["x"] = {"equals": {"type": "int", "value": "17"}}
|
|
self.assertNotIn("x @ main.cpp:17", names)
|
|
self.assertNotIn("x @ main.cpp:19", names)
|
|
self.assertNotIn("x @ main.cpp:21", names)
|
|
|
|
self.verify_variables(verify_locals, locals)
|
|
|
|
@skipIfWindows
|
|
@skipIfRemote
|
|
def test_scopes_variables_setVariable_evaluate(self):
|
|
self.do_test_scopes_variables_setVariable_evaluate(
|
|
enableAutoVariableSummaries=False
|
|
)
|
|
|
|
@skipIfWindows
|
|
@skipIfRemote
|
|
def test_scopes_variables_setVariable_evaluate_with_descriptive_summaries(self):
|
|
self.do_test_scopes_variables_setVariable_evaluate(
|
|
enableAutoVariableSummaries=True
|
|
)
|
|
|
|
def do_test_scopes_and_evaluate_expansion(self, enableAutoVariableSummaries: bool):
|
|
"""
|
|
Tests the evaluated expression expands successfully after "scopes" packets
|
|
and permanent expressions persist.
|
|
"""
|
|
program = self.getBuildArtifact("a.out")
|
|
self.build_and_launch(
|
|
program, enableAutoVariableSummaries=enableAutoVariableSummaries
|
|
)
|
|
source = "main.cpp"
|
|
breakpoint1_line = line_number(source, "// breakpoint 1")
|
|
lines = [breakpoint1_line]
|
|
# Set breakpoint in the thread function so we can step the threads
|
|
breakpoint_ids = self.set_source_breakpoints(source, lines)
|
|
self.assertEqual(
|
|
len(breakpoint_ids), len(lines), "expect correct number of breakpoints"
|
|
)
|
|
self.continue_to_breakpoints(breakpoint_ids)
|
|
|
|
# Verify locals
|
|
locals = self.dap_server.get_local_variables()
|
|
buffer_children = make_buffer_verify_dict(0, 32)
|
|
verify_locals = {
|
|
"argc": {
|
|
"equals": {"type": "int", "value": "1"},
|
|
"missing": ["indexedVariables"],
|
|
},
|
|
"argv": {
|
|
"equals": {"type": "const char **"},
|
|
"startswith": {"value": "0x"},
|
|
"hasVariablesReference": True,
|
|
"missing": ["indexedVariables"],
|
|
},
|
|
"pt": {
|
|
"equals": {"type": "PointType"},
|
|
"hasVariablesReference": True,
|
|
"missing": ["indexedVariables"],
|
|
"children": {
|
|
"x": {
|
|
"equals": {"type": "int", "value": "11"},
|
|
"missing": ["indexedVariables"],
|
|
},
|
|
"y": {
|
|
"equals": {"type": "int", "value": "22"},
|
|
"missing": ["indexedVariables"],
|
|
},
|
|
"buffer": {
|
|
"children": buffer_children,
|
|
"equals": {"indexedVariables": 32},
|
|
},
|
|
},
|
|
},
|
|
"x": {
|
|
"equals": {"type": "int"},
|
|
"missing": ["indexedVariables"],
|
|
},
|
|
}
|
|
self.verify_variables(verify_locals, locals)
|
|
|
|
# Evaluate expandable expression twice: once permanent (from repl)
|
|
# the other temporary (from other UI).
|
|
expandable_expression = {
|
|
"name": "pt",
|
|
"response": {
|
|
"equals": {"type": "PointType"},
|
|
"startswith": {
|
|
"result": "{x:11, y:22}"
|
|
if enableAutoVariableSummaries
|
|
else "PointType @ 0x"
|
|
},
|
|
"missing": ["indexedVariables"],
|
|
"hasVariablesReference": True,
|
|
},
|
|
"children": {
|
|
"x": {"equals": {"type": "int", "value": "11"}},
|
|
"y": {"equals": {"type": "int", "value": "22"}},
|
|
"buffer": {"children": buffer_children},
|
|
},
|
|
}
|
|
|
|
# Evaluate from permanent UI.
|
|
permanent_expr_varref_dict = {}
|
|
response = self.dap_server.request_evaluate(
|
|
expandable_expression["name"], frameIndex=0, threadId=None, context="repl"
|
|
)
|
|
self.verify_values(
|
|
expandable_expression["response"],
|
|
response["body"],
|
|
permanent_expr_varref_dict,
|
|
expandable_expression["name"],
|
|
)
|
|
|
|
# Evaluate from temporary UI.
|
|
temporary_expr_varref_dict = {}
|
|
response = self.dap_server.request_evaluate(expandable_expression["name"])
|
|
self.verify_values(
|
|
expandable_expression["response"],
|
|
response["body"],
|
|
temporary_expr_varref_dict,
|
|
expandable_expression["name"],
|
|
)
|
|
|
|
# Evaluate locals again.
|
|
locals = self.dap_server.get_local_variables()
|
|
self.verify_variables(verify_locals, locals)
|
|
|
|
# Verify the evaluated expressions before second locals evaluation
|
|
# can be expanded.
|
|
var_ref = temporary_expr_varref_dict[expandable_expression["name"]]
|
|
response = self.dap_server.request_variables(var_ref)
|
|
self.verify_variables(
|
|
expandable_expression["children"], response["body"]["variables"]
|
|
)
|
|
|
|
# Continue to breakpoint 3, permanent variable should still exist
|
|
# after resume.
|
|
breakpoint3_line = line_number(source, "// breakpoint 3")
|
|
lines = [breakpoint3_line]
|
|
breakpoint_ids = self.set_source_breakpoints(source, lines)
|
|
self.assertEqual(
|
|
len(breakpoint_ids), len(lines), "expect correct number of breakpoints"
|
|
)
|
|
self.continue_to_breakpoints(breakpoint_ids)
|
|
|
|
var_ref = permanent_expr_varref_dict[expandable_expression["name"]]
|
|
response = self.dap_server.request_variables(var_ref)
|
|
self.verify_variables(
|
|
expandable_expression["children"], response["body"]["variables"]
|
|
)
|
|
|
|
# Test that frame scopes have corresponding presentation hints.
|
|
frame_id = self.dap_server.get_stackFrame()["id"]
|
|
scopes = self.dap_server.request_scopes(frame_id)["body"]["scopes"]
|
|
|
|
scope_names = [scope["name"] for scope in scopes]
|
|
self.assertIn("Locals", scope_names)
|
|
self.assertIn("Registers", scope_names)
|
|
|
|
for scope in scopes:
|
|
if scope["name"] == "Locals":
|
|
self.assertEquals(scope.get("presentationHint"), "locals")
|
|
if scope["name"] == "Registers":
|
|
self.assertEquals(scope.get("presentationHint"), "registers")
|
|
|
|
@skipIfWindows
|
|
@skipIfRemote
|
|
def test_scopes_and_evaluate_expansion(self):
|
|
self.do_test_scopes_and_evaluate_expansion(enableAutoVariableSummaries=False)
|
|
|
|
@skipIfWindows
|
|
@skipIfRemote
|
|
def test_scopes_and_evaluate_expansion_with_descriptive_summaries(self):
|
|
self.do_test_scopes_and_evaluate_expansion(enableAutoVariableSummaries=True)
|
|
|
|
def do_test_indexedVariables(self, enableSyntheticChildDebugging: bool):
|
|
"""
|
|
Tests that arrays and lldb.SBValue objects that have synthetic child
|
|
providers have "indexedVariables" key/value pairs. This helps the IDE
|
|
not to fetch too many children all at once.
|
|
"""
|
|
program = self.getBuildArtifact("a.out")
|
|
self.build_and_launch(
|
|
program, enableSyntheticChildDebugging=enableSyntheticChildDebugging
|
|
)
|
|
source = "main.cpp"
|
|
breakpoint1_line = line_number(source, "// breakpoint 4")
|
|
lines = [breakpoint1_line]
|
|
# Set breakpoint in the thread function so we can step the threads
|
|
breakpoint_ids = self.set_source_breakpoints(source, lines)
|
|
self.assertEqual(
|
|
len(breakpoint_ids), len(lines), "expect correct number of breakpoints"
|
|
)
|
|
self.continue_to_breakpoints(breakpoint_ids)
|
|
|
|
# Verify locals
|
|
locals = self.dap_server.get_local_variables()
|
|
# The vector variables might have one additional entry from the fake
|
|
# "[raw]" child.
|
|
raw_child_count = 1 if enableSyntheticChildDebugging else 0
|
|
verify_locals = {
|
|
"small_array": {"equals": {"indexedVariables": 5}},
|
|
"large_array": {"equals": {"indexedVariables": 200}},
|
|
"small_vector": {"equals": {"indexedVariables": 5 + raw_child_count}},
|
|
"large_vector": {"equals": {"indexedVariables": 200 + raw_child_count}},
|
|
"pt": {"missing": ["indexedVariables"]},
|
|
}
|
|
self.verify_variables(verify_locals, locals)
|
|
|
|
# We also verify that we produce a "[raw]" fake child with the real
|
|
# SBValue for the synthetic type.
|
|
verify_children = {
|
|
"[0]": {"equals": {"type": "int", "value": "0"}},
|
|
"[1]": {"equals": {"type": "int", "value": "0"}},
|
|
"[2]": {"equals": {"type": "int", "value": "0"}},
|
|
"[3]": {"equals": {"type": "int", "value": "0"}},
|
|
"[4]": {"equals": {"type": "int", "value": "0"}},
|
|
}
|
|
if enableSyntheticChildDebugging:
|
|
verify_children["[raw]"] = ({"contains": {"type": ["vector"]}},)
|
|
|
|
children = self.dap_server.request_variables(locals[2]["variablesReference"])[
|
|
"body"
|
|
]["variables"]
|
|
self.verify_variables(verify_children, children)
|
|
|
|
@skipIfWindows
|
|
@skipIfRemote
|
|
def test_indexedVariables(self):
|
|
self.do_test_indexedVariables(enableSyntheticChildDebugging=False)
|
|
|
|
@skipIfWindows
|
|
@skipIfRemote
|
|
def test_indexedVariables_with_raw_child_for_synthetics(self):
|
|
self.do_test_indexedVariables(enableSyntheticChildDebugging=True)
|
|
|
|
@skipIfWindows
|
|
@skipIfRemote
|
|
def test_registers(self):
|
|
"""
|
|
Test that registers whose byte size is the size of a pointer on
|
|
the current system get formatted as lldb::eFormatAddressInfo. This
|
|
will show the pointer value followed by a description of the address
|
|
itself. To test this we attempt to find the PC value in the general
|
|
purpose registers, and since we will be stopped in main.cpp, verify
|
|
that the value for the PC starts with a pointer and is followed by
|
|
a description that contains main.cpp.
|
|
"""
|
|
program = self.getBuildArtifact("a.out")
|
|
self.build_and_launch(program)
|
|
source = "main.cpp"
|
|
breakpoint1_line = line_number(source, "// breakpoint 1")
|
|
lines = [breakpoint1_line]
|
|
# Set breakpoint in the thread function so we can step the threads
|
|
breakpoint_ids = self.set_source_breakpoints(source, lines)
|
|
self.assertEqual(
|
|
len(breakpoint_ids), len(lines), "expect correct number of breakpoints"
|
|
)
|
|
self.continue_to_breakpoints(breakpoint_ids)
|
|
|
|
pc_name = None
|
|
arch = self.getArchitecture()
|
|
if arch == "x86_64":
|
|
pc_name = "rip"
|
|
elif arch == "x86":
|
|
pc_name = "rip"
|
|
elif arch.startswith("arm"):
|
|
pc_name = "pc"
|
|
|
|
if pc_name is None:
|
|
return
|
|
# Verify locals
|
|
reg_sets = self.dap_server.get_registers()
|
|
for reg_set in reg_sets:
|
|
if reg_set["name"] == "General Purpose Registers":
|
|
varRef = reg_set["variablesReference"]
|
|
regs = self.dap_server.request_variables(varRef)["body"]["variables"]
|
|
for reg in regs:
|
|
if reg["name"] == pc_name:
|
|
value = reg["value"]
|
|
self.assertTrue(value.startswith("0x"))
|
|
self.assertTrue("a.out`main + " in value)
|
|
self.assertTrue("at main.cpp:" in value)
|
|
|
|
@no_debug_info_test
|
|
@skipUnlessDarwin
|
|
def test_darwin_dwarf_missing_obj(self):
|
|
"""
|
|
Test that if we build a binary with DWARF in .o files and we remove
|
|
the .o file for main.cpp, that we get a variable named "<error>"
|
|
whose value matches the appriopriate error. Errors when getting
|
|
variables are returned in the LLDB API when the user should be
|
|
notified of issues that can easily be solved by rebuilding or
|
|
changing compiler options and are designed to give better feedback
|
|
to the user.
|
|
"""
|
|
self.darwin_dwarf_missing_obj(None)
|
|
|
|
@no_debug_info_test
|
|
@skipUnlessDarwin
|
|
def test_darwin_dwarf_missing_obj_with_symbol_ondemand_enabled(self):
|
|
"""
|
|
Test that if we build a binary with DWARF in .o files and we remove
|
|
the .o file for main.cpp, that we get a variable named "<error>"
|
|
whose value matches the appriopriate error. Test with symbol_ondemand_enabled.
|
|
"""
|
|
initCommands = ["settings set symbols.load-on-demand true"]
|
|
self.darwin_dwarf_missing_obj(initCommands)
|