From 7823d3a4f3edf348514ef2486ba6819a7ca0c964 Mon Sep 17 00:00:00 2001 From: Zach Date: Wed, 17 Nov 2021 15:28:01 -0600 Subject: [PATCH 1/7] locate the pace for cyclic check --- src/main/java/org/json/JSONObject.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index 761df1d..8891673 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -1540,10 +1540,12 @@ public class JSONObject { try { final Object result = method.invoke(bean); if (result != null) { + // check cyclic dependency and throw error if needed + this.map.put(key, wrap(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(); From b5bcb68968f0f3f20598ca930e108a8657ba0c2d Mon Sep 17 00:00:00 2001 From: Zach Date: Wed, 17 Nov 2021 16:57:41 -0600 Subject: [PATCH 2/7] added set check logic --- src/main/java/org/json/JSONObject.java | 27 ++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index 8891673..c1e3b70 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -39,6 +39,7 @@ import java.math.BigInteger; 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; @@ -1510,6 +1511,11 @@ public class JSONObject { return NULL.equals(object) ? defaultValue : object.toString(); } + // Set to store the current seen objects in the recursive reaversal + // If the next value to be added to this set is a duplicate, a cycle + // is found + private final Set objectsRecord = new HashSet(); + /** * Populates the internal map of the JSONObject with the bean properties. The * bean can not be recursive. @@ -1541,8 +1547,18 @@ public class JSONObject { final Object result = method.invoke(bean); if (result != null) { // 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.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 @@ -2678,4 +2694,15 @@ public class JSONObject { "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) + ); + } } From 4565bddcbb227ccebc36e145d01966859f0db69b Mon Sep 17 00:00:00 2001 From: Zach Date: Wed, 17 Nov 2021 17:23:40 -0600 Subject: [PATCH 3/7] data class for recursive test --- .../org/json/junit/data/RecursiveBean.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/test/java/org/json/junit/data/RecursiveBean.java 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 0000000..00caf13 --- /dev/null +++ b/src/test/java/org/json/junit/data/RecursiveBean.java @@ -0,0 +1,22 @@ +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; + public String getName() { return name; } + public Object getRef() {return reference;} + + public RecursiveBean(String name) { + this.name = name; + reference = null; + } + public RecursiveBean(String name, Object refObj) { + this.name = name; + reference = refObj; + } +} \ No newline at end of file From 1ffcf3915c655527dd534474ef004335d00704af Mon Sep 17 00:00:00 2001 From: Zach Date: Wed, 17 Nov 2021 19:35:53 -0600 Subject: [PATCH 4/7] successful test --- src/main/java/org/json/JSONObject.java | 30 ++++++++++++++----- .../java/org/json/junit/JSONObjectTest.java | 9 ++++++ .../org/json/junit/data/RecursiveBean.java | 1 + 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index c1e3b70..77d509b 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -366,6 +366,11 @@ public class JSONObject { 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 @@ -1511,11 +1516,6 @@ public class JSONObject { return NULL.equals(object) ? defaultValue : object.toString(); } - // Set to store the current seen objects in the recursive reaversal - // If the next value to be added to this set is a duplicate, a cycle - // is found - private final Set objectsRecord = new HashSet(); - /** * Populates the internal map of the JSONObject with the bean properties. The * bean can not be recursive. @@ -1526,6 +1526,10 @@ public class JSONObject { * 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. @@ -1555,7 +1559,7 @@ public class JSONObject { objectsRecord.add(result); - this.map.put(key, wrap(result)); + this.map.put(key, wrap(result, objectsRecord)); objectsRecord.remove(result); @@ -2449,6 +2453,10 @@ public class JSONObject { * @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; @@ -2483,7 +2491,15 @@ public class JSONObject { || 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; } diff --git a/src/test/java/org/json/junit/JSONObjectTest.java b/src/test/java/org/json/junit/JSONObjectTest.java index 94c3e4b..e19a157 100644 --- a/src/test/java/org/json/junit/JSONObjectTest.java +++ b/src/test/java/org/json/junit/JSONObjectTest.java @@ -73,6 +73,7 @@ import org.json.junit.data.MyJsonString; 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,14 @@ public class JSONObjectTest { jsonObject.put(null, new Object()); fail("Expected an exception"); } + @Test(expected=JSONException.class) + public void testSimpleRecursiveObject() { + RecursiveBean ObjA = new RecursiveBean("ObjA"); + RecursiveBean ObjB = new RecursiveBean("ObjB", ObjA); + ObjA.setRef(ObjB); + JSONObject jsonObject = new JSONObject(ObjA); + fail("Expected an exception"); + } @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 index 00caf13..18ec8bd 100644 --- a/src/test/java/org/json/junit/data/RecursiveBean.java +++ b/src/test/java/org/json/junit/data/RecursiveBean.java @@ -10,6 +10,7 @@ public class RecursiveBean { private Object reference; public String getName() { return name; } public Object getRef() {return reference;} + public void setRef(Object refObj) {reference = refObj;} public RecursiveBean(String name) { this.name = name; From 638273af7a782d8291d87c5d56d4a259ea46bd7b Mon Sep 17 00:00:00 2001 From: Zach Date: Wed, 17 Nov 2021 19:41:00 -0600 Subject: [PATCH 5/7] long circle test --- src/test/java/org/json/junit/JSONObjectTest.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/json/junit/JSONObjectTest.java b/src/test/java/org/json/junit/JSONObjectTest.java index e19a157..df2144a 100644 --- a/src/test/java/org/json/junit/JSONObjectTest.java +++ b/src/test/java/org/json/junit/JSONObjectTest.java @@ -3224,7 +3224,17 @@ public class JSONObjectTest { RecursiveBean ObjA = new RecursiveBean("ObjA"); RecursiveBean ObjB = new RecursiveBean("ObjB", ObjA); ObjA.setRef(ObjB); - JSONObject jsonObject = new JSONObject(ObjA); + new JSONObject(ObjA); + fail("Expected an exception"); + } + @Test(expected=JSONException.class) + public void testLongRecursiveObject() { + RecursiveBean ObjA = new RecursiveBean("ObjA"); + RecursiveBean ObjB = new RecursiveBean("ObjB", ObjA); + RecursiveBean ObjC = new RecursiveBean("ObjB", ObjB); + RecursiveBean ObjD = new RecursiveBean("ObjB", ObjC); + ObjA.setRef(ObjD); + new JSONObject(ObjB); fail("Expected an exception"); } From fb96e870a95ed86c247e73578a75335a0586ac13 Mon Sep 17 00:00:00 2001 From: Zach Date: Wed, 17 Nov 2021 20:00:12 -0600 Subject: [PATCH 6/7] add test case and modified old ones --- .../java/org/json/junit/JSONObjectTest.java | 45 +++++++++++++++++-- .../org/json/junit/data/RecursiveBean.java | 8 ++-- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/src/test/java/org/json/junit/JSONObjectTest.java b/src/test/java/org/json/junit/JSONObjectTest.java index df2144a..cb31c7a 100644 --- a/src/test/java/org/json/junit/JSONObjectTest.java +++ b/src/test/java/org/json/junit/JSONObjectTest.java @@ -3221,22 +3221,59 @@ public class JSONObjectTest { } @Test(expected=JSONException.class) public void testSimpleRecursiveObject() { + // B -> A -> B ... RecursiveBean ObjA = new RecursiveBean("ObjA"); - RecursiveBean ObjB = new RecursiveBean("ObjB", 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", ObjA); - RecursiveBean ObjC = new RecursiveBean("ObjB", ObjB); - RecursiveBean ObjD = new RecursiveBean("ObjB", ObjC); + 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 + 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(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 testIssue548ObjectWithEmptyJsonArray() { diff --git a/src/test/java/org/json/junit/data/RecursiveBean.java b/src/test/java/org/json/junit/data/RecursiveBean.java index 18ec8bd..dad6e7a 100644 --- a/src/test/java/org/json/junit/data/RecursiveBean.java +++ b/src/test/java/org/json/junit/data/RecursiveBean.java @@ -8,16 +8,16 @@ package org.json.junit.data; 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; - } - public RecursiveBean(String name, Object refObj) { - this.name = name; - reference = refObj; + reference2 = null; } } \ No newline at end of file From fca7e17b389912e03f5da405f00ee89f793b3899 Mon Sep 17 00:00:00 2001 From: Zach Date: Thu, 18 Nov 2021 14:53:22 -0600 Subject: [PATCH 7/7] Added test cases for self recursion and complex but no recursion --- .../java/org/json/junit/JSONObjectTest.java | 50 ++++++++++++++++--- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/src/test/java/org/json/junit/JSONObjectTest.java b/src/test/java/org/json/junit/JSONObjectTest.java index cb31c7a..381c942 100644 --- a/src/test/java/org/json/junit/JSONObjectTest.java +++ b/src/test/java/org/json/junit/JSONObjectTest.java @@ -3220,6 +3220,24 @@ public class JSONObjectTest { 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"); @@ -3243,6 +3261,22 @@ public class JSONObjectTest { 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 @@ -3257,21 +3291,25 @@ public class JSONObjectTest { new JSONObject(ObjB); new JSONObject(ObjA); } - @Test(expected=JSONException.class) - public void testRepeatObjectRecursive() { - // C -> B -> A -> D -> C - // -> D -> C + @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(ObjC); + ObjD.setRef(ObjE); new JSONObject(ObjC); - fail("Expected an exception"); + new JSONObject(ObjB); + new JSONObject(ObjA); + new JSONObject(ObjD); + new JSONObject(ObjE); }