You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@avro.apache.org by ni...@apache.org on 2016/10/04 14:25:03 UTC

avro git commit: AVRO-1923: Stop infinite recursion in GenericData.toString

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 <nb...@bol.com>
Authored: Thu Sep 22 14:42:47 2016 +0200
Committer: Niels Basjes <nb...@bol.com>
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");
+    }
+  }
+
 }