Repository: avro Updated Branches: refs/heads/master 2b1639ce5 -> e276aa5e4
AVRO-1923: Stop infinite recursion in GenericData.toString Project: http://git-wip-us.apache.org/repos/asf/avro/repo Commit: http://git-wip-us.apache.org/repos/asf/avro/commit/e276aa5e Tree: http://git-wip-us.apache.org/repos/asf/avro/tree/e276aa5e Diff: http://git-wip-us.apache.org/repos/asf/avro/diff/e276aa5e Branch: refs/heads/master Commit: e276aa5e40cf51e36b581750c135c9c3875cac84 Parents: 2b1639c Author: Niels Basjes <[email protected]> Authored: Thu Sep 22 14:42:47 2016 +0200 Committer: Niels Basjes <[email protected]> Committed: Tue Oct 4 16:22:20 2016 +0200 ---------------------------------------------------------------------- CHANGES.txt | 2 + .../org/apache/avro/generic/GenericData.java | 21 ++++--- .../apache/avro/generic/TestGenericData.java | 60 ++++++++++++++++++++ 3 files changed, 76 insertions(+), 7 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/avro/blob/e276aa5e/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index 6bcafa3..f205783 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -73,6 +73,8 @@ Trunk (not yet released) AVRO-1853: C++: Compiler::toBin crashes on empty string. (Zoltan Ivanfi via tomwhite) + AVRO-1923: Stop infinite recursion in GenericData.toString. (Niels Basjes) + Avro 1.8.1 (14 May 2016) INCOMPATIBLE CHANGES http://git-wip-us.apache.org/repos/asf/avro/blob/e276aa5e/lang/java/avro/src/main/java/org/apache/avro/generic/GenericData.java ---------------------------------------------------------------------- diff --git a/lang/java/avro/src/main/java/org/apache/avro/generic/GenericData.java b/lang/java/avro/src/main/java/org/apache/avro/generic/GenericData.java index 76eb625..0484c03 100644 --- a/lang/java/avro/src/main/java/org/apache/avro/generic/GenericData.java +++ b/lang/java/avro/src/main/java/org/apache/avro/generic/GenericData.java @@ -501,19 +501,24 @@ public class GenericData { /** Renders a Java datum as <a href="http://www.json.org/">JSON</a>. */ public String toString(Object datum) { StringBuilder buffer = new StringBuilder(); - toString(datum, buffer); + toString(datum, buffer, new IdentityHashMap<Object, Object>(128) ); return buffer.toString(); } /** Renders a Java datum as <a href="http://www.json.org/">JSON</a>. */ - protected void toString(Object datum, StringBuilder buffer) { + protected void toString(Object datum, StringBuilder buffer, IdentityHashMap<Object, Object> seenObjects) { + if (seenObjects.containsKey(datum)) { + buffer.append(" \">>> CIRCULAR REFERENCE CANNOT BE PUT IN JSON STRING, ABORTING RECURSION<<<\" "); + return; + } + seenObjects.put(datum, datum); if (isRecord(datum)) { buffer.append("{"); int count = 0; Schema schema = getRecordSchema(datum); for (Field f : schema.getFields()) { - toString(f.name(), buffer); + toString(f.name(), buffer, seenObjects); buffer.append(": "); - toString(getField(datum, f.name(), f.pos()), buffer); + toString(getField(datum, f.name(), f.pos()), buffer, seenObjects); if (++count < schema.getFields().size()) buffer.append(", "); } @@ -524,7 +529,7 @@ public class GenericData { long last = array.size()-1; int i = 0; for (Object element : array) { - toString(element, buffer); + toString(element, buffer, seenObjects); if (i++ < last) buffer.append(", "); } @@ -535,9 +540,9 @@ public class GenericData { @SuppressWarnings(value="unchecked") Map<Object,Object> map = (Map<Object,Object>)datum; for (Map.Entry<Object,Object> entry : map.entrySet()) { - toString(entry.getKey(), buffer); + toString(entry.getKey(), buffer, seenObjects); buffer.append(": "); - toString(entry.getValue(), buffer); + toString(entry.getValue(), buffer, seenObjects); if (++count < map.size()) buffer.append(", "); } @@ -558,6 +563,8 @@ public class GenericData { buffer.append("\""); buffer.append(datum); buffer.append("\""); + } else if (datum instanceof GenericData) { + toString(datum, buffer, seenObjects); } else { buffer.append(datum); } http://git-wip-us.apache.org/repos/asf/avro/blob/e276aa5e/lang/java/avro/src/test/java/org/apache/avro/generic/TestGenericData.java ---------------------------------------------------------------------- diff --git a/lang/java/avro/src/test/java/org/apache/avro/generic/TestGenericData.java b/lang/java/avro/src/test/java/org/apache/avro/generic/TestGenericData.java index 7b33971..09ed66a 100644 --- a/lang/java/avro/src/test/java/org/apache/avro/generic/TestGenericData.java +++ b/lang/java/avro/src/test/java/org/apache/avro/generic/TestGenericData.java @@ -25,6 +25,8 @@ import java.util.List; import java.util.Collection; import java.util.ArrayDeque; +import static org.apache.avro.TestCircularReferences.Reference; +import static org.apache.avro.TestCircularReferences.Referenceable; import static org.junit.Assert.*; import java.util.Arrays; @@ -34,6 +36,7 @@ import org.apache.avro.Schema.Field; import org.apache.avro.AvroRuntimeException; import org.apache.avro.Schema.Type; import org.apache.avro.SchemaBuilder; +import org.apache.avro.TestCircularReferences.ReferenceManager; import org.apache.avro.io.BinaryData; import org.apache.avro.io.BinaryEncoder; import org.apache.avro.io.EncoderFactory; @@ -492,4 +495,61 @@ public class TestGenericData { record.put("myString", "myValue"); assertTrue(GenericData.get().validate(unionSchema, record)); } + + // Test copied from Apache Parquet: org.apache.parquet.avro.TestCircularReferences + @Test + public void testToStringRecursive() throws IOException { + ReferenceManager manager = new ReferenceManager(); + GenericData model = new GenericData(); + model.addLogicalTypeConversion(manager.getTracker()); + model.addLogicalTypeConversion(manager.getHandler()); + + Schema parentSchema = Schema.createRecord("Parent", null, null, false); + + Schema placeholderSchema = Schema.createRecord("Placeholder", null, null, false); + List<Schema.Field> placeholderFields = new ArrayList<Schema.Field>(); + placeholderFields.add( // at least one field is needed to be a valid schema + new Schema.Field("id", Schema.create(Schema.Type.LONG), null, (Object)null)); + placeholderSchema.setFields(placeholderFields); + + Referenceable idRef = new Referenceable("id"); + + Schema parentRefSchema = Schema.createUnion( + Schema.create(Schema.Type.NULL), + Schema.create(Schema.Type.LONG), + idRef.addToSchema(placeholderSchema)); + + Reference parentRef = new Reference("parent"); + + List<Schema.Field> childFields = new ArrayList<Schema.Field>(); + childFields.add(new Schema.Field("c", Schema.create(Schema.Type.STRING), null, (Object)null)); + childFields.add(new Schema.Field("parent", parentRefSchema, null, (Object)null)); + Schema childSchema = parentRef.addToSchema( + Schema.createRecord("Child", null, null, false, childFields)); + + List<Schema.Field> parentFields = new ArrayList<Schema.Field>(); + parentFields.add(new Schema.Field("id", Schema.create(Schema.Type.LONG), null, (Object)null)); + parentFields.add(new Schema.Field("p", Schema.create(Schema.Type.STRING), null, (Object)null)); + parentFields.add(new Schema.Field("child", childSchema, null, (Object)null)); + parentSchema.setFields(parentFields); + + Schema schema = idRef.addToSchema(parentSchema); + + Record parent = new Record(schema); + parent.put("id", 1L); + parent.put("p", "parent data!"); + + Record child = new Record(childSchema); + child.put("c", "child data!"); + child.put("parent", parent); + + parent.put("child", child); + + try { + assertNotNull(parent.toString()); // This should not fail with an infinite recursion (StackOverflowError) + } catch (StackOverflowError e) { + fail("StackOverflowError occurred"); + } + } + }
