Skip to content

Commit ffd173b

Browse files
[lldb-dap] Emit more structured info along with variables (#75244)
In order to allow smarter vscode extensions, it's useful to send additional structured information of SBValues to the client. Specifically, I'm now sending error, summary, autoSummary and inMemoryValue in addition to the existing properties being sent. This is cheap because these properties have to be calculated anyway to generate the display value of the variable, but they are now available for extensions to better analyze variables. For example, if the error field is not present, the extension might be able to provide cool features, and the current way to do that is to look for the `"<error: "` prefix, which is error-prone. This also incorporates a tiny feedback from #74865 (comment)
1 parent 71f8ea3 commit ffd173b

File tree

6 files changed

+225
-150
lines changed

6 files changed

+225
-150
lines changed

lldb/test/API/tools/lldb-dap/optimized/TestDAP_optimized.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
"""
44

55
import dap_server
6+
import lldbdap_testcase
7+
from lldbsuite.test import lldbutil
68
from lldbsuite.test.decorators import *
79
from lldbsuite.test.lldbtest import *
8-
from lldbsuite.test import lldbutil
9-
import lldbdap_testcase
1010

1111

1212
class TestDAP_optimized(lldbdap_testcase.DAPTestCaseBase):
@@ -47,3 +47,7 @@ def test_optimized_variable(self):
4747
optimized_variable = self.dap_server.get_local_variable("argc")
4848

4949
self.assertTrue(optimized_variable["value"].startswith("<error:"))
50+
self.assertEqual(
51+
optimized_variable["$__lldb_extensions"]["error"],
52+
"Could not evaluate DW_OP_entry_value.",
53+
)

lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,18 @@ def do_test_scopes_variables_setVariable_evaluate(
153153
buffer_children = make_buffer_verify_dict(0, 32)
154154
verify_locals = {
155155
"argc": {
156-
"equals": {"type": "int", "value": "1"},
157-
"declaration": {
158-
"equals": {"line": 12, "column": 14},
159-
"contains": {"path": ["lldb-dap", "variables", "main.cpp"]},
156+
"equals": {
157+
"type": "int",
158+
"value": "1",
159+
},
160+
"$__lldb_extensions": {
161+
"equals": {
162+
"value": "1",
163+
},
164+
"declaration": {
165+
"equals": {"line": 12, "column": 14},
166+
"contains": {"path": ["lldb-dap", "variables", "main.cpp"]},
167+
},
160168
},
161169
},
162170
"argv": {
@@ -165,7 +173,9 @@ def do_test_scopes_variables_setVariable_evaluate(
165173
"hasVariablesReference": True,
166174
},
167175
"pt": {
168-
"equals": {"type": "PointType"},
176+
"equals": {
177+
"type": "PointType",
178+
},
169179
"hasVariablesReference": True,
170180
"children": {
171181
"x": {"equals": {"type": "int", "value": "11"}},
@@ -175,6 +185,10 @@ def do_test_scopes_variables_setVariable_evaluate(
175185
},
176186
"x": {"equals": {"type": "int"}},
177187
}
188+
if enableAutoVariableSummaries:
189+
verify_locals["pt"]["$__lldb_extensions"] = {
190+
"equals": {"autoSummary": "{x:11, y:22}"}
191+
}
178192
verify_globals = {
179193
"s_local": {"equals": {"type": "float", "value": "2.25"}},
180194
"::g_global": {"equals": {"type": "int", "value": "123"}},

lldb/tools/lldb-dap/BreakpointBase.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ bool BreakpointBase::BreakpointHitCallback(
296296
frame.GetValueForVariablePath(expr, lldb::eDynamicDontRunTarget);
297297
if (value.GetError().Fail())
298298
value = frame.EvaluateExpression(expr);
299-
output += ValueToString(value);
299+
output += VariableDescription(value).display_value;
300300
} else {
301301
output += messagePart.text;
302302
}

lldb/tools/lldb-dap/JSONUtils.cpp

Lines changed: 160 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -211,46 +211,6 @@ static std::optional<std::string> TryCreateAutoSummary(lldb::SBValue value) {
211211
return TryCreateAutoSummaryForContainer(value);
212212
}
213213

214-
std::string ValueToString(lldb::SBValue v) {
215-
std::string result;
216-
llvm::raw_string_ostream strm(result);
217-
218-
lldb::SBError error = v.GetError();
219-
if (!error.Success()) {
220-
strm << "<error: " << error.GetCString() << ">";
221-
} else {
222-
llvm::StringRef value = v.GetValue();
223-
llvm::StringRef nonAutoSummary = v.GetSummary();
224-
std::optional<std::string> summary = !nonAutoSummary.empty()
225-
? nonAutoSummary.str()
226-
: TryCreateAutoSummary(v);
227-
if (!value.empty()) {
228-
strm << value;
229-
if (summary)
230-
strm << ' ' << *summary;
231-
} else if (summary) {
232-
strm << *summary;
233-
234-
// As last resort, we print its type and address if available.
235-
} else {
236-
if (llvm::StringRef type_name = v.GetType().GetDisplayTypeName();
237-
!type_name.empty()) {
238-
strm << type_name;
239-
lldb::addr_t address = v.GetLoadAddress();
240-
if (address != LLDB_INVALID_ADDRESS)
241-
strm << " @ " << llvm::format_hex(address, 0);
242-
}
243-
}
244-
}
245-
return result;
246-
}
247-
248-
void SetValueForKey(lldb::SBValue &v, llvm::json::Object &object,
249-
llvm::StringRef key) {
250-
std::string result = ValueToString(v);
251-
EmplaceSafeString(object, key, result);
252-
}
253-
254214
void FillResponse(const llvm::json::Object &request,
255215
llvm::json::Object &response) {
256216
// Fill in all of the needed response fields to a "request" and set "success"
@@ -1045,6 +1005,92 @@ std::string CreateUniqueVariableNameForDisplay(lldb::SBValue v,
10451005
return name_builder.GetData();
10461006
}
10471007

1008+
VariableDescription::VariableDescription(lldb::SBValue v, bool format_hex,
1009+
bool is_name_duplicated,
1010+
std::optional<std::string> custom_name)
1011+
: v(v) {
1012+
name = custom_name
1013+
? *custom_name
1014+
: CreateUniqueVariableNameForDisplay(v, is_name_duplicated);
1015+
1016+
type_obj = v.GetType();
1017+
std::string raw_display_type_name =
1018+
llvm::StringRef(type_obj.GetDisplayTypeName()).str();
1019+
display_type_name =
1020+
!raw_display_type_name.empty() ? raw_display_type_name : NO_TYPENAME;
1021+
1022+
if (format_hex)
1023+
v.SetFormat(lldb::eFormatHex);
1024+
1025+
llvm::raw_string_ostream os_display_value(display_value);
1026+
1027+
if (lldb::SBError sb_error = v.GetError(); sb_error.Fail()) {
1028+
error = sb_error.GetCString();
1029+
os_display_value << "<error: " << error << ">";
1030+
} else {
1031+
value = llvm::StringRef(v.GetValue()).str();
1032+
summary = llvm::StringRef(v.GetSummary()).str();
1033+
if (summary.empty())
1034+
auto_summary = TryCreateAutoSummary(v);
1035+
1036+
std::optional<std::string> effective_summary =
1037+
!summary.empty() ? summary : auto_summary;
1038+
1039+
if (!value.empty()) {
1040+
os_display_value << value;
1041+
if (effective_summary)
1042+
os_display_value << " " << *effective_summary;
1043+
} else if (effective_summary) {
1044+
os_display_value << *effective_summary;
1045+
1046+
// As last resort, we print its type and address if available.
1047+
} else {
1048+
if (!raw_display_type_name.empty()) {
1049+
os_display_value << raw_display_type_name;
1050+
lldb::addr_t address = v.GetLoadAddress();
1051+
if (address != LLDB_INVALID_ADDRESS)
1052+
os_display_value << " @ " << llvm::format_hex(address, 0);
1053+
}
1054+
}
1055+
}
1056+
1057+
lldb::SBStream evaluateStream;
1058+
v.GetExpressionPath(evaluateStream);
1059+
evaluate_name = llvm::StringRef(evaluateStream.GetData()).str();
1060+
}
1061+
1062+
llvm::json::Object VariableDescription::GetVariableExtensionsJSON() {
1063+
llvm::json::Object extensions;
1064+
if (error)
1065+
EmplaceSafeString(extensions, "error", *error);
1066+
if (!value.empty())
1067+
EmplaceSafeString(extensions, "value", value);
1068+
if (!summary.empty())
1069+
EmplaceSafeString(extensions, "summary", summary);
1070+
if (auto_summary)
1071+
EmplaceSafeString(extensions, "autoSummary", *auto_summary);
1072+
1073+
if (lldb::SBDeclaration decl = v.GetDeclaration(); decl.IsValid()) {
1074+
llvm::json::Object decl_obj;
1075+
if (lldb::SBFileSpec file = decl.GetFileSpec(); file.IsValid()) {
1076+
char path[PATH_MAX] = "";
1077+
if (file.GetPath(path, sizeof(path)) &&
1078+
lldb::SBFileSpec::ResolvePath(path, path, PATH_MAX)) {
1079+
decl_obj.try_emplace("path", std::string(path));
1080+
}
1081+
}
1082+
1083+
if (int line = decl.GetLine())
1084+
decl_obj.try_emplace("line", line);
1085+
if (int column = decl.GetColumn())
1086+
decl_obj.try_emplace("column", column);
1087+
1088+
if (!decl_obj.empty())
1089+
extensions.try_emplace("declaration", std::move(decl_obj));
1090+
}
1091+
return extensions;
1092+
}
1093+
10481094
// "Variable": {
10491095
// "type": "object",
10501096
// "description": "A Variable is a name/value pair. Optionally a variable
@@ -1104,27 +1150,56 @@ std::string CreateUniqueVariableNameForDisplay(lldb::SBValue v,
11041150
// can use this optional information to present the
11051151
// children in a paged UI and fetch them in chunks."
11061152
// }
1107-
// "declaration": {
1108-
// "type": "object | undefined",
1109-
// "description": "Extension to the protocol that indicates the source
1110-
// location where the variable was declared. This value
1111-
// might not be present if no declaration is available.",
1153+
//
1154+
//
1155+
// "$__lldb_extensions": {
1156+
// "description": "Unofficial extensions to the protocol",
11121157
// "properties": {
1113-
// "path": {
1114-
// "type": "string | undefined",
1115-
// "description": "The source file path where the variable was
1116-
// declared."
1117-
// },
1118-
// "line": {
1119-
// "type": "number | undefined",
1120-
// "description": "The 1-indexed source line where the variable was
1121-
// declared."
1122-
// },
1123-
// "column": {
1124-
// "type": "number | undefined",
1125-
// "description": "The 1-indexed source column where the variable was
1126-
// declared."
1158+
// "declaration": {
1159+
// "type": "object",
1160+
// "description": "The source location where the variable was declared.
1161+
// This value won't be present if no declaration is
1162+
// available.",
1163+
// "properties": {
1164+
// "path": {
1165+
// "type": "string",
1166+
// "description": "The source file path where the variable was
1167+
// declared."
1168+
// },
1169+
// "line": {
1170+
// "type": "number",
1171+
// "description": "The 1-indexed source line where the variable was
1172+
// declared."
1173+
// },
1174+
// "column": {
1175+
// "type": "number",
1176+
// "description": "The 1-indexed source column where the variable
1177+
// was declared."
1178+
// }
11271179
// }
1180+
// },
1181+
// "value":
1182+
// "type": "string",
1183+
// "description": "The internal value of the variable as returned by
1184+
// This is effectively SBValue.GetValue(). The other
1185+
// `value` entry in the top-level variable response is,
1186+
// on the other hand, just a display string for the
1187+
// variable."
1188+
// },
1189+
// "summary":
1190+
// "type": "string",
1191+
// "description": "The summary string of the variable. This is
1192+
// effectively SBValue.GetSummary()."
1193+
// },
1194+
// "autoSummary":
1195+
// "type": "string",
1196+
// "description": "The auto generated summary if using
1197+
// `enableAutoVariableSummaries`."
1198+
// },
1199+
// "error":
1200+
// "type": "string",
1201+
// "description": "An error message generated if LLDB couldn't inspect
1202+
// the variable."
11281203
// }
11291204
// }
11301205
// },
@@ -1134,81 +1209,57 @@ llvm::json::Value CreateVariable(lldb::SBValue v, int64_t variablesReference,
11341209
int64_t varID, bool format_hex,
11351210
bool is_name_duplicated,
11361211
std::optional<std::string> custom_name) {
1212+
VariableDescription desc(v, format_hex, is_name_duplicated, custom_name);
11371213
llvm::json::Object object;
1138-
EmplaceSafeString(
1139-
object, "name",
1140-
custom_name ? *custom_name
1141-
: CreateUniqueVariableNameForDisplay(v, is_name_duplicated));
1214+
EmplaceSafeString(object, "name", desc.name);
1215+
EmplaceSafeString(object, "value", desc.display_value);
1216+
1217+
if (!desc.evaluate_name.empty())
1218+
EmplaceSafeString(object, "evaluateName", desc.evaluate_name);
11421219

1143-
if (format_hex)
1144-
v.SetFormat(lldb::eFormatHex);
1145-
SetValueForKey(v, object, "value");
1146-
auto type_obj = v.GetType();
1147-
auto type_cstr = type_obj.GetDisplayTypeName();
11481220
// If we have a type with many children, we would like to be able to
11491221
// give a hint to the IDE that the type has indexed children so that the
1150-
// request can be broken up in grabbing only a few children at a time. We want
1151-
// to be careful and only call "v.GetNumChildren()" if we have an array type
1152-
// or if we have a synthetic child provider. We don't want to call
1153-
// "v.GetNumChildren()" on all objects as class, struct and union types don't
1154-
// need to be completed if they are never expanded. So we want to avoid
1155-
// calling this to only cases where we it makes sense to keep performance high
1156-
// during normal debugging.
1157-
1158-
// If we have an array type, say that it is indexed and provide the number of
1159-
// children in case we have a huge array. If we don't do this, then we might
1160-
// take a while to produce all children at onces which can delay your debug
1161-
// session.
1162-
const bool is_array = type_obj.IsArrayType();
1222+
// request can be broken up in grabbing only a few children at a time. We
1223+
// want to be careful and only call "v.GetNumChildren()" if we have an array
1224+
// type or if we have a synthetic child provider. We don't want to call
1225+
// "v.GetNumChildren()" on all objects as class, struct and union types
1226+
// don't need to be completed if they are never expanded. So we want to
1227+
// avoid calling this to only cases where we it makes sense to keep
1228+
// performance high during normal debugging.
1229+
1230+
// If we have an array type, say that it is indexed and provide the number
1231+
// of children in case we have a huge array. If we don't do this, then we
1232+
// might take a while to produce all children at onces which can delay your
1233+
// debug session.
1234+
const bool is_array = desc.type_obj.IsArrayType();
11631235
const bool is_synthetic = v.IsSynthetic();
11641236
if (is_array || is_synthetic) {
11651237
const auto num_children = v.GetNumChildren();
11661238
// We create a "[raw]" fake child for each synthetic type, so we have to
1167-
// account for it when returning indexed variables. We don't need to do this
1168-
// for non-indexed ones.
1239+
// account for it when returning indexed variables. We don't need to do
1240+
// this for non-indexed ones.
11691241
bool has_raw_child = is_synthetic && g_dap.enable_synthetic_child_debugging;
11701242
int actual_num_children = num_children + (has_raw_child ? 1 : 0);
11711243
if (is_array) {
11721244
object.try_emplace("indexedVariables", actual_num_children);
11731245
} else if (num_children > 0) {
1174-
// If a type has a synthetic child provider, then the SBType of "v" won't
1175-
// tell us anything about what might be displayed. So we can check if the
1176-
// first child's name is "[0]" and then we can say it is indexed.
1246+
// If a type has a synthetic child provider, then the SBType of "v"
1247+
// won't tell us anything about what might be displayed. So we can check
1248+
// if the first child's name is "[0]" and then we can say it is indexed.
11771249
const char *first_child_name = v.GetChildAtIndex(0).GetName();
11781250
if (first_child_name && strcmp(first_child_name, "[0]") == 0)
11791251
object.try_emplace("indexedVariables", actual_num_children);
11801252
}
11811253
}
1182-
EmplaceSafeString(object, "type", type_cstr ? type_cstr : NO_TYPENAME);
1254+
EmplaceSafeString(object, "type", desc.display_type_name);
11831255
if (varID != INT64_MAX)
11841256
object.try_emplace("id", varID);
11851257
if (v.MightHaveChildren())
11861258
object.try_emplace("variablesReference", variablesReference);
11871259
else
11881260
object.try_emplace("variablesReference", (int64_t)0);
1189-
lldb::SBStream evaluateStream;
1190-
v.GetExpressionPath(evaluateStream);
1191-
const char *evaluateName = evaluateStream.GetData();
1192-
if (evaluateName && evaluateName[0])
1193-
EmplaceSafeString(object, "evaluateName", std::string(evaluateName));
1194-
1195-
if (lldb::SBDeclaration decl = v.GetDeclaration(); decl.IsValid()) {
1196-
llvm::json::Object decl_obj;
1197-
if (lldb::SBFileSpec file = decl.GetFileSpec(); file.IsValid()) {
1198-
char path[PATH_MAX] = "";
1199-
if (file.GetPath(path, sizeof(path)) &&
1200-
lldb::SBFileSpec::ResolvePath(path, path, PATH_MAX)) {
1201-
decl_obj.try_emplace("path", std::string(path));
1202-
}
1203-
}
12041261

1205-
if (int line = decl.GetLine())
1206-
decl_obj.try_emplace("line", line);
1207-
if (int column = decl.GetColumn())
1208-
decl_obj.try_emplace("column", column);
1209-
1210-
object.try_emplace("declaration", std::move(decl_obj));
1211-
}
1262+
object.try_emplace("$__lldb_extensions", desc.GetVariableExtensionsJSON());
12121263
return llvm::json::Value(std::move(object));
12131264
}
12141265

0 commit comments

Comments
 (0)