This is an automated email from the ASF dual-hosted git repository.
matthiasblaesing pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/netbeans.git
The following commit(s) were added to refs/heads/master by this push:
new b5a597f11d [NETBEANS-4123] Implemented handling large strings for Java
9+
new d06fcf1f3a Merge pull request #5299 from SirIntellegence/NETBEANS-4123
b5a597f11d is described below
commit b5a597f11d3cc6b084138268bd572115ecda10f5
Author: Austin Stephens <[email protected]>
AuthorDate: Mon Jan 16 11:28:17 2023 -0700
[NETBEANS-4123] Implemented handling large strings for Java 9+
In the debugger (+7 squashed commits)
Squashed commits:
[5e450181470f] [NETBEANS-4123] Lifted exceptions from
ShortenedStrings.isLittleEndian
Waiting for this change to be reviewed before squishing
[fc97d860fd01] [NETBEANS-4123] Checkpoint before extracting exceptions...
[e6cedcda8c5d] [NETBEANS-4123] Moved sync blocks around as per
@matthiasblaesing
[1b1da080614d] [NETBEANS-4123] Now caching whether or not a VM is little
endian
Check is now deferred to when it is needed instead of right away.
[4f52c498989c] [NETBEANS-4123] Finished implementing the handling of long
strings
Renamed 'compressed' things to 'compact'
Tested with a long UTF16 string on little endian. It detects the byte order
properly
[e8d001f2fd5e] [NETBEANS-4123] Apply suggestions from code review
Will apply other changes and testing in a bit since you can't do those in
GitHub
Co-authored-by: Matthias Bläsing <[email protected]>
[2caef567d9a3] [NETBEANS-4123] Initial implementation of handling large
strings
in Java 9+ where the String class uses a byte[] instead of a char[]
---
.../jpda/models/AbstractObjectVariable.java | 10 +-
.../debugger/jpda/models/AbstractVariable.java | 10 +-
.../debugger/jpda/models/ShortenedStrings.java | 206 +++++++++++++++++++--
.../jpda/models/VariableMirrorTranslator.java | 6 +-
4 files changed, 210 insertions(+), 22 deletions(-)
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 2efa14cb17..360d5d7706 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 @@ package org.netbeans.modules.debugger.jpda.models;
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 @@ public class AbstractObjectVariable extends
AbstractVariable implements ObjectVa
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 1160b81f08..56f31b16e3 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 @@ public class AbstractVariable implements JDIVariable,
Customizer, Cloneable {
"(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 4e7f6420d3..f2e8892055 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 @@ package org.netbeans.modules.debugger.jpda.models;
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.ObjectReferenceWrapper;
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<String, StringInfo> infoStrings = new
WeakHashMap<String, StringInfo>();
private static final Map<StringReference, StringValueInfo> stringsCache =
new WeakHashMap<StringReference, StringValueInfo>();
private static final Set<StringReference> retrievingStrings = new
HashSet<StringReference>();
+ private static final Map<VirtualMachine, Boolean> isLittleEndianCache =
+ new WeakHashMap<>();
static {
DebuggerManager.getDebuggerManager().addDebuggerListener(DebuggerManager.PROP_SESSIONS,
@@ -78,6 +93,9 @@ public final class ShortenedStrings {
stringsCache.clear();
retrievingStrings.clear();
}
+ synchronized (isLittleEndianCache) {
+ isLittleEndianCache.clear();
+ }
}
}
@@ -92,6 +110,61 @@ public final class ShortenedStrings {
}
}
+ 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<ReferenceType> 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 @@ public final class ShortenedStrings {
}
}
- 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 @@ public final class ShortenedStrings {
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 = "<Unreadable>";
try {
Field valuesField = ReferenceTypeWrapper.fieldByName(st,
"value");
//System.err.println("value field = "+valuesField);
@@ -141,25 +224,59 @@ public final class ShortenedStrings {
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 @@ public final class ShortenedStrings {
} else {
assert sa != null;
int l = AbstractObjectVariable.MAX_STRING_LENGTH;
- List<Value> values = ArrayReferenceWrapper.getValues(sa, 0, l);
+ List<Value> 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 "<Unreadable>";
+ 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 26612f0832..be74e4d245 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 @@ public class VariableMirrorTranslator {
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) {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]
For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists