Skip to content

Commit 1f6e52b

Browse files
committed
Merge branch '2.22' into 2.x
2 parents ea14381 + ade072d commit 1f6e52b

7 files changed

Lines changed: 194 additions & 20 deletions

File tree

protobuf/src/main/java/com/fasterxml/jackson/dataformat/protobuf/ProtobufParser.java

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ public class ProtobufParser extends ParserMinimalBase
5151
// State after either reaching end-of-input, or getting explicitly closed
5252
private final static int STATE_CLOSED = 12;
5353

54+
// State after returning END_OBJECT for root level, before closing
55+
// (added for [dataformats-binary#598] to separate END_OBJECT return from close())
56+
private final static int STATE_ROOT_END = 13;
57+
5458
private final static int[] UTF8_UNIT_CODES = ProtobufUtil.sUtf8UnitLengths;
5559

5660
// @since 2.14
@@ -592,7 +596,8 @@ public JsonToken nextToken() throws IOException
592596
// end-of-input?
593597
if (_inputPtr >= _inputEnd) {
594598
if (!loadMore()) {
595-
close();
599+
_state = STATE_ROOT_END;
600+
_parsingContext.setCurrentName(null);
596601
return _updateToken(JsonToken.END_OBJECT);
597602
}
598603
}
@@ -617,6 +622,12 @@ public JsonToken nextToken() throws IOException
617622

618623
int len = _decodeLength();
619624
int newEnd = _inputPtr + len;
625+
// Guard against integer overflow: _inputPtr and len are both non-negative,
626+
// so a result smaller than _inputPtr means the sum wrapped.
627+
if (newEnd < _inputPtr) {
628+
_reportErrorF("Packed array length overflows for field '%s': ptr=%d, len=%d",
629+
_currentField.name, _inputPtr, len);
630+
}
620631

621632
// First: validate that we do not extend past end offset of enclosing message
622633
if (!_parsingContext.inRoot()) {
@@ -687,9 +698,14 @@ public JsonToken nextToken() throws IOException
687698
return _updateToken(_readNextValue(_currentField.type, STATE_NESTED_KEY));
688699

689700
case STATE_MESSAGE_END: // occurs if we end with array
690-
close(); // sets state to STATE_CLOSED
701+
_state = STATE_ROOT_END;
702+
_parsingContext.setCurrentName(null);
691703
return _updateToken(JsonToken.END_OBJECT);
692704

705+
case STATE_ROOT_END: // returned END_OBJECT, now close on next call
706+
close(); // sets state to STATE_CLOSED
707+
return null;
708+
693709
case STATE_CLOSED:
694710
return null;
695711

@@ -923,6 +939,12 @@ private JsonToken _readNextValue(FieldType t, int nextState) throws IOException
923939
_currentMessage = msg;
924940
int len = _decodeLength();
925941
int newEnd = _inputPtr + len;
942+
// Guard against integer overflow: _inputPtr and len are both non-negative,
943+
// so a result smaller than _inputPtr means the sum wrapped.
944+
if (newEnd < _inputPtr) {
945+
_reportErrorF("Message length overflows for field '%s': ptr=%d, len=%d",
946+
_currentField.name, _inputPtr, len);
947+
}
926948

927949
// First: validate that we do not extend past end offset of enclosing message
928950
if (newEnd > _currentEndOffset) {
@@ -964,7 +986,8 @@ private JsonToken _skipUnknownField(int tag, int wireType) throws IOException
964986
}
965987
} else if (_inputPtr >= _inputEnd) {
966988
if (!loadMore()) {
967-
close();
989+
_state = STATE_ROOT_END;
990+
_parsingContext.setCurrentName(null);
968991
return _updateToken(JsonToken.END_OBJECT);
969992
}
970993
}
@@ -1025,7 +1048,8 @@ public boolean nextFieldName(SerializableString sstr) throws IOException
10251048
if (_state == STATE_ROOT_KEY) {
10261049
if (_inputPtr >= _inputEnd) {
10271050
if (!loadMore()) {
1028-
close();
1051+
_state = STATE_ROOT_END;
1052+
_parsingContext.setCurrentName(null);
10291053
_updateToken(JsonToken.END_OBJECT);
10301054
return false;
10311055
}
@@ -1106,7 +1130,8 @@ public String nextFieldName() throws IOException
11061130
if (_state == STATE_ROOT_KEY) {
11071131
if (_inputPtr >= _inputEnd) {
11081132
if (!loadMore()) {
1109-
close();
1133+
_state = STATE_ROOT_END;
1134+
_parsingContext.setCurrentName(null);
11101135
_updateToken(JsonToken.END_OBJECT);
11111136
return null;
11121137
}

protobuf/src/main/java/com/fasterxml/jackson/dataformat/protobuf/schema/ProtobufField.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,6 @@ public String toString() // for debugging
212212

213213
@Override
214214
public int compareTo(ProtobufField other) {
215-
return id - other.id;
215+
return Integer.compare(id, other.id);
216216
}
217217
}

protobuf/src/main/java/com/fasterxml/jackson/dataformat/protobuf/schema/TypeResolver.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,31 @@ private ProtobufField _findAndResolve(FieldElement nativeField, String typeStr)
223223
if (nativeMt != null) {
224224
return new ProtobufField(nativeField, resolve(this, nativeMt));
225225
}
226-
return null;
226+
// [dataformats-binary#73] Handle dot-notation references to nested message types
227+
// (e.g. "OuterType.InnerType")
228+
return _findDottedType(nativeField, typeStr);
229+
}
230+
231+
/**
232+
* Try to resolve a dot-notation type reference (e.g. {@code "OuterType.InnerType"})
233+
* by navigating the message type hierarchy declared at this scope level.
234+
*/
235+
private ProtobufField _findDottedType(FieldElement nativeField, String typeStr)
236+
{
237+
int dotIx = typeStr.indexOf('.');
238+
if (dotIx <= 0) {
239+
return null;
240+
}
241+
String outerName = typeStr.substring(0, dotIx);
242+
String innerPath = typeStr.substring(dotIx + 1);
243+
MessageElement outerMsg = _declaredMessageTypes.get(outerName);
244+
if (outerMsg == null) {
245+
return null;
246+
}
247+
// Create a resolver in the context of the outer type and recursively
248+
// resolve the remaining path (handles arbitrary nesting depth)
249+
TypeResolver outerResolver = TypeResolver.construct(this, outerName, outerMsg.nestedElements());
250+
return outerResolver._findAndResolve(nativeField, innerPath);
227251
}
228252

229253
private StringBuilder _knownEnums(StringBuilder sb) {
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package com.fasterxml.jackson.dataformat.protobuf;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import com.fasterxml.jackson.core.*;
6+
7+
import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchema;
8+
import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchemaLoader;
9+
10+
import static org.junit.jupiter.api.Assertions.*;
11+
12+
// [dataformats-binary#598]
13+
public class ParserStateEndTest extends ProtobufTestBase
14+
{
15+
private final ProtobufMapper MAPPER = newObjectMapper();
16+
17+
@Test
18+
public void testParserStateAtEndObject() throws Exception
19+
{
20+
ProtobufSchema schema = ProtobufSchemaLoader.std.parse(PROTOC_POINT);
21+
22+
Point input = new Point(42, 13);
23+
byte[] bytes = MAPPER.writerFor(Point.class)
24+
.with(schema)
25+
.writeValueAsBytes(input);
26+
27+
try (JsonParser p = MAPPER.reader()
28+
.with(schema)
29+
.createParser(bytes)) {
30+
assertToken(JsonToken.START_OBJECT, p.nextToken());
31+
32+
assertToken(JsonToken.FIELD_NAME, p.nextToken());
33+
assertEquals("x", p.currentName());
34+
35+
assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
36+
assertEquals(42, p.getIntValue());
37+
38+
assertToken(JsonToken.FIELD_NAME, p.nextToken());
39+
assertEquals("y", p.currentName());
40+
41+
assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
42+
assertEquals(13, p.getIntValue());
43+
44+
assertToken(JsonToken.END_OBJECT, p.nextToken());
45+
46+
assertFalse(p.isClosed(),
47+
"Parser should NOT be closed immediately after returning END_OBJECT");
48+
49+
assertEquals(JsonToken.END_OBJECT, p.getCurrentToken(),
50+
"currentToken() should return END_OBJECT, not null");
51+
52+
assertNull(p.nextToken(), "After END_OBJECT, nextToken() should return null");
53+
assertTrue(p.isClosed(), "Parser should be closed after nextToken() returns null");
54+
}
55+
}
56+
57+
@Test
58+
public void testParserStateAtEndObjectWithNextFieldName() throws Exception
59+
{
60+
ProtobufSchema schema = ProtobufSchemaLoader.std.parse(PROTOC_POINT);
61+
62+
Point input = new Point(42, 13);
63+
byte[] bytes = MAPPER.writerFor(Point.class)
64+
.with(schema)
65+
.writeValueAsBytes(input);
66+
67+
try (JsonParser p = MAPPER.reader()
68+
.with(schema)
69+
.createParser(bytes)) {
70+
assertToken(JsonToken.START_OBJECT, p.nextToken());
71+
72+
assertEquals("x", p.nextFieldName());
73+
assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
74+
75+
assertEquals("y", p.nextFieldName());
76+
assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
77+
78+
assertNull(p.nextFieldName());
79+
80+
assertEquals(JsonToken.END_OBJECT, p.getCurrentToken(),
81+
"currentToken() should return END_OBJECT after nextFieldName() returns null");
82+
83+
assertFalse(p.isClosed(),
84+
"Parser should NOT be closed when currentToken is END_OBJECT");
85+
86+
assertNull(p.nextToken());
87+
assertTrue(p.isClosed());
88+
}
89+
}
90+
91+
@Test
92+
public void testParserStateWithEmptyMessage() throws Exception
93+
{
94+
final String PROTOC_EMPTY = "message Empty {}\n";
95+
ProtobufSchema schema = ProtobufSchemaLoader.std.parse(PROTOC_EMPTY);
96+
97+
// Empty Protobuf message is legitimately zero bytes: no fields to encode
98+
byte[] bytes = new byte[0];
99+
100+
try (JsonParser p = MAPPER.reader()
101+
.with(schema)
102+
.createParser(bytes)) {
103+
assertToken(JsonToken.START_OBJECT, p.nextToken());
104+
assertFalse(p.isClosed());
105+
106+
assertToken(JsonToken.END_OBJECT, p.nextToken());
107+
108+
assertFalse(p.isClosed(),
109+
"Parser should NOT be closed immediately after END_OBJECT");
110+
assertEquals(JsonToken.END_OBJECT, p.getCurrentToken());
111+
112+
assertNull(p.nextToken());
113+
assertTrue(p.isClosed());
114+
}
115+
}
116+
}

protobuf/src/test/java/com/fasterxml/jackson/dataformat/protobuf/tofix/GenerateNestedType73Test.java renamed to protobuf/src/test/java/com/fasterxml/jackson/dataformat/protobuf/schema/NestedTypeRef73Test.java

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,22 @@
1-
package com.fasterxml.jackson.dataformat.protobuf.tofix;
1+
package com.fasterxml.jackson.dataformat.protobuf.schema;
22

33
import java.io.StringReader;
44

55
import org.junit.jupiter.api.Test;
66

77
import com.fasterxml.jackson.dataformat.protobuf.ProtobufMapper;
88
import com.fasterxml.jackson.dataformat.protobuf.ProtobufTestBase;
9-
import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchema;
10-
import com.fasterxml.jackson.dataformat.protobuf.testutil.failure.JacksonTestFailureExpected;
119

1210
import static org.junit.jupiter.api.Assertions.assertNotNull;
1311

14-
public class GenerateNestedType73Test extends ProtobufTestBase
12+
// [dataformats-binary#73]
13+
public class NestedTypeRef73Test extends ProtobufTestBase
1514
{
16-
/*
17-
/**********************************************************
18-
/* Test methods
19-
/**********************************************************
20-
*/
21-
2215
final ProtobufMapper MAPPER = new ProtobufMapper();
2316

24-
// [dataformats-binary#68]
25-
@JacksonTestFailureExpected
17+
// [dataformats-binary#73]: dot-notation reference to a nested message type
2618
@Test
27-
public void testNestedTypes() throws Exception
19+
public void testNestedTypeRefViaRootType() throws Exception
2820
{
2921
final String SCHEMA_STR =
3022
" package mypackage;\n"

release-notes/CREDITS-2.x

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ Kenji Noguchi (knoguchi@github)
1919

2020
* Reported #70 (protobuf), contributed fix: Can't deserialize packed repeated field
2121
(2.8.9)
22+
* Reported #73: (protobuf) Cannot resolve inner types in protoc definitions
23+
(2.21.5)
2224

2325
marsqing@github
2426

release-notes/VERSION-2.x

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,25 @@ Active maintainers:
1818

1919
No changes since 2.22
2020

21+
2.22.1 (not yet released)
22+
23+
#73: (protobuf) Cannot resolve inner types in protoc definitions
24+
(reported by Kenji N)
25+
#598: (protobuf) Protobuf parser state handling wrong for implicit close
26+
(END_OBJECT)
27+
28+
2129
2.22.0 (31-May-2026)
2230

2331
No changes since 2.21
2432

33+
2.21.5 (not yet released)
34+
35+
#73: (protobuf) Cannot resolve inner types in protoc definitions
36+
(reported by Kenji N)
37+
#598: (protobuf) Protobuf parser state handling wrong for implicit close
38+
(END_OBJECT)
39+
2540
2.21.4 (28-May-2026)
2641

2742
#691: (cbor) Add parameterized tests covering all ASCII-optimization exit paths in CBORParser

0 commit comments

Comments
 (0)