Skip to content

Commit f704199

Browse files
authored
Support lazy loading object values from toString() (microsoft#401)
1 parent 798d259 commit f704199

File tree

4 files changed

+89
-15
lines changed

4 files changed

+89
-15
lines changed

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/VariablesRequestHandler.java

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import com.microsoft.java.debug.core.protocol.Requests.Arguments;
4848
import com.microsoft.java.debug.core.protocol.Requests.Command;
4949
import com.microsoft.java.debug.core.protocol.Requests.VariablesArguments;
50+
import com.microsoft.java.debug.core.protocol.Types.VariablePresentationHint;
5051
import com.microsoft.java.debug.core.protocol.Responses;
5152
import com.microsoft.java.debug.core.protocol.Types;
5253
import com.sun.jdi.AbsentInformationException;
@@ -94,6 +95,15 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
9495
}
9596

9697
VariableProxy containerNode = (VariableProxy) container;
98+
99+
if (containerNode.isLazyVariable() && DebugSettings.getCurrent().showToString) {
100+
Types.Variable typedVariable = this.resolveLazyVariable(context, containerNode, variableFormatter, options, evaluationEngine);
101+
if (typedVariable != null) {
102+
list.add(typedVariable);
103+
response.body = new Responses.VariablesResponseBody(list);
104+
return CompletableFuture.completedFuture(response);
105+
}
106+
}
97107
List<Variable> childrenList = new ArrayList<>();
98108
IStackFrameManager stackFrameManager = context.getStackFrameManager();
99109
String containerEvaluateName = containerNode.getEvaluateName();
@@ -266,10 +276,9 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
266276
}
267277
}
268278

269-
int referenceId = 0;
279+
VariableProxy varProxy = null;
270280
if (indexedVariables > 0 || (indexedVariables < 0 && value instanceof ObjectReference)) {
271-
VariableProxy varProxy = new VariableProxy(containerNode.getThread(), containerNode.getScope(), value, containerNode, evaluateName);
272-
referenceId = context.getRecyclableIdPool().addObject(containerNode.getThreadId(), varProxy);
281+
varProxy = new VariableProxy(containerNode.getThread(), containerNode.getScope(), value, containerNode, evaluateName);
273282
varProxy.setIndexedVariable(indexedVariables >= 0);
274283
varProxy.setUnboundedType(javaVariable.isUnboundedType());
275284
}
@@ -296,26 +305,38 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
296305
typeString = "";
297306
}
298307

299-
Types.Variable typedVariables = new Types.Variable(name, valueString, typeString, referenceId, evaluateName);
300-
typedVariables.indexedVariables = Math.max(indexedVariables, 0);
301-
302308
String detailsValue = null;
303309
if (hasErrors) {
304310
// If failed to resolve the variable value, skip the details info as well.
305311
} else if (sizeValue != null) {
306312
detailsValue = "size=" + variableFormatter.valueToString(sizeValue, options);
307313
} else if (DebugSettings.getCurrent().showToString) {
308-
try {
309-
detailsValue = VariableDetailUtils.formatDetailsValue(value, containerNode.getThread(), variableFormatter, options, evaluationEngine);
310-
} catch (OutOfMemoryError e) {
311-
logger.log(Level.SEVERE, "Failed to compute the toString() value of a large object", e);
312-
detailsValue = "<Unable to display the details of a large object>";
313-
} catch (Exception e) {
314-
logger.log(Level.SEVERE, "Failed to compute the toString() value", e);
315-
detailsValue = "<Failed to resolve the variable details due to \"" + e.getMessage() + "\">";
314+
if (VariableDetailUtils.isLazyLoadingSupported(value) && varProxy != null) {
315+
varProxy.setLazyVariable(true);
316+
} else {
317+
try {
318+
detailsValue = VariableDetailUtils.formatDetailsValue(value, containerNode.getThread(), variableFormatter, options, evaluationEngine);
319+
} catch (OutOfMemoryError e) {
320+
logger.log(Level.SEVERE, "Failed to compute the toString() value of a large object", e);
321+
detailsValue = "<Unable to display the details of a large object>";
322+
} catch (Exception e) {
323+
logger.log(Level.SEVERE, "Failed to compute the toString() value", e);
324+
detailsValue = "<Failed to resolve the variable details due to \"" + e.getMessage() + "\">";
325+
}
316326
}
317327
}
318328

329+
int referenceId = 0;
330+
if (varProxy != null) {
331+
referenceId = context.getRecyclableIdPool().addObject(containerNode.getThreadId(), varProxy);
332+
}
333+
334+
Types.Variable typedVariables = new Types.Variable(name, valueString, typeString, referenceId, evaluateName);
335+
typedVariables.indexedVariables = Math.max(indexedVariables, 0);
336+
if (varProxy != null && varProxy.isLazyVariable()) {
337+
typedVariables.presentationHint = new VariablePresentationHint(true);
338+
}
339+
319340
if (detailsValue != null) {
320341
typedVariables.value = typedVariables.value + " " + detailsValue;
321342
}
@@ -331,6 +352,25 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
331352
return CompletableFuture.completedFuture(response);
332353
}
333354

355+
private Types.Variable resolveLazyVariable(IDebugAdapterContext context, VariableProxy containerNode, IVariableFormatter variableFormatter,
356+
Map<String, Object> options, IEvaluationProvider evaluationEngine) {
357+
VariableProxy valueReferenceProxy = new VariableProxy(containerNode.getThread(), containerNode.getScope(),
358+
containerNode.getProxiedVariable(), null /** container */, containerNode.getEvaluateName());
359+
valueReferenceProxy.setIndexedVariable(containerNode.isIndexedVariable());
360+
valueReferenceProxy.setUnboundedType(containerNode.isUnboundedType());
361+
int referenceId = context.getRecyclableIdPool().addObject(containerNode.getThreadId(), valueReferenceProxy);
362+
// this proxiedVariable is intermediate object, see https://github.com/microsoft/vscode/issues/135147#issuecomment-1076240074
363+
Object proxiedVariable = containerNode.getProxiedVariable();
364+
if (proxiedVariable instanceof ObjectReference) {
365+
ObjectReference variable = (ObjectReference) proxiedVariable;
366+
String valueString = variableFormatter.valueToString(variable, options);
367+
String detailString = VariableDetailUtils.formatDetailsValue(variable, containerNode.getThread(), variableFormatter, options,
368+
evaluationEngine);
369+
return new Types.Variable("", valueString + " " + detailString, "", referenceId, containerNode.getEvaluateName());
370+
}
371+
return null;
372+
}
373+
334374
private Set<String> getDuplicateNames(Collection<String> list) {
335375
Set<String> result = new HashSet<>();
336376
Set<String> set = new HashSet<>();

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableDetailUtils.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,4 +154,18 @@ private static boolean isClassType(Value value, String typeName) {
154154

155155
return Objects.equals(((ObjectReference) value).type().name(), typeName);
156156
}
157+
158+
public static boolean isLazyLoadingSupported(Value value) {
159+
if (isClassType(value, STRING_TYPE)) {
160+
return false;
161+
}
162+
if (!(value instanceof ObjectReference)) {
163+
return false;
164+
}
165+
String inheritedType = findInheritedType(value, COLLECTION_TYPES);
166+
if (inheritedType == null && !containsToStringMethod((ObjectReference) value)) {
167+
return false;
168+
}
169+
return true;
170+
}
157171
}

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableProxy.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public class VariableProxy {
2424
private final String evaluateName;
2525
private boolean isIndexedVariable;
2626
private boolean isUnboundedType = false;
27+
private boolean isLazyVariable = false;
2728

2829
/**
2930
* Create a variable reference.
@@ -75,7 +76,8 @@ public boolean equals(Object obj) {
7576
}
7677
VariableProxy other = (VariableProxy) obj;
7778
return Objects.equals(scopeName, other.scopeName) && Objects.equals(getThreadId(), other.getThreadId())
78-
&& Objects.equals(variable, other.variable) && Objects.equals(evaluateName, other.evaluateName);
79+
&& Objects.equals(variable, other.variable) && Objects.equals(evaluateName, other.evaluateName)
80+
&& Objects.equals(isLazyVariable, other.isLazyVariable);
7981
}
8082

8183
public long getThreadId() {
@@ -109,4 +111,13 @@ public boolean isUnboundedType() {
109111
public void setUnboundedType(boolean isUnboundedType) {
110112
this.isUnboundedType = isUnboundedType;
111113
}
114+
115+
public boolean isLazyVariable() {
116+
return isLazyVariable;
117+
}
118+
119+
public void setLazyVariable(boolean isLazyVariable) {
120+
this.isLazyVariable = isLazyVariable;
121+
}
122+
112123
}

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Types.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ public static class Variable {
9090
public int namedVariables;
9191
public int indexedVariables;
9292
public String evaluateName;
93+
public VariablePresentationHint presentationHint;
9394

9495
/**
9596
* Constructor.
@@ -343,6 +344,14 @@ public static class ExceptionDetails {
343344
public ExceptionDetails[] innerException;
344345
}
345346

347+
public static class VariablePresentationHint {
348+
public boolean lazy;
349+
350+
public VariablePresentationHint(boolean lazy) {
351+
this.lazy = lazy;
352+
}
353+
}
354+
346355
public static class Capabilities {
347356
public boolean supportsConfigurationDoneRequest;
348357
public boolean supportsHitConditionalBreakpoints;

0 commit comments

Comments
 (0)