Skip to content

Commit 83cee1f

Browse files
committed
Fix #2398 (TokenBuffer.copyCurrentStructure() recursion -> iteration)
1 parent b6e6e57 commit 83cee1f

File tree

3 files changed

+124
-18
lines changed

3 files changed

+124
-18
lines changed

release-notes/VERSION-2.x

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Project: jackson-databind
1616
(reported by andreasbaus@github)
1717
#2393: `TreeTraversingParser.getLongValue()` incorrectly checks `canConvertToInt()`
1818
(reported by RabbidDog@github)
19+
#2398: Replace recursion in `TokenBuffer.copyCurrentStructure()` with iteration
1920
#2415: Builder-based POJO deserializer should pass builder instance, not type,
2021
to `handleUnknownVanilla()`
2122
(proposed by Vladimir T, follow up to #822)

src/main/java/com/fasterxml/jackson/databind/util/TokenBuffer.java

+121-16
Original file line numberDiff line numberDiff line change
@@ -1103,37 +1103,142 @@ public void copyCurrentStructure(JsonParser p) throws IOException
11031103
throw new IllegalStateException("No token available from argument `JsonParser`");
11041104
}
11051105

1106-
if (_mayHaveNativeIds) {
1107-
_checkNativeIds(p);
1108-
}
1109-
1106+
// We'll do minor handling here to separate structured, scalar values,
1107+
// then delegate appropriately
11101108
switch (t) {
11111109
case START_ARRAY:
1112-
writeStartArray();
1113-
while (p.nextToken() != JsonToken.END_ARRAY) {
1114-
copyCurrentStructure(p);
1110+
if (_mayHaveNativeIds) {
1111+
_checkNativeIds(p);
11151112
}
1116-
writeEndArray();
1113+
writeStartArray();
1114+
_copyContents(p);
11171115
break;
11181116
case START_OBJECT:
1119-
writeStartObject();
1120-
while (p.nextToken() != JsonToken.END_OBJECT) {
1121-
copyCurrentStructure(p);
1117+
if (_mayHaveNativeIds) {
1118+
_checkNativeIds(p);
11221119
}
1123-
writeEndObject();
1120+
writeStartObject();
1121+
_copyContents(p);
11241122
break;
11251123
default: // others are simple:
1126-
copyCurrentEvent(p);
1124+
_copyCurrentValue(p, t);
11271125
}
11281126
}
11291127

1128+
private final void _copyContents(JsonParser p) throws IOException
1129+
{
1130+
int depth = 1;
1131+
JsonToken t;
1132+
1133+
while ((t = p.nextToken()) != null) {
1134+
switch (t) {
1135+
case FIELD_NAME:
1136+
if (_mayHaveNativeIds) {
1137+
_checkNativeIds(p);
1138+
}
1139+
writeFieldName(p.getCurrentName());
1140+
break;
1141+
1142+
case START_ARRAY:
1143+
if (_mayHaveNativeIds) {
1144+
_checkNativeIds(p);
1145+
}
1146+
writeStartArray();
1147+
++depth;
1148+
break;
1149+
1150+
case START_OBJECT:
1151+
if (_mayHaveNativeIds) {
1152+
_checkNativeIds(p);
1153+
}
1154+
writeStartObject();
1155+
++depth;
1156+
break;
1157+
1158+
case END_ARRAY:
1159+
if (--depth == 0) {
1160+
return;
1161+
}
1162+
writeEndArray();
1163+
break;
1164+
case END_OBJECT:
1165+
if (--depth == 0) {
1166+
return;
1167+
}
1168+
writeEndObject();
1169+
break;
1170+
1171+
default:
1172+
_copyCurrentValue(p, t);
1173+
}
1174+
}
1175+
}
1176+
1177+
// NOTE: Copied from earlier `copyCurrentEvent()`
1178+
private void _copyCurrentValue(JsonParser p, JsonToken t) throws IOException
1179+
{
1180+
if (_mayHaveNativeIds) {
1181+
_checkNativeIds(p);
1182+
}
1183+
switch (t) {
1184+
case VALUE_STRING:
1185+
if (p.hasTextCharacters()) {
1186+
writeString(p.getTextCharacters(), p.getTextOffset(), p.getTextLength());
1187+
} else {
1188+
writeString(p.getText());
1189+
}
1190+
break;
1191+
case VALUE_NUMBER_INT:
1192+
switch (p.getNumberType()) {
1193+
case INT:
1194+
writeNumber(p.getIntValue());
1195+
break;
1196+
case BIG_INTEGER:
1197+
writeNumber(p.getBigIntegerValue());
1198+
break;
1199+
default:
1200+
writeNumber(p.getLongValue());
1201+
}
1202+
break;
1203+
case VALUE_NUMBER_FLOAT:
1204+
if (_forceBigDecimal) {
1205+
writeNumber(p.getDecimalValue());
1206+
} else {
1207+
switch (p.getNumberType()) {
1208+
case BIG_DECIMAL:
1209+
writeNumber(p.getDecimalValue());
1210+
break;
1211+
case FLOAT:
1212+
writeNumber(p.getFloatValue());
1213+
break;
1214+
default:
1215+
writeNumber(p.getDoubleValue());
1216+
}
1217+
}
1218+
break;
1219+
case VALUE_TRUE:
1220+
writeBoolean(true);
1221+
break;
1222+
case VALUE_FALSE:
1223+
writeBoolean(false);
1224+
break;
1225+
case VALUE_NULL:
1226+
writeNull();
1227+
break;
1228+
case VALUE_EMBEDDED_OBJECT:
1229+
writeObject(p.getEmbeddedObject());
1230+
break;
1231+
default:
1232+
throw new RuntimeException("Internal error: should never end up through this code path");
1233+
}
1234+
}
11301235

1131-
private final void _checkNativeIds(JsonParser jp) throws IOException
1236+
private final void _checkNativeIds(JsonParser p) throws IOException
11321237
{
1133-
if ((_typeId = jp.getTypeId()) != null) {
1238+
if ((_typeId = p.getTypeId()) != null) {
11341239
_hasNativeId = true;
11351240
}
1136-
if ((_objectId = jp.getObjectId()) != null) {
1241+
if ((_objectId = p.getObjectId()) != null) {
11371242
_hasNativeId = true;
11381243
}
11391244
}

src/test/java/com/fasterxml/jackson/failing/TokenBufferRecursion2398Test.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ public class TokenBufferRecursion2398Test extends BaseMapTest
88
{
99
private final ObjectMapper MAPPER = sharedMapper();
1010

11-
// 10k does it, 5k not
12-
private final static int RECURSION = 9999;
11+
// 10k does it, 5k not, but use bit higher values just in case
12+
private final static int RECURSION = 25000;
1313

1414
public void testDeeplyNestedArrays() throws Exception
1515
{

0 commit comments

Comments
 (0)