Skip to content

Commit d461719

Browse files
committed
The JsonParser class does not progress as much as it could when the processing faulty input.
When the JsonParser process malformed input, it does halt the parsing and handle the error leaving unprocessed tokens in the underlying Jackson parser. These tokens will be processed when the JsonParser handle another input. When the Jackson parser fails we handle the error and then give up and reported behavior is a consequence. The JsonParser has been modified to continue when a malformed input is reported instead of stopping after the error has been processed. Errors are collected and delivered after we all tokens have been processed. fixes #4338
1 parent b293bdf commit d461719

File tree

2 files changed

+100
-27
lines changed

2 files changed

+100
-27
lines changed

src/main/java/io/vertx/core/parsetools/impl/JsonParserImpl.java

Lines changed: 71 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
package io.vertx.core.parsetools.impl;
1313

1414
import com.fasterxml.jackson.core.JsonFactory;
15+
import com.fasterxml.jackson.core.JsonLocation;
1516
import com.fasterxml.jackson.core.JsonToken;
1617
import com.fasterxml.jackson.core.ObjectCodec;
1718
import com.fasterxml.jackson.core.base.ParserBase;
@@ -31,10 +32,7 @@
3132
import io.vertx.core.streams.ReadStream;
3233

3334
import java.io.IOException;
34-
import java.util.ArrayDeque;
35-
import java.util.Deque;
36-
import java.util.List;
37-
import java.util.Map;
35+
import java.util.*;
3836

3937
/**
4038
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
@@ -54,6 +52,7 @@ public class JsonParserImpl implements JsonParser {
5452
private final ReadStream<Buffer> stream;
5553
private boolean emitting;
5654
private final Deque<JsonEventImpl> pending = new ArrayDeque<>();
55+
private List<IOException> collectedExceptions;
5756

5857
public JsonParserImpl(ReadStream<Buffer> stream) {
5958
this.stream = stream;
@@ -143,21 +142,24 @@ private void handleEvent(JsonEventImpl event) {
143142
}
144143
}
145144

145+
private void handle(IOException ioe) {
146+
if (collectedExceptions == null) {
147+
collectedExceptions = new ArrayList<>();
148+
}
149+
collectedExceptions.add(ioe);
150+
}
151+
146152
@Override
147153
public void handle(Buffer data) {
148154
byte[] bytes = data.getBytes();
149155
try {
150156
parser.feedInput(bytes, 0, bytes.length);
151-
checkTokens();
152157
} catch (IOException e) {
153-
if (exceptionHandler != null) {
154-
exceptionHandler.handle(e);
155-
return;
156-
} else {
157-
throw new DecodeException(e.getMessage(), e);
158-
}
158+
handle(e);
159159
}
160+
checkTokens();
160161
checkPending();
162+
checkExceptions();
161163
}
162164

163165
@Override
@@ -167,25 +169,33 @@ public void end() {
167169
}
168170
ended = true;
169171
parser.endOfInput();
170-
try {
171-
checkTokens();
172-
} catch (IOException e) {
173-
if (exceptionHandler != null) {
174-
exceptionHandler.handle(e);
175-
return;
176-
} else {
177-
throw new DecodeException(e.getMessage(), e);
178-
}
179-
}
172+
checkTokens();
180173
checkPending();
174+
checkExceptions();
181175
}
182176

183-
private void checkTokens() throws IOException {
177+
private void checkTokens() {
178+
JsonLocation prevLocation = null;
184179
while (true) {
185-
JsonToken token = parser.nextToken();
180+
JsonToken token;
181+
try {
182+
token = parser.nextToken();
183+
} catch (IOException e) {
184+
JsonLocation location = parser.currentLocation();
185+
if (prevLocation != null) {
186+
if (location.equals(prevLocation)) {
187+
// If we haven't done any progress, give up
188+
return;
189+
}
190+
}
191+
prevLocation = location;
192+
handle(e);
193+
continue;
194+
}
186195
if (token == null || token == JsonToken.NOT_AVAILABLE) {
187196
break;
188197
}
198+
prevLocation = null;
189199
String field = currentField;
190200
currentField = null;
191201
JsonEventImpl event;
@@ -199,11 +209,20 @@ private void checkTokens() throws IOException {
199209
break;
200210
}
201211
case FIELD_NAME: {
202-
currentField = parser.getCurrentName();
212+
try {
213+
currentField = parser.getCurrentName();
214+
} catch (IOException e) {
215+
handle(e);
216+
}
203217
continue;
204218
}
205219
case VALUE_STRING: {
206-
event = new JsonEventImpl(token, JsonEventType.VALUE, field, parser.getText());
220+
try {
221+
event = new JsonEventImpl(token, JsonEventType.VALUE, field, parser.getText());
222+
} catch (IOException e) {
223+
handle(e);
224+
continue;
225+
}
207226
break;
208227
}
209228
case VALUE_TRUE: {
@@ -219,11 +238,21 @@ private void checkTokens() throws IOException {
219238
break;
220239
}
221240
case VALUE_NUMBER_INT: {
222-
event = new JsonEventImpl(token, JsonEventType.VALUE, field, parser.getLongValue());
241+
try {
242+
event = new JsonEventImpl(token, JsonEventType.VALUE, field, parser.getLongValue());
243+
} catch (IOException e) {
244+
handle(e);
245+
continue;
246+
}
223247
break;
224248
}
225249
case VALUE_NUMBER_FLOAT: {
226-
event = new JsonEventImpl(token, JsonEventType.VALUE, field, parser.getDoubleValue());
250+
try {
251+
event = new JsonEventImpl(token, JsonEventType.VALUE, field, parser.getDoubleValue());
252+
} catch (IOException e) {
253+
handle(e);
254+
continue;
255+
}
227256
break;
228257
}
229258
case END_OBJECT: {
@@ -288,6 +317,21 @@ private void checkPending() {
288317
}
289318
}
290319

320+
private void checkExceptions() {
321+
List<IOException> exceptions = collectedExceptions;
322+
collectedExceptions = null;
323+
if (exceptions != null && exceptions.size() > 0) {
324+
if (exceptionHandler != null) {
325+
for (IOException ioe : exceptions) {
326+
exceptionHandler.handle(ioe);
327+
}
328+
} else {
329+
IOException ioe = exceptions.get(0);
330+
throw new DecodeException(ioe.getMessage(), ioe);
331+
}
332+
}
333+
}
334+
291335
@Override
292336
public JsonParser objectEventMode() {
293337
objectValueMode = false;

src/test/java/io/vertx/core/parsetools/JsonParserTest.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,35 @@ public void parseUnfinished() {
113113
assertEquals(1, errors.size());
114114
}
115115

116+
@Test
117+
public void testParseWithErrors() {
118+
Buffer data = Buffer.buffer("{\"foo\":\"foo_value\"},{\"bar\":\"bar_value\"},{\"juu\":\"juu_value\"}");
119+
JsonParser parser = JsonParser.newParser();
120+
List<JsonObject> objects = new ArrayList<>();
121+
List<Throwable> errors = new ArrayList<>();
122+
AtomicInteger endCount = new AtomicInteger();
123+
parser.objectValueMode()
124+
.handler(event -> objects.add(event.objectValue()))
125+
.exceptionHandler(errors::add)
126+
.endHandler(v -> endCount.incrementAndGet());
127+
parser.write(data);
128+
assertEquals(3, objects.size());
129+
List<JsonObject> expected = Arrays.asList(
130+
new JsonObject().put("foo", "foo_value"),
131+
new JsonObject().put("bar", "bar_value"),
132+
new JsonObject().put("juu", "juu_value")
133+
);
134+
assertEquals(expected, objects);
135+
assertEquals(2, errors.size());
136+
assertEquals(0, endCount.get());
137+
objects.clear();
138+
errors.clear();
139+
parser.end();
140+
assertEquals(Collections.emptyList(), objects);
141+
assertEquals(Collections.emptyList(), errors);
142+
assertEquals(1, endCount.get());
143+
}
144+
116145
@Test
117146
public void parseNumberFormatException() {
118147
Buffer data = Buffer.buffer(Long.MAX_VALUE + "0");

0 commit comments

Comments
 (0)