From 5ebd2e71ee9be6afafa2f3c7195cf3e04c685d90 Mon Sep 17 00:00:00 2001
From: Tatu Saloranta <tatu.saloranta@iki.fi>
Date: Sun, 19 Mar 2017 21:57:44 -0700
Subject: [PATCH] Fix #62

---
 .../dataformat/cbor/CBORGenerator.java        |  32 ++---
 .../cbor/GeneratorDeepNestingTest.java        | 109 ++++++++++++++++
 .../cbor/mapper/BiggerDataTest.java           | 118 +++++++++---------
 release-notes/VERSION                         |   1 +
 4 files changed, 186 insertions(+), 74 deletions(-)
 create mode 100644 cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/GeneratorDeepNestingTest.java

diff --git a/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java b/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java
index 21aa6d9e6..f813f4c9c 100644
--- a/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java
+++ b/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java
@@ -14,7 +14,7 @@
 
 /**
  * {@link JsonGenerator} implementation that writes CBOR encoded content.
- * 
+ *
  * @author Tatu Saloranta
  */
 public class CBORGenerator extends GeneratorBase
@@ -183,18 +183,18 @@ public int getMask() {
     /* Tracking of remaining elements to write
     /**********************************************************
      */
-    
+
     protected int[] _elementCounts = NO_INTS;
 
     protected int _elementCountsPtr;
-    
+
     /**
      * Number of elements remaining in the current complex structure (if any),
      * when writing defined-length Arrays, Objects; marker {@link #INDEFINITE_LENGTH}
      * otherwise.
      */
     protected int _currentRemainingElements = INDEFINITE_LENGTH;
-    
+
     /*
     /**********************************************************
     /* Shared String detection
@@ -490,7 +490,7 @@ public final void writeStartArray() throws IOException {
         _verifyValueWrite("start an array");
         _writeContext = _writeContext.createChildArrayContext();
         if (_elementCountsPtr > 0) {
-            _elementCounts[_elementCountsPtr++] = _currentRemainingElements;
+            _pushRemainingElements();
         }
         _currentRemainingElements = INDEFINITE_LENGTH;
         _writeByte(BYTE_ARRAY_INDEFINITE);
@@ -505,10 +505,7 @@ public final void writeStartArray() throws IOException {
     public void writeStartArray(int elementsToWrite) throws IOException {
         _verifyValueWrite("start an array");
         _writeContext = _writeContext.createChildArrayContext();
-        if (_elementCounts.length == _elementCountsPtr) { // initially, as well as if full
-            _elementCounts = Arrays.copyOf(_elementCounts, _elementCounts.length+10);
-        }
-        _elementCounts[_elementCountsPtr++] = _currentRemainingElements;
+        _pushRemainingElements();
         _currentRemainingElements = elementsToWrite;
         _writeLengthMarker(PREFIX_TYPE_ARRAY, elementsToWrite);
     }
@@ -527,7 +524,7 @@ public final void writeStartObject() throws IOException {
         _verifyValueWrite("start an object");
         _writeContext = _writeContext.createChildObjectContext();
         if (_elementCountsPtr > 0) {
-            _elementCounts[_elementCountsPtr++] = _currentRemainingElements;
+            _pushRemainingElements();
         }
         _currentRemainingElements = INDEFINITE_LENGTH;
         _writeByte(BYTE_OBJECT_INDEFINITE);
@@ -543,7 +540,7 @@ public final void writeStartObject(Object forValue) throws IOException {
             ctxt.setCurrentValue(forValue);
         }
         if (_elementCountsPtr > 0) {
-            _elementCounts[_elementCountsPtr++] = _currentRemainingElements;
+            _pushRemainingElements();
         }
         _currentRemainingElements = INDEFINITE_LENGTH;
         _writeByte(BYTE_OBJECT_INDEFINITE);
@@ -552,10 +549,7 @@ public final void writeStartObject(Object forValue) throws IOException {
     public final void writeStartObject(int elementsToWrite) throws IOException {
         _verifyValueWrite("start an object");
         _writeContext = _writeContext.createChildObjectContext();
-        if (_elementCounts.length == _elementCountsPtr) { // initially, as well as if full
-            _elementCounts = Arrays.copyOf(_elementCounts, _elementCounts.length+10);
-        }
-        _elementCounts[_elementCountsPtr++] = _currentRemainingElements;
+        _pushRemainingElements();
         _currentRemainingElements = elementsToWrite;
         _writeLengthMarker(PREFIX_TYPE_OBJECT, elementsToWrite);
     }
@@ -605,6 +599,14 @@ public void writeArray(double[] array, int offset, int length) throws IOExceptio
         }
     }
 
+    // @since 2.8.8
+    private final void _pushRemainingElements() {
+        if (_elementCounts.length == _elementCountsPtr) { // initially, as well as if full
+            _elementCounts = Arrays.copyOf(_elementCounts, _elementCounts.length+10);
+        }
+        _elementCounts[_elementCountsPtr++] = _currentRemainingElements;
+    }
+
     private final void _writeNumberNoCheck(int i) throws IOException {
         int marker;
         if (i < 0) {
diff --git a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/GeneratorDeepNestingTest.java b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/GeneratorDeepNestingTest.java
new file mode 100644
index 000000000..983df1774
--- /dev/null
+++ b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/GeneratorDeepNestingTest.java
@@ -0,0 +1,109 @@
+package com.fasterxml.jackson.dataformat.cbor;
+
+import java.io.ByteArrayOutputStream;
+import java.util.*;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class GeneratorDeepNestingTest extends CBORTestBase
+{
+    /*
+    /**********************************************************
+    /* Test methods
+    /**********************************************************
+     */
+
+    final ObjectMapper MAPPER = cborMapper();
+
+    // for [dataformats-binary#62]
+    @SuppressWarnings("unchecked")
+    public void testDeeplyNestedMap() throws Exception
+    {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        JsonGenerator gen = MAPPER.getFactory().createGenerator(out);
+        _writeNestedMap(gen, 23);
+        gen.close();
+        byte[] encoded = out.toByteArray();
+        Map<String,Object> result = (Map<String,Object>) MAPPER.readValue(encoded, Map.class);
+        _verifyNestedMap(result, 23);
+    }
+
+    private void _writeNestedMap(JsonGenerator gen, int levelsLeft) throws Exception
+    {
+        if (levelsLeft == 0) {
+            gen.writeStartObject();
+            gen.writeEndObject();
+            return;
+        }
+
+        // exercise different kinds of write methods...
+        switch (levelsLeft % 3) {
+        case 0:
+            gen.writeStartObject();
+            break;
+        case 1:
+            gen.writeStartObject(1);
+            break;
+        default:
+            gen.writeStartObject(gen); // bogus "current" object
+            break;
+        }
+        gen.writeFieldName("level"+levelsLeft);
+        _writeNestedMap(gen, levelsLeft-1);
+        gen.writeEndObject();
+    }
+
+    @SuppressWarnings("unchecked")
+    private void _verifyNestedMap(Map<String,?> map, int level) {
+        if (level == 0) {
+            assertEquals(0, map.size());
+        } else {
+            assertEquals(1, map.size());
+            assertEquals("level"+level, map.keySet().iterator().next());
+            _verifyNestedMap((Map<String,?>) map.values().iterator().next(), level-1);
+        }
+    }
+
+    public void testDeeplyNestedArray() throws Exception
+    {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        JsonGenerator gen = MAPPER.getFactory().createGenerator(out);
+        _writeNestedArray(gen, 23);
+        gen.close();
+        byte[] encoded = out.toByteArray();
+        List<?> result = (List<?>) MAPPER.readValue(encoded, List.class);
+        _verifyNesteArray(result, 23);
+    }
+
+    private void _writeNestedArray(JsonGenerator gen, int levelsLeft) throws Exception
+    {
+        if (levelsLeft == 0) {
+            gen.writeStartArray();
+            gen.writeEndArray();
+            return;
+        }
+        // exercise different kinds of write methods...
+        switch (levelsLeft % 2) {
+        case 0:
+            gen.writeStartArray();
+            break;
+        default:
+            gen.writeStartArray(2);
+            break;
+        }
+        gen.writeNumber(levelsLeft);
+        _writeNestedArray(gen, levelsLeft-1);
+        gen.writeEndArray();
+    }
+
+    private void _verifyNesteArray(List<?> list, int level) {
+        if (level == 0) {
+            assertEquals(0, list.size());
+        } else {
+            assertEquals(2,list.size());
+            assertEquals(Integer.valueOf(level), list.get(0));
+            _verifyNesteArray((List<?>) list.get(1), level-1);
+        }
+    }
+}
diff --git a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/mapper/BiggerDataTest.java b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/mapper/BiggerDataTest.java
index 2815516a5..0e6159bee 100644
--- a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/mapper/BiggerDataTest.java
+++ b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/mapper/BiggerDataTest.java
@@ -12,65 +12,65 @@
  */
 public class BiggerDataTest extends CBORTestBase
 {
-	static class Citm
-	{
-		public Map<Integer,String> areaNames;
-		public Map<Integer,String> audienceSubCategoryNames;
-		public Map<Integer,String> blockNames;
-		public Map<Integer,String> seatCategoryNames;
-		public Map<Integer,String> subTopicNames;
-		public Map<Integer,String> subjectNames;
-		public Map<Integer,String> topicNames;
-		public Map<Integer,int[]> topicSubTopics;
-		public Map<String,String> venueNames;
-
-		public Map<Integer,Event> events;
-		public List<Performance> performances;
-	}
-
-	static class Event
-	{
-		public int id;
-		public String name;
-		public String description;
-		public String subtitle;
-		public String logo;
-		public int subjectCode;
-		public int[] topicIds;
-		public LinkedHashSet<Integer> subTopicIds;
-	}
-
-	static class Performance
-	{
-		public int id;
-		public int eventId;
-		public String name;
-		public String description;
-		public String logo;
-
-		public List<Price> prices;
-		public List<SeatCategory> seatCategories;
-
-		public long start;
-		public String seatMapImage;
-		public String venueCode;
-}
-
-	static class Price {
-		public int amount;
-		public int audienceSubCategoryId;
-		public int seatCategoryId;
-	}
-
-	static class SeatCategory {
-		public int seatCategoryId;
-		public List<Area> areas;
-	}
-
-	static class Area {
-		public int areaId;
-		public int[] blockIds;
-	}
+    static class Citm
+    {
+        public Map<Integer,String> areaNames;
+        public Map<Integer,String> audienceSubCategoryNames;
+        public Map<Integer,String> blockNames;
+        public Map<Integer,String> seatCategoryNames;
+        public Map<Integer,String> subTopicNames;
+        public Map<Integer,String> subjectNames;
+        public Map<Integer,String> topicNames;
+        public Map<Integer,int[]> topicSubTopics;
+        public Map<String,String> venueNames;
+
+        public Map<Integer,Event> events;
+        public List<Performance> performances;
+    }
+
+    static class Event
+    {
+        public int id;
+        public String name;
+        public String description;
+        public String subtitle;
+        public String logo;
+        public int subjectCode;
+        public int[] topicIds;
+        public LinkedHashSet<Integer> subTopicIds;
+    }
+
+    static class Performance
+    {
+        public int id;
+        public int eventId;
+        public String name;
+        public String description;
+        public String logo;
+
+        public List<Price> prices;
+        public List<SeatCategory> seatCategories;
+
+        public long start;
+        public String seatMapImage;
+        public String venueCode;
+    }
+
+    static class Price {
+        public int amount;
+        public int audienceSubCategoryId;
+        public int seatCategoryId;
+    }
+
+    static class SeatCategory {
+        public int seatCategoryId;
+        public List<Area> areas;
+    }
+
+    static class Area {
+        public int areaId;
+        public int[] blockIds;
+    }
 
     /*
     /**********************************************************
diff --git a/release-notes/VERSION b/release-notes/VERSION
index 34baf4abc..32907f9d8 100644
--- a/release-notes/VERSION
+++ b/release-notes/VERSION
@@ -16,6 +16,7 @@ Modules:
 #54 (protobuf): Some fields are left null
 #58 (avro): Regression due to changed namespace of inner enum types
  (reported by Peter R)
+#62: `java.lang.ArrayIndexOutOfBoundsException` at `CBORGenerator.java`:548
 
 2.8.7 (21-Feb-2017)