Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,18 @@ private static LatLngBounds parseBoundingBox(JSONArray coordinates) throws JSONE
* @param geoJsonGeometry geometry object to parse
* @return Geometry object
*/
/** Maximum nesting depth for GeometryCollection to prevent stack overflow. */
private static final int MAX_GEOMETRY_DEPTH = 20;

public static Geometry parseGeometry(JSONObject geoJsonGeometry) {
return parseGeometry(geoJsonGeometry, MAX_GEOMETRY_DEPTH);
}

public static Geometry parseGeometry(JSONObject geoJsonGeometry, int maxDepth) {
if (maxDepth < 0) {
Log.w(LOG_TAG, "GeoJSON geometry nesting depth limit exhausted, ignoring.");
return null;
}
try {
String geometryType = geoJsonGeometry.getString("type");
JSONArray geometryArray;
Expand All @@ -190,7 +201,7 @@ public static Geometry parseGeometry(JSONObject geoJsonGeometry) {
// No geometries or coordinates array
return null;
}
return createGeometry(geometryType, geometryArray);
return createGeometry(geometryType, geometryArray, maxDepth);
} catch (JSONException e) {
return null;
}
Expand Down Expand Up @@ -239,7 +250,7 @@ private static HashMap<String, String> parseProperties(JSONObject properties)
* @return Geometry object
* @throws JSONException if the coordinates or geometries could be parsed
*/
private static Geometry createGeometry(String geometryType, JSONArray geometryArray)
private static Geometry createGeometry(String geometryType, JSONArray geometryArray, int maxDepth)
throws JSONException {
switch (geometryType) {
case POINT:
Expand All @@ -255,7 +266,7 @@ private static Geometry createGeometry(String geometryType, JSONArray geometryAr
case MULTIPOLYGON:
return createMultiPolygon(geometryArray);
case GEOMETRY_COLLECTION:
return createGeometryCollection(geometryArray);
return createGeometryCollection(geometryArray, maxDepth - 1);
}
return null;
}
Expand Down Expand Up @@ -360,13 +371,13 @@ private static GeoJsonMultiPolygon createMultiPolygon(JSONArray coordinates)
* @return GeoJsonGeometryCollection object
* @throws JSONException if geometries cannot be parsed
*/
private static GeoJsonGeometryCollection createGeometryCollection(JSONArray geometries)
private static GeoJsonGeometryCollection createGeometryCollection(JSONArray geometries, int maxDepth)
throws JSONException {
ArrayList<Geometry> geometryCollectionElements
= new ArrayList<>();
for (int i = 0; i < geometries.length(); i++) {
JSONObject geometryElement = geometries.getJSONObject(i);
Geometry geometry = parseGeometry(geometryElement);
Geometry geometry = parseGeometry(geometryElement, maxDepth);
if (geometry != null) {
// Do not add geometries that could not be parsed
geometryCollectionElements.add(geometry);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -386,4 +386,65 @@ public void testInvalidGeometry() throws Exception {
parser = new GeoJsonParser(invalidGeometryInvalidCoordinatesString());
assertEquals(0, parser.getFeatures().size());
}

// ---------------------------------------------------------------
// Tests for fix: StackOverflowError on deeply nested GeometryCollection
// PR #1699
// ---------------------------------------------------------------

private static JSONObject buildNestedGeometryCollection(int depth) throws Exception {
JSONObject innermost = new JSONObject();
innermost.put("type", "Point");
innermost.put("coordinates", new org.json.JSONArray("[0, 0]"));

JSONObject current = innermost;
for (int i = 0; i < depth; i++) {
JSONObject wrapper = new JSONObject();
wrapper.put("type", "GeometryCollection");
wrapper.put("geometries", new org.json.JSONArray().put(current));
current = wrapper;
}
return current;
}

@Test
public void testDeeplyNestedGeometryCollection_doesNotThrowStackOverflow() throws Exception {
JSONObject deeplyNested = buildNestedGeometryCollection(2000);
try {
GeoJsonParser.parseGeometry(deeplyNested);
} catch (StackOverflowError e) {
throw new AssertionError(
"StackOverflowError thrown for deeply nested GeometryCollection — fix did not work!", e);
}
}


@Test
public void testGeometryBeyondMaxDepth_returnsNull() throws Exception {
JSONObject point = new JSONObject();
point.put("type", "Point");
point.put("coordinates", new org.json.JSONArray("[0, 0]"));
Geometry result = GeoJsonParser.parseGeometry(point, -1);
assertNull("Geometry exceeding max depth (countdown < 0) should be null", result);
}

@Test
public void testCustomMaxDepth_respectsLimit() throws Exception {
JSONObject point = new JSONObject();
point.put("type", "Point");
point.put("coordinates", new org.json.JSONArray("[0, 0]"));
Geometry valid = GeoJsonParser.parseGeometry(point, 0);
assertNotNull("Geometry at maxDepth 0 should not be null", valid);
Geometry invalid = GeoJsonParser.parseGeometry(point, -1);
assertNull("Geometry at maxDepth -1 should be null", invalid);
}

@Test
public void testShallowNestedGeometryCollection_parsedCorrectly() throws Exception {
JSONObject shallow = buildNestedGeometryCollection(3);
Geometry result = GeoJsonParser.parseGeometry(shallow);
assertNotNull("GeometryCollection with normal nesting should not be null", result);
assertTrue("Geometry type must be GeoJsonGeometryCollection",
result instanceof GeoJsonGeometryCollection);
}
}
Loading