Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NETBEANS-4123] Initial implementation of handling large strings #5299

Merged
merged 1 commit into from
Feb 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 "";
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
Expand All @@ -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,
Expand All @@ -78,6 +93,9 @@ public void sessionRemoved(Session session) {
stringsCache.clear();
retrievingStrings.clear();
}
synchronized (isLittleEndianCache) {
isLittleEndianCache.clear();
}
}
}

Expand All @@ -92,14 +110,73 @@ 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<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) {
infoStrings.put(shortedString, si);
}
}

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);
Expand Down Expand Up @@ -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 = "<Unreadable>";
try {
Field valuesField = ReferenceTypeWrapper.fieldByName(st, "value");
//System.err.println("value field = "+valuesField);
Expand All @@ -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) {
Expand All @@ -171,24 +288,87 @@ static String getStringWithLengthControl(StringReference sr) throws InternalExce
} 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,11 @@ private static Object createMirrorObject(Value value, Map<Value, Object> 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) {
Expand Down