Skip to content

Commit 89a81ec

Browse files
[lldb-vscode] Display a more descriptive summary for containers and pointers (#65514)
We've been displaying types and addresses for containers, but that's not very useful information. A better approach is to compose the summary of containers with the summary of a few of its children. Not only that, we can dereference simple pointers and references to get the summary of the pointer variable, which is also better than just showing an anddress. And in the rare case where the user wants to inspect the raw address, they can always use the debug console for that. For the record, this is very similar to what the CodeLLDB extension does, and it seems to give a better experience. An example of the new output: <img width="494" alt="Screenshot 2023-09-06 at 2 24 27 PM" src="https://github.com/llvm/llvm-project/assets/1613874/588659b8-421a-4865-8d67-ce4b6182c4f9"> And this is the <img width="476" alt="Screenshot 2023-09-06 at 2 46 30 PM" src="https://github.com/llvm/llvm-project/assets/1613874/5768a52e-a773-449d-9aab-1b2fb2a98035"> old output:
1 parent 776e3b0 commit 89a81ec

File tree

3 files changed

+123
-22
lines changed

3 files changed

+123
-22
lines changed

lldb/test/API/tools/lldb-vscode/evaluate/TestVSCode_evaluate.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def run_test_evaluate_expressions(self, context=None):
5555
self.assertEvaluate("var2", "21")
5656
self.assertEvaluate("static_int", "42")
5757
self.assertEvaluate("non_static_int", "43")
58-
self.assertEvaluate("struct1", "my_struct @ 0x.*")
58+
self.assertEvaluate("struct1", "{foo:15}")
5959
self.assertEvaluate("struct1.foo", "15")
6060
self.assertEvaluate("struct2->foo", "16")
6161

@@ -85,7 +85,7 @@ def run_test_evaluate_expressions(self, context=None):
8585
self.assertEvaluate(
8686
"non_static_int", "10"
8787
) # different variable with the same name
88-
self.assertEvaluate("struct1", "my_struct @ 0x.*")
88+
self.assertEvaluate("struct1", "{foo:15}")
8989
self.assertEvaluate("struct1.foo", "15")
9090
self.assertEvaluate("struct2->foo", "16")
9191

lldb/test/API/tools/lldb-vscode/variables/TestVSCode_variables.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22
Test lldb-vscode setBreakpoints request
33
"""
44

5+
import os
6+
7+
import lldbvscode_testcase
58
import vscode
9+
from lldbsuite.test import lldbutil
610
from lldbsuite.test.decorators import *
711
from lldbsuite.test.lldbtest import *
8-
from lldbsuite.test import lldbutil
9-
import lldbvscode_testcase
10-
import os
1112

1213

1314
def make_buffer_verify_dict(start_idx, count, offset=0):
@@ -218,12 +219,12 @@ def test_scopes_variables_setVariable_evaluate(self):
218219
},
219220
"pt": {
220221
"equals": {"type": "PointType"},
221-
"startswith": {"result": "PointType @ 0x"},
222+
"startswith": {"result": "{x:11, y:22}"},
222223
"hasVariablesReference": True,
223224
},
224225
"pt.buffer": {
225226
"equals": {"type": "int[32]"},
226-
"startswith": {"result": "int[32] @ 0x"},
227+
"startswith": {"result": "{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...}"},
227228
"hasVariablesReference": True,
228229
},
229230
"argv": {
@@ -409,7 +410,7 @@ def test_scopes_and_evaluate_expansion(self):
409410
"name": "pt",
410411
"response": {
411412
"equals": {"type": "PointType"},
412-
"startswith": {"result": "PointType @ 0x"},
413+
"startswith": {"result": "{x:11, y:22}"},
413414
"missing": ["indexedVariables"],
414415
"hasVariablesReference": True,
415416
},

lldb/tools/lldb-vscode/JSONUtils.cpp

Lines changed: 114 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,84 @@ std::vector<std::string> GetStrings(const llvm::json::Object *obj,
132132
return strs;
133133
}
134134

135+
/// Create a short summary for a container that contains the summary of its
136+
/// first children, so that the user can get a glimpse of its contents at a
137+
/// glance.
138+
static std::optional<std::string>
139+
GetSyntheticSummaryForContainer(lldb::SBValue &v) {
140+
if (v.TypeIsPointerType() || !v.MightHaveChildren())
141+
return std::nullopt;
142+
/// As this operation can be potentially slow, we limit the total time spent
143+
/// fetching children to a few ms.
144+
const auto max_evaluation_time = std::chrono::milliseconds(10);
145+
/// We don't want to generate a extremely long summary string, so we limit its
146+
/// length.
147+
const size_t max_length = 32;
148+
149+
auto start = std::chrono::steady_clock::now();
150+
std::string summary;
151+
llvm::raw_string_ostream os(summary);
152+
os << "{";
153+
154+
llvm::StringRef separator = "";
155+
156+
for (size_t i = 0, e = v.GetNumChildren(); i < e; ++i) {
157+
// If we reached the time limit or exceeded the number of characters, we
158+
// dump `...` to signal that there are more elements in the collection.
159+
if (summary.size() > max_length ||
160+
(std::chrono::steady_clock::now() - start) > max_evaluation_time) {
161+
os << separator << "...";
162+
break;
163+
}
164+
lldb::SBValue child = v.GetChildAtIndex(i);
165+
166+
if (llvm::StringRef name = child.GetName(); !name.empty()) {
167+
llvm::StringRef value;
168+
if (llvm::StringRef summary = child.GetSummary(); !summary.empty())
169+
value = summary;
170+
else
171+
value = child.GetValue();
172+
173+
if (!value.empty()) {
174+
// If the child is an indexed entry, we don't show its index to save
175+
// characters.
176+
if (name.starts_with("["))
177+
os << separator << value;
178+
else
179+
os << separator << name << ":" << value;
180+
separator = ", ";
181+
}
182+
}
183+
}
184+
os << "}";
185+
186+
if (summary == "{...}" || summary == "{}")
187+
return std::nullopt;
188+
return summary;
189+
}
190+
191+
/// Return whether we should dereference an SBValue in order to generate a more
192+
/// meaningful summary string.
193+
static bool ShouldBeDereferencedForSummary(lldb::SBValue &v) {
194+
if (!v.GetType().IsPointerType() && !v.GetType().IsReferenceType())
195+
return false;
196+
197+
// If it's a pointer to a pointer, we don't dereference to avoid confusing
198+
// the user with the addresses that could shown in the summary.
199+
if (v.GetType().IsPointerType() &&
200+
v.GetType().GetDereferencedType().IsPointerType())
201+
return false;
202+
203+
// If it's synthetic or a pointer to a basic type that provides a summary, we
204+
// don't dereference.
205+
if ((v.IsSynthetic() || v.GetType().GetPointeeType().GetBasicType() !=
206+
lldb::eBasicTypeInvalid) &&
207+
!llvm::StringRef(v.GetSummary()).empty()) {
208+
return false;
209+
}
210+
return true;
211+
}
212+
135213
void SetValueForKey(lldb::SBValue &v, llvm::json::Object &object,
136214
llvm::StringRef key) {
137215
std::string result;
@@ -141,23 +219,45 @@ void SetValueForKey(lldb::SBValue &v, llvm::json::Object &object,
141219
if (!error.Success()) {
142220
strm << "<error: " << error.GetCString() << ">";
143221
} else {
144-
llvm::StringRef value = v.GetValue();
145-
llvm::StringRef summary = v.GetSummary();
146-
llvm::StringRef type_name = v.GetType().GetDisplayTypeName();
147-
if (!value.empty()) {
148-
strm << value;
149-
if (!summary.empty())
222+
auto tryDumpSummaryAndValue = [&strm](lldb::SBValue value) {
223+
llvm::StringRef val = value.GetValue();
224+
llvm::StringRef summary = value.GetSummary();
225+
if (!val.empty()) {
226+
strm << val;
227+
if (!summary.empty())
228+
strm << ' ' << summary;
229+
return true;
230+
}
231+
if (!summary.empty()) {
150232
strm << ' ' << summary;
151-
} else if (!summary.empty()) {
152-
strm << ' ' << summary;
153-
} else if (!type_name.empty()) {
154-
strm << type_name;
155-
lldb::addr_t address = v.GetLoadAddress();
156-
if (address != LLDB_INVALID_ADDRESS)
157-
strm << " @ " << llvm::format_hex(address, 0);
233+
return true;
234+
}
235+
if (auto container_summary = GetSyntheticSummaryForContainer(value)) {
236+
strm << *container_summary;
237+
return true;
238+
}
239+
return false;
240+
};
241+
242+
// We first try to get the summary from its dereferenced value.
243+
bool done = ShouldBeDereferencedForSummary(v) &&
244+
tryDumpSummaryAndValue(v.Dereference());
245+
246+
// We then try to get it from the current value itself.
247+
if (!done)
248+
done = tryDumpSummaryAndValue(v);
249+
250+
// As last resort, we print its type and address if available.
251+
if (!done) {
252+
if (llvm::StringRef type_name = v.GetType().GetDisplayTypeName();
253+
!type_name.empty()) {
254+
strm << type_name;
255+
lldb::addr_t address = v.GetLoadAddress();
256+
if (address != LLDB_INVALID_ADDRESS)
257+
strm << " @ " << llvm::format_hex(address, 0);
258+
}
158259
}
159260
}
160-
strm.flush();
161261
EmplaceSafeString(object, key, result);
162262
}
163263

0 commit comments

Comments
 (0)