diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSettings.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSettings.java index f3975fbfe..363f6742c 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSettings.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSettings.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017-2019 Microsoft Corporation and others. + * Copyright (c) 2017-2020 Microsoft Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -41,6 +41,7 @@ public final class DebugSettings { public StepFilters stepFilters = new StepFilters(); public ClassFilters exceptionFilters = new ClassFilters(); public boolean exceptionFiltersUpdated = false; + public int limitOfVariablesPerJdwpRequest = 100; public static DebugSettings getCurrent() { return current; diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/EvaluateRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/EvaluateRequestHandler.java index 5da8c24f5..2a498608e 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/EvaluateRequestHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/EvaluateRequestHandler.java @@ -1,5 +1,5 @@ /******************************************************************************* -* Copyright (c) 2017-2019 Microsoft Corporation and others. +* Copyright (c) 2017-2020 Microsoft Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -32,6 +32,7 @@ import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; import com.microsoft.java.debug.core.adapter.IEvaluationProvider; import com.microsoft.java.debug.core.adapter.variables.IVariableFormatter; +import com.microsoft.java.debug.core.adapter.variables.JavaLogicalStructure; import com.microsoft.java.debug.core.adapter.variables.JavaLogicalStructureManager; import com.microsoft.java.debug.core.adapter.variables.StackFrameReference; import com.microsoft.java.debug.core.adapter.variables.VariableDetailUtils; @@ -93,13 +94,14 @@ public CompletableFuture handle(Command command, Arguments arguments, Value sizeValue = null; if (value instanceof ArrayReference) { indexedVariables = ((ArrayReference) value).length(); - } else if (value instanceof ObjectReference && DebugSettings.getCurrent().showLogicalStructure - && engine != null - && JavaLogicalStructureManager.isIndexedVariable((ObjectReference) value)) { + } else if (value instanceof ObjectReference && DebugSettings.getCurrent().showLogicalStructure && engine != null) { try { - sizeValue = JavaLogicalStructureManager.getLogicalSize((ObjectReference) value, stackFrameReference.getThread(), engine); - if (sizeValue != null && sizeValue instanceof IntegerValue) { - indexedVariables = ((IntegerValue) sizeValue).value(); + JavaLogicalStructure structure = JavaLogicalStructureManager.getLogicalStructure((ObjectReference) value); + if (structure != null && structure.getSizeExpression() != null) { + sizeValue = structure.getSize((ObjectReference) value, stackFrameReference.getThread(), engine); + if (sizeValue != null && sizeValue instanceof IntegerValue) { + indexedVariables = ((IntegerValue) sizeValue).value(); + } } } catch (CancellationException | IllegalArgumentException | InterruptedException | ExecutionException | UnsupportedOperationException e) { @@ -108,7 +110,7 @@ public CompletableFuture handle(Command command, Arguments arguments, } } int referenceId = 0; - if (indexedVariables > 0 || (indexedVariables < 0 && VariableUtils.hasChildren(value, showStaticVariables))) { + if (indexedVariables > 0 || (indexedVariables < 0 && value instanceof ObjectReference)) { referenceId = context.getRecyclableIdPool().addObject(threadId, varProxy); } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/VariablesRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/VariablesRequestHandler.java index b3a9f5a05..70d5dae31 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/VariablesRequestHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/VariablesRequestHandler.java @@ -232,13 +232,14 @@ public CompletableFuture handle(Command command, Arguments arguments, Value sizeValue = null; if (value instanceof ArrayReference) { indexedVariables = ((ArrayReference) value).length(); - } else if (value instanceof ObjectReference && DebugSettings.getCurrent().showLogicalStructure - && evaluationEngine != null - && JavaLogicalStructureManager.isIndexedVariable((ObjectReference) value)) { + } else if (value instanceof ObjectReference && DebugSettings.getCurrent().showLogicalStructure && evaluationEngine != null) { try { - sizeValue = JavaLogicalStructureManager.getLogicalSize((ObjectReference) value, containerNode.getThread(), evaluationEngine); - if (sizeValue != null && sizeValue instanceof IntegerValue) { - indexedVariables = ((IntegerValue) sizeValue).value(); + JavaLogicalStructure structure = JavaLogicalStructureManager.getLogicalStructure((ObjectReference) value); + if (structure != null && structure.getSizeExpression() != null) { + sizeValue = structure.getSize((ObjectReference) value, containerNode.getThread(), evaluationEngine); + if (sizeValue != null && sizeValue instanceof IntegerValue) { + indexedVariables = ((IntegerValue) sizeValue).value(); + } } } catch (CancellationException | IllegalArgumentException | InterruptedException | ExecutionException | UnsupportedOperationException e) { logger.log(Level.INFO, @@ -267,7 +268,7 @@ public CompletableFuture handle(Command command, Arguments arguments, } int referenceId = 0; - if (indexedVariables > 0 || (indexedVariables < 0 && VariableUtils.hasChildren(value, showStaticVariables))) { + if (indexedVariables > 0 || (indexedVariables < 0 && value instanceof ObjectReference)) { VariableProxy varProxy = new VariableProxy(containerNode.getThread(), containerNode.getScope(), value, containerNode, evaluateName); referenceId = context.getRecyclableIdPool().addObject(containerNode.getThreadId(), varProxy); varProxy.setIndexedVariable(indexedVariables >= 0); @@ -290,6 +291,11 @@ public CompletableFuture handle(Command command, Arguments arguments, } list.add(typedVariables); } + + if (list.isEmpty() && containerNode.getProxiedVariable() instanceof ObjectReference) { + list.add(new Types.Variable("Class has no fields", "", null, 0, null)); + } + response.body = new Responses.VariablesResponseBody(list); return CompletableFuture.completedFuture(response); diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/variables/JavaLogicalStructure.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/variables/JavaLogicalStructure.java index 6bf65a9f8..0d065cf51 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/variables/JavaLogicalStructure.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/variables/JavaLogicalStructure.java @@ -33,6 +33,8 @@ public class JavaLogicalStructure { private final LogicalStructureExpression valueExpression; private final LogicalStructureExpression sizeExpression; private final LogicalVariable[] variables; + // Indicates whether the specified type is an interface. + private final boolean isInterface; /** * Constructor. @@ -47,9 +49,15 @@ public JavaLogicalStructure(String type, LogicalStructureExpression valueExpress */ public JavaLogicalStructure(String type, String fullyQualifiedName, LogicalStructureExpression valueExpression, LogicalStructureExpression sizeExpression, LogicalVariable[] variables) { + this(type, type, true, valueExpression, sizeExpression, variables); + } + + public JavaLogicalStructure(String type, String fullyQualifiedName, boolean isInterface, LogicalStructureExpression valueExpression, + LogicalStructureExpression sizeExpression, LogicalVariable[] variables) { this.valueExpression = valueExpression; this.type = type; this.fullyQualifiedName = fullyQualifiedName; + this.isInterface = isInterface; this.sizeExpression = sizeExpression; this.variables = variables; } @@ -84,18 +92,24 @@ public boolean providesLogicalStructure(ObjectReference obj) { } ClassType classType = (ClassType) variableType; - while (classType != null) { - if (Objects.equals(type, classType.name())) { - return true; - } - - classType = classType.superclass(); + if (Objects.equals(type, classType.name())) { + return true; } - List interfaceTypes = ((ClassType) variableType).allInterfaces(); - for (InterfaceType interfaceType : interfaceTypes) { - if (Objects.equals(type, interfaceType.name())) { - return true; + if (isInterface) { + List interfaceTypes = ((ClassType) variableType).allInterfaces(); + for (InterfaceType interfaceType : interfaceTypes) { + if (Objects.equals(type, interfaceType.name())) { + return true; + } + } + } else { + while (classType != null) { + if (Objects.equals(type, classType.name())) { + return true; + } + + classType = classType.superclass(); } } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableUtils.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableUtils.java index 578532bca..d53e2705c 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableUtils.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableUtils.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017-2019 Microsoft Corporation and others. + * Copyright (c) 2017-2020 Microsoft Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -15,6 +15,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -55,12 +56,11 @@ public static boolean hasChildren(Value value, boolean includeStatic) { if (value == null || !(value instanceof ObjectReference)) { return false; } - Type type = value.type(); + ReferenceType type = ((ObjectReference) value).referenceType(); if (type instanceof ArrayType) { return ((ArrayReference) value).length() > 0; } - return value.type() instanceof ReferenceType && ((ReferenceType) type).allFields().stream() - .filter(t -> includeStatic || !t.isStatic()).toArray().length > 0; + return type.allFields().stream().anyMatch(t -> includeStatic || !t.isStatic()); } /** @@ -74,7 +74,7 @@ public static boolean hasChildren(Value value, boolean includeStatic) { */ public static List listFieldVariables(ObjectReference obj, boolean includeStatic) throws AbsentInformationException { List res = new ArrayList<>(); - Type type = obj.type(); + ReferenceType type = obj.referenceType(); if (type instanceof ArrayType) { int arrayIndex = 0; boolean isUnboundedArrayType = Objects.equals(type.signature(), "[Ljava/lang/Object;"); @@ -85,7 +85,7 @@ public static List listFieldVariables(ObjectReference obj, boolean inc } return res; } - List fields = obj.referenceType().allFields().stream().filter(t -> includeStatic || !t.isStatic()) + List fields = type.allFields().stream().filter(t -> includeStatic || !t.isStatic()) .sorted((a, b) -> { try { boolean v1isStatic = a.isStatic(); @@ -102,11 +102,16 @@ public static List listFieldVariables(ObjectReference obj, boolean inc return -1; } }).collect(Collectors.toList()); - fields.forEach(f -> { - Variable var = new Variable(f.name(), obj.getValue(f)); - var.field = f; - res.add(var); - }); + + bulkFetchValues(fields, DebugSettings.getCurrent().limitOfVariablesPerJdwpRequest, (currentPage -> { + Map fieldValues = obj.getValues(currentPage); + for (Field currentField : currentPage) { + Variable var = new Variable(currentField.name(), fieldValues.get(currentField)); + var.field = currentField; + res.add(var); + } + })); + return res; } @@ -155,13 +160,18 @@ public static List listLocalVariables(StackFrame stackFrame) throws Ab return res; } try { - List localVariables = stackFrame.visibleVariables(); - Map values = stackFrame.getValues(localVariables); - for (LocalVariable localVariable : localVariables) { - Variable var = new Variable(localVariable.name(), values.get(localVariable)); - var.local = localVariable; - res.add(var); - } + List visibleVariables = stackFrame.visibleVariables(); + // When using the API StackFrame.getValues() to batch fetch the variable values, the JDI + // probably throws timeout exception if the variables to be passed at one time are large. + // So use paging to fetch the values in chunks. + bulkFetchValues(visibleVariables, DebugSettings.getCurrent().limitOfVariablesPerJdwpRequest, (currentPage -> { + Map values = stackFrame.getValues(currentPage); + for (LocalVariable localVariable : currentPage) { + Variable var = new Variable(localVariable.name(), values.get(localVariable)); + var.local = localVariable; + res.add(var); + } + })); } catch (AbsentInformationException ex) { // avoid listing variable on native methods @@ -228,11 +238,16 @@ public static Variable getThisVariable(StackFrame stackFrame) { public static List listStaticVariables(StackFrame stackFrame) { List res = new ArrayList<>(); ReferenceType type = stackFrame.location().declaringType(); - type.allFields().stream().filter(TypeComponent::isStatic).forEach(field -> { - Variable staticVar = new Variable(field.name(), type.getValue(field)); - staticVar.field = field; - res.add(staticVar); - }); + List fields = type.allFields().stream().filter(TypeComponent::isStatic).collect(Collectors.toList()); + bulkFetchValues(fields, DebugSettings.getCurrent().limitOfVariablesPerJdwpRequest, (currentPage -> { + Map fieldValues = type.getValues(currentPage); + for (Field currentField : currentPage) { + Variable var = new Variable(currentField.name(), fieldValues.get(currentField)); + var.field = currentField; + res.add(var); + } + })); + return res; } @@ -289,6 +304,18 @@ public static String getEvaluateName(String name, String containerName, boolean return String.format("%s.%s", containerName, name); } + private static void bulkFetchValues(List elements, int numberPerPage, Consumer> consumer) { + int size = elements.size(); + numberPerPage = numberPerPage < 1 ? 1 : numberPerPage; + int page = size / numberPerPage + Math.min(size % numberPerPage, 1); + for (int i = 0; i < page; i++) { + int pageStart = i * numberPerPage; + int pageEnd = Math.min(pageStart + numberPerPage, size); + List currentPage = elements.subList(pageStart, pageEnd); + consumer.accept(currentPage); + } + } + private VariableUtils() { }