Skip to content

Commit e2f93eb

Browse files
committed
Fix four issues flagged in AI code review
- MessagePackParser: make inner constructor package-private so MessagePackFactory can use it directly - MessagePackFactory._createParser(byte[],int,int): replace Arrays.copyOfRange with ArrayBufferInput(data, offset, len) to avoid an unnecessary copy when parsing a byte-array slice - MessagePackSerializedString: add capacity checks to all append/put methods (return -1 instead of throwing on overflow) and propagate IOException from writeQuotedUTF8/writeUnquotedUTF8 - TimestampExtensionModule.InstantDeserializer: replace bare RuntimeException with ctxt.reportInputMismatch for proper Jackson error reporting - plans/preexisting-issues.md: document node-buffering architectural constraint and nested-POJO generator inefficiency as known FYI items
1 parent 504e02b commit e2f93eb

6 files changed

Lines changed: 79 additions & 29 deletions

File tree

msgpack-jackson3/src/main/java/org/msgpack/jackson/dataformat/MessagePackFactory.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,13 @@
3333
import org.msgpack.core.MessagePack;
3434
import org.msgpack.core.annotations.VisibleForTesting;
3535

36+
import org.msgpack.core.buffer.ArrayBufferInput;
37+
import org.msgpack.core.buffer.MessageBufferInput;
38+
3639
import java.io.DataInput;
3740
import java.io.IOException;
3841
import java.io.InputStream;
3942
import java.io.OutputStream;
40-
import java.util.Arrays;
4143

4244
public class MessagePackFactory
4345
extends BinaryTSFactory
@@ -131,11 +133,9 @@ protected JsonParser _createParser(ObjectReadContext readCtxt, IOContext ioCtxt,
131133
byte[] data, int offset, int len) throws JacksonException
132134
{
133135
try {
134-
if (offset != 0 || len != data.length) {
135-
data = Arrays.copyOfRange(data, offset, offset + len);
136-
}
136+
MessageBufferInput input = new ArrayBufferInput(data, offset, len);
137137
MessagePackParser parser = new MessagePackParser(readCtxt, ioCtxt,
138-
readCtxt.getStreamReadFeatures(_streamReadFeatures), data, reuseResourceInParser);
138+
readCtxt.getStreamReadFeatures(_streamReadFeatures), input, data, reuseResourceInParser);
139139
if (extTypeCustomDesers != null) {
140140
parser.setExtensionTypeCustomDeserializers(extTypeCustomDesers);
141141
}

msgpack-jackson3/src/main/java/org/msgpack/jackson/dataformat/MessagePackParser.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public MessagePackParser(
9393
this(readCtxt, ioCtxt, streamReadFeatures, new ArrayBufferInput(bytes), bytes, reuseResourceInParser);
9494
}
9595

96-
private MessagePackParser(ObjectReadContext readCtxt,
96+
MessagePackParser(ObjectReadContext readCtxt,
9797
IOContext ioCtxt,
9898
int streamReadFeatures,
9999
MessageBufferInput input,

msgpack-jackson3/src/main/java/org/msgpack/jackson/dataformat/MessagePackSerializedString.java

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ public byte[] asQuotedUTF8()
6868
public int appendQuotedUTF8(byte[] bytes, int i)
6969
{
7070
byte[] utf8 = asUnquotedUTF8();
71+
if (utf8.length > bytes.length - i) {
72+
return -1;
73+
}
7174
System.arraycopy(utf8, 0, bytes, i, utf8.length);
7275
return utf8.length;
7376
}
@@ -76,6 +79,9 @@ public int appendQuotedUTF8(byte[] bytes, int i)
7679
public int appendQuoted(char[] chars, int i)
7780
{
7881
char[] q = asQuotedChars();
82+
if (q.length > chars.length - i) {
83+
return -1;
84+
}
7985
System.arraycopy(q, 0, chars, i, q.length);
8086
return q.length;
8187
}
@@ -84,6 +90,9 @@ public int appendQuoted(char[] chars, int i)
8490
public int appendUnquotedUTF8(byte[] bytes, int i)
8591
{
8692
byte[] utf8 = asUnquotedUTF8();
93+
if (utf8.length > bytes.length - i) {
94+
return -1;
95+
}
8796
System.arraycopy(utf8, 0, bytes, i, utf8.length);
8897
return utf8.length;
8998
}
@@ -92,40 +101,36 @@ public int appendUnquotedUTF8(byte[] bytes, int i)
92101
public int appendUnquoted(char[] chars, int i)
93102
{
94103
String v = getValue();
104+
if (v.length() > chars.length - i) {
105+
return -1;
106+
}
95107
v.getChars(0, v.length(), chars, i);
96108
return v.length();
97109
}
98110

99111
@Override
100-
public int writeQuotedUTF8(OutputStream outputStream)
112+
public int writeQuotedUTF8(OutputStream outputStream) throws IOException
101113
{
102-
try {
103-
byte[] utf8 = asUnquotedUTF8();
104-
outputStream.write(utf8);
105-
return utf8.length;
106-
}
107-
catch (IOException e) {
108-
return -1;
109-
}
114+
byte[] utf8 = asUnquotedUTF8();
115+
outputStream.write(utf8);
116+
return utf8.length;
110117
}
111118

112119
@Override
113-
public int writeUnquotedUTF8(OutputStream outputStream)
120+
public int writeUnquotedUTF8(OutputStream outputStream) throws IOException
114121
{
115-
try {
116-
byte[] utf8 = asUnquotedUTF8();
117-
outputStream.write(utf8);
118-
return utf8.length;
119-
}
120-
catch (IOException e) {
121-
return -1;
122-
}
122+
byte[] utf8 = asUnquotedUTF8();
123+
outputStream.write(utf8);
124+
return utf8.length;
123125
}
124126

125127
@Override
126128
public int putQuotedUTF8(ByteBuffer byteBuffer)
127129
{
128130
byte[] utf8 = asUnquotedUTF8();
131+
if (utf8.length > byteBuffer.remaining()) {
132+
return -1;
133+
}
129134
byteBuffer.put(utf8);
130135
return utf8.length;
131136
}
@@ -134,6 +139,9 @@ public int putQuotedUTF8(ByteBuffer byteBuffer)
134139
public int putUnquotedUTF8(ByteBuffer byteBuffer)
135140
{
136141
byte[] utf8 = asUnquotedUTF8();
142+
if (utf8.length > byteBuffer.remaining()) {
143+
return -1;
144+
}
137145
byteBuffer.put(utf8);
138146
return utf8.length;
139147
}

msgpack-jackson3/src/main/java/org/msgpack/jackson/dataformat/TimestampExtensionModule.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,10 @@ public Instant deserialize(JsonParser p, DeserializationContext ctxt)
6969
try {
7070
MessagePackExtensionType ext = p.readValueAs(MessagePackExtensionType.class);
7171
if (ext.getType() != EXT_TYPE) {
72-
throw new RuntimeException(
73-
String.format("Unexpected extension type (0x%X) for Instant object", ext.getType()));
72+
ctxt.reportInputMismatch(Instant.class,
73+
"Unexpected extension type (0x%X) for Instant object", ext.getType());
74+
return null; // unreachable
7475
}
75-
7676
try (MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(ext.getData())) {
7777
return unpacker.unpackTimestamp(new ExtensionTypeHeader(EXT_TYPE, ext.getData().length));
7878
}

msgpack-jackson3/src/test/java/org/msgpack/jackson/dataformat/MessagePackGeneratorTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1188,7 +1188,7 @@ public void testVersion()
11881188
}
11891189

11901190
@Test
1191-
public void testSerializedStringMethods()
1191+
public void testSerializedStringMethods() throws IOException
11921192
{
11931193
MessagePackSerializedString s = new MessagePackSerializedString("hello");
11941194

plans/preexisting-issues.md

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,49 @@ buffer) into a `StringBuilder`, matching the pattern already used in the len<0 b
7272
The len<0 path (StringBuilder + 1024-char chunk) still allocates an extra copy vs.
7373
`CharArrayWriter`, but the overhead is minor and not worth optimizing at this layer.
7474

75-
### 6. `writeRaw*` / `writeRawValue*` — FYI, no action needed
75+
### 6. Node buffering: full-tree design — FYI, no action needed
76+
77+
**File:** `msgpack-jackson3/.../MessagePackGenerator.java` (node buffering design)
78+
79+
`MessagePackGenerator` buffers the entire document as a tree of `ValueNode` objects and
80+
only serializes to MessagePack bytes when the root container closes (or on `flush()`).
81+
This is required because MessagePack's array and map headers encode the element count
82+
upfront (e.g. `fixarray 0x9X` or `array 16 0xdc`), and the count is not known until all
83+
children have been written.
84+
85+
A potential optimization would be to eagerly serialize completed sub-trees (e.g. flush a
86+
completed nested array into bytes as soon as its `writeEndArray` is called). This reduces
87+
the peak heap footprint for large flat nested collections, since the child nodes can be
88+
GC'd once their bytes are emitted. However:
89+
90+
- It does not eliminate the root-container buffering problem: the root map/array must
91+
still buffer all its children until the root closes, because its own header depends on
92+
the total count.
93+
- It adds an extra copy per sub-tree (the bytes for each sub-tree must be merged into the
94+
parent's payload at close time).
95+
- In practice, the dominant cost is the root-level buffer, which this optimization does
96+
not address.
97+
98+
Decision: left as-is. Document as a known architectural constraint.
99+
100+
### 7. Nested POJO generator inefficiency — FYI, no action needed
101+
102+
**File:** `msgpack-jackson3/.../MessagePackGenerator.java` (writePOJO / nested generators)
103+
104+
When a serializer calls `gen.writePOJO(someObject)`, Jackson internally creates a new
105+
`JsonGenerator` wrapping the same output stream and serializes the nested object into it.
106+
For `MessagePackGenerator` this means the nested generator also buffers a full node tree
107+
and emits bytes only when it closes — triggering a `flush()` of its own root context.
108+
The parent generator then reads those bytes back as a raw blob and re-wraps them in a
109+
`RawValueNode`.
110+
111+
The round-trip (buffer → bytes → re-buffer as raw) means one extra allocation and copy
112+
per nested POJO. For deeply nested structures this can add up.
113+
114+
Decision: this is an inherent consequence of the deferred-flush design (see item 6) and
115+
cannot be fixed without a larger architectural change. Left as-is.
116+
117+
### 8. `writeRaw*` / `writeRawValue*` — FYI, no action needed
76118

77119
**File:** `msgpack-jackson3/.../MessagePackGenerator.java`
78120

0 commit comments

Comments
 (0)