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 <sirintellegen...@gmail.com>
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 <mblaes...@doppel-helix.eu>
    [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: commits-unsubscr...@netbeans.apache.org
For additional commands, e-mail: commits-h...@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists

Reply via email to