Skip to content

Commit

Permalink
[lldb-vscode] Distinguish shadowed variables in the scopes request
Browse files Browse the repository at this point in the history
VSCode doesn't render multiple variables with the same name in the variables view. It only renders one of them. This is a situation that happens often when there are shadowed variables.
The nodejs debugger solves this by adding a number suffix to the variable, e.g. "x", "x2", "x3" are the different x variables in nested blocks.

In this patch I'm doing something similar, but the suffix is " @ <file_name:line>), e.g. "x @ main.cpp:17", "x @ main.cpp:21". The fallback would be an address if the source and line information is not present, which should be rare.

This fix is only needed for globals and locals. Children of variables don't suffer of this problem.

When there are shadowed variables
{F16182150}

Without shadowed variables
{F16182152}

Modifying these variables through the UI works

Reviewed By: clayborg

Differential Revision: https://reviews.llvm.org/D99989
  • Loading branch information
walter-erquinigo committed Apr 21, 2021
1 parent 3d8f205 commit c9a0754
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ def verify_breakpoint_hit(self, breakpoint_ids):
# the right breakpoint matches and not worry about the actual
# location.
description = body['description']
print("description: %s" % (description))
for breakpoint_id in breakpoint_ids:
match_desc = 'breakpoint %s.' % (breakpoint_id)
if match_desc in description:
Expand Down
61 changes: 61 additions & 0 deletions lldb/test/API/tools/lldb-vscode/variables/TestVSCode_variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ def test_scopes_variables_setVariable_evaluate(self):
'y': {'equals': {'type': 'int', 'value': '22'}},
'buffer': {'children': buffer_children}
}
},
'x': {
'equals': {'type': 'int'}
}
}
verify_globals = {
Expand Down Expand Up @@ -221,3 +224,61 @@ def test_scopes_variables_setVariable_evaluate(self):
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)
10 changes: 9 additions & 1 deletion lldb/test/API/tools/lldb-vscode/variables/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,13 @@ int main(int argc, char const *argv[]) {
PointType pt = { 11,22, {0}};
for (int i=0; i<BUFFER_SIZE; ++i)
pt.buffer[i] = i;
return s_global - g_global - pt.y; // breakpoint 1
int x = s_global - g_global - pt.y; // breakpoint 1
{
int x = 42;
{
int x = 72;
s_global = x; // breakpoint 2
}
}
return 0; // breakpoint 3
}
28 changes: 25 additions & 3 deletions lldb/tools/lldb-vscode/JSONUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include "lldb/API/SBBreakpoint.h"
#include "lldb/API/SBBreakpointLocation.h"
#include "lldb/API/SBDeclaration.h"
#include "lldb/API/SBValue.h"
#include "lldb/Host/PosixApi.h"

Expand Down Expand Up @@ -904,6 +905,25 @@ llvm::json::Value CreateThreadStopped(lldb::SBThread &thread,
return llvm::json::Value(std::move(event));
}

std::string CreateUniqueVariableNameForDisplay(lldb::SBValue v,
bool is_name_duplicated) {
lldb::SBStream name_builder;
const char *name = v.GetName();
name_builder.Print(name ? name : "<null>");
if (is_name_duplicated) {
name_builder.Print(" @ ");
lldb::SBDeclaration declaration = v.GetDeclaration();
std::string file_name(declaration.GetFileSpec().GetFilename());
const uint32_t line = declaration.GetLine();

if (!file_name.empty() && line > 0)
name_builder.Printf("%s:%u", file_name.c_str(), line);
else
name_builder.Print(v.GetLocation());
}
return name_builder.GetData();
}

// "Variable": {
// "type": "object",
// "description": "A Variable is a name/value pair. Optionally a variable
Expand Down Expand Up @@ -967,10 +987,12 @@ llvm::json::Value CreateThreadStopped(lldb::SBThread &thread,
// "required": [ "name", "value", "variablesReference" ]
// }
llvm::json::Value CreateVariable(lldb::SBValue v, int64_t variablesReference,
int64_t varID, bool format_hex) {
int64_t varID, bool format_hex,
bool is_name_duplicated) {
llvm::json::Object object;
auto name = v.GetName();
EmplaceSafeString(object, "name", name ? name : "<null>");
EmplaceSafeString(object, "name",
CreateUniqueVariableNameForDisplay(v, is_name_duplicated));

if (format_hex)
v.SetFormat(lldb::eFormatHex);
SetValueForKey(v, object, "value");
Expand Down
19 changes: 18 additions & 1 deletion lldb/tools/lldb-vscode/JSONUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,14 @@ llvm::json::Value CreateThread(lldb::SBThread &thread);
/// definition outlined by Microsoft.
llvm::json::Value CreateThreadStopped(lldb::SBThread &thread, uint32_t stop_id);

/// VSCode can't display two variables with the same name, so we need to
/// distinguish them by using a suffix.
///
/// If the source and line information is present, we use it as the suffix.
/// Otherwise, we fallback to the variable address or register location.
std::string CreateUniqueVariableNameForDisplay(lldb::SBValue v,
bool is_name_duplicated);

/// Create a "Variable" object for a LLDB thread object.
///
/// This function will fill in the following keys in the returned
Expand Down Expand Up @@ -435,11 +443,20 @@ llvm::json::Value CreateThreadStopped(lldb::SBThread &thread, uint32_t stop_id);
/// It set to true the variable will be formatted as hex in
/// the "value" key value pair for the value of the variable.
///
/// \param[in] is_name_duplicated
/// Whether the same variable name appears multiple times within the same
/// context (e.g. locals). This can happen due to shadowed variables in
/// nested blocks.
///
/// As VSCode doesn't render two of more variables with the same name, we
/// apply a suffix to distinguish duplicated variables.
///
/// \return
/// A "Variable" JSON object with that follows the formal JSON
/// definition outlined by Microsoft.
llvm::json::Value CreateVariable(lldb::SBValue v, int64_t variablesReference,
int64_t varID, bool format_hex);
int64_t varID, bool format_hex,
bool is_name_duplicated = false);

llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit unit);

Expand Down
38 changes: 28 additions & 10 deletions lldb/tools/lldb-vscode/lldb-vscode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include <set>
#include <sstream>
#include <thread>
#include <unordered_map>
#include <vector>

#include "llvm/ADT/ArrayRef.h"
Expand Down Expand Up @@ -2716,7 +2717,9 @@ void request_setVariable(const llvm::json::Object &request) {
// This is a reference to the containing variable/scope
const auto variablesReference =
GetUnsigned(arguments, "variablesReference", 0);
const auto name = GetString(arguments, "name");
llvm::StringRef name = GetString(arguments, "name");
bool is_duplicated_variable_name = name.find(" @") != llvm::StringRef::npos;

const auto value = GetString(arguments, "value");
// Set success to false just in case we don't find the variable by name
response.try_emplace("success", false);
Expand Down Expand Up @@ -2758,14 +2761,10 @@ void request_setVariable(const llvm::json::Object &request) {
break;
}

// Find the variable by name in the correct scope and hope we don't have
// multiple variables with the same name. We search backwards because
// the list of variables has the top most variables first and variables
// in deeper scopes are last. This means we will catch the deepest
// variable whose name matches which is probably what the user wants.
for (int64_t i = end_idx - 1; i >= start_idx; --i) {
auto curr_variable = g_vsc.variables.GetValueAtIndex(i);
llvm::StringRef variable_name(curr_variable.GetName());
lldb::SBValue curr_variable = g_vsc.variables.GetValueAtIndex(i);
std::string variable_name = CreateUniqueVariableNameForDisplay(
curr_variable, is_duplicated_variable_name);
if (variable_name == name) {
variable = curr_variable;
if (curr_variable.MightHaveChildren())
Expand All @@ -2774,6 +2773,9 @@ void request_setVariable(const llvm::json::Object &request) {
}
}
} else {
// This is not under the globals or locals scope, so there are no duplicated
// names.

// We have a named item within an actual variable so we need to find it
// withing the container variable by name.
const int64_t var_idx = VARREF_TO_VARIDX(variablesReference);
Expand Down Expand Up @@ -2810,6 +2812,8 @@ void request_setVariable(const llvm::json::Object &request) {
EmplaceSafeString(body, "message", std::string(error.GetCString()));
}
response["success"] = llvm::json::Value(success);
} else {
response["success"] = llvm::json::Value(false);
}

response.try_emplace("body", std::move(body));
Expand Down Expand Up @@ -2925,12 +2929,26 @@ void request_variables(const llvm::json::Object &request) {
break;
}
const int64_t end_idx = start_idx + ((count == 0) ? num_children : count);

// We first find out which variable names are duplicated
std::unordered_map<const char *, int> variable_name_counts;
for (auto i = start_idx; i < end_idx; ++i) {
lldb::SBValue variable = g_vsc.variables.GetValueAtIndex(i);
if (!variable.IsValid())
break;
variable_name_counts[variable.GetName()]++;
}

// Now we construct the result with unique display variable names
for (auto i = start_idx; i < end_idx; ++i) {
lldb::SBValue variable = g_vsc.variables.GetValueAtIndex(i);
const char *name = variable.GetName();

if (!variable.IsValid())
break;
variables.emplace_back(
CreateVariable(variable, VARIDX_TO_VARREF(i), i, hex));
variables.emplace_back(CreateVariable(variable, VARIDX_TO_VARREF(i), i,
hex,
variable_name_counts[name] > 1));
}
} else {
// We are expanding a variable that has children, so we will return its
Expand Down

0 comments on commit c9a0754

Please sign in to comment.