diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index 761df1df0..77d509bd5 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -39,6 +39,7 @@ of this software and associated documentation files (the "Software"), to deal import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.Locale; import java.util.Map; @@ -365,6 +366,11 @@ public JSONObject(Object bean) { this.populateMap(bean); } + private JSONObject(Object bean, Set objectsRecord) { + this(); + this.populateMap(bean, objectsRecord); + } + /** * Construct a JSONObject from an Object, using reflection to find the * public members. The resulting JSONObject's keys will be the strings from @@ -1520,6 +1526,10 @@ public String optString(String key, String defaultValue) { * the bean */ private void populateMap(Object bean) { + populateMap(bean, new HashSet()); + } + + private void populateMap(Object bean, Set objectsRecord) { Class klass = bean.getClass(); // If klass is a System class then set includeSuperClass to false. @@ -1540,10 +1550,22 @@ && isValidMethodName(method.getName())) { try { final Object result = method.invoke(bean); if (result != null) { - this.map.put(key, wrap(result)); + // check cyclic dependency and throw error if needed + // the wrap and populateMap combination method is + // itself DFS recursive + if (objectsRecord.contains(result)) { + throw recursivelyDefinedObjectException(key); + } + + objectsRecord.add(result); + + this.map.put(key, wrap(result, objectsRecord)); + + objectsRecord.remove(result); + // we don't use the result anywhere outside of wrap // if it's a resource we should be sure to close it - // after calling toString + // after calling toString if (result instanceof Closeable) { try { ((Closeable) result).close(); @@ -2431,6 +2453,10 @@ public static String valueToString(Object value) throws JSONException { * @return The wrapped value */ public static Object wrap(Object object) { + return wrap(object, null); + } + + private static Object wrap(Object object, Set objectsRecord) { try { if (NULL.equals(object)) { return NULL; @@ -2465,7 +2491,15 @@ public static Object wrap(Object object) { || object.getClass().getClassLoader() == null) { return object.toString(); } - return new JSONObject(object); + if (objectsRecord != null) { + return new JSONObject(object, objectsRecord); + } + else { + return new JSONObject(object); + } + } + catch (JSONException exception) { + throw exception; } catch (Exception exception) { return null; } @@ -2676,4 +2710,15 @@ private static JSONException wrongValueFormatException( "JSONObject[" + quote(key) + "] is not a " + valueType + " (" + value + ")." , cause); } + + /** + * Create a new JSONException in a common format for recursive object definition. + * @param key name of the key + * @return JSONException that can be thrown. + */ + private static JSONException recursivelyDefinedObjectException(String key) { + return new JSONException( + "JavaBean object contains recursively defined member variable of key " + quote(key) + ); + } } diff --git a/src/test/java/org/json/junit/JSONObjectTest.java b/src/test/java/org/json/junit/JSONObjectTest.java index 94c3e4b8f..381c94207 100644 --- a/src/test/java/org/json/junit/JSONObjectTest.java +++ b/src/test/java/org/json/junit/JSONObjectTest.java @@ -73,6 +73,7 @@ of this software and associated documentation files (the "Software"), to deal import org.json.junit.data.MyNumber; import org.json.junit.data.MyNumberContainer; import org.json.junit.data.MyPublicClass; +import org.json.junit.data.RecursiveBean; import org.json.junit.data.Singleton; import org.json.junit.data.SingletonEnum; import org.json.junit.data.WeirdList; @@ -3218,6 +3219,99 @@ public void testPutNullObject() { jsonObject.put(null, new Object()); fail("Expected an exception"); } + @Test(expected=JSONException.class) + public void testSelfRecursiveObject() { + // A -> A ... + RecursiveBean ObjA = new RecursiveBean("ObjA"); + ObjA.setRef(ObjA); + new JSONObject(ObjA); + fail("Expected an exception"); + } + @Test(expected=JSONException.class) + public void testLongSelfRecursiveObject() { + // B -> A -> A ... + RecursiveBean ObjA = new RecursiveBean("ObjA"); + RecursiveBean ObjB = new RecursiveBean("ObjB"); + ObjB.setRef(ObjA); + ObjA.setRef(ObjA); + new JSONObject(ObjB); + fail("Expected an exception"); + } + @Test(expected=JSONException.class) + public void testSimpleRecursiveObject() { + // B -> A -> B ... + RecursiveBean ObjA = new RecursiveBean("ObjA"); + RecursiveBean ObjB = new RecursiveBean("ObjB"); + ObjB.setRef(ObjA); + ObjA.setRef(ObjB); + new JSONObject(ObjA); + fail("Expected an exception"); + } + @Test(expected=JSONException.class) + public void testLongRecursiveObject() { + // D -> C -> B -> A -> D ... + RecursiveBean ObjA = new RecursiveBean("ObjA"); + RecursiveBean ObjB = new RecursiveBean("ObjB"); + RecursiveBean ObjC = new RecursiveBean("ObjC"); + RecursiveBean ObjD = new RecursiveBean("ObjD"); + ObjC.setRef(ObjB); + ObjB.setRef(ObjA); + ObjD.setRef(ObjC); + ObjA.setRef(ObjD); + new JSONObject(ObjB); + fail("Expected an exception"); + } + @Test(expected=JSONException.class) + public void testRepeatObjectRecursive() { + // C -> B -> A -> D -> C ... + // -> D -> C ... + RecursiveBean ObjA = new RecursiveBean("ObjA"); + RecursiveBean ObjB = new RecursiveBean("ObjB"); + RecursiveBean ObjC = new RecursiveBean("ObjC"); + RecursiveBean ObjD = new RecursiveBean("ObjD"); + ObjC.setRef(ObjB); + ObjB.setRef(ObjA); + ObjB.setRef2(ObjD); + ObjA.setRef(ObjD); + ObjD.setRef(ObjC); + new JSONObject(ObjC); + fail("Expected an exception"); + } + @Test + public void testRepeatObjectNotRecursive() { + // C -> B -> A + // -> A + RecursiveBean ObjA = new RecursiveBean("ObjA"); + RecursiveBean ObjB = new RecursiveBean("ObjB"); + RecursiveBean ObjC = new RecursiveBean("ObjC"); + ObjC.setRef(ObjA); + ObjB.setRef(ObjA); + ObjB.setRef2(ObjA); + new JSONObject(ObjC); + new JSONObject(ObjB); + new JSONObject(ObjA); + } + @Test + public void testLongRepeatObjectNotRecursive() { + // C -> B -> A -> D -> E + // -> D -> E + RecursiveBean ObjA = new RecursiveBean("ObjA"); + RecursiveBean ObjB = new RecursiveBean("ObjB"); + RecursiveBean ObjC = new RecursiveBean("ObjC"); + RecursiveBean ObjD = new RecursiveBean("ObjD"); + RecursiveBean ObjE = new RecursiveBean("ObjE"); + ObjC.setRef(ObjB); + ObjB.setRef(ObjA); + ObjB.setRef2(ObjD); + ObjA.setRef(ObjD); + ObjD.setRef(ObjE); + new JSONObject(ObjC); + new JSONObject(ObjB); + new JSONObject(ObjA); + new JSONObject(ObjD); + new JSONObject(ObjE); + } + @Test public void testIssue548ObjectWithEmptyJsonArray() { diff --git a/src/test/java/org/json/junit/data/RecursiveBean.java b/src/test/java/org/json/junit/data/RecursiveBean.java new file mode 100644 index 000000000..dad6e7a65 --- /dev/null +++ b/src/test/java/org/json/junit/data/RecursiveBean.java @@ -0,0 +1,23 @@ +package org.json.junit.data; + +/** + * test class for verifying if recursively defined bean can be correctly identified + * @author Zetmas + * + */ +public class RecursiveBean { + private String name; + private Object reference; + private Object reference2; + public String getName() { return name; } + public Object getRef() {return reference;} + public Object getRef2() {return reference2;} + public void setRef(Object refObj) {reference = refObj;} + public void setRef2(Object refObj) {reference2 = refObj;} + + public RecursiveBean(String name) { + this.name = name; + reference = null; + reference2 = null; + } +} \ No newline at end of file