Skip to content

Commit f38ebec

Browse files
authored
[lldb-dap] Don't call GetNumChildren on non-indexed synthetic variables (#93534)
A synthetic child provider might need to do considerable amount of work to compute the number of children. lldb-dap is currently calling that for all synthethic variables, but it's only actually using the value for values which it deems to be "indexed" (which is determined by looking at the name of the first child). This patch reverses the logic so that GetNumChildren is only called for variables with a suitable first child.
1 parent 3cabbf6 commit f38ebec

File tree

5 files changed

+118
-22
lines changed

5 files changed

+118
-22
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
CXX_SOURCES := main.cpp
2+
3+
include Makefile.rules
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import os
2+
3+
import dap_server
4+
import lldbdap_testcase
5+
from lldbsuite.test import lldbutil
6+
from lldbsuite.test.decorators import *
7+
from lldbsuite.test.lldbtest import *
8+
9+
10+
class TestDAP_variables_children(lldbdap_testcase.DAPTestCaseBase):
11+
def test_get_num_children(self):
12+
"""Test that GetNumChildren is not called for formatters not producing indexed children."""
13+
program = self.getBuildArtifact("a.out")
14+
self.build_and_launch(
15+
program,
16+
preRunCommands=[
17+
"command script import '%s'" % self.getSourcePath("formatter.py")
18+
],
19+
)
20+
source = "main.cpp"
21+
breakpoint1_line = line_number(source, "// break here")
22+
lines = [breakpoint1_line]
23+
24+
breakpoint_ids = self.set_source_breakpoints(
25+
source, [line_number(source, "// break here")]
26+
)
27+
self.continue_to_breakpoints(breakpoint_ids)
28+
29+
local_vars = self.dap_server.get_local_variables()
30+
print(local_vars)
31+
indexed = next(filter(lambda x: x["name"] == "indexed", local_vars))
32+
not_indexed = next(filter(lambda x: x["name"] == "not_indexed", local_vars))
33+
self.assertIn("indexedVariables", indexed)
34+
self.assertEquals(indexed["indexedVariables"], 1)
35+
self.assertNotIn("indexedVariables", not_indexed)
36+
37+
self.assertIn(
38+
"['Indexed']",
39+
self.dap_server.request_evaluate(
40+
"`script formatter.num_children_calls", context="repl"
41+
)["body"]["result"],
42+
)
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import lldb
2+
3+
4+
num_children_calls = []
5+
6+
7+
class TestSyntheticProvider:
8+
def __init__(self, valobj, dict):
9+
target = valobj.GetTarget()
10+
self._type = valobj.GetType()
11+
data = lldb.SBData.CreateDataFromCString(lldb.eByteOrderLittle, 8, "S")
12+
name = "child" if "Not" in self._type.GetName() else "[0]"
13+
self._child = valobj.CreateValueFromData(
14+
name, data, target.GetBasicType(lldb.eBasicTypeChar)
15+
)
16+
17+
def num_children(self):
18+
num_children_calls.append(self._type.GetName())
19+
return 1
20+
21+
def get_child_at_index(self, index):
22+
if index != 0:
23+
return None
24+
return self._child
25+
26+
def get_child_index(self, name):
27+
if name == self._child.GetName():
28+
return 0
29+
return None
30+
31+
32+
def __lldb_init_module(debugger, dict):
33+
cat = debugger.CreateCategory("TestCategory")
34+
cat.AddTypeSynthetic(
35+
lldb.SBTypeNameSpecifier("Indexed"),
36+
lldb.SBTypeSynthetic.CreateWithClassName("formatter.TestSyntheticProvider"),
37+
)
38+
cat.AddTypeSynthetic(
39+
lldb.SBTypeNameSpecifier("NotIndexed"),
40+
lldb.SBTypeSynthetic.CreateWithClassName("formatter.TestSyntheticProvider"),
41+
)
42+
cat.SetEnabled(True)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
struct Indexed {};
2+
struct NotIndexed {};
3+
4+
int main() {
5+
Indexed indexed;
6+
NotIndexed not_indexed;
7+
return 0; // break here
8+
}

lldb/tools/lldb-dap/JSONUtils.cpp

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <sstream>
1313
#include <string.h>
1414

15+
#include "llvm/ADT/StringRef.h"
1516
#include "llvm/Support/FormatAdapters.h"
1617
#include "llvm/Support/FormatVariadic.h"
1718
#include "llvm/Support/Path.h"
@@ -1211,34 +1212,34 @@ llvm::json::Value CreateVariable(lldb::SBValue v, int64_t variablesReference,
12111212
// give a hint to the IDE that the type has indexed children so that the
12121213
// request can be broken up in grabbing only a few children at a time. We
12131214
// want to be careful and only call "v.GetNumChildren()" if we have an array
1214-
// type or if we have a synthetic child provider. We don't want to call
1215-
// "v.GetNumChildren()" on all objects as class, struct and union types
1216-
// don't need to be completed if they are never expanded. So we want to
1217-
// avoid calling this to only cases where we it makes sense to keep
1215+
// type or if we have a synthetic child provider producing indexed children.
1216+
// We don't want to call "v.GetNumChildren()" on all objects as class, struct
1217+
// and union types don't need to be completed if they are never expanded. So
1218+
// we want to avoid calling this to only cases where we it makes sense to keep
12181219
// performance high during normal debugging.
12191220

12201221
// If we have an array type, say that it is indexed and provide the number
12211222
// of children in case we have a huge array. If we don't do this, then we
12221223
// might take a while to produce all children at onces which can delay your
12231224
// debug session.
1224-
const bool is_array = desc.type_obj.IsArrayType();
1225-
const bool is_synthetic = v.IsSynthetic();
1226-
if (is_array || is_synthetic) {
1227-
const auto num_children = v.GetNumChildren();
1228-
// We create a "[raw]" fake child for each synthetic type, so we have to
1229-
// account for it when returning indexed variables. We don't need to do
1230-
// this for non-indexed ones.
1231-
bool has_raw_child = is_synthetic && g_dap.enable_synthetic_child_debugging;
1232-
int actual_num_children = num_children + (has_raw_child ? 1 : 0);
1233-
if (is_array) {
1234-
object.try_emplace("indexedVariables", actual_num_children);
1235-
} else if (num_children > 0) {
1236-
// If a type has a synthetic child provider, then the SBType of "v"
1237-
// won't tell us anything about what might be displayed. So we can check
1238-
// if the first child's name is "[0]" and then we can say it is indexed.
1239-
const char *first_child_name = v.GetChildAtIndex(0).GetName();
1240-
if (first_child_name && strcmp(first_child_name, "[0]") == 0)
1241-
object.try_emplace("indexedVariables", actual_num_children);
1225+
if (desc.type_obj.IsArrayType()) {
1226+
object.try_emplace("indexedVariables", v.GetNumChildren());
1227+
} else if (v.IsSynthetic()) {
1228+
// For a type with a synthetic child provider, the SBType of "v" won't tell
1229+
// us anything about what might be displayed. Instead, we check if the first
1230+
// child's name is "[0]" and then say it is indexed. We call
1231+
// GetNumChildren() only if the child name matches to avoid a potentially
1232+
// expensive operation.
1233+
if (lldb::SBValue first_child = v.GetChildAtIndex(0)) {
1234+
llvm::StringRef first_child_name = first_child.GetName();
1235+
if (first_child_name == "[0]") {
1236+
size_t num_children = v.GetNumChildren();
1237+
// If we are creating a "[raw]" fake child for each synthetic type, we
1238+
// have to account for it when returning indexed variables.
1239+
if (g_dap.enable_synthetic_child_debugging)
1240+
++num_children;
1241+
object.try_emplace("indexedVariables", num_children);
1242+
}
12421243
}
12431244
}
12441245
EmplaceSafeString(object, "type", desc.display_type_name);

0 commit comments

Comments
 (0)