diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroGenerator.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroGenerator.java index 18ce25b23..b30dcf3b6 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroGenerator.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroGenerator.java @@ -25,7 +25,7 @@ public enum Feature { /** * Feature that can be disabled to prevent Avro from buffering any more - * data then absolutely necessary. + * data than absolutely necessary. * This affects buffering by underlying codec. * Note that disabling buffer is likely to reduce performance if the underlying * input/output is unbuffered. @@ -157,6 +157,11 @@ public void setSchema(AvroSchema schema) schema.getAvroSchema(), _encoder); } + @Override + public StreamWriteConstraints streamWriteConstraints() { + return _ioContext.streamWriteConstraints(); + } + /* /********************************************************** /* Versioned @@ -374,6 +379,7 @@ public void close() throws IOException @Override public final void writeStartArray() throws IOException { _avroContext = _avroContext.createChildArrayContext(null); + streamWriteConstraints().validateNestingDepth(_avroContext.getNestingDepth()); _complete = false; } @@ -392,12 +398,14 @@ public final void writeEndArray() throws IOException @Override public final void writeStartObject() throws IOException { _avroContext = _avroContext.createChildObjectContext(null); + streamWriteConstraints().validateNestingDepth(_avroContext.getNestingDepth()); _complete = false; } @Override public void writeStartObject(Object forValue) throws IOException { _avroContext = _avroContext.createChildObjectContext(forValue); + streamWriteConstraints().validateNestingDepth(_avroContext.getNestingDepth()); _complete = false; } diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroWriteContext.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroWriteContext.java index 008056ef0..2aa93cb16 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroWriteContext.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroWriteContext.java @@ -52,6 +52,7 @@ protected AvroWriteContext(int type, AvroWriteContext parent, super(); _type = type; _parent = parent; + _nestingDepth = parent == null ? 0 : parent._nestingDepth + 1; _generator = generator; _schema = schema; _currentValue = currValue; diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/dos/CyclicDataSerTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/dos/CyclicDataSerTest.java new file mode 100644 index 000000000..046780177 --- /dev/null +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/dos/CyclicDataSerTest.java @@ -0,0 +1,46 @@ +package com.fasterxml.jackson.dataformat.avro.dos; + +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.exc.InvalidDefinitionException; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroSchema; +import com.fasterxml.jackson.dataformat.avro.AvroTestBase; + +/** + * Simple unit tests to verify that we fail gracefully if you attempt to serialize + * data that is cyclic (eg a list that contains itself). + */ +public class CyclicDataSerTest extends AvroTestBase +{ + + public static class Bean + { + Bean _next; + final String _name; + + public Bean(Bean next, String name) { + _next = next; + _name = name; + } + + public Bean getNext() { return _next; } + public String getName() { return _name; } + + public void assignNext(Bean n) { _next = n; } + } + + private final AvroMapper MAPPER = getMapper(); + + public void testCyclic() throws Exception { + Bean bean = new Bean(null, "123"); + bean.assignNext(bean); + try { + AvroSchema schema = MAPPER.schemaFor(Bean.class); + MAPPER.writer(schema).writeValueAsBytes(bean); + fail("expected InvalidDefinitionException"); + } catch (InvalidDefinitionException idex) { + assertTrue("InvalidDefinitionException message is as expected?", + idex.getMessage().startsWith("Direct self-reference leading to cycle")); + } + } +} 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 0a158e377..562392e6e 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 @@ -379,6 +379,11 @@ public JacksonFeatureSet getWriteCapabilities() { /********************************************************** */ + @Override + public StreamWriteConstraints streamWriteConstraints() { + return _ioContext.streamWriteConstraints(); + } + /** * No way (or need) to indent anything, so let's block any attempts. (should * we throw an exception instead?) @@ -570,6 +575,7 @@ public final void writeFieldId(long id) throws IOException { public final void writeStartArray() throws IOException { _verifyValueWrite("start an array"); _streamWriteContext = _streamWriteContext.createChildArrayContext(null); + streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth()); if (_elementCountsPtr > 0) { _pushRemainingElements(); } @@ -581,6 +587,7 @@ public final void writeStartArray() throws IOException { public void writeStartArray(Object forValue) throws IOException { _verifyValueWrite("start an array"); _streamWriteContext = _streamWriteContext.createChildArrayContext(forValue); + streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth()); if (_elementCountsPtr > 0) { _pushRemainingElements(); } @@ -596,6 +603,7 @@ public void writeStartArray(Object forValue) throws IOException { public void writeStartArray(Object forValue, int elementsToWrite) throws IOException { _verifyValueWrite("start an array"); _streamWriteContext = _streamWriteContext.createChildArrayContext(forValue); + streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth()); _pushRemainingElements(); _currentRemainingElements = elementsToWrite; _writeLengthMarker(PREFIX_TYPE_ARRAY, elementsToWrite); @@ -606,6 +614,7 @@ public void writeStartArray(Object forValue, int elementsToWrite) throws IOExcep public void writeStartArray(int elementsToWrite) throws IOException { _verifyValueWrite("start an array"); _streamWriteContext = _streamWriteContext.createChildArrayContext(null); + streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth()); _pushRemainingElements(); _currentRemainingElements = elementsToWrite; _writeLengthMarker(PREFIX_TYPE_ARRAY, elementsToWrite); @@ -624,6 +633,7 @@ public final void writeEndArray() throws IOException { public final void writeStartObject() throws IOException { _verifyValueWrite("start an object"); _streamWriteContext = _streamWriteContext.createChildObjectContext(null); + streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth()); if (_elementCountsPtr > 0) { _pushRemainingElements(); } @@ -636,6 +646,7 @@ public final void writeStartObject() throws IOException { public final void writeStartObject(Object forValue) throws IOException { _verifyValueWrite("start an object"); CBORWriteContext ctxt = _streamWriteContext.createChildObjectContext(forValue); + streamWriteConstraints().validateNestingDepth(ctxt.getNestingDepth()); _streamWriteContext = ctxt; if (_elementCountsPtr > 0) { _pushRemainingElements(); @@ -652,6 +663,7 @@ public final void writeStartObject(int elementsToWrite) throws IOException { public void writeStartObject(Object forValue, int elementsToWrite) throws IOException { _verifyValueWrite("start an object"); _streamWriteContext = _streamWriteContext.createChildObjectContext(forValue); + streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth()); _pushRemainingElements(); _currentRemainingElements = elementsToWrite; _writeLengthMarker(PREFIX_TYPE_OBJECT, elementsToWrite); diff --git a/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORWriteContext.java b/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORWriteContext.java index 292acbd18..66434a2f7 100644 --- a/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORWriteContext.java +++ b/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORWriteContext.java @@ -65,6 +65,7 @@ protected CBORWriteContext(int type, CBORWriteContext parent, DupDetector dups, super(); _type = type; _parent = parent; + _nestingDepth = parent == null ? 0 : parent._nestingDepth + 1; _dups = dups; _index = -1; _currentValue = currentValue; diff --git a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/gen/dos/CyclicDataSerTest.java b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/gen/dos/CyclicDataSerTest.java new file mode 100644 index 000000000..386e91d15 --- /dev/null +++ b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/gen/dos/CyclicDataSerTest.java @@ -0,0 +1,32 @@ +package com.fasterxml.jackson.dataformat.cbor.gen.dos; + +import com.fasterxml.jackson.core.StreamWriteConstraints; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.cbor.CBORTestBase; + +import java.util.ArrayList; +import java.util.List; + +/** + * Simple unit tests to verify that we fail gracefully if you attempt to serialize + * data that is cyclic (eg a list that contains itself). + */ +public class CyclicDataSerTest extends CBORTestBase +{ + private final ObjectMapper MAPPER = cborMapper(); + + public void testListWithSelfReference() throws Exception { + List list = new ArrayList<>(); + list.add(list); + try { + MAPPER.writeValueAsBytes(list); + fail("expected JsonMappingException"); + } catch (JsonMappingException jmex) { + String exceptionPrefix = String.format("Document nesting depth (%d) exceeds the maximum allowed", + StreamWriteConstraints.DEFAULT_MAX_DEPTH + 1); + assertTrue("JsonMappingException message is as expected?", + jmex.getMessage().startsWith(exceptionPrefix)); + } + } +} diff --git a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/dos/DeepNestingCBORParserTest.java b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/dos/DeepNestingCBORParserTest.java index 8705f8c6e..8ba1eac61 100644 --- a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/dos/DeepNestingCBORParserTest.java +++ b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/dos/DeepNestingCBORParserTest.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.StreamReadConstraints; +import com.fasterxml.jackson.core.StreamWriteConstraints; import com.fasterxml.jackson.core.exc.StreamConstraintsException; import com.fasterxml.jackson.dataformat.cbor.CBORFactory; import com.fasterxml.jackson.dataformat.cbor.CBORTestBase; @@ -28,8 +29,10 @@ public void testDeeplyNestedObjects() throws Exception } fail("expected StreamConstraintsException"); } catch (StreamConstraintsException e) { - assertTrue("unexpected message: " + e.getMessage(), - e.getMessage().startsWith("Document nesting depth (1001) exceeds the maximum allowed")); + String exceptionPrefix = String.format("Document nesting depth (%d) exceeds the maximum allowed", + StreamReadConstraints.DEFAULT_MAX_DEPTH + 1); + assertTrue("JsonMappingException message is as expected?", + e.getMessage().startsWith(exceptionPrefix)); } } @@ -61,8 +64,10 @@ public void testDeeplyNestedArrays() throws Exception } fail("expected StreamConstraintsException"); } catch (StreamConstraintsException e) { - assertTrue("unexpected message: " + e.getMessage(), - e.getMessage().startsWith("Document nesting depth (1001) exceeds the maximum allowed")); + String exceptionPrefix = String.format("Document nesting depth (%d) exceeds the maximum allowed", + StreamReadConstraints.DEFAULT_MAX_DEPTH + 1); + assertTrue("JsonMappingException message is as expected?", + e.getMessage().startsWith(exceptionPrefix)); } } @@ -83,7 +88,10 @@ public void testDeeplyNestedArraysWithUnconstrainedMapper() throws Exception } private void genDeepDoc(final ByteArrayOutputStream out, final int depth) throws IOException { - try (JsonGenerator gen = cborGenerator(out)) { + CBORFactory cborFactory = cborFactoryBuilder() + .streamWriteConstraints(StreamWriteConstraints.builder().maxNestingDepth(Integer.MAX_VALUE).build()) + .build(); + try (JsonGenerator gen = cborGenerator(cborFactory, out)) { for (int i = 0; i < depth; i++) { gen.writeStartObject(); gen.writeFieldName("a"); @@ -96,7 +104,10 @@ private void genDeepDoc(final ByteArrayOutputStream out, final int depth) throws } private void genDeepArrayDoc(final ByteArrayOutputStream out, final int depth) throws IOException { - try (JsonGenerator gen = cborGenerator(out)) { + CBORFactory cborFactory = cborFactoryBuilder() + .streamWriteConstraints(StreamWriteConstraints.builder().maxNestingDepth(Integer.MAX_VALUE).build()) + .build(); + try (JsonGenerator gen = cborGenerator(cborFactory, out)) { for (int i = 0; i < depth; i++) { gen.writeStartObject(); gen.writeFieldName("a"); diff --git a/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/IonGenerator.java b/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/IonGenerator.java index 841f57914..15ed4b214 100644 --- a/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/IonGenerator.java +++ b/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/IonGenerator.java @@ -26,6 +26,7 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.ObjectCodec; import com.fasterxml.jackson.core.StreamWriteCapability; +import com.fasterxml.jackson.core.StreamWriteConstraints; import com.fasterxml.jackson.core.Version; import com.fasterxml.jackson.core.base.GeneratorBase; import com.fasterxml.jackson.core.io.IOContext; @@ -148,6 +149,11 @@ public Version version() { return PackageVersion.VERSION; } + @Override + public StreamWriteConstraints streamWriteConstraints() { + return _ioContext.streamWriteConstraints(); + } + /* /********************************************************************** /* JsonGenerator implementation: state handling @@ -524,6 +530,7 @@ protected void _writeFieldName(String value) throws IOException { public void writeStartArray() throws IOException { _verifyValueWrite("start an array"); // <-- copied from UTF8JsonGenerator _writeContext = _writeContext.createChildArrayContext(); // <-- copied from UTF8JsonGenerator + streamWriteConstraints().validateNestingDepth(_writeContext.getNestingDepth()); _writer.stepIn(IonType.LIST); } @@ -531,6 +538,7 @@ public void writeStartArray() throws IOException { public void writeStartObject() throws IOException { _verifyValueWrite("start an object"); // <-- copied from UTF8JsonGenerator _writeContext = _writeContext.createChildObjectContext(); // <-- copied from UTF8JsonGenerator + streamWriteConstraints().validateNestingDepth(_writeContext.getNestingDepth()); _writer.stepIn(IonType.STRUCT); } @@ -540,6 +548,7 @@ public void writeStartObject() throws IOException { public void writeStartSexp() throws IOException { _verifyValueWrite("start a sexp"); _writeContext = ((IonWriteContext) _writeContext).createChildSexpContext(); + streamWriteConstraints().validateNestingDepth(_writeContext.getNestingDepth()); _writer.stepIn(IonType.SEXP); } diff --git a/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/IonWriteContext.java b/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/IonWriteContext.java index 450a3e30a..0171c87c7 100644 --- a/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/IonWriteContext.java +++ b/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/IonWriteContext.java @@ -26,7 +26,7 @@ * context to track that. Sexp handling is modeled after arrays. */ public class IonWriteContext extends JsonWriteContext { - // Both contstants are in the tens instead of the ones to avoid conflict with the native + // Both constants are in the tens instead of the ones to avoid conflict with the native // Jackson ones // Ion-specific contexts diff --git a/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/dos/CyclicDataSerTest.java b/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/dos/CyclicDataSerTest.java new file mode 100644 index 000000000..b83525264 --- /dev/null +++ b/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/dos/CyclicDataSerTest.java @@ -0,0 +1,37 @@ +package com.fasterxml.jackson.dataformat.ion.dos; + +import com.fasterxml.jackson.core.StreamWriteConstraints; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.ion.IonObjectMapper; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * Simple unit tests to verify that we fail gracefully if you attempt to serialize + * data that is cyclic (eg a list that contains itself). + */ +public class CyclicDataSerTest +{ + private final ObjectMapper MAPPER = IonObjectMapper.builderForTextualWriters().build(); + + @Test + public void testListWithSelfReference() throws Exception { + List list = new ArrayList<>(); + list.add(list); + try { + MAPPER.writeValueAsBytes(list); + fail("expected JsonMappingException"); + } catch (JsonMappingException jmex) { + String exceptionPrefix = String.format("Document nesting depth (%d) exceeds the maximum allowed", + StreamWriteConstraints.DEFAULT_MAX_DEPTH + 1); + assertTrue("JsonMappingException message is as expected?", + jmex.getMessage().startsWith(exceptionPrefix)); + } + } +} diff --git a/protobuf/src/main/java/com/fasterxml/jackson/dataformat/protobuf/ProtobufGenerator.java b/protobuf/src/main/java/com/fasterxml/jackson/dataformat/protobuf/ProtobufGenerator.java index 065595b3e..dc0b523d4 100644 --- a/protobuf/src/main/java/com/fasterxml/jackson/dataformat/protobuf/ProtobufGenerator.java +++ b/protobuf/src/main/java/com/fasterxml/jackson/dataformat/protobuf/ProtobufGenerator.java @@ -155,6 +155,11 @@ public void setSchema(ProtobufSchema schema) _pbContext = _rootContext = ProtobufWriteContext.createRootContext(schema.getRootType()); } + @Override + public StreamWriteConstraints streamWriteConstraints() { + return _ioContext.streamWriteConstraints(); + } + @Override // since 2.13 public Object currentValue() { return _pbContext.getCurrentValue(); @@ -413,6 +418,7 @@ public final void writeStartArray() throws IOException // NOTE: do NOT clear _currField; needed for actual element type _pbContext = _pbContext.createChildArrayContext(); + streamWriteConstraints().validateNestingDepth(_pbContext.getNestingDepth()); _writeTag = !_currField.packed; /* Unpacked vs packed: if unpacked, nothing special is needed, since it * is equivalent to just replicating same field N times. @@ -481,6 +487,7 @@ public final void writeStartObject() throws IOException // but do NOT clear next field here _inObject = true; } + streamWriteConstraints().validateNestingDepth(_pbContext.getNestingDepth()); // even if within array, object fields use tags _writeTag = true; } diff --git a/protobuf/src/main/java/com/fasterxml/jackson/dataformat/protobuf/ProtobufWriteContext.java b/protobuf/src/main/java/com/fasterxml/jackson/dataformat/protobuf/ProtobufWriteContext.java index 93177b1a1..eb6df991d 100644 --- a/protobuf/src/main/java/com/fasterxml/jackson/dataformat/protobuf/ProtobufWriteContext.java +++ b/protobuf/src/main/java/com/fasterxml/jackson/dataformat/protobuf/ProtobufWriteContext.java @@ -49,6 +49,7 @@ protected ProtobufWriteContext(int type, ProtobufWriteContext parent, super(); _type = type; _parent = parent; + _nestingDepth = parent == null ? 0 : parent._nestingDepth + 1; _message = msg; } diff --git a/protobuf/src/test/java/com/fasterxml/jackson/dataformat/protobuf/dos/DeepNestingProtobufParserTest.java b/protobuf/src/test/java/com/fasterxml/jackson/dataformat/protobuf/dos/DeepNestingProtobufParserTest.java index af5787670..5a16bdbe4 100644 --- a/protobuf/src/test/java/com/fasterxml/jackson/dataformat/protobuf/dos/DeepNestingProtobufParserTest.java +++ b/protobuf/src/test/java/com/fasterxml/jackson/dataformat/protobuf/dos/DeepNestingProtobufParserTest.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.StreamReadConstraints; +import com.fasterxml.jackson.core.StreamWriteConstraints; import com.fasterxml.jackson.core.exc.StreamConstraintsException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -31,6 +32,7 @@ public Node(int id, Node next) { { ProtobufFactory f = ProtobufFactory.builder() .streamReadConstraints(StreamReadConstraints.builder().maxNestingDepth(Integer.MAX_VALUE).build()) + .streamWriteConstraints(StreamWriteConstraints.builder().maxNestingDepth(Integer.MAX_VALUE).build()) .build(); MAPPER_UNLIMITED = new ProtobufMapper(f); } @@ -60,8 +62,10 @@ public void testDeeplyNestedObjectsLowLimits() throws Exception while (p.nextToken() != null) { } fail("expected StreamConstraintsException"); } catch (StreamConstraintsException e) { - assertTrue("unexpected message: " + e.getMessage(), - e.getMessage().startsWith("Document nesting depth (1001) exceeds the maximum allowed")); + String exceptionPrefix = String.format("Document nesting depth (%d) exceeds the maximum allowed", + StreamReadConstraints.DEFAULT_MAX_DEPTH + 1); + assertTrue("JsonMappingException message is as expected?", + e.getMessage().startsWith(exceptionPrefix)); } } @@ -71,7 +75,7 @@ private byte[] genDeepDoc(int depth) throws Exception { while (--depth > 0) { node = new Node(depth, node); } - return DEFAULT_MAPPER.writer(NODE_SCHEMA) + return MAPPER_UNLIMITED.writer(NODE_SCHEMA) .writeValueAsBytes(node); } diff --git a/smile/src/main/java/com/fasterxml/jackson/dataformat/smile/SmileGenerator.java b/smile/src/main/java/com/fasterxml/jackson/dataformat/smile/SmileGenerator.java index 4f9c0126b..4ddbb150f 100644 --- a/smile/src/main/java/com/fasterxml/jackson/dataformat/smile/SmileGenerator.java +++ b/smile/src/main/java/com/fasterxml/jackson/dataformat/smile/SmileGenerator.java @@ -586,6 +586,11 @@ public SmileGenerator configure(Feature f, boolean state) { return this; } + @Override + public StreamWriteConstraints streamWriteConstraints() { + return _ioContext.streamWriteConstraints(); + } + /* /********************************************************** /* Extended API, other @@ -628,6 +633,7 @@ public final void writeStartArray() throws IOException { _verifyValueWrite("start an array"); _streamWriteContext = _streamWriteContext.createChildArrayContext(null); + streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth()); _writeByte(TOKEN_LITERAL_START_ARRAY); } @@ -636,6 +642,7 @@ public final void writeStartArray(Object forValue) throws IOException { _verifyValueWrite("start an array"); _streamWriteContext = _streamWriteContext.createChildArrayContext(forValue); + streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth()); _writeByte(TOKEN_LITERAL_START_ARRAY); } @@ -644,6 +651,7 @@ public final void writeStartArray(Object forValue, int elementsToWrite) throws I { _verifyValueWrite("start an array"); _streamWriteContext = _streamWriteContext.createChildArrayContext(forValue); + streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth()); _writeByte(TOKEN_LITERAL_START_ARRAY); } @@ -653,6 +661,7 @@ public final void writeStartArray(int size) throws IOException { _verifyValueWrite("start an array"); _streamWriteContext = _streamWriteContext.createChildArrayContext(null); + streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth()); _writeByte(TOKEN_LITERAL_START_ARRAY); } @@ -671,6 +680,7 @@ public final void writeStartObject() throws IOException { _verifyValueWrite("start an object"); _streamWriteContext = _streamWriteContext.createChildObjectContext(null); + streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth()); _writeByte(TOKEN_LITERAL_START_OBJECT); } @@ -679,6 +689,7 @@ public final void writeStartObject(Object forValue) throws IOException { _verifyValueWrite("start an object"); SmileWriteContext ctxt = _streamWriteContext.createChildObjectContext(forValue); + streamWriteConstraints().validateNestingDepth(ctxt.getNestingDepth()); _streamWriteContext = ctxt; _writeByte(TOKEN_LITERAL_START_OBJECT); } @@ -687,6 +698,7 @@ public final void writeStartObject(Object forValue) throws IOException public void writeStartObject(Object forValue, int elementsToWrite) throws IOException { _verifyValueWrite("start an object"); SmileWriteContext ctxt = _streamWriteContext.createChildObjectContext(forValue); + streamWriteConstraints().validateNestingDepth(ctxt.getNestingDepth()); _streamWriteContext = ctxt; _writeByte(TOKEN_LITERAL_START_OBJECT); } diff --git a/smile/src/main/java/com/fasterxml/jackson/dataformat/smile/SmileWriteContext.java b/smile/src/main/java/com/fasterxml/jackson/dataformat/smile/SmileWriteContext.java index 5b88c1f45..b7dd6b2a0 100644 --- a/smile/src/main/java/com/fasterxml/jackson/dataformat/smile/SmileWriteContext.java +++ b/smile/src/main/java/com/fasterxml/jackson/dataformat/smile/SmileWriteContext.java @@ -61,6 +61,7 @@ protected SmileWriteContext(int type, SmileWriteContext parent, DupDetector dups super(); _type = type; _parent = parent; + _nestingDepth = parent == null ? 0 : parent._nestingDepth + 1; _dups = dups; _index = -1; _currentValue = currentValue; diff --git a/smile/src/test/java/com/fasterxml/jackson/dataformat/smile/gen/dos/CyclicDataSerTest.java b/smile/src/test/java/com/fasterxml/jackson/dataformat/smile/gen/dos/CyclicDataSerTest.java new file mode 100644 index 000000000..3315a3e4e --- /dev/null +++ b/smile/src/test/java/com/fasterxml/jackson/dataformat/smile/gen/dos/CyclicDataSerTest.java @@ -0,0 +1,32 @@ +package com.fasterxml.jackson.dataformat.smile.gen.dos; + +import com.fasterxml.jackson.core.StreamWriteConstraints; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.smile.BaseTestForSmile; + +import java.util.ArrayList; +import java.util.List; + +/** + * Simple unit tests to verify that we fail gracefully if you attempt to serialize + * data that is cyclic (eg a list that contains itself). + */ +public class CyclicDataSerTest extends BaseTestForSmile +{ + private final ObjectMapper MAPPER = smileMapper(); + + public void testListWithSelfReference() throws Exception { + List list = new ArrayList<>(); + list.add(list); + try { + MAPPER.writeValueAsBytes(list); + fail("expected JsonMappingException"); + } catch (JsonMappingException jmex) { + String exceptionPrefix = String.format("Document nesting depth (%d) exceeds the maximum allowed", + StreamWriteConstraints.DEFAULT_MAX_DEPTH + 1); + assertTrue("JsonMappingException message is as expected?", + jmex.getMessage().startsWith(exceptionPrefix)); + } + } +} diff --git a/smile/src/test/java/com/fasterxml/jackson/dataformat/smile/parse/dos/DeepNestingSmileParserTest.java b/smile/src/test/java/com/fasterxml/jackson/dataformat/smile/parse/dos/DeepNestingSmileParserTest.java index dd60e155c..0151b979b 100644 --- a/smile/src/test/java/com/fasterxml/jackson/dataformat/smile/parse/dos/DeepNestingSmileParserTest.java +++ b/smile/src/test/java/com/fasterxml/jackson/dataformat/smile/parse/dos/DeepNestingSmileParserTest.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.StreamReadConstraints; +import com.fasterxml.jackson.core.StreamWriteConstraints; import com.fasterxml.jackson.core.exc.StreamConstraintsException; import com.fasterxml.jackson.dataformat.smile.BaseTestForSmile; import com.fasterxml.jackson.dataformat.smile.SmileFactory; @@ -28,8 +29,10 @@ public void testDeeplyNestedObjects() throws Exception } fail("expected StreamConstraintsException"); } catch (StreamConstraintsException e) { - assertTrue("unexpected message: " + e.getMessage(), - e.getMessage().startsWith("Document nesting depth (1001) exceeds the maximum allowed")); + String exceptionPrefix = String.format("Document nesting depth (%d) exceeds the maximum allowed", + StreamReadConstraints.DEFAULT_MAX_DEPTH + 1); + assertTrue("JsonMappingException message is as expected?", + e.getMessage().startsWith(exceptionPrefix)); } } @@ -61,8 +64,10 @@ public void testDeeplyNestedArrays() throws Exception } fail("expected StreamConstraintsException"); } catch (StreamConstraintsException e) { - assertTrue("unexpected message: " + e.getMessage(), - e.getMessage().startsWith("Document nesting depth (1001) exceeds the maximum allowed")); + String exceptionPrefix = String.format("Document nesting depth (%d) exceeds the maximum allowed", + StreamReadConstraints.DEFAULT_MAX_DEPTH + 1); + assertTrue("JsonMappingException message is as expected?", + e.getMessage().startsWith(exceptionPrefix)); } } @@ -83,7 +88,10 @@ public void testDeeplyNestedArraysWithUnconstrainedMapper() throws Exception } private void genDeepDoc(final ByteArrayOutputStream out, final int depth) throws IOException { - try (JsonGenerator gen = smileGenerator(out, true)) { + SmileFactory smileFactory = SmileFactory.builder() + .streamWriteConstraints(StreamWriteConstraints.builder().maxNestingDepth(Integer.MAX_VALUE).build()) + .build(); + try (JsonGenerator gen = smileGenerator(smileFactory, out, true)) { for (int i = 0; i < depth; i++) { gen.writeStartObject(); gen.writeFieldName("a"); @@ -96,7 +104,10 @@ private void genDeepDoc(final ByteArrayOutputStream out, final int depth) throws } private void genDeepArrayDoc(final ByteArrayOutputStream out, final int depth) throws IOException { - try (JsonGenerator gen = smileGenerator(out, true)) { + SmileFactory smileFactory = SmileFactory.builder() + .streamWriteConstraints(StreamWriteConstraints.builder().maxNestingDepth(Integer.MAX_VALUE).build()) + .build(); + try (JsonGenerator gen = smileGenerator(smileFactory, out, true)) { for (int i = 0; i < depth; i++) { gen.writeStartObject(); gen.writeFieldName("a");