diff --git a/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/models/AbstractObjectVariable.java b/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/models/AbstractObjectVariable.java index 2efa14cb17b0..360d5d770604 100644 --- a/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/models/AbstractObjectVariable.java +++ b/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/models/AbstractObjectVariable.java @@ -22,8 +22,12 @@ import com.sun.jdi.ArrayReference; import com.sun.jdi.ArrayType; import com.sun.jdi.CharValue; +import com.sun.jdi.ClassNotLoadedException; import com.sun.jdi.ClassObjectReference; import com.sun.jdi.ClassType; +import com.sun.jdi.IncompatibleThreadStateException; +import com.sun.jdi.InvalidTypeException; +import com.sun.jdi.InvocationException; import com.sun.jdi.Method; import com.sun.jdi.ObjectReference; import com.sun.jdi.PrimitiveValue; @@ -519,14 +523,14 @@ static String getToStringValue (Value v, com.sun.jdi.Type type, JPDADebuggerImpl str = "\"" + str + "\""; // NOI18N } return str; - } catch (InternalExceptionWrapper ex) { + } catch (InternalExceptionWrapper | ClassNotPreparedExceptionWrapper | + ClassNotLoadedException | IncompatibleThreadStateException | + InvalidTypeException | InvocationException ex) { return ex.getLocalizedMessage(); } catch (VMDisconnectedExceptionWrapper ex) { return NbBundle.getMessage(AbstractVariable.class, "MSG_Disconnected"); } catch (ObjectCollectedExceptionWrapper ocex) { return NbBundle.getMessage(AbstractVariable.class, "MSG_ObjCollected"); - } catch (ClassNotPreparedExceptionWrapper cnpex) { - return cnpex.getLocalizedMessage(); } } diff --git a/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/models/AbstractVariable.java b/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/models/AbstractVariable.java index 1160b81f0813..56f31b16e3a1 100644 --- a/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/models/AbstractVariable.java +++ b/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/models/AbstractVariable.java @@ -152,11 +152,11 @@ static String getValue (Value v) { "(length=" + ArrayReferenceWrapper.length((ArrayReference) v) + ")"; } return "#" + ObjectReferenceWrapper.uniqueID((ObjectReference) v); - } catch (InternalExceptionWrapper iex) { - return ""; - } catch (ObjectCollectedExceptionWrapper oex) { - return ""; - } catch (VMDisconnectedExceptionWrapper dex) { + } catch (InternalExceptionWrapper | ObjectCollectedExceptionWrapper | + VMDisconnectedExceptionWrapper | ClassNotLoadedException | + ClassNotPreparedExceptionWrapper | + IncompatibleThreadStateException | InvalidTypeException | + InvocationException e) { return ""; } } diff --git a/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/models/ShortenedStrings.java b/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/models/ShortenedStrings.java index 4e7f6420d38e..f2e889205547 100644 --- a/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/models/ShortenedStrings.java +++ b/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/models/ShortenedStrings.java @@ -21,22 +21,34 @@ import com.sun.jdi.ArrayReference; import com.sun.jdi.ArrayType; +import com.sun.jdi.ByteValue; import com.sun.jdi.CharValue; import com.sun.jdi.ClassNotLoadedException; +import com.sun.jdi.ClassType; import com.sun.jdi.Field; +import com.sun.jdi.IncompatibleThreadStateException; +import com.sun.jdi.InvalidTypeException; +import com.sun.jdi.InvocationException; +import com.sun.jdi.Method; +import com.sun.jdi.PrimitiveValue; import com.sun.jdi.ReferenceType; import com.sun.jdi.StringReference; +import com.sun.jdi.ThreadReference; import com.sun.jdi.Type; import com.sun.jdi.Value; +import com.sun.jdi.VirtualMachine; import java.io.IOException; import java.io.Reader; import java.lang.ref.Reference; import java.lang.ref.WeakReference; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; import org.netbeans.api.debugger.DebuggerManager; import org.netbeans.api.debugger.DebuggerManagerAdapter; import org.netbeans.api.debugger.Session; @@ -48,6 +60,7 @@ import org.netbeans.modules.debugger.jpda.jdi.ReferenceTypeWrapper; import org.netbeans.modules.debugger.jpda.jdi.StringReferenceWrapper; import org.netbeans.modules.debugger.jpda.jdi.VMDisconnectedExceptionWrapper; +import org.openide.util.Exceptions; /** * A collector of shorted String values, that were too long. @@ -61,6 +74,8 @@ public final class ShortenedStrings { private static final Map infoStrings = new WeakHashMap(); private static final Map stringsCache = new WeakHashMap(); private static final Set retrievingStrings = new HashSet(); + private static final Map isLittleEndianCache = + new WeakHashMap<>(); static { DebuggerManager.getDebuggerManager().addDebuggerListener(DebuggerManager.PROP_SESSIONS, @@ -78,6 +93,9 @@ public void sessionRemoved(Session session) { stringsCache.clear(); retrievingStrings.clear(); } + synchronized (isLittleEndianCache) { + isLittleEndianCache.clear(); + } } } @@ -92,6 +110,61 @@ public static StringInfo getShortenedInfo(String s) { } } + private static boolean isLittleEndian(VirtualMachine virtualMachine) throws + InvalidTypeException, IncompatibleThreadStateException, + ClassNotLoadedException, InvocationException, + InternalExceptionWrapper, VMDisconnectedExceptionWrapper, + ObjectCollectedExceptionWrapper, ClassNotPreparedExceptionWrapper { + synchronized(isLittleEndianCache){ + Boolean cached = isLittleEndianCache.get(virtualMachine); + if (cached != null){ + return cached; + } + List possibleClasses = virtualMachine.classesByName( + "java.lang.StringUTF16"); + //If we don't know, we are going to assume little endian encoding. + //This should work for most architectures (x86, arm, riscv), but + //will result in bogus data on big endian architectures + final boolean defaultValue = true; + if (possibleClasses.isEmpty()){ + ClassType ct = (ClassType) virtualMachine.classesByName( + "java.lang.Class").iterator().next(); + Method m = ct.concreteMethodByName("forName", + "(Ljava/lang/String;)Ljava/lang/Class;"); + StringReference referenceString = virtualMachine.mirrorOf( + "java.lang.StringUTF16"); + ThreadReference threadReference = virtualMachine. + allThreads().get(0); + ct.invokeMethod(threadReference, m, Collections. + singletonList(referenceString), 0); + possibleClasses = virtualMachine.classesByName( + "java.lang.StringUTF16"); + } + ReferenceType utf16; + if (possibleClasses.size() == 1){ + utf16 = possibleClasses.get(0); + } + else { + isLittleEndianCache.put(virtualMachine, defaultValue); + return defaultValue; + } + Field hiByteShiftField = ReferenceTypeWrapper.fieldByName(utf16, + "HI_BYTE_SHIFT"); + if (hiByteShiftField == null){ + isLittleEndianCache.put(virtualMachine, defaultValue); + return defaultValue; + } + Value hiByteShiftValue = utf16.getValue(hiByteShiftField); + if (!(hiByteShiftValue instanceof PrimitiveValue)){ + isLittleEndianCache.put(virtualMachine, defaultValue); + return defaultValue; + } + boolean result = ((PrimitiveValue)hiByteShiftValue).intValue() == 0; + isLittleEndianCache.put(virtualMachine, result); + return result; + } + } + private static void register(String shortedString, StringReference sr, int length, ArrayReference chars) { StringInfo si = new StringInfo(sr, shortedString.length() - 3, length, chars); synchronized (infoStrings) { @@ -99,7 +172,11 @@ private static void register(String shortedString, StringReference sr, int lengt } } - static String getStringWithLengthControl(StringReference sr) throws InternalExceptionWrapper, VMDisconnectedExceptionWrapper, ObjectCollectedExceptionWrapper { + static String getStringWithLengthControl(StringReference sr) throws + InternalExceptionWrapper, VMDisconnectedExceptionWrapper, + ObjectCollectedExceptionWrapper, ClassNotLoadedException, + ClassNotPreparedExceptionWrapper, IncompatibleThreadStateException, + InvalidTypeException, InvocationException { boolean retrieved = false; synchronized (stringsCache) { StringValueInfo svi = stringsCache.get(sr); @@ -130,7 +207,13 @@ static String getStringWithLengthControl(StringReference sr) throws InternalExce try { ReferenceType st = ObjectReferenceWrapper.referenceType(sr); ArrayReference sa = null; + //only applicable if the string implementation uses a byte[] instead + //of a char[] + boolean isUTF16 = false; + //See JEP 254: Compact Strings after the boolean + boolean isCompactImpl = false; int saLength = 0; + final String ERROR_RESULT = ""; try { Field valuesField = ReferenceTypeWrapper.fieldByName(st, "value"); //System.err.println("value field = "+valuesField); @@ -141,25 +224,59 @@ static String getStringWithLengthControl(StringReference sr) throws InternalExce continue; } Type type = f.type(); - if (type instanceof ArrayType && - "char".equals(((ArrayType) type).componentTypeName())) { - valuesField = f; + if (type instanceof ArrayType) { + String componentType = ((ArrayType)type).componentTypeName(); + if ("byte".equals(componentType)){ + isCompactImpl = true; + valuesField = f; + } + else if ("char".equals(componentType)){ + valuesField = f; + } + else{ + continue; + } break; } } } + else if (valuesField.type() instanceof ArrayType && + "byte".equals(((ArrayType)valuesField.type()). + componentTypeName())){ + isCompactImpl = true; + } if (valuesField == null) { isShort = true; // We did not find the values field. } else { + if (isCompactImpl){ + //is it UTF16? + final int LATIN1 = 0; + Field coderField = ReferenceTypeWrapper.fieldByName(st, + "coder"); + Value coderValue; + if (coderField != null){ + coderValue = ObjectReferenceWrapper.getValue(sr, + coderField); + if (coderValue instanceof PrimitiveValue && + ((PrimitiveValue)coderValue).intValue() != LATIN1){ + isUTF16 = true; + } + } + } + int limit = AbstractObjectVariable.MAX_STRING_LENGTH; + if (isUTF16){ + limit *= 2; + } Value values = ObjectReferenceWrapper.getValue(sr, valuesField); if (values instanceof ArrayReference) { sa = (ArrayReference) values; saLength = ArrayReferenceWrapper.length(sa); - isShort = saLength <= AbstractObjectVariable.MAX_STRING_LENGTH; + isShort = saLength <= limit; } else { isShort = true; } } + } catch (ClassNotPreparedExceptionWrapper cnpex) { isShort = true; } catch (ClassNotLoadedException cnlex) { @@ -171,24 +288,87 @@ static String getStringWithLengthControl(StringReference sr) throws InternalExce } else { assert sa != null; int l = AbstractObjectVariable.MAX_STRING_LENGTH; - List values = ArrayReferenceWrapper.getValues(sa, 0, l); + List values = ArrayReferenceWrapper.getValues(sa, 0, + isUTF16 ? (l * 2) : l); char[] characters = new char[l + 3]; - for (int i = 0; i < l; i++) { - Value v = values.get(i); - if (!(v instanceof CharValue)) { - return ""; + if (isCompactImpl) { + //java compact string + if (!isUTF16) { + //we can just cast to char + for (int i = 0; i < l; i++) { + Value v = values.get(i); + if (!(v instanceof ByteValue)) { + return ERROR_RESULT; + } + char c = (char)((ByteValue) v).byteValue(); + //remove the extended sign + c &= 0xFF; + characters[i] = c; + } + } + else { + int hiByteShift; + int lowByteShift; + //is it little or big endian? + if (isLittleEndian(sr.virtualMachine())){ + hiByteShift = 0; + lowByteShift = 8; + } + else{ + hiByteShift = 8; + lowByteShift = 0; + } + for (int i = 0; i < l; i++) { + int index = i * 2; + Value v = values.get(index); + if (!(v instanceof ByteValue)) { + return ERROR_RESULT; + } + Value v2 = values.get(index + 1); + if (!(v instanceof ByteValue)) { + return ERROR_RESULT; + } + char c1 = (char) ((ByteValue) v).byteValue(); + char c2 = (char) ((ByteValue) v2).byteValue(); + //remove the extended sign + c1 = (char) (0xFF & c1); + c2 = (char) (0xFF & c2); + char c = (char)(c1 << hiByteShift | + c2 << lowByteShift); + characters[i] = c; + } + } + } + else{ + for (int i = 0; i < l; i++) { + Value v = values.get(i); + if (!(v instanceof CharValue)) { + return ERROR_RESULT; + } + characters[i] = ((CharValue) v).charValue(); } - characters[i] = ((CharValue) v).charValue(); } // Add 3 dots: for (int i = l; i < (l + 3); i++) { characters[i] = '.'; } String shortedString = new String(characters); - ShortenedStrings.register(shortedString, sr, saLength, sa); + int stringLength = isUTF16 ? saLength / 2 : saLength; + ShortenedStrings.register(shortedString, sr, stringLength, sa); string = shortedString; } - } finally { + } + catch (ClassNotLoadedException | ClassNotPreparedExceptionWrapper | + IncompatibleThreadStateException | InternalExceptionWrapper | + InvalidTypeException | InvocationException | + ObjectCollectedExceptionWrapper | + VMDisconnectedExceptionWrapper e){ + Logger.getLogger(ShortenedStrings.class.getSimpleName()).log( + Level.INFO, "Error in getStringWithLengthControl", + e); + throw e; + } + finally { synchronized (stringsCache) { if (string != null) { StringValueInfo svi; diff --git a/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/models/VariableMirrorTranslator.java b/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/models/VariableMirrorTranslator.java index 26612f08328b..be74e4d2453d 100644 --- a/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/models/VariableMirrorTranslator.java +++ b/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/models/VariableMirrorTranslator.java @@ -112,7 +112,11 @@ private static Object createMirrorObject(Value value, Map mirrors return ShortenedStrings.getStringWithLengthControl((StringReference) value); } return createMirrorObject((ObjectReference) value, (ReferenceType) type, clazz, mirrorsMap); - } catch (ClassNotFoundException | ClassNotPreparedExceptionWrapper ex) { + } catch (ClassNotFoundException | ClassNotLoadedException | + ClassNotPreparedExceptionWrapper | + InvalidTypeException | InvocationException | + IncompatibleThreadStateException ex) { + //nom } } else { if (null == typeStr) {