Skip to content

Commit

Permalink
Add special support for enum values
Browse files Browse the repository at this point in the history
Enum in java are in fact regular classes with a special treatment by
the java compiler. from the POV of the JVM there is no difference.
From user perspective however it would be nice to have constant name
as enum values.
We are serializing then the enum constant name in the snapshot instead
of handling enum as regular complex class
  • Loading branch information
jpbempel committed Jun 21, 2023
1 parent 30cb962 commit b6de77a
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
Expand Down Expand Up @@ -124,81 +123,15 @@ public void serialize(Object value, String type, Limits limits) throws Exception
if (value == null) {
tokenWriter.nullValue();
} else if (isPrimitive(type) || WellKnownClasses.isToStringSafe(type)) {
if (value instanceof String) {
String strValue = (String) value;
int originalLength = strValue.length();
boolean isComplete = true;
if (originalLength > limits.maxLength) {
strValue = strValue.substring(0, limits.maxLength);
isComplete = false;
}
tokenWriter.string(strValue, isComplete, originalLength);
} else {
tokenWriter.primitiveValue(value);
}
serializePrimitive(value, limits);
} else if (value.getClass().isArray() && (limits.maxReferenceDepth > 0)) {
int arraySize = Array.getLength(value);
boolean isComplete = true;
tokenWriter.arrayPrologue(value);
if (value.getClass().getComponentType().isPrimitive()) {
Class<?> componentType = value.getClass().getComponentType();
if (componentType == long.class) {
isComplete = serializeLongArray((long[]) value, limits.maxCollectionSize);
}
if (componentType == int.class) {
isComplete = serializeIntArray((int[]) value, limits.maxCollectionSize);
}
if (componentType == short.class) {
isComplete = serializeShortArray((short[]) value, limits.maxCollectionSize);
}
if (componentType == char.class) {
isComplete = serializeCharArray((char[]) value, limits.maxCollectionSize);
}
if (componentType == byte.class) {
isComplete = serializeByteArray((byte[]) value, limits.maxCollectionSize);
}
if (componentType == boolean.class) {
isComplete = serializeBooleanArray((boolean[]) value, limits.maxCollectionSize);
}
if (componentType == float.class) {
isComplete = serializeFloatArray((float[]) value, limits.maxCollectionSize);
}
if (componentType == double.class) {
isComplete = serializeDoubleArray((double[]) value, limits.maxCollectionSize);
}
} else {
isComplete = serializeObjectArray((Object[]) value, limits);
}
tokenWriter.arrayEpilogue(value, isComplete, arraySize);
serializeArray(value, limits);
} else if (value instanceof Collection && (limits.maxReferenceDepth > 0)) {
tokenWriter.collectionPrologue(value);
Collection<?> col;
boolean isComplete = true;
int size = 0;
try {
col = (Collection<?>) value;
size = col.size(); // /!\ alien call /!\
isComplete = serializeCollection(col, limits); // /!\ contains alien calls /!\
tokenWriter.collectionEpilogue(value, isComplete, size);
} catch (Exception ex) {
tokenWriter.collectionEpilogue(value, isComplete, size);
tokenWriter.notCaptured(ex.toString());
}
serializeCollection(value, limits);
} else if (value instanceof Map && (limits.maxReferenceDepth > 0)) {
tokenWriter.mapPrologue(value);
Map<?, ?> map = Collections.emptyMap();
boolean isComplete = true;
int size = 0;
try {
map = (Map<?, ?>) value;
size = map.size(); // /!\ alien call /!\
Set<? extends Map.Entry<?, ?>> entries = map.entrySet(); // /!\ alien call /!\
isComplete = serializeMap(entries, limits); // /!\ contains alien calls /!\
tokenWriter.mapEpilogue(isComplete, size);
} catch (Exception ex) {
tokenWriter.mapEpilogue(isComplete, size);
tokenWriter.notCaptured(ex.toString());
}
serializeMap(value, limits);
} else if (value instanceof Enum) {
serializeEnum(value, limits);
} else if (limits.maxReferenceDepth > 0) {
serializeObjectValue(value, limits);
} else {
Expand All @@ -207,6 +140,95 @@ public void serialize(Object value, String type, Limits limits) throws Exception
tokenWriter.epilogue(value);
}

private void serializeEnum(Object value, Limits limits) throws Exception {
Enum<?> enumValue = (Enum<?>) value;
serializePrimitive(enumValue.name(), limits);
}

private void serializeMap(Object value, Limits limits) throws Exception {
tokenWriter.mapPrologue(value);
Map<?, ?> map;
boolean isComplete = true;
int size = 0;
try {
map = (Map<?, ?>) value;
size = map.size(); // /!\ alien call /!\
Set<? extends Map.Entry<?, ?>> entries = map.entrySet(); // /!\ alien call /!\
isComplete = serializeMapEntries(entries, limits); // /!\ contains alien calls /!\
tokenWriter.mapEpilogue(isComplete, size);
} catch (Exception ex) {
tokenWriter.mapEpilogue(isComplete, size);
tokenWriter.notCaptured(ex.toString());
}
}

private void serializeCollection(Object value, Limits limits) throws Exception {
tokenWriter.collectionPrologue(value);
Collection<?> col;
boolean isComplete = true;
int size = 0;
try {
col = (Collection<?>) value;
size = col.size(); // /!\ alien call /!\
isComplete = serializeCollection(col, limits); // /!\ contains alien calls /!\
tokenWriter.collectionEpilogue(value, isComplete, size);
} catch (Exception ex) {
tokenWriter.collectionEpilogue(value, isComplete, size);
tokenWriter.notCaptured(ex.toString());
}
}

private void serializeArray(Object value, Limits limits) throws Exception {
int arraySize = Array.getLength(value);
boolean isComplete = true;
tokenWriter.arrayPrologue(value);
if (value.getClass().getComponentType().isPrimitive()) {
Class<?> componentType = value.getClass().getComponentType();
if (componentType == long.class) {
isComplete = serializeLongArray((long[]) value, limits.maxCollectionSize);
}
if (componentType == int.class) {
isComplete = serializeIntArray((int[]) value, limits.maxCollectionSize);
}
if (componentType == short.class) {
isComplete = serializeShortArray((short[]) value, limits.maxCollectionSize);
}
if (componentType == char.class) {
isComplete = serializeCharArray((char[]) value, limits.maxCollectionSize);
}
if (componentType == byte.class) {
isComplete = serializeByteArray((byte[]) value, limits.maxCollectionSize);
}
if (componentType == boolean.class) {
isComplete = serializeBooleanArray((boolean[]) value, limits.maxCollectionSize);
}
if (componentType == float.class) {
isComplete = serializeFloatArray((float[]) value, limits.maxCollectionSize);
}
if (componentType == double.class) {
isComplete = serializeDoubleArray((double[]) value, limits.maxCollectionSize);
}
} else {
isComplete = serializeObjectArray((Object[]) value, limits);
}
tokenWriter.arrayEpilogue(value, isComplete, arraySize);
}

private void serializePrimitive(Object value, Limits limits) throws Exception {
if (value instanceof String) {
String strValue = (String) value;
int originalLength = strValue.length();
boolean isComplete = true;
if (originalLength > limits.maxLength) {
strValue = strValue.substring(0, limits.maxLength);
isComplete = false;
}
tokenWriter.string(strValue, isComplete, originalLength);
} else {
tokenWriter.primitiveValue(value);
}
}

private void serializeObjectValue(Object value, Limits limits) throws Exception {
tokenWriter.objectPrologue(value);
Class<?> currentClass = value.getClass();
Expand Down Expand Up @@ -381,7 +403,7 @@ private boolean serializeCollection(Collection<?> collection, Limits limits) thr
return maxSize == colSize;
}

private boolean serializeMap(Set<? extends Map.Entry<?, ?>> entries, Limits limits)
private boolean serializeMapEntries(Set<? extends Map.Entry<?, ?>> entries, Limits limits)
throws Exception {
int mapSize = entries.size(); // /!\ alien call /!\
int maxSize = Math.min(mapSize, limits.maxCollectionSize);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1387,7 +1387,7 @@ public void enumConstructorArgs() throws IOException, URISyntaxException {
DebuggerTransformerTest.TestSnapshotListener listener =
installProbes(ENUM_CLASS, createProbe(PROBE_ID, ENUM_CLASS, "<init>", null));
Class<?> testClass = compileAndLoadClass(CLASS_NAME);
int result = Reflect.on(testClass).call("main", "2").get();
int result = Reflect.on(testClass).call("main", "").get();
Assertions.assertEquals(2, result);
assertSnapshots(listener, 3, PROBE_ID);
Map<String, CapturedContext.CapturedValue> arguments =
Expand All @@ -1398,6 +1398,21 @@ public void enumConstructorArgs() throws IOException, URISyntaxException {
assertTrue(arguments.containsKey("strValue"));
}

@Test
public void enumValues() throws IOException, URISyntaxException {
final String CLASS_NAME = "com.datadog.debugger.CapturedSnapshot23";
DebuggerTransformerTest.TestSnapshotListener listener =
installProbes(CLASS_NAME, createProbe(PROBE_ID, CLASS_NAME, "convert", null));
Class<?> testClass = compileAndLoadClass(CLASS_NAME);
int result = Reflect.on(testClass).call("main", "2").get();
Assertions.assertEquals(2, result);
Snapshot snapshot = assertOneSnapshot(listener);
assertCaptureReturnValue(
snapshot.getCaptures().getReturn(),
"com.datadog.debugger.CapturedSnapshot23$MyEnum",
"TWO");
}

@Test
public void recursiveCapture() throws IOException, URISyntaxException {
final String CLASS_NAME = "com.datadog.debugger.CapturedSnapshot24";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,28 @@ public void valueTimeout() throws IOException {
Assertions.assertEquals(TIMEOUT_REASON, json.get(NOT_CAPTURED_REASON));
}

enum MyEnum {
ONE,
TWO,
THREE;
}

@Test
public void enumValues() throws IOException {
JsonAdapter<Snapshot> adapter = createSnapshotAdapter();
Snapshot snapshot = createSnapshot();
CapturedContext context = new CapturedContext();
CapturedContext.CapturedValue enumValue =
CapturedContext.CapturedValue.of("enumValue", MyEnum.class.getTypeName(), MyEnum.TWO);
context.addLocals(new CapturedContext.CapturedValue[] {enumValue});
snapshot.setExit(context);
String buffer = adapter.toJson(snapshot);
System.out.println(buffer);
Map<String, Object> locals = getLocalsFromJson(buffer);
Map<String, Object> enumValueJson = (Map<String, Object>) locals.get("enumValue");
assertEquals("TWO", enumValueJson.get("value"));
}

private Map<String, Object> doFieldCount(int maxFieldCount) throws IOException {
JsonAdapter<Snapshot> adapter = createSnapshotAdapter();
Snapshot snapshot = createSnapshotForFieldCount(maxFieldCount);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ private static Object convertPrimitive(String strValue, String type) {
case "java.lang.String":
return strValue;
}
return null;
return strValue;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,20 @@ public String getStrValue() {
}

private int doit(String arg) {
return Integer.parseInt(MyEnum.TWO.getStrValue());
if ("".equals(arg)) {
return Integer.parseInt(MyEnum.TWO.getStrValue());
}
return Integer.parseInt(convert(arg).getStrValue());
}

private MyEnum convert(String value) {
switch (value) {
case "1": return MyEnum.ONE;
case "2": return MyEnum.TWO;
case "3": return MyEnum.THREE;
default:
throw new IllegalArgumentException("Unknown value: " + value);
}
}

public static int main(String arg) {
Expand Down

0 comments on commit b6de77a

Please sign in to comment.