Prior to this fix if we have a really large array or collection class, we would end up always creating all of the child variables for an array or collection class. If the number of children was very high this can cause delays when expanding variables. By adding the "indexedVariables" to variables with lots of children, we can keep good performance in the variables view at all times. This patch will add the "indexedVariables" key/value pair to any "Variable" JSON dictionairies when we have an array of synthetic child provider that will create more than 100 children. We have to be careful to not call "uint32_t SBValue::GetNumChildren()" on any lldb::SBValue that we use because it can cause a class, struct or union to complete the type in order to be able to properly tell us how many children it has and this can be expensive if you have a lot of variables. By default LLDB won't need to complete a type if we have variables that are classes, structs or unions unless the user expands the variable in the variable view. So we try to only get the GetNumChildren() when we have an array, as this is a cheap operation, or a synthetic child provider, most of which are for showing collections that typically fall into this category. We add a variable reference, which indicates that something can be expanded, when the function "bool SBValue::MightHaveChildren()" is true as this call doesn't need to complete the type in order to return true. This way if no one ever expands class variables, we don't need to complete the type. Differential Revision: https://reviews.llvm.org/D125347
467 lines
20 KiB
Python
467 lines
20 KiB
Python
"""
|
|
Test lldb-vscode setBreakpoints request
|
|
"""
|
|
|
|
from __future__ import print_function
|
|
|
|
import unittest2
|
|
import vscode
|
|
from lldbsuite.test.decorators import *
|
|
from lldbsuite.test.lldbtest import *
|
|
from lldbsuite.test import lldbutil
|
|
import lldbvscode_testcase
|
|
|
|
|
|
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 TestVSCode_variables(lldbvscode_testcase.VSCodeTestCaseBase):
|
|
|
|
mydir = TestBase.compute_mydir(__file__)
|
|
|
|
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 '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.vscode.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']
|
|
self.assertIn(name, verify_dict,
|
|
'variable "%s" in verify dictionary' % (name))
|
|
self.verify_values(verify_dict[name], variable, varref_dict)
|
|
|
|
@skipIfWindows
|
|
@skipIfRemote
|
|
def test_scopes_variables_setVariable_evaluate(self):
|
|
'''
|
|
Tests the "scopes", "variables", "setVariable", and "evaluate"
|
|
packets.
|
|
'''
|
|
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)
|
|
locals = self.vscode.get_local_variables()
|
|
globals = self.vscode.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.vscode.request_variables(varRef)
|
|
self.verify_variables(buffer_children, response['body']['variables'])
|
|
# Verify setting start=0 in the arguments still gets all children
|
|
response = self.vscode.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.vscode.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.vscode.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.vscode.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.vscode.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': 'PointType @ 0x'},
|
|
'hasVariablesReference': True
|
|
},
|
|
'pt.buffer': {
|
|
'equals': {'type': 'int[32]'},
|
|
'startswith': {'result': '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.vscode.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.vscode.request_setVariable(varRef, "[0]", 100)
|
|
response = self.vscode.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.vscode.request_setVariable(varRef, "x", 111)
|
|
response = self.vscode.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.vscode.get_local_variables())
|
|
|
|
# Now we verify that we correctly change the name of a variable with and without differentiator suffix
|
|
self.assertFalse(self.vscode.request_setVariable(1, "x2", 9)['success'])
|
|
self.assertFalse(self.vscode.request_setVariable(1, "x @ main.cpp:0", 9)['success'])
|
|
|
|
self.assertTrue(self.vscode.request_setVariable(1, "x @ main.cpp:17", 17)['success'])
|
|
self.assertTrue(self.vscode.request_setVariable(1, "x @ main.cpp:19", 19)['success'])
|
|
self.assertTrue(self.vscode.request_setVariable(1, "x @ main.cpp:21", 21)['success'])
|
|
|
|
# The following should have no effect
|
|
self.assertFalse(self.vscode.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.vscode.get_local_variables())
|
|
|
|
# The plain x variable shold refer to the innermost x
|
|
self.assertTrue(self.vscode.request_setVariable(1, "x", 22)['success'])
|
|
verify_locals['x @ main.cpp:21']['equals']['value'] = '22'
|
|
|
|
self.verify_variables(verify_locals, self.vscode.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.vscode.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_and_evaluate_expansion(self):
|
|
"""
|
|
Tests the evaluated expression expands successfully after "scopes" packets
|
|
and permanent expressions persist.
|
|
"""
|
|
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)
|
|
|
|
# Verify locals
|
|
locals = self.vscode.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": "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.vscode.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.vscode.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.vscode.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.vscode.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.vscode.request_variables(var_ref)
|
|
self.verify_variables(
|
|
expandable_expression["children"], response["body"]["variables"]
|
|
)
|
|
|
|
# Test that frame scopes have corresponding presentation hints.
|
|
frame_id = self.vscode.get_stackFrame()["id"]
|
|
scopes = self.vscode.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_indexedVariables(self):
|
|
"""
|
|
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)
|
|
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.vscode.get_local_variables()
|
|
buffer_children = make_buffer_verify_dict(0, 32)
|
|
verify_locals = {
|
|
"small_array": {"equals": {"indexedVariables": 5}},
|
|
"large_array": {"equals": {"indexedVariables": 200}},
|
|
"small_vector": {"equals": {"indexedVariables": 5}},
|
|
"large_vector": {"equals": {"indexedVariables": 200}},
|
|
"pt": {"missing": ["indexedVariables"]},
|
|
}
|
|
self.verify_variables(verify_locals, locals)
|