Skip to content

Commit 19ae19f

Browse files
coheigeadkulp
authored andcommitted
Fixing StackOverflow error
1 parent 325b51b commit 19ae19f

4 files changed

Lines changed: 114 additions & 9 deletions

File tree

src/main/java/org/codehaus/jettison/json/JSONArray.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,9 @@ public JSONArray(String string) throws JSONException {
179179
/**
180180
* Construct a JSONArray from a Collection.
181181
* @param collection A Collection.
182+
* @throws JSONException If there is a syntax error.
182183
*/
183-
public JSONArray(Collection collection) {
184+
public JSONArray(Collection collection) throws JSONException {
184185
this.myArrayList = (collection == null) ?
185186
new ArrayList() :
186187
new ArrayList(collection);
@@ -580,8 +581,9 @@ public JSONArray put(boolean value) {
580581
* JSONArray which is produced from a Collection.
581582
* @param value A Collection value.
582583
* @return this.
584+
* @throws JSONException If there is a syntax error.
583585
*/
584-
public JSONArray put(Collection value) {
586+
public JSONArray put(Collection value) throws JSONException {
585587
put(new JSONArray(value));
586588
return this;
587589
}
@@ -631,8 +633,9 @@ public JSONArray put(long value) {
631633
* JSONObject which is produced from a Map.
632634
* @param value A Map value.
633635
* @return this.
636+
* @throws JSONException If there is a syntax error.
634637
*/
635-
public JSONArray put(Map value) {
638+
public JSONArray put(Map value) throws JSONException {
636639
put(new JSONObject(value));
637640
return this;
638641
}

src/main/java/org/codehaus/jettison/json/JSONObject.java

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,13 @@
8484
*/
8585
public class JSONObject implements Serializable {
8686

87+
/**
88+
* The default recursion depth limit to prevent stack overflow issues on deeply nested structures.
89+
*/
90+
final static int DEFAULT_RECURSION_DEPTH_LIMIT = 500;
91+
92+
static int RECURSION_DEPTH_LIMIT = DEFAULT_RECURSION_DEPTH_LIMIT;
93+
8794
/**
8895
* JSONObject.NULL is equivalent to the value that JavaScript calls null,
8996
* whilst Java's null is equivalent to the value that JavaScript calls
@@ -257,8 +264,17 @@ public JSONObject(JSONTokener x) throws JSONException {
257264
* Construct a JSONObject from a Map.
258265
* @param map A map object that can be used to initialize the contents of
259266
* the JSONObject.
267+
* @throws JSONException If there is a syntax error.
260268
*/
261-
public JSONObject(Map map) {
269+
public JSONObject(Map map) throws JSONException {
270+
this(map, 0);
271+
}
272+
273+
private JSONObject(Map map, int recursionDepth) throws JSONException {
274+
275+
if (recursionDepth > RECURSION_DEPTH_LIMIT) {
276+
throw new JSONException("JSONObject has reached recursion depth limit of " + RECURSION_DEPTH_LIMIT);
277+
}
262278
this.myHashMap = (map == null) ?
263279
new LinkedHashMap<Object,Object>() :
264280
new LinkedHashMap<Object,Object>(map);
@@ -268,8 +284,8 @@ public JSONObject(Map map) {
268284
if (v instanceof Collection) {
269285
myHashMap.put(entry.getKey(), new JSONArray((Collection) v));
270286
}
271-
if (v instanceof Map) {
272-
myHashMap.put(entry.getKey(), new JSONObject((Map) v));
287+
if (v instanceof Map && v != map) {
288+
myHashMap.put(entry.getKey(), new JSONObject((Map) v, recursionDepth + 1));
273289
}
274290
}
275291
}
@@ -1025,6 +1041,12 @@ public static String quote(String string, boolean escapeForwardSlashAlways) {
10251041
c = string.charAt(i);
10261042
switch (c) {
10271043
case '\\':
1044+
// Escape a backslash, but only if it isn't already escaped
1045+
if (i == len - 1 || string.charAt(i + 1) != '\\') {
1046+
sb.append('\\');
1047+
}
1048+
sb.append(c);
1049+
break;
10281050
case '"':
10291051
sb.append('\\');
10301052
sb.append(c);
@@ -1319,6 +1341,23 @@ static String valueToString(Object value, int indentFactor, int indent, boolean
13191341
return quote(value.toString(), escapeForwardSlash);
13201342
}
13211343

1344+
/**
1345+
* Set the new recursion depth limit to prevent stack overflow issues on deeply nested structures. The default
1346+
* value is 500
1347+
* @param newRecursionDepthLimit the new recursion depth limit to set
1348+
*/
1349+
public void setRecursionDepthLimit(int newRecursionDepthLimit) {
1350+
RECURSION_DEPTH_LIMIT = newRecursionDepthLimit;
1351+
}
1352+
1353+
/**
1354+
* Get the new recursion depth limit to prevent stack overflow issues on deeply nested structures. The default
1355+
* value is 500
1356+
* @return the recursion depth limit
1357+
*/
1358+
public int getRecursionDepthLimit() {
1359+
return RECURSION_DEPTH_LIMIT;
1360+
}
13221361

13231362
/**
13241363
* Write the contents of the JSONObject as JSON text to a writer.
@@ -1396,4 +1435,5 @@ public void setEscapeForwardSlashAlways(boolean escapeForwardSlashAlways) {
13961435
public Map toMap() {
13971436
return Collections.unmodifiableMap(myHashMap);
13981437
}
1438+
13991439
}

src/main/java/org/codehaus/jettison/json/JSONTokener.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ public class JSONTokener {
4444

4545

4646
private int threshold = -1;
47-
47+
48+
private int recursionDepth;
49+
4850
/**
4951
* Construct a JSONTokener from a string.
5052
*
@@ -54,7 +56,7 @@ public JSONTokener(String s) {
5456
this.myIndex = 0;
5557
this.mySource = s.trim();
5658
}
57-
59+
5860
/**
5961
* Construct a JSONTokener from a string.
6062
*
@@ -423,12 +425,21 @@ public Object nextValue() throws JSONException {
423425
}
424426

425427
protected JSONObject newJSONObject() throws JSONException {
428+
checkRecursionDepth();
426429
return new JSONObject(this);
427430
}
428-
431+
429432
protected JSONArray newJSONArray() throws JSONException {
433+
checkRecursionDepth();
430434
return new JSONArray(this);
431435
}
436+
437+
private void checkRecursionDepth() throws JSONException {
438+
recursionDepth++;
439+
if (recursionDepth > JSONObject.RECURSION_DEPTH_LIMIT) {
440+
throw new JSONException("JSONTokener has reached recursion depth limit of " + JSONObject.RECURSION_DEPTH_LIMIT);
441+
}
442+
}
432443

433444
/**
434445
* Skip characters until the next character is the requested character.

src/test/java/org/codehaus/jettison/json/JSONObjectTest.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@
22

33
import junit.framework.TestCase;
44

5+
import java.util.ArrayList;
6+
import java.util.HashMap;
7+
import java.util.List;
8+
import java.util.Map;
9+
510
public class JSONObjectTest extends TestCase {
11+
612
public void testEquals() throws Exception {
713
JSONObject aJsonObj = new JSONObject("{\"x\":\"y\"}");
814
JSONObject bJsonObj = new JSONObject("{\"x\":\"y\"}");
@@ -148,4 +154,49 @@ public void testMalformedArray() throws Exception {
148154
}
149155
}
150156

157+
// https://github.com/jettison-json/jettison/issues/52
158+
public void testIssue52() throws Exception {
159+
Map<String,Object> map = new HashMap<>();
160+
map.put("t",map);
161+
new JSONObject(map);
162+
}
163+
164+
// https://github.com/jettison-json/jettison/issues/52
165+
public void testIssue52Recursive() throws Exception {
166+
try {
167+
Map<String, Object> map = new HashMap<>();
168+
Map<String, Object> map2 = new HashMap<>();
169+
map.put("t", map2);
170+
map2.put("t", map);
171+
new JSONObject(map);
172+
fail("Failure expected");
173+
} catch (JSONException e) {
174+
assertTrue(e.getMessage().contains("JSONObject has reached recursion depth limit"));
175+
// expected
176+
}
177+
}
178+
179+
// https://github.com/jettison-json/jettison/issues/45
180+
public void testFuzzerTestCase() throws Exception, JSONException {
181+
try {
182+
new JSONObject("{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{\"G\":[30018084,1,6,32768,1,1,6,1,1]}:[3,1,6,32768,1,1,6,1,1]}:[3,1,6,32768,1,1,6,1,1]}:[3,1,6,32768,1,1,6,1,1]}:[3,1,6,32768,1,1,6,1,1]}:[3,1,6,32768,1,1,6,1,1]}:[3,1,6,32768,1,1,6,1,1]}:[3,1,6,38,1,1,6,1,1]}:[3,1,6,32768,1,1,6,1,1]}:[3,1,6,32768,1,1,6,1,1]}:[3,1,6,32768,1,1,6,1,1]}:[3,1,6,32768,1,1,6,1,1]}:[3,1,6,32768,1,1,6,1,1]}:[3,1,6,32768,1,1,6,1,0]}:[3,1,6,32768,1,1,6,1,1]}:[3,1,6,32768,1,340282366920938463463374607431768211458,6,1,1]}:[32768,1,1,6,1,0]}:[3,1,6,32768,1,1,6,1,1]}:[3,1,6,32768,1,340282366920938463463374607431768211458,6,1,1]}:[3,1,6,32768,1,1,6,1,1]}:[3,1,6,32768,9 68,1,127,1,1]}:[3,1,6,32768,1,1,6,1,1]}:[3,1,6,32768,1,1,6,1,1]}:[3,1,6,32768,1,1,6,1,1]}:[3,1,6,32768,1,1,6,1,1]}:[3,1,6,32768,1,1,6,1,1]}:[3,1,6,32768,1,1,6,1,9223372036854775807]}:[3,1,6,32768,1,1,6,1,1]}:[3,1,10,32768,1,1,6,1,1]}");
183+
fail("Failure expected");
184+
} catch (JSONException ex) {
185+
// expected
186+
}
187+
}
188+
189+
public void testFuzzerTestCase2() throws Exception {
190+
StringBuilder sb = new StringBuilder();
191+
for (int i = 0; i < 100000; i++) {
192+
sb.append("{");
193+
}
194+
try {
195+
new JSONObject(sb.toString());
196+
fail("Failure expected");
197+
} catch (JSONException e) {
198+
assertTrue(e.getMessage().contains("JSONTokener has reached recursion depth limit"));
199+
// expected
200+
}
201+
}
151202
}

0 commit comments

Comments
 (0)