Skip to content

Commit 6aaf6c4

Browse files
authored
Fix #387: check StreamWriteConstraints.maxNestingDepth for Avro (#663)
1 parent 608b64f commit 6aaf6c4

6 files changed

Lines changed: 83 additions & 51 deletions

File tree

avro/src/main/java/tools/jackson/dataformat/avro/AvroGenerator.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,20 +315,23 @@ protected void _closeInput() throws IOException
315315
@Override
316316
public JsonGenerator writeStartArray() throws JacksonException {
317317
_streamWriteContext = _streamWriteContext.createChildArrayContext(null);
318+
streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth());
318319
_complete = false;
319320
return this;
320321
}
321322

322323
@Override
323324
public JsonGenerator writeStartArray(Object currValue) throws JacksonException {
324325
_streamWriteContext = _streamWriteContext.createChildArrayContext(currValue);
326+
streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth());
325327
_complete = false;
326328
return this;
327329
}
328330

329331
@Override
330332
public JsonGenerator writeStartArray(Object currValue, int len) throws JacksonException {
331333
_streamWriteContext = _streamWriteContext.createChildArrayContext(currValue);
334+
streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth());
332335
_complete = false;
333336
return this;
334337
}
@@ -349,13 +352,15 @@ public JsonGenerator writeEndArray() throws JacksonException
349352
@Override
350353
public JsonGenerator writeStartObject() throws JacksonException {
351354
_streamWriteContext = _streamWriteContext.createChildObjectContext(null);
355+
streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth());
352356
_complete = false;
353357
return this;
354358
}
355359

356360
@Override
357361
public JsonGenerator writeStartObject(Object forValue) throws JacksonException {
358362
_streamWriteContext = _streamWriteContext.createChildObjectContext(forValue);
363+
streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth());
359364
_complete = false;
360365
return this;
361366
}

avro/src/test/java/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
opens tools.jackson.dataformat.avro;
2020
opens tools.jackson.dataformat.avro.annotation;
21+
opens tools.jackson.dataformat.avro.constraints;
2122
opens tools.jackson.dataformat.avro.dos;
2223
opens tools.jackson.dataformat.avro.fuzz;
2324
opens tools.jackson.dataformat.avro.gen;

avro/src/test/java/tools/jackson/dataformat/avro/dos/DeepNestingAvroParserTest.java renamed to avro/src/test/java/tools/jackson/dataformat/avro/constraints/DeeplyNestedAvroReadTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package tools.jackson.dataformat.avro.dos;
1+
package tools.jackson.dataformat.avro.constraints;
22

33
import java.io.IOException;
44

@@ -21,7 +21,7 @@
2121
/**
2222
* Unit tests for deeply nested Documents
2323
*/
24-
public class DeepNestingAvroParserTest extends AvroTestBase
24+
public class DeeplyNestedAvroReadTest extends AvroTestBase
2525
{
2626
protected final String NODE_SCHEMA_JSON = "{\n"
2727
+"\"type\": \"record\",\n"
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package tools.jackson.dataformat.avro.constraints;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import tools.jackson.core.StreamWriteConstraints;
6+
import tools.jackson.core.exc.StreamConstraintsException;
7+
8+
import tools.jackson.dataformat.avro.*;
9+
10+
import static org.junit.jupiter.api.Assertions.fail;
11+
12+
/**
13+
* Unit test to verify enforcement of {@code StreamWriteConstraints.maxNestingDepth}
14+
* when writing Avro content.
15+
*/
16+
public class DeeplyNestedAvroWriteTest extends AvroTestBase
17+
{
18+
public static class LinkedBean
19+
{
20+
LinkedBean _next;
21+
final String _name;
22+
23+
public LinkedBean(LinkedBean next, String name) {
24+
_next = next;
25+
_name = name;
26+
}
27+
28+
public LinkedBean getNext() { return _next; }
29+
public String getName() { return _name; }
30+
31+
public void assignNext(LinkedBean n) { _next = n; }
32+
}
33+
34+
// Unlike default depth of 1000 for other formats, use lower (400) here
35+
// because we cannot actually generate 1000 levels due to Avro codec's
36+
// limitations
37+
private final AvroMapper MAPPER_400;
38+
{
39+
AvroFactory f = AvroFactory.builder()
40+
.streamWriteConstraints(StreamWriteConstraints.builder().maxNestingDepth(400).build())
41+
.build();
42+
MAPPER_400 = new AvroMapper(f);
43+
}
44+
45+
@Test
46+
public void testTooDeepCyclic() throws Exception
47+
{
48+
LinkedBean beanRoot = new LinkedBean(null, "123");
49+
LinkedBean bean2 = new LinkedBean(beanRoot, "456");
50+
beanRoot.assignNext(bean2);
51+
52+
try {
53+
AvroSchema schema = MAPPER_400.schemaFor(LinkedBean.class);
54+
MAPPER_400.writer(schema).writeValueAsBytes(beanRoot);
55+
fail("expected StreamConstraintsException");
56+
} catch (StreamConstraintsException e) {
57+
verifyException(e, "Document nesting depth");
58+
}
59+
}
60+
}

avro/src/test/java/tools/jackson/dataformat/avro/dos/CyclicAvroDataSerTest.java

Lines changed: 11 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2,84 +2,46 @@
22

33
import org.junit.jupiter.api.Test;
44

5-
import tools.jackson.core.StreamReadConstraints;
65
import tools.jackson.databind.exc.InvalidDefinitionException;
76

87
import tools.jackson.dataformat.avro.*;
98

10-
import static org.junit.jupiter.api.Assertions.assertTrue;
119
import static org.junit.jupiter.api.Assertions.fail;
1210

1311
/**
14-
* Simple unit tests to verify that we fail gracefully if you attempt to serialize
15-
* data that is cyclic (eg a list that contains itself).
12+
* Simple unit test to verify that we fail gracefully if you attempt to serialize
13+
* data that is directly cyclic (eg a list that contains itself).
1614
*/
1715
public class CyclicAvroDataSerTest extends AvroTestBase
1816
{
19-
20-
public static class Bean
17+
public static class LinkedBean
2118
{
22-
Bean _next;
19+
LinkedBean _next;
2320
final String _name;
2421

25-
public Bean(Bean next, String name) {
22+
public LinkedBean(LinkedBean next, String name) {
2623
_next = next;
2724
_name = name;
2825
}
2926

30-
public Bean getNext() { return _next; }
27+
public LinkedBean getNext() { return _next; }
3128
public String getName() { return _name; }
3229

33-
public void assignNext(Bean n) { _next = n; }
30+
public void assignNext(LinkedBean n) { _next = n; }
3431
}
3532

3633
private final AvroMapper MAPPER = getMapper();
3734

38-
// Unlike default depth of 1000 for other formats, use lower (400) here
39-
// because we cannot actually generate 1000 levels due to Avro codec's
40-
// limitations
41-
private final AvroMapper MAPPER_400;
42-
{
43-
AvroFactory f = AvroFactory.builder()
44-
.streamReadConstraints(StreamReadConstraints.builder().maxNestingDepth(400).build())
45-
.build();
46-
MAPPER_400 = new AvroMapper(f);
47-
}
48-
4935
@Test
5036
public void testDirectCyclic() throws Exception {
51-
Bean bean = new Bean(null, "123");
37+
LinkedBean bean = new LinkedBean(null, "123");
5238
bean.assignNext(bean);
5339
try {
54-
AvroSchema schema = MAPPER.schemaFor(Bean.class);
40+
AvroSchema schema = MAPPER.schemaFor(LinkedBean.class);
5541
MAPPER.writer(schema).writeValueAsBytes(bean);
5642
fail("expected InvalidDefinitionException");
57-
} catch (InvalidDefinitionException idex) {
58-
assertTrue(idex.getMessage().startsWith("Direct self-reference leading to cycle"),
59-
"InvalidDefinitionException message is as expected?");
60-
}
61-
}
62-
63-
// With 2.16 also test looser loops, wrt new limits
64-
@Test
65-
public void testLooserCyclic() throws Exception
66-
{
67-
Bean beanRoot = new Bean(null, "123");
68-
Bean bean2 = new Bean(beanRoot, "456");
69-
beanRoot.assignNext(bean2);
70-
71-
// 12-Jul-2023, tatu: Alas, won't work -- Avro serialization by-passes many
72-
// checks. Needs more work in future
73-
74-
if (false) {
75-
try {
76-
AvroSchema schema = MAPPER_400.schemaFor(Bean.class);
77-
MAPPER_400.writer(schema).writeValueAsBytes(beanRoot);
78-
fail("expected InvalidDefinitionException");
79-
} catch (InvalidDefinitionException idex) {
80-
assertTrue(idex.getMessage().startsWith("Direct self-reference leading to cycle"),
81-
"InvalidDefinitionException message is as expected?");
82-
}
43+
} catch (InvalidDefinitionException e) {
44+
verifyException(e, "Direct self-reference leading to cycle");
8345
}
8446
}
8547
}

release-notes/VERSION

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ implementations)
1414
=== Releases ===
1515
------------------------------------------------------------------------
1616

17+
3.1.1 (not yet released)
18+
19+
#387: Avro generator should enforce `StreamWriteConstraints.maxNestingDepth`
20+
1721
3.1.0 (23-Feb-2026)
1822

1923
#73: (protobuf) Cannot resolve inner types in protoc definitions

0 commit comments

Comments
 (0)