Skip to content

Commit bdb2d13

Browse files
authored
Merge branch 'FasterXML:2.14' into double-parser
2 parents e17edbe + 96657ef commit bdb2d13

File tree

7 files changed

+243
-41
lines changed

7 files changed

+243
-41
lines changed

release-notes/VERSION-2.x

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ JSON library.
3232
(contributed by @pjfanning)
3333
#759: JsonGenerator to provide current value to the context before starting objects
3434
(reported by Illia O)
35+
#763: `JsonFactory.createParser()` with `File` may leak `InputStream`s
36+
#764: `JsonFactory.createGenerator()` with `File` may leak `OutputStream`s
3537

3638
2.13.3 (14-May-2022)
3739

src/main/java/com/fasterxml/jackson/core/JsonFactory.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,7 +1026,7 @@ public JsonFactory setCodec(ObjectCodec oc) {
10261026
public JsonParser createParser(File f) throws IOException, JsonParseException {
10271027
// true, since we create InputStream from File
10281028
IOContext ctxt = _createContext(_createContentReference(f), true);
1029-
InputStream in = new FileInputStream(f);
1029+
InputStream in = _fileInputStream(f);
10301030
return _createParser(_decorate(in, ctxt), ctxt);
10311031
}
10321032

@@ -1336,7 +1336,7 @@ public JsonGenerator createGenerator(Writer w) throws IOException {
13361336
@Override
13371337
public JsonGenerator createGenerator(File f, JsonEncoding enc) throws IOException
13381338
{
1339-
OutputStream out = new FileOutputStream(f);
1339+
OutputStream out = _fileOutputStream(f);
13401340
// true -> yes, we have to manage the stream since we created it
13411341
IOContext ctxt = _createContext(_createContentReference(out), true);
13421342
ctxt.setEncoding(enc);
@@ -1651,9 +1651,20 @@ public JsonGenerator createJsonGenerator(OutputStream out) throws IOException {
16511651
* @since 2.1
16521652
*/
16531653
protected JsonParser _createParser(InputStream in, IOContext ctxt) throws IOException {
1654-
// As per [JACKSON-259], may want to fully disable canonicalization:
1655-
return new ByteSourceJsonBootstrapper(ctxt, in).constructParser(_parserFeatures,
1656-
_objectCodec, _byteSymbolCanonicalizer, _rootCharSymbols, _factoryFeatures);
1654+
try {
1655+
return new ByteSourceJsonBootstrapper(ctxt, in).constructParser(_parserFeatures,
1656+
_objectCodec, _byteSymbolCanonicalizer, _rootCharSymbols, _factoryFeatures);
1657+
} catch (IOException | RuntimeException e) {
1658+
// 10-Jun-2022, tatu: For [core#763] may need to close InputStream here
1659+
if (ctxt.isResourceManaged()) {
1660+
try {
1661+
in.close();
1662+
} catch (Exception e2) {
1663+
e.addSuppressed(e2);
1664+
}
1665+
}
1666+
throw e;
1667+
}
16571668
}
16581669

16591670
/**

src/main/java/com/fasterxml/jackson/core/TokenStreamFactory.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,9 @@ public abstract class TokenStreamFactory
178178
protected OutputStream _createDataOutputWrapper(DataOutput out) {
179179
return new DataOutputAsStream(out);
180180
}
181+
181182
/**
182-
* Helper methods used for constructing an optimal stream for
183+
* Helper method used for constructing an optimal stream for
183184
* parsers to use, when input is to be read from an URL.
184185
* This helps when reading file content via URL.
185186
*
@@ -210,4 +211,24 @@ protected InputStream _optimizedStreamFromURL(URL url) throws IOException {
210211
}
211212
return url.openStream();
212213
}
214+
215+
/**
216+
* Helper methods used for constructing an {@link InputStream} for
217+
* parsers to use, when input is to be read from given {@link File}.
218+
*
219+
* @since 2.14
220+
*/
221+
protected InputStream _fileInputStream(File f) throws IOException {
222+
return new FileInputStream(f);
223+
}
224+
225+
/**
226+
* Helper methods used for constructing an {@link OutputStream} for
227+
* generator to use, when target is to be written into given {@link File}.
228+
*
229+
* @since 2.14
230+
*/
231+
protected OutputStream _fileOutputStream(File f) throws IOException {
232+
return new FileOutputStream(f);
233+
}
213234
}

src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1199,24 +1199,30 @@ public void close() throws IOException
11991199
{
12001200
super.close();
12011201

1202-
/* 05-Dec-2008, tatu: To add [JACKSON-27], need to close open
1203-
* scopes.
1204-
*/
1202+
// 05-Dec-2008, tatu: To add [JACKSON-27], need to close open scopes.
12051203
// First: let's see that we still have buffers...
1206-
if ((_outputBuffer != null)
1207-
&& isEnabled(Feature.AUTO_CLOSE_JSON_CONTENT)) {
1208-
while (true) {
1209-
JsonStreamContext ctxt = getOutputContext();
1210-
if (ctxt.inArray()) {
1211-
writeEndArray();
1212-
} else if (ctxt.inObject()) {
1213-
writeEndObject();
1214-
} else {
1215-
break;
1204+
IOException flushFail = null;
1205+
try {
1206+
if ((_outputBuffer != null)
1207+
&& isEnabled(Feature.AUTO_CLOSE_JSON_CONTENT)) {
1208+
while (true) {
1209+
JsonStreamContext ctxt = getOutputContext();
1210+
if (ctxt.inArray()) {
1211+
writeEndArray();
1212+
} else if (ctxt.inObject()) {
1213+
writeEndObject();
1214+
} else {
1215+
break;
1216+
}
12161217
}
12171218
}
1219+
_flushBuffer();
1220+
} catch (IOException e) {
1221+
// 10-Jun-2022, tatu: [core#764] Need to avoid failing here; may
1222+
// still need to close the underlying output stream
1223+
flushFail = e;
12181224
}
1219-
_flushBuffer();
1225+
12201226
_outputTail = 0; // just to ensure we don't think there's anything buffered
12211227

12221228
/* 25-Nov-2008, tatus: As per [JACKSON-16] we are not to call close()
@@ -1226,15 +1232,26 @@ && isEnabled(Feature.AUTO_CLOSE_JSON_CONTENT)) {
12261232
* may not be properly recycled if we don't close the writer.
12271233
*/
12281234
if (_outputStream != null) {
1229-
if (_ioContext.isResourceManaged() || isEnabled(Feature.AUTO_CLOSE_TARGET)) {
1230-
_outputStream.close();
1231-
} else if (isEnabled(Feature.FLUSH_PASSED_TO_STREAM)) {
1232-
// If we can't close it, we should at least flush
1233-
_outputStream.flush();
1235+
try {
1236+
if (_ioContext.isResourceManaged() || isEnabled(Feature.AUTO_CLOSE_TARGET)) {
1237+
_outputStream.close();
1238+
} else if (isEnabled(Feature.FLUSH_PASSED_TO_STREAM)) {
1239+
// If we can't close it, we should at least flush
1240+
_outputStream.flush();
1241+
}
1242+
} catch (IOException | RuntimeException e) {
1243+
if (flushFail != null) {
1244+
e.addSuppressed(flushFail);
1245+
}
1246+
throw e;
12341247
}
12351248
}
12361249
// Internal buffer(s) generator has can now be released as well
12371250
_releaseBuffers();
1251+
1252+
if (flushFail != null) {
1253+
throw flushFail;
1254+
}
12381255
}
12391256

12401257
@Override

src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -981,20 +981,27 @@ public void close() throws IOException
981981

982982
// 05-Dec-2008, tatu: To add [JACKSON-27], need to close open scopes
983983
// First: let's see that we still have buffers...
984-
if (_outputBuffer != null
985-
&& isEnabled(Feature.AUTO_CLOSE_JSON_CONTENT)) {
986-
while (true) {
987-
JsonStreamContext ctxt = getOutputContext();
988-
if (ctxt.inArray()) {
989-
writeEndArray();
990-
} else if (ctxt.inObject()) {
991-
writeEndObject();
992-
} else {
993-
break;
984+
IOException flushFail = null;
985+
try {
986+
if ((_outputBuffer != null)
987+
&& isEnabled(Feature.AUTO_CLOSE_JSON_CONTENT)) {
988+
while (true) {
989+
JsonStreamContext ctxt = getOutputContext();
990+
if (ctxt.inArray()) {
991+
writeEndArray();
992+
} else if (ctxt.inObject()) {
993+
writeEndObject();
994+
} else {
995+
break;
996+
}
994997
}
995998
}
999+
_flushBuffer();
1000+
} catch (IOException e) {
1001+
// 10-Jun-2022, tatu: [core#764] Need to avoid failing here; may
1002+
// still need to close the underlying output stream
1003+
flushFail = e;
9961004
}
997-
_flushBuffer();
9981005
_outputHead = 0;
9991006
_outputTail = 0;
10001007

@@ -1005,15 +1012,26 @@ && isEnabled(Feature.AUTO_CLOSE_JSON_CONTENT)) {
10051012
* may not be properly recycled if we don't close the writer.
10061013
*/
10071014
if (_writer != null) {
1008-
if (_ioContext.isResourceManaged() || isEnabled(Feature.AUTO_CLOSE_TARGET)) {
1009-
_writer.close();
1010-
} else if (isEnabled(Feature.FLUSH_PASSED_TO_STREAM)) {
1011-
// If we can't close it, we should at least flush
1012-
_writer.flush();
1015+
try {
1016+
if (_ioContext.isResourceManaged() || isEnabled(Feature.AUTO_CLOSE_TARGET)) {
1017+
_writer.close();
1018+
} else if (isEnabled(Feature.FLUSH_PASSED_TO_STREAM)) {
1019+
// If we can't close it, we should at least flush
1020+
_writer.flush();
1021+
}
1022+
} catch (IOException | RuntimeException e) {
1023+
if (flushFail != null) {
1024+
e.addSuppressed(flushFail);
1025+
}
1026+
throw e;
10131027
}
10141028
}
10151029
// Internal buffer(s) generator has can now be released as well
10161030
_releaseBuffers();
1031+
1032+
if (flushFail != null) {
1033+
throw flushFail;
1034+
}
10171035
}
10181036

10191037
@Override
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package com.fasterxml.jackson.core.json;
2+
3+
import java.io.File;
4+
import java.io.IOException;
5+
import java.io.InputStream;
6+
import java.net.URL;
7+
8+
import com.fasterxml.jackson.core.*;
9+
10+
// [core#763] (and [databind#3455]
11+
public class InputStreamInitTest
12+
extends com.fasterxml.jackson.core.BaseTest
13+
{
14+
static class FailingInputStream extends InputStream {
15+
public boolean closed = false;
16+
17+
@Override
18+
public void close() {
19+
closed = true;
20+
}
21+
22+
@Override
23+
public int read() throws IOException {
24+
throw new IOException("Will not read, ever!");
25+
}
26+
}
27+
28+
static class FailingJsonFactory extends JsonFactory {
29+
private static final long serialVersionUID = 1L;
30+
31+
public FailingInputStream lastStream;
32+
33+
@Override
34+
protected InputStream _fileInputStream(File f) {
35+
return (lastStream = new FailingInputStream());
36+
}
37+
38+
@Override
39+
protected InputStream _optimizedStreamFromURL(URL url) {
40+
return (lastStream = new FailingInputStream());
41+
}
42+
}
43+
44+
public void testForFile() throws Exception
45+
{
46+
final FailingJsonFactory jsonF = new FailingJsonFactory();
47+
try {
48+
/*JsonParser p =*/ jsonF.createParser(new File("/tmp/test.json"));
49+
fail("Should not pass");
50+
} catch (Exception e) {
51+
verifyException(e, "Will not read");
52+
}
53+
assertNotNull(jsonF.lastStream);
54+
assertTrue(jsonF.lastStream.closed);
55+
}
56+
57+
public void testForURL() throws Exception
58+
{
59+
final FailingJsonFactory jsonF = new FailingJsonFactory();
60+
try {
61+
/*JsonParser p =*/ jsonF.createParser(new URL("http://localhost:80/"));
62+
fail("Should not pass");
63+
} catch (Exception e) {
64+
verifyException(e, "Will not read");
65+
}
66+
assertNotNull(jsonF.lastStream);
67+
assertTrue(jsonF.lastStream.closed);
68+
}
69+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package com.fasterxml.jackson.core.json;
2+
3+
import java.io.*;
4+
5+
import com.fasterxml.jackson.core.JsonEncoding;
6+
import com.fasterxml.jackson.core.JsonFactory;
7+
import com.fasterxml.jackson.core.JsonGenerator;
8+
9+
//[core#764] (and [databind#3508]
10+
public class OutputStreamInitTest
11+
extends com.fasterxml.jackson.core.BaseTest
12+
{
13+
static class FailingOutputStream extends OutputStream {
14+
public int written = 0;
15+
public boolean failWrites = false;
16+
public boolean closed = false;
17+
18+
public void startFailingWrites() {
19+
failWrites = true;
20+
}
21+
22+
@Override
23+
public void close() {
24+
closed = true;
25+
}
26+
27+
@Override
28+
public void write(int b) throws IOException {
29+
++written;
30+
if (failWrites) {
31+
throw new IOException("No writes!");
32+
}
33+
}
34+
}
35+
36+
static class FailingJsonFactory extends JsonFactory {
37+
private static final long serialVersionUID = 1L;
38+
39+
public FailingOutputStream lastStream;
40+
41+
@Override
42+
protected OutputStream _fileOutputStream(File f) {
43+
return (lastStream = new FailingOutputStream());
44+
}
45+
}
46+
47+
public void testForFile() throws Exception
48+
{
49+
final FailingJsonFactory jsonF = new FailingJsonFactory();
50+
try {
51+
JsonGenerator g = jsonF.createGenerator(new File("/tmp/test.json"),
52+
JsonEncoding.UTF8);
53+
g.writeString("foo");
54+
jsonF.lastStream.startFailingWrites();
55+
g.close();
56+
fail("Should not pass");
57+
} catch (Exception e) {
58+
verifyException(e, "No writes");
59+
}
60+
assertNotNull(jsonF.lastStream);
61+
assertTrue(jsonF.lastStream.closed);
62+
}
63+
64+
}

0 commit comments

Comments
 (0)