From 5ebd2e71ee9be6afafa2f3c7195cf3e04c685d90 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta 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 result = (Map) 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 map, int level) { + if (level == 0) { + assertEquals(0, map.size()); + } else { + assertEquals(1, map.size()); + assertEquals("level"+level, map.keySet().iterator().next()); + _verifyNestedMap((Map) 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 areaNames; - public Map audienceSubCategoryNames; - public Map blockNames; - public Map seatCategoryNames; - public Map subTopicNames; - public Map subjectNames; - public Map topicNames; - public Map topicSubTopics; - public Map venueNames; - - public Map events; - public List 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 subTopicIds; - } - - static class Performance - { - public int id; - public int eventId; - public String name; - public String description; - public String logo; - - public List prices; - public List 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 areas; - } - - static class Area { - public int areaId; - public int[] blockIds; - } + static class Citm + { + public Map areaNames; + public Map audienceSubCategoryNames; + public Map blockNames; + public Map seatCategoryNames; + public Map subTopicNames; + public Map subjectNames; + public Map topicNames; + public Map topicSubTopics; + public Map venueNames; + + public Map events; + public List 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 subTopicIds; + } + + static class Performance + { + public int id; + public int eventId; + public String name; + public String description; + public String logo; + + public List prices; + public List 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 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)