From ac86e59bd7d72027e8138150c148b1f5bd3510cb Mon Sep 17 00:00:00 2001 From: Martin Entlicher Date: Mon, 16 May 2022 13:20:16 +0200 Subject: [PATCH] Adapt Native Image debugging for CE images. --- .../debugger/displayer/DynamicHub.java | 126 +++++ .../displayer/JavaFrameDisplayer.java | 31 +- .../displayer/JavaVariablesDisplayer.java | 522 ++++++++++++++---- .../nativeimage/debugger/displayer/Utils.java | 146 +++++ 4 files changed, 721 insertions(+), 104 deletions(-) create mode 100644 java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/displayer/DynamicHub.java create mode 100644 java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/displayer/Utils.java diff --git a/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/displayer/DynamicHub.java b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/displayer/DynamicHub.java new file mode 100644 index 000000000000..542a4469ed19 --- /dev/null +++ b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/displayer/DynamicHub.java @@ -0,0 +1,126 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.java.nativeimage.debugger.displayer; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.modules.nativeimage.api.debug.NIVariable; + +import static org.netbeans.modules.java.nativeimage.debugger.displayer.JavaVariablesDisplayer.PRIVATE; +import static org.netbeans.modules.java.nativeimage.debugger.displayer.JavaVariablesDisplayer.PUBLIC; +import static org.netbeans.modules.java.nativeimage.debugger.displayer.Utils.findChild; +import static org.netbeans.modules.java.nativeimage.debugger.displayer.Utils.getVarsByName; + +/** + * Hub of the native image object. + */ +final class DynamicHub { + + private static final String HUB = "hub"; // NOI18N + private static final String HUB_TYPE = "hubType"; // NOI18N + private static final int HUB_TYPE_INSTANCE = 2; // Instances are less than or equal to 2 + private static final int HUB_TYPE_ARRAY = 4; // Arrays are greater than or equal to 4 + private static final String OBJ_HEADER = "_objhdr"; // NOI18N + private static final String NAME = "name"; // NOI18N + + private final NIVariable hub; + private final Map childrenByName; + + enum HubType { + + OBJECT, + ARRAY; + + static HubType getFrom(int hubType) { + if (hubType <= HUB_TYPE_INSTANCE) { + return OBJECT; + } + if (hubType >= HUB_TYPE_ARRAY) { + return ARRAY; + } + return null; + } + } + + private DynamicHub(NIVariable hub) { + this.hub = hub; + this.childrenByName = getVarsByName(hub.getChildren()); + } + + @CheckForNull + static DynamicHub find(NIVariable var) { + NIVariable object = findObjectType(var); + if (object == null) { + return null; + } + NIVariable[] children = object.getChildren(); + NIVariable hub = findChild(children, OBJ_HEADER, PUBLIC, HUB, Class.class.getName(), PRIVATE); + if (hub != null) { + return new DynamicHub(hub); + } else { + return null; + } + } + + @CheckForNull + private static NIVariable findObjectType(NIVariable var) { + NIVariable[] children = var.getChildren(); + if (children.length < 1) { + return null; + } + if (children[0].getName().equals(Object.class.getName())) { + return children[0]; + } + // Prevent from infinite recursion + Set visitedNames = new HashSet<>(); + var = children[0]; + do { + children = var.getChildren(); + if (children.length < 1) { + return null; + } + var = children[0]; + if (var.getName().equals(Object.class.getName())) { + return var; + } + } while (visitedNames.add(var.getName())); + return null; + } + + @CheckForNull + HubType getType() { + NIVariable hubTypeVar = childrenByName.get(HUB_TYPE); + if (hubTypeVar == null) { + return null; + } + try { + int hubType = Integer.parseInt(hubTypeVar.getValue()); + return HubType.getFrom(hubType); + } catch (NumberFormatException ex) { + return null; + } + } + + @CheckForNull + NIVariable findClassNameVar() { + return childrenByName.get(NAME); + } +} diff --git a/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/displayer/JavaFrameDisplayer.java b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/displayer/JavaFrameDisplayer.java index 6c40c8b7244d..9cd025427a70 100644 --- a/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/displayer/JavaFrameDisplayer.java +++ b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/displayer/JavaFrameDisplayer.java @@ -77,17 +77,30 @@ private static String getDisplayName(NIFrame frame) { if (methodEnd < 0) { methodEnd = functionName.length(); } - int methodStart = functionName.lastIndexOf('.', methodEnd); - if (methodStart < 0) { - clsMethod = functionName.substring(0, methodEnd); - } else { - int clsStart = functionName.lastIndexOf('.', methodStart - 1); + int methodStart = functionName.indexOf("::"); + if (methodStart > 0) { + int clsStart = functionName.lastIndexOf('.', methodStart); if (clsStart < 0) { clsStart = 0; } else { clsStart++; } - clsMethod = functionName.substring(clsStart, methodEnd); + String clazz = functionName.substring(clsStart, methodStart); + String method = functionName.substring(methodStart + 2, methodEnd); + clsMethod = clazz + '.' + method; + } else { + methodStart = functionName.lastIndexOf('.', methodEnd); + if (methodStart < 0) { + clsMethod = functionName.substring(0, methodEnd); + } else { + int clsStart = functionName.lastIndexOf('.', methodStart - 1); + if (clsStart < 0) { + clsStart = 0; + } else { + clsStart++; + } + clsMethod = functionName.substring(clsStart, methodEnd); + } } int line = frame.getLine(); if (line < 0) { @@ -104,6 +117,7 @@ private static String getDescription(NIFrame frame) { methodEnd = functionName.length(); } String clsMethod = functionName.substring(0, methodEnd); + clsMethod = clsMethod.replace("::", "."); int line = frame.getLine(); if (line < 0) { return clsMethod; @@ -119,7 +133,10 @@ private URI getSourceURI(NIFrame frame) { methodEnd = functionName.length(); } if (methodEnd > 0) { - int methodStart = functionName.lastIndexOf('.', methodEnd); + int methodStart = functionName.indexOf("::"); + if (methodStart < 0) { + methodStart = functionName.lastIndexOf('.', methodEnd); + } if (methodStart > 0) { String className = functionName.substring(0, methodStart); URI uri = findClassURI(sourcePath, className); diff --git a/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/displayer/JavaVariablesDisplayer.java b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/displayer/JavaVariablesDisplayer.java index a4009387e578..f0e2755265af 100644 --- a/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/displayer/JavaVariablesDisplayer.java +++ b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/displayer/JavaVariablesDisplayer.java @@ -25,16 +25,21 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.WeakHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; +import static org.netbeans.modules.java.nativeimage.debugger.displayer.Utils.findChild; +import static org.netbeans.modules.java.nativeimage.debugger.displayer.Utils.getVarsByName; +import static org.netbeans.modules.java.nativeimage.debugger.displayer.Utils.quoteJavaTypes; import org.netbeans.modules.nativeimage.api.debug.EvaluateException; import org.netbeans.modules.nativeimage.api.debug.NIDebugger; import org.netbeans.modules.nativeimage.api.debug.NIFrame; import org.netbeans.modules.nativeimage.api.debug.NIVariable; import org.netbeans.modules.nativeimage.spi.debug.filters.VariableDisplayer; +import org.openide.util.NbBundle; /** * @@ -42,22 +47,29 @@ */ public final class JavaVariablesDisplayer implements VariableDisplayer { - private static final String HUB = "__hub__"; - private static final String ARRAY = "__array__"; - private static final String ARRAY_LENGTH = "__length__"; - private static final String COMPRESSED_REF_REFIX = "_z_."; - private static final String PUBLIC = "public"; - private static final String STRING_VALUE = "value"; - private static final String STRING_CODER = "coder"; - private static final String HASH = "hash"; - private static final String NAME = "name"; - private static final String UNSET = ""; + private static final String HUB = "__hub__"; // NOI18N + private static final String ARRAY = "__array__"; // NOI18N + private static final String ARRAY_LENGTH = "__length__"; // NOI18N + private static final String ARRAY_LENGTH_CE = "len"; // NOI18N + private static final String ARRAY_DATA_CE = "data"; // NOI18N + private static final String OBJ_HEADER_CE = "_objhdr"; // NOI18N + private static final String COMPRESSED_REF_REFIX = "_z_."; // NOI18N + static final String PUBLIC = "public"; // NOI18N + static final String PRIVATE = "private"; // NOI18N + private static final String PROTECTED = "protected"; // NOI18N + private static final String STRING_VALUE = "value"; // NOI18N + private static final String STRING_CODER = "coder"; // NOI18N + private static final String HASH = "hash"; // NOI18N + private static final String NAME = "name"; // NOI18N + private static final String UNSET = ""; // NOI18N private static final String[] STRING_TYPES = new String[] { String.class.getName(), StringBuilder.class.getName(), StringBuffer.class.getName() }; // Variable names with this prefix contain space-separated variable name and expression path private static final String PREFIX_VAR_PATH = "{ "; + private static final Logger LOG = Logger.getLogger(JavaVariablesDisplayer.class.getName()); + private NIDebugger debugger; private final Map variablePaths = Collections.synchronizedMap(new WeakHashMap<>()); @@ -122,15 +134,42 @@ public NIVariable[] displayed(NIVariable[] variables) { if (likeString) { displayedVar = new StringVar(var, name, path, type, isString ? null : subChildren); } else { + DynamicHub hub; if (children.length == 1 && PUBLIC.equals(children[0].getName())) { // Object children - displayedVar = new ObjectVar(var, name, path, subChildren); + displayedVar = new ObjectVarEE(var, name, path, subChildren); + } else if ((hub = DynamicHub.find(var)) != null) { + NIVariable objectVar = null; + DynamicHub.HubType hubType = hub.getType(); + if (hubType != null) { + switch (hubType) { + case OBJECT: + objectVar = new ObjectVarCE(var, name, path, children, hub); + break; + case ARRAY: + NIVariable lengthVar = findChild(children, PUBLIC, ARRAY_LENGTH_CE); + NIVariable dataVar = findChild(children, PUBLIC, ARRAY_DATA_CE); + if (lengthVar != null || dataVar != null) { + objectVar = new ArrayVarCE(var, name, lengthVar, dataVar, hub); + break; + } + } + } + if (objectVar != null) { + displayedVar = objectVar; + } else { + // ordinary var: + displayedVar = new Var(var, name, path); + } } else { displayedVar = new Var(var, name, path); } } } } + if (LOG.isLoggable(Level.FINE)) { + LOG.fine(var.getName() + " => " + displayedVar + ((displayedVar != null) ? "[" + displayedVar.getName() + "]" : "")); + } if (displayedVar != var) { synchronized (variablePaths) { variablePaths.put(displayedVar, variablePaths.get(var)); @@ -155,7 +194,7 @@ private static String getSimpleType(String type) { type = displayType(type); for (int i = 0; i < type.length(); i++) { char c = type.charAt(i); - if (c != '.' && !Character.isJavaIdentifierPart(c)) { + if (c != '.' && c != '[' && c != ']' && !Character.isJavaIdentifierPart(c)) { return type.substring(0, i); } } @@ -187,21 +226,6 @@ private static int getTypeSize(String type) { } } - private static Map getVarsByName(NIVariable[] vars) { - switch (vars.length) { - case 0: - return Collections.emptyMap(); - case 1: - return Collections.singletonMap(vars[0].getName(), vars[0]); - default: - Map varsByName = new HashMap<>(vars.length); - for (NIVariable var : vars) { - varsByName.put(var.getName(), var); - } - return varsByName; - } - } - private static NIVariable[] restrictChildren(NIVariable[] children, int from, int to) { if (from > 0 || to < children.length) { to = Math.min(to, children.length); @@ -249,7 +273,7 @@ private static String getNameOrIndex(String name) { return name; } - private String readArray(NIVariable lengthVariable, int itemSize) { + private String readArrayEE(NIVariable lengthVariable, int itemSize) { int length = Integer.parseInt(lengthVariable.getValue()); String expressionPath = getExpressionPath(lengthVariable); if (expressionPath != null && !expressionPath.isEmpty()) { @@ -259,14 +283,13 @@ private String readArray(NIVariable lengthVariable, int itemSize) { return null; } - private String readArray(NIVariable lengthVariable, int offset, int itemSize) { - int length = Integer.parseInt(lengthVariable.getValue()); - String expressionPath = getExpressionPath(lengthVariable); - if (expressionPath != null && !expressionPath.isEmpty()) { - String addressExpr = "&" + expressionPath; - return debugger.readMemory(addressExpr, 4 + offset, length * itemSize); // length has 4 bytes + private String readArrayCE(String arrayPath, int length, int itemSize) { + if (arrayPath != null && !arrayPath.isEmpty()) { + String addressExpr = "&" + arrayPath; + return debugger.readMemory(addressExpr, 0, length * itemSize); + } else { + return null; } - return null; } private static NIVariable[] getObjectChildren(NIVariable[] children, int from, int to) { @@ -289,15 +312,6 @@ private static NIVariable[] getObjectChildren(NIVariable[] children, int from, i return restrictChildren(children, from, to); } - private static NIVariable findChild(String name, NIVariable[] children) { - for (NIVariable var : children) { - if (name.equals(var.getName())) { - return var; - } - } - return null; - } - private static boolean isPrimitiveArray(String type) { int i = type.indexOf(' '); if (i < 0) { @@ -322,6 +336,7 @@ private static boolean isPrimitiveArray(String type) { private String getExpressionPath(NIVariable var) { String path = var.getExpressionPath(); if (!path.isEmpty()) { + path = quoteJavaTypes(path); return path; } else { return createExpressionPath(var); @@ -340,7 +355,7 @@ private String createExpressionPath(NIVariable var) { return path; } else { String parentPath = createExpressionPath(parent); - if (PUBLIC.equals(path)) { + if (PUBLIC.equals(path) || PRIVATE.equals(path) || path.contains(" ")) { return parentPath; } else { return parentPath + '.' + path; @@ -372,6 +387,14 @@ public String getType() { @Override public String getValue() { NIVariable pub = getVarsByName(var.getChildren()).get(PUBLIC); + if (pub != null && getVarsByName(pub.getChildren()).get(HUB) != null) { + return getValueEE(pub); + } else { + return getValueCE(); + } + } + + private String getValueEE(NIVariable pub) { Map varChildren = getVarsByName(pub.getChildren()); Map arrayInfo = getVarsByName(varChildren.get(STRING_VALUE).getChildren()); arrayInfo = getVarsByName(arrayInfo.get(PUBLIC).getChildren()); @@ -379,13 +402,72 @@ public String getValue() { NIVariable lengthVariable = arrayInfo.get(ARRAY_LENGTH); String lengthStr = lengthVariable.getValue(); if (lengthStr.isEmpty()) { - return ""; + return "?"; } int length = Integer.parseInt(lengthStr); if (length <= 0) { - return ""; + return "?"; + } + NIVariable coderVar = varChildren.get(STRING_CODER); + int coder = parseCoder(coderVar); + String hexArray = readArrayEE(lengthVariable, 2); + if (hexArray != null) { + return parseStringFromHEX(hexArray, length, coder); + } else { // legacy code for older gdb version (less than 10.x) + String arrayExpression = JavaVariablesDisplayer.this.getExpressionPath(arrayVariable); + return parseStringFromArray(arrayExpression, length, coder); + } + } + + private String getValueCE() { + Map varPrivChildren = getVarsByName(var.getChildren()); + NIVariable priv = varPrivChildren.get(PRIVATE); + if (priv == null) { + NIVariable strVar = varPrivChildren.get(String.class.getName()); + if (strVar != null) { + priv = getVarsByName(strVar.getChildren()).get(PRIVATE); + } + if (priv == null) { + return "?"; + } } + Map varChildren = getVarsByName(priv.getChildren()); + NIVariable value = varChildren.get(STRING_VALUE); NIVariable coderVar = varChildren.get(STRING_CODER); + int coder = parseCoder(coderVar); + if (value == null || value.getNumChildren() == 0) { + return "?"; + } + NIVariable valueTypeChild = value.getChildren()[0]; + NIVariable valuePublic = getVarsByName(valueTypeChild.getChildren()).get(PUBLIC); + if (valuePublic == null) { + return ""; + } + Map arrayChildren = getVarsByName(valuePublic.getChildren()); + NIVariable lengthVar = arrayChildren.get(ARRAY_LENGTH_CE); + NIVariable dataVar = arrayChildren.get(ARRAY_DATA_CE); + if (lengthVar == null || dataVar == null) { + return "?"; + } + String lengthStr = lengthVar.getValue(); + if (lengthStr.isEmpty()) { + return ""; + } + int length = Integer.parseInt(lengthStr); + String arrayPath = JavaVariablesDisplayer.this.getExpressionPath(value); + String hexArray = null; + if (arrayPath != null && !arrayPath.isEmpty()) { + arrayPath += "." + ARRAY_DATA_CE; + hexArray = readArrayCE(arrayPath, length, 2); + } + if (hexArray != null) { + return parseStringFromHEX(hexArray, length, coder); + } else { // legacy code for older gdb version (less than 10.x) + return parseStringFromArray(arrayPath, length, coder); + } + } + + private int parseCoder(NIVariable coderVar) { int coder = -1; if (coderVar != null) { String coderStr = coderVar.getValue(); @@ -398,47 +480,7 @@ public String getValue() { } catch (NumberFormatException ex) { } } - String hexArray = readArray(lengthVariable, 0, 2); - if (hexArray != null) { - switch (coder) { - case 0: // Compressed String on JDK 9+ - return parseLatin1(hexArray, length); - case 1: // UTF-16 String on JDK 9+ - return parseUTF16(hexArray, length/2); - default: // UTF-16 String on JDK 8 - return parseUTF16(hexArray, length); - } - } else { // legacy code for older gdb version (less than 10.x) - String arrayExpression = JavaVariablesDisplayer.this.getExpressionPath(arrayVariable); //getArrayExpression(arrayVariable); - NIFrame frame = var.getFrame(); - try { - NIVariable charVar = debugger.evaluate(arrayExpression + "[0]", null, frame); - if ("byte".equals(charVar.getType())) { - // bytes to be parsed to String - switch (coder) { - case 0: // Compressed String on JDK 9+ - return parseLatin1(arrayExpression, frame, length); - case 1: // UTF-16 String on JDK 9+ - return parseUTF16(arrayExpression, frame, length/2); - default: // UTF-16 String on JDK 8 - return parseUTF16(arrayExpression, frame, length); - } - } else { - char[] characters = new char[length]; - for(int i = 0; ; ) { - String charStr = charVar.getValue(); - characters[i] = parseCharacter(charStr); - if (++i >= length) { - break; - } - charVar = debugger.evaluate(arrayExpression + "[" + i + "]", null, frame); - } - return new String(characters); - } - } catch (EvaluateException ex) { - return ex.getLocalizedMessage(); - } - } + return coder; } private char parseCharacter(String charValue) { @@ -510,6 +552,17 @@ private byte getByteFromChar(String charValue, int[] pos) { } } + private String parseStringFromHEX(String hexArray, int length, int coder) { + switch (coder) { + case 0: // Compressed String on JDK 9+ + return parseLatin1(hexArray, length); + case 1: // UTF-16 String on JDK 9+ + return parseUTF16(hexArray, length/2); + default: // UTF-16 String on JDK 8 + return parseUTF16(hexArray, length); + } + } + private String parseUTF16(String hexArray, int length) { CharsetDecoder cd = Charset.forName("utf-16").newDecoder(); // NOI18N ByteBuffer buffer = ByteBuffer.allocate(2); @@ -558,6 +611,37 @@ private byte parseByte(String hexArray, int offset) { return (byte) (Integer.parseInt(hex, 16) & 0xFF); } + private String parseStringFromArray(String arrayExpression, int length, int coder) { + NIFrame frame = var.getFrame(); + try { + NIVariable charVar = debugger.evaluate(arrayExpression + "[0]", null, frame); + if ("byte".equals(charVar.getType())) { + // bytes to be parsed to String + switch (coder) { + case 0: // Compressed String on JDK 9+ + return parseLatin1(arrayExpression, frame, length); + case 1: // UTF-16 String on JDK 9+ + return parseUTF16(arrayExpression, frame, length/2); + default: // UTF-16 String on JDK 8 + return parseUTF16(arrayExpression, frame, length); + } + } else { + char[] characters = new char[length]; + for(int i = 0; ; ) { + String charStr = charVar.getValue(); + characters[i] = parseCharacter(charStr); + if (++i >= length) { + break; + } + charVar = debugger.evaluate(arrayExpression + "[" + i + "]", null, frame); + } + return new String(characters); + } + } catch (EvaluateException ex) { + return ex.getLocalizedMessage(); + } + } + private String parseUTF16(String arrayExpression, NIFrame frame, int length) throws EvaluateException { CharsetDecoder cd = Charset.forName("utf-16").newDecoder(); // NOI18N ByteBuffer buffer = ByteBuffer.allocate(2); @@ -727,12 +811,126 @@ public NIVariable[] getChildren(int from, int to) { } - private class ObjectVar extends AbstractVar { + private class ArrayVarCE extends AbstractVar { - private final NIVariable[] children; + private final NIVariable lengthVariable; + private final int length; + private final NIVariable array; + + ArrayVarCE(NIVariable var, String name, NIVariable lengthVariable, NIVariable dataVar, DynamicHub hub) { + super(var, name, ""); + this.lengthVariable = lengthVariable; + int arrayLength; + try { + arrayLength = Integer.parseInt(lengthVariable.getValue()); + } catch (NumberFormatException ex) { + arrayLength = 0; + } + this.length = arrayLength; + this.array = dataVar; + } + + @Override + public NIFrame getFrame() { + return var.getFrame(); + } + + @Override + public NIVariable getParent() { + return var.getParent(); + } + + @Override + public String getType() { + return displayType(var.getType()); + } + + @Override + public String getValue() { + String value = var.getValue(); + if (value.startsWith("@")) { + value = getType() + value; + } + return value + "(length="+length+")"; + } + + @Override + public int getNumChildren() { + return length; + } + + @Override + public NIVariable[] getChildren(int from, int to) { + if (from >= 0) { + to = Math.min(to, length); + } else { + from = 0; + to = length; + } + if (from >= to) { + return new NIVariable[]{}; + } + + String arrayAddress = null; + if (isPrimitiveArray(array.getType())) { + String expressionPath = JavaVariablesDisplayer.this.getExpressionPath(lengthVariable); + if (expressionPath != null && !expressionPath.isEmpty()) { + String addressExpr = "&" + expressionPath; + NIVariable addressVariable; + try { + addressVariable = debugger.evaluate(addressExpr, null, lengthVariable.getFrame()); + } catch (EvaluateException ex) { + addressVariable = null; + } + if (addressVariable != null) { + String address = addressVariable.getValue(); + address = address.toLowerCase(); + if (address.startsWith("0x")) { + arrayAddress = address; + } + } + } + } + NIVariable[] elements = new NIVariable[to - from]; + try { + if (arrayAddress != null) { + int offset = (getTypeSize(getType()) == 8) ? 8 : 4; + String itemExpression = "*(((" + getSimpleType(getType()) + "*)(" + arrayAddress + "+"+offset+"))+"; + int size = getTypeSize(getType()); + for (int i = from; i < to; i++) { + String expr = itemExpression + i + ")"; + NIVariable element = debugger.evaluate(expr, Integer.toString(i), var.getFrame()); + // When gdb could retrieve variable address, it did resolve the expression path. + // Thus there is no need to remember it in variablePaths. + elements[i - from] = element; + } + } else { + String arrayExpression = JavaVariablesDisplayer.this.getExpressionPath(array); + for (int i = from; i < to; i++) { + String expr = arrayExpression + "[" + i + "]"; + String namePath = PREFIX_VAR_PATH + Integer.toString(i) + ' ' + expr; + NIVariable element = debugger.evaluate(expr, namePath, var.getFrame()); + variablePaths.put(element, expr); + elements[i - from] = element; + } + } + } catch (EvaluateException ex) { + return new NIVariable[]{}; + } + return elements; + } + + } - ObjectVar(NIVariable var, String name, String path, NIVariable[] children) { + private class ObjectVarEE extends AbstractVar { + + protected final NIVariable[] children; + + ObjectVarEE(NIVariable var, String name, String path, NIVariable[] children) { super(var, name, path); + if (children == null) { + throw new NullPointerException("Null children."); + } this.children = children; } @@ -778,11 +976,9 @@ public NIVariable[] getChildren(int from, int to) { return getObjectChildren(children, from, to); } - private String findRuntimeType() { - NIVariable hub = findChild(HUB, children); - if (hub != null) { - NIVariable pub = findChild(PUBLIC, hub.getChildren()); - NIVariable nameVar = findChild(NAME, pub.getChildren()); + protected String findRuntimeType() { + if (children.length > 0) { + NIVariable nameVar = findChild(children, HUB, PUBLIC, NAME); if (nameVar != null) { String name = new StringVar(nameVar, varName, varPath, null, null).getValue(); if (!name.isEmpty()) { @@ -794,6 +990,135 @@ private String findRuntimeType() { } } + private class ObjectVarCE extends ObjectVarEE { + + private final DynamicHub hub; + private NIVariable[] members; + + ObjectVarCE(NIVariable var, String name, String path, NIVariable[] children, DynamicHub hub) { + super(var, name, path, children); + this.hub = hub; + } + + @Override + public int getNumChildren() { + return getMemberChildren().length; + } + + @Override + public NIVariable[] getChildren(int from, int to) { + return getMemberChildren(); + } + + @Override + protected String findRuntimeType() { + NIVariable nameVar = hub.findClassNameVar(); + if (nameVar != null) { + String name = new StringVar(nameVar, varName, varPath, null, null).getValue(); + if (!name.isEmpty()) { + return name; + } + } + return var.getType(); + } + + private synchronized NIVariable[] getMemberChildren() { + if (members == null) { + members = computeMembers(children); + } + return members; + } + + } + + private NIVariable[] computeMembers(NIVariable[] children) { + Map varsByName = getVarsByName(children); + NIVariable[] vars = new NIVariable[] { varsByName.get(PRIVATE), varsByName.get(PROTECTED), varsByName.get(PUBLIC) }; + List collected = new ArrayList<>(); + for (NIVariable folder : vars) { + if (folder != null) { + for (NIVariable v : folder.getChildren()) { + collected.add(v); + } + } + } + NIVariable inherited = createInherited(children); + if (collected.isEmpty()) { + if (inherited == null) { + return new NIVariable[]{}; + } else { + return inherited.getChildren(); + } + } + if (inherited != null) { + collected.add(inherited); + } + return collected.toArray(new NIVariable[collected.size()]); + } + + private NIVariable createInherited(NIVariable[] children) { + if (children.length == 0) { + return null; + } + NIVariable superClass = children[0]; + if (superClass.getName().equals(OBJ_HEADER_CE)) { + return null; + } + NIVariable[] superChildren = superClass.getChildren(); + if (superChildren.length == 1) { + // There are no fields here + return createInherited(superChildren); + } + return new Inherited(superClass); + } + + private class Inherited extends AbstractVar { + + private NIVariable[] members; + + @NbBundle.Messages({"# {0} - Name of class from which are the members inherited.", "LBL_Inherited="}) + Inherited(NIVariable typeVar) { + super(typeVar, Bundle.LBL_Inherited(typeVar.getName()), ""); + } + + @Override + public String getType() { + return ""; + } + + @Override + public String getValue() { + return ""; + } + + @Override + public NIVariable getParent() { + return var.getParent(); + } + + @Override + public int getNumChildren() { + return getMemberChildren().length; + } + + @Override + public NIVariable[] getChildren(int from, int to) { + return getMemberChildren(); + } + + private synchronized NIVariable[] getMemberChildren() { + if (members == null) { + members = computeMembers(var.getChildren()); + } + return members; + } + + @Override + public NIFrame getFrame() { + return var.getFrame(); + } + } + private class Var extends AbstractVar { Var(NIVariable var, String name, String path) { @@ -844,6 +1169,9 @@ private abstract class AbstractVar implements NIVariable { protected final String varPath; AbstractVar(NIVariable var, String varName, String varPath) { + if (var == null) { + throw new NullPointerException("Null variable."); + } this.var = var; this.varName = varName; this.varPath = varPath; diff --git a/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/displayer/Utils.java b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/displayer/Utils.java new file mode 100644 index 000000000000..1b154cf5547b --- /dev/null +++ b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/displayer/Utils.java @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.java.nativeimage.debugger.displayer; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.modules.nativeimage.api.debug.NIVariable; + +/** + * Various static utilities. + */ +final class Utils { + + private Utils() { + } + + static Map getVarsByName(NIVariable[] vars) { + switch (vars.length) { + case 0: + return Collections.emptyMap(); + case 1: + return Collections.singletonMap(vars[0].getName(), vars[0]); + default: + Map varsByName = new HashMap<>(vars.length); + for (NIVariable var : vars) { + varsByName.put(var.getName(), var); + } + return Collections.unmodifiableMap(varsByName); + } + } + + @CheckForNull + static NIVariable findChild(NIVariable[] children, String... names) { + NIVariable ch = null; + for (String name : names) { + if (ch != null) { + children = ch.getChildren(); + } + ch = findChild(name, children); + if (ch == null) { + return null; + } + } + return ch; + } + + @CheckForNull + private static NIVariable findChild(String name, NIVariable[] children) { + for (NIVariable var : children) { + if (name.equals(var.getName())) { + return var; + } + } + return null; + } + + // Quote all types which follow the class keyword and variables having dots in name. + // This is crucial for types containing dots like Java class names. + static String quoteJavaTypes(String expr) { + final String clazz = "(class "; // NOI18N + int i = expr.indexOf(clazz); + if (i >= 0) { + StringBuilder quoted = new StringBuilder(); + int j = 0; + do { + i += clazz.length(); + quoted.append(quoteJavaVarNames(expr.substring(j, i))); + j = expr.indexOf(')', i); + if (j > 0) { + while (expr.charAt(j - 1) == '*') { + j--; + } + quoted.append('\''); + quoted.append(expr.substring(i, j)); + quoted.append('\''); + } else { + // Inconsistent parenthesis + return expr; + } + i = expr.indexOf(clazz, j); + } while (i > 0); + quoted.append(quoteJavaVarNames(expr.substring(j))); + return quoted.toString(); + } else { + return expr; + } + } + + // Variables that contain dots are quoted + private static String quoteJavaVarNames(String expr) { + int i = expr.indexOf('.'); + if (i > 0) { + StringBuilder quoted = new StringBuilder(); + int i0 = 0; + do { + char c = 0; + int i1 = i - 1; + while (i1 >= i0 && ((c = expr.charAt(i1)) == '.' || Character.isJavaIdentifierPart(c))) { + i1--; + } + if (i1 >= i0) { + i1++; + c = expr.charAt(i1); + } + if (!Character.isJavaIdentifierStart(c)) { + i++; + quoted.append(expr.substring(i0, i)); + i0 = i; + continue; + } + int i2 = i + 1; + while (i2 < expr.length() && ((c = expr.charAt(i2)) == '.' || Character.isJavaIdentifierPart(c))) { + i2++; + } + quoted.append(expr.substring(i0, i1)); + quoted.append('\''); + quoted.append(expr.substring(i1, i2)); + quoted.append('\''); + i0 = i2; + } while ((i = expr.indexOf('.', i0)) > 0); + quoted.append(expr.substring(i0)); + return quoted.toString(); + } else { + return expr; + } + } + +}