From 95b376ecaa8349a5174b660e0bbfc52d64b058c5 Mon Sep 17 00:00:00 2001 From: jean-philippe bempel Date: Wed, 3 Jul 2024 18:26:30 +0200 Subject: [PATCH] Serialize restricted collections as regular object We specially serialize collections/maps inot snapshot using interfaces and calling methods like size() but we restrict this for JDK collections that are guarantee to be only in-memory. For all others we are raising an exception. Instead we should just serialize then as regular object with fields. --- .../debugger/util/SerializerWithLimits.java | 30 +++++++++---------- .../agent/SnapshotSerializationTest.java | 19 +++++++----- .../debugger/util/StringTokenWriterTest.java | 22 ++++++++------ 3 files changed, 39 insertions(+), 32 deletions(-) diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/SerializerWithLimits.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/SerializerWithLimits.java index 86188d500e2..334153d03af 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/SerializerWithLimits.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/SerializerWithLimits.java @@ -145,9 +145,17 @@ public void serialize(Object value, String type, Limits limits) throws Exception } else if (value.getClass().isArray() && (limits.maxReferenceDepth > 0)) { serializeArray(value, limits); } else if (value instanceof Collection && (limits.maxReferenceDepth > 0)) { - serializeCollection(value, limits); + if (WellKnownClasses.isSafe((Collection) value)) { + serializeCollection(value, limits); + } else { + serializeObjectValue(value, limits); + } } else if (value instanceof Map && (limits.maxReferenceDepth > 0)) { - serializeMap(value, limits); + if (WellKnownClasses.isSafe((Map) value)) { + serializeMap(value, limits); + } else { + serializeObjectValue(value, limits); + } } else if (value instanceof Enum) { serializeEnum(value, limits); } else if (limits.maxReferenceDepth > 0) { @@ -170,13 +178,9 @@ private void serializeMap(Object value, Limits limits) throws Exception { int size = 0; try { map = (Map) value; - if (WellKnownClasses.isSafe(map)) { - size = map.size(); // /!\ alien call /!\ - Set> entries = map.entrySet(); // /!\ alien call /!\ - isComplete = serializeMapEntries(entries, limits); // /!\ contains alien calls /!\ - } else { - throw new RuntimeException("Unsupported Map type: " + map.getClass().getTypeName()); - } + size = map.size(); // /!\ alien call /!\ + Set> entries = map.entrySet(); // /!\ alien call /!\ + isComplete = serializeMapEntries(entries, limits); // /!\ contains alien calls /!\ tokenWriter.mapEpilogue(isComplete, size); } catch (Exception ex) { tokenWriter.mapEpilogue(isComplete, size); @@ -191,12 +195,8 @@ private void serializeCollection(Object value, Limits limits) throws Exception { int size = 0; try { col = (Collection) value; - if (WellKnownClasses.isSafe(col)) { - size = col.size(); // /!\ alien call /!\ - isComplete = serializeCollection(col, limits); // /!\ contains alien calls /!\ - } else { - throw new RuntimeException("Unsupported Collection type: " + col.getClass().getTypeName()); - } + 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); diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/SnapshotSerializationTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/SnapshotSerializationTest.java index a889aec1836..ddbb5873bff 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/SnapshotSerializationTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/SnapshotSerializationTest.java @@ -744,10 +744,11 @@ public void collectionUnknown() throws IOException { String buffer = adapter.toJson(snapshot); System.out.println(buffer); Map locals = getLocalsFromJson(buffer); - Map mapFieldObj = (Map) locals.get("listLocal"); - Assertions.assertEquals( - "java.lang.RuntimeException: Unsupported Collection type: com.datadog.debugger.agent.SnapshotSerializationTest$1", - mapFieldObj.get(NOT_CAPTURED_REASON)); + Map listLocalField = (Map) locals.get("listLocal"); + Map listLocalFieldFields = (Map) listLocalField.get(FIELDS); + assertTrue(listLocalFieldFields.containsKey("elementData")); + assertTrue(listLocalFieldFields.containsKey("size")); + assertTrue(listLocalFieldFields.containsKey("modCount")); } @Test @@ -763,10 +764,12 @@ public void mapValueUnknown() throws IOException { String buffer = adapter.toJson(snapshot); System.out.println(buffer); Map locals = getLocalsFromJson(buffer); - Map mapFieldObj = (Map) locals.get("mapLocal"); - Assertions.assertEquals( - "java.lang.RuntimeException: Unsupported Map type: com.datadog.debugger.agent.SnapshotSerializationTest$2", - mapFieldObj.get(NOT_CAPTURED_REASON)); + Map mapLocalField = (Map) locals.get("mapLocal"); + Map mapLocalFieldFields = (Map) mapLocalField.get(FIELDS); + assertTrue(mapLocalFieldFields.containsKey("table")); + assertTrue(mapLocalFieldFields.containsKey("size")); + assertTrue(mapLocalFieldFields.containsKey("threshold")); + assertTrue(mapLocalFieldFields.containsKey("loadFactor")); } @Test diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/util/StringTokenWriterTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/util/StringTokenWriterTest.java index b3bce5e78a4..9deef3f853e 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/util/StringTokenWriterTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/util/StringTokenWriterTest.java @@ -91,7 +91,7 @@ public void collections() throws Exception { list.add("foo"); list.add("bar"); list.add(null); - assertEquals("[foo, bar, null]", serializeValue(list, Limits.DEFAULT)); + assertEquals("[foo, bar, null]", serializeValue(list, DEPTH_1)); } @Test @@ -99,7 +99,7 @@ public void maps() throws Exception { HashMap map = new HashMap<>(); map.put("foo1", "bar1"); map.put(null, null); - String serializedStr = serializeValue(map, Limits.DEFAULT); + String serializedStr = serializeValue(map, DEPTH_1); assertTrue(serializedStr.contains("[foo1=bar1]")); assertTrue(serializedStr.contains("[null=null]")); } @@ -107,9 +107,10 @@ public void maps() throws Exception { @Test public void collectionUnknown() throws Exception { class MyArrayList extends ArrayList {} - assertEquals( - "[](Error: java.lang.RuntimeException: Unsupported Collection type: com.datadog.debugger.util.StringTokenWriterTest$1MyArrayList)", - serializeValue(new MyArrayList<>(), Limits.DEFAULT)); + String str = serializeValue(new MyArrayList<>(), DEPTH_1); + assertTrue(str.contains("elementData=")); + assertTrue(str.contains("size=")); + assertTrue(str.contains("modCount=")); } @Test @@ -120,9 +121,12 @@ public Set> entrySet() { throw new UnsupportedOperationException("entrySet"); } } - assertEquals( - "{}(Error: java.lang.RuntimeException: Unsupported Map type: com.datadog.debugger.util.StringTokenWriterTest$1MyMap)", - serializeValue(new MyMap(), Limits.DEFAULT)); + String str = serializeValue(new MyMap(), DEPTH_1); + assertTrue(str.contains("table=")); + assertTrue(str.contains("size=")); + assertTrue(str.contains("modCount=")); + assertTrue(str.contains("threshold=")); + assertTrue(str.contains("loadFactor=")); } static class Person { @@ -171,7 +175,7 @@ private String serializeValue(Object value, Limits limits) throws Exception { SerializerWithLimits serializer = new SerializerWithLimits( new StringTokenWriter(sb, new ArrayList<>()), - new TimeoutChecker(Duration.of(1, ChronoUnit.SECONDS))); + new TimeoutChecker(Duration.of(300, ChronoUnit.SECONDS))); serializer.serialize( value, value != null ? value.getClass().getTypeName() : Object.class.getTypeName(), limits); return sb.toString();