Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixing StackOverflow error #53

Merged
merged 1 commit into from Nov 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/main/java/org/codehaus/jettison/json/JSONArray.java
Expand Up @@ -179,8 +179,9 @@ public JSONArray(String string) throws JSONException {
/**
* Construct a JSONArray from a Collection.
* @param collection A Collection.
* @throws JSONException If there is a syntax error.
*/
public JSONArray(Collection collection) {
public JSONArray(Collection collection) throws JSONException {
this.myArrayList = (collection == null) ?
new ArrayList() :
new ArrayList(collection);
Expand Down Expand Up @@ -580,8 +581,9 @@ public JSONArray put(boolean value) {
* JSONArray which is produced from a Collection.
* @param value A Collection value.
* @return this.
* @throws JSONException If there is a syntax error.
*/
public JSONArray put(Collection value) {
public JSONArray put(Collection value) throws JSONException {
put(new JSONArray(value));
return this;
}
Expand Down Expand Up @@ -631,8 +633,9 @@ public JSONArray put(long value) {
* JSONObject which is produced from a Map.
* @param value A Map value.
* @return this.
* @throws JSONException If there is a syntax error.
*/
public JSONArray put(Map value) {
public JSONArray put(Map value) throws JSONException {
put(new JSONObject(value));
return this;
}
Expand Down
46 changes: 43 additions & 3 deletions src/main/java/org/codehaus/jettison/json/JSONObject.java
Expand Up @@ -84,6 +84,13 @@
*/
public class JSONObject implements Serializable {

/**
* The default recursion depth limit to prevent stack overflow issues on deeply nested structures.
*/
final static int DEFAULT_RECURSION_DEPTH_LIMIT = 500;

static int RECURSION_DEPTH_LIMIT = DEFAULT_RECURSION_DEPTH_LIMIT;

/**
* JSONObject.NULL is equivalent to the value that JavaScript calls null,
* whilst Java's null is equivalent to the value that JavaScript calls
Expand Down Expand Up @@ -257,8 +264,17 @@ public JSONObject(JSONTokener x) throws JSONException {
* Construct a JSONObject from a Map.
* @param map A map object that can be used to initialize the contents of
* the JSONObject.
* @throws JSONException If there is a syntax error.
*/
public JSONObject(Map map) {
public JSONObject(Map map) throws JSONException {
this(map, 0);
}

private JSONObject(Map map, int recursionDepth) throws JSONException {

if (recursionDepth > RECURSION_DEPTH_LIMIT) {
throw new JSONException("JSONObject has reached recursion depth limit of " + RECURSION_DEPTH_LIMIT);
}
this.myHashMap = (map == null) ?
new LinkedHashMap<Object,Object>() :
new LinkedHashMap<Object,Object>(map);
Expand All @@ -268,8 +284,8 @@ public JSONObject(Map map) {
if (v instanceof Collection) {
myHashMap.put(entry.getKey(), new JSONArray((Collection) v));
}
if (v instanceof Map) {
myHashMap.put(entry.getKey(), new JSONObject((Map) v));
if (v instanceof Map && v != map) {
myHashMap.put(entry.getKey(), new JSONObject((Map) v, recursionDepth + 1));
}
}
}
Expand Down Expand Up @@ -1025,6 +1041,12 @@ public static String quote(String string, boolean escapeForwardSlashAlways) {
c = string.charAt(i);
switch (c) {
case '\\':
// Escape a backslash, but only if it isn't already escaped
if (i == len - 1 || string.charAt(i + 1) != '\\') {
sb.append('\\');
}
sb.append(c);
break;
case '"':
sb.append('\\');
sb.append(c);
Expand Down Expand Up @@ -1319,6 +1341,23 @@ static String valueToString(Object value, int indentFactor, int indent, boolean
return quote(value.toString(), escapeForwardSlash);
}

/**
* Set the new recursion depth limit to prevent stack overflow issues on deeply nested structures. The default
* value is 500
* @param newRecursionDepthLimit the new recursion depth limit to set
*/
public void setRecursionDepthLimit(int newRecursionDepthLimit) {
RECURSION_DEPTH_LIMIT = newRecursionDepthLimit;
}

/**
* Get the new recursion depth limit to prevent stack overflow issues on deeply nested structures. The default
* value is 500
* @return the recursion depth limit
*/
public int getRecursionDepthLimit() {
return RECURSION_DEPTH_LIMIT;
}

/**
* Write the contents of the JSONObject as JSON text to a writer.
Expand Down Expand Up @@ -1396,4 +1435,5 @@ public void setEscapeForwardSlashAlways(boolean escapeForwardSlashAlways) {
public Map toMap() {
return Collections.unmodifiableMap(myHashMap);
}

}
17 changes: 14 additions & 3 deletions src/main/java/org/codehaus/jettison/json/JSONTokener.java
Expand Up @@ -44,7 +44,9 @@ public class JSONTokener {


private int threshold = -1;


private int recursionDepth;

/**
* Construct a JSONTokener from a string.
*
Expand All @@ -54,7 +56,7 @@ public JSONTokener(String s) {
this.myIndex = 0;
this.mySource = s.trim();
}

/**
* Construct a JSONTokener from a string.
*
Expand Down Expand Up @@ -423,12 +425,21 @@ public Object nextValue() throws JSONException {
}

protected JSONObject newJSONObject() throws JSONException {
checkRecursionDepth();
return new JSONObject(this);
}

protected JSONArray newJSONArray() throws JSONException {
checkRecursionDepth();
return new JSONArray(this);
}

private void checkRecursionDepth() throws JSONException {
recursionDepth++;
if (recursionDepth > JSONObject.RECURSION_DEPTH_LIMIT) {
throw new JSONException("JSONTokener has reached recursion depth limit of " + JSONObject.RECURSION_DEPTH_LIMIT);
}
}

/**
* Skip characters until the next character is the requested character.
Expand Down
51 changes: 51 additions & 0 deletions src/test/java/org/codehaus/jettison/json/JSONObjectTest.java
Expand Up @@ -2,7 +2,13 @@

import junit.framework.TestCase;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class JSONObjectTest extends TestCase {

public void testEquals() throws Exception {
JSONObject aJsonObj = new JSONObject("{\"x\":\"y\"}");
JSONObject bJsonObj = new JSONObject("{\"x\":\"y\"}");
Expand Down Expand Up @@ -148,4 +154,49 @@ public void testMalformedArray() throws Exception {
}
}

// https://github.com/jettison-json/jettison/issues/52
public void testIssue52() throws Exception {
Map<String,Object> map = new HashMap<>();
map.put("t",map);
new JSONObject(map);
}

// https://github.com/jettison-json/jettison/issues/52
public void testIssue52Recursive() throws Exception {
try {
Map<String, Object> map = new HashMap<>();
Map<String, Object> map2 = new HashMap<>();
map.put("t", map2);
map2.put("t", map);
new JSONObject(map);
fail("Failure expected");
} catch (JSONException e) {
assertTrue(e.getMessage().contains("JSONObject has reached recursion depth limit"));
// expected
}
}

// https://github.com/jettison-json/jettison/issues/45
public void testFuzzerTestCase() throws Exception, JSONException {
try {
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]}");
fail("Failure expected");
} catch (JSONException ex) {
// expected
}
}

public void testFuzzerTestCase2() throws Exception {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100000; i++) {
sb.append("{");
}
try {
new JSONObject(sb.toString());
fail("Failure expected");
} catch (JSONException e) {
assertTrue(e.getMessage().contains("JSONTokener has reached recursion depth limit"));
// expected
}
}
}