[geos-commits] [SCM] GEOS branch 3.13 updated. c3a9f82726a5c90663f79272152194f1d6a43105

git at osgeo.org git at osgeo.org
Wed May 13 14:37:17 PDT 2026


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "GEOS".

The branch, 3.13 has been updated
       via  c3a9f82726a5c90663f79272152194f1d6a43105 (commit)
       via  f8910e84a0f3580f0847373cd77205703bf18c78 (commit)
      from  1d2f7a27e98237776e365c25e4ab7b2a34370ff9 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit c3a9f82726a5c90663f79272152194f1d6a43105
Author: Paul Ramsey <pramsey at cleverelephant.ca>
Date:   Wed May 13 13:46:33 2026 -0700

    Fix CI issue for mingw64

diff --git a/include/geos/algorithm/distance/DistanceToPoint.h b/include/geos/algorithm/distance/DistanceToPoint.h
index 108b85506..f1889397b 100644
--- a/include/geos/algorithm/distance/DistanceToPoint.h
+++ b/include/geos/algorithm/distance/DistanceToPoint.h
@@ -19,6 +19,7 @@
 #pragma once
 
 #include <geos/geom/LineSegment.h> // for composition
+#include <geos/export.h>
 
 namespace geos {
 namespace algorithm {
@@ -43,7 +44,7 @@ namespace distance { // geos::algorithm::distance
  *
  * Also computes two points which are separated by the distance.
  */
-class DistanceToPoint {
+class GEOS_DLL DistanceToPoint {
 public:
 
     DistanceToPoint() {}

commit f8910e84a0f3580f0847373cd77205703bf18c78
Author: Paul Ramsey <pramsey at cleverelephant.ca>
Date:   Wed May 13 12:11:50 2026 -0700

    Limit recursion depth in WKT/WKB/GeoJSON readers
    
    Prevents stack overflow on malformed input.

diff --git a/capi/geos_ts_c.cpp b/capi/geos_ts_c.cpp
index ef238229a..f76dc25ee 100644
--- a/capi/geos_ts_c.cpp
+++ b/capi/geos_ts_c.cpp
@@ -948,7 +948,6 @@ extern "C" {
         });
     }
 
-
     Geometry*
     GEOSGeomFromWKT_r(GEOSContextHandle_t extHandle, const char* wkt)
     {
diff --git a/include/geos/io/GeoJSONReader.h b/include/geos/io/GeoJSONReader.h
index 41834f1dd..9d9d09d20 100644
--- a/include/geos/io/GeoJSONReader.h
+++ b/include/geos/io/GeoJSONReader.h
@@ -75,6 +75,7 @@ public:
 private:
 
     const geom::GeometryFactory& geometryFactory;
+    mutable int parseDepth_ = 0;
 
     std::unique_ptr<geom::Geometry> readFeatureForGeometry(const geos_nlohmann::json& j) const;
 
diff --git a/include/geos/io/WKBReader.h b/include/geos/io/WKBReader.h
index 9d410c250..2b8b5beae 100644
--- a/include/geos/io/WKBReader.h
+++ b/include/geos/io/WKBReader.h
@@ -146,6 +146,8 @@ private:
 
     std::array<double, 4> ordValues;
 
+    int parseDepth_ = 0;
+
     std::unique_ptr<geom::Geometry> readGeometry();
 
     std::unique_ptr<geom::Point> readPoint();
diff --git a/include/geos/io/WKTReader.h b/include/geos/io/WKTReader.h
index 7294d64f5..46529c3c8 100644
--- a/include/geos/io/WKTReader.h
+++ b/include/geos/io/WKTReader.h
@@ -148,6 +148,7 @@ private:
     const geom::GeometryFactory* geometryFactory;
     const geom::PrecisionModel* precisionModel;
     bool fixStructure;
+    mutable int parseDepth_ = 0;
 
     void getPreciseCoordinate(io::StringTokenizer* tokenizer, OrdinateSet& ordinateFlags, geom::CoordinateXYZM&) const;
 
diff --git a/src/io/GeoJSONReader.cpp b/src/io/GeoJSONReader.cpp
index 0f0817e32..1ab54dd9e 100644
--- a/src/io/GeoJSONReader.cpp
+++ b/src/io/GeoJSONReader.cpp
@@ -40,12 +40,15 @@ using json = geos_nlohmann::json;
 namespace geos {
 namespace io { // geos.io
 
+static constexpr int MAX_PARSE_DEPTH = 100;
+
 GeoJSONReader::GeoJSONReader(): GeoJSONReader(*(GeometryFactory::getDefaultInstance())) {}
 
 GeoJSONReader::GeoJSONReader(const geom::GeometryFactory& gf) : geometryFactory(gf) {}
 
 std::unique_ptr<geom::Geometry> GeoJSONReader::read(const std::string& geoJsonText) const
 {
+    parseDepth_ = 0;
     try {
         const json& j = json::parse(geoJsonText);
         const std::string& type = j.at("type");
@@ -66,6 +69,7 @@ std::unique_ptr<geom::Geometry> GeoJSONReader::read(const std::string& geoJsonTe
 
 GeoJSONFeatureCollection GeoJSONReader::readFeatures(const std::string& geoJsonText) const
 {
+    parseDepth_ = 0;
     try {
         const json& j = json::parse(geoJsonText);
         const std::string& type = j.at("type");
@@ -121,6 +125,12 @@ std::map<std::string, GeoJSONValue> GeoJSONReader::readProperties(
 GeoJSONValue GeoJSONReader::readProperty(
     const geos_nlohmann::json& value) const
 {
+    if (parseDepth_ >= MAX_PARSE_DEPTH) {
+        throw ParseException("Input property exceeds nesting depth limit");
+    }
+    ++parseDepth_;
+    struct DepthGuard { int& d; ~DepthGuard() { --d; } } guard{parseDepth_};
+
     if (value.is_string()) {
         return GeoJSONValue { value.get<std::string>() };
     }
@@ -179,6 +189,12 @@ GeoJSONFeatureCollection GeoJSONReader::readFeatureCollection(
 std::unique_ptr<geom::Geometry> GeoJSONReader::readGeometry(
     const geos_nlohmann::json& j) const
 {
+    if (parseDepth_ >= MAX_PARSE_DEPTH) {
+        throw ParseException("Input geometry exceeds nesting depth limit");
+    }
+    ++parseDepth_;
+    struct DepthGuard { int& d; ~DepthGuard() { --d; } } guard{parseDepth_};
+
     const std::string& type = j.at("type");
     if (type == "Point") {
         return readPoint(j);
diff --git a/src/io/WKBReader.cpp b/src/io/WKBReader.cpp
index 5410ab0c7..d81a88ddc 100644
--- a/src/io/WKBReader.cpp
+++ b/src/io/WKBReader.cpp
@@ -245,13 +245,22 @@ WKBReader::read(std::istream& is)
 std::unique_ptr<Geometry>
 WKBReader::read(const unsigned char* buf, size_t size)
 {
+    parseDepth_ = 0;
     dis = ByteOrderDataInStream(buf, size); // will default to machine endian
     return readGeometry();
 }
 
+static constexpr int MAX_PARSE_DEPTH = 100;
+
 std::unique_ptr<Geometry>
 WKBReader::readGeometry()
 {
+    if (parseDepth_ >= MAX_PARSE_DEPTH) {
+        throw ParseException("Input geometry exceeds nesting depth limit");
+    }
+    ++parseDepth_;
+    struct DepthGuard { int& d; ~DepthGuard() { --d; } } guard{parseDepth_};
+
     // determine byte order
     unsigned char byteOrder = dis.readByte();
 
diff --git a/src/io/WKTReader.cpp b/src/io/WKTReader.cpp
index fc850d220..8b66e021c 100644
--- a/src/io/WKTReader.cpp
+++ b/src/io/WKTReader.cpp
@@ -49,9 +49,12 @@ using namespace geos::geom;
 namespace geos {
 namespace io { // geos.io
 
+static constexpr int MAX_PARSE_DEPTH = 100;
+
 std::unique_ptr<Geometry>
 WKTReader::read(const std::string& wellKnownText) const
 {
+    parseDepth_ = 0;
     CLocalizer clocale;
     StringTokenizer tokenizer(wellKnownText);
     OrdinateSet ordinateFlags = OrdinateSet::createXY();
@@ -267,6 +270,12 @@ WKTReader::getNextWord(StringTokenizer* tokenizer)
 std::unique_ptr<Geometry>
 WKTReader::readGeometryTaggedText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags, const GeometryTypeId* emptyType) const
 {
+    if (parseDepth_ >= MAX_PARSE_DEPTH) {
+        throw ParseException("Input geometry exceeds nesting depth limit");
+    }
+    ++parseDepth_;
+    struct DepthGuard { int& d; ~DepthGuard() { --d; } } guard{parseDepth_};
+
     std::string type = getNextWord(tokenizer);
 
     std::unique_ptr<Geometry> geom;
diff --git a/tests/unit/io/GeoJSONReaderTest.cpp b/tests/unit/io/GeoJSONReaderTest.cpp
index c5ff39db8..bafb21ffc 100644
--- a/tests/unit/io/GeoJSONReaderTest.cpp
+++ b/tests/unit/io/GeoJSONReaderTest.cpp
@@ -620,4 +620,21 @@ void object::test<40>
     ensure_equals(static_cast<size_t>(geom->getCoordinateDimension()), 3u);
 }
 
+// Deeply nested GeometryCollection should throw ParseException (not stack overflow)
+template<>
+template<>
+void object::test<41>
+()
+{
+    std::string geojson;
+    for (int i = 0; i < 200; i++) geojson += "{\"type\":\"GeometryCollection\",\"geometries\":[";
+    geojson += "{\"type\":\"Point\",\"coordinates\":[0,0]}";
+    for (int i = 0; i < 200; i++) geojson += "]}";
+
+    try {
+        geojsonreader.read(geojson);
+        fail("Expected ParseException for deeply nested GeoJSON");
+    } catch (const geos::io::ParseException&) {}
+}
+
 }
diff --git a/tests/unit/io/WKBReaderTest.cpp b/tests/unit/io/WKBReaderTest.cpp
index 6628351a8..0b6b540b6 100644
--- a/tests/unit/io/WKBReaderTest.cpp
+++ b/tests/unit/io/WKBReaderTest.cpp
@@ -7,11 +7,13 @@
 
 // tut
 #include <tut/tut.hpp>
+#include <tut/tut_macros.hpp>
 // geos
 #include <geos/io/WKBReader.h>
 #include <geos/io/WKBConstants.h>
 #include <geos/io/WKBWriter.h>
 #include <geos/io/WKTReader.h>
+#include <geos/io/ParseException.h>
 #include <geos/geom/CompoundCurve.h>
 #include <geos/geom/CurvePolygon.h>
 #include <geos/geom/PrecisionModel.h>
@@ -838,5 +840,24 @@ void object::test<36>
                    "ParseException: Expected SimpleCurve but got Point");
 }
 
+
+template<>
+template<>
+void object::test<37>
+()
+{
+    set_test_name("ParseException on deeply nested WKB collection avoid stack overflow");
+
+    // Each GeometryCollection header (NDR): byteOrder=01, type=07000000, numGeoms=01000000
+    const std::vector<unsigned char> header = {0x01, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00};
+    // Innermost empty GeometryCollection: byteOrder=01, type=07000000, numGeoms=00000000
+    const std::vector<unsigned char> inner = {0x01, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+    std::vector<unsigned char> buf;
+    for (int i = 0; i < 200; i++) buf.insert(buf.end(), header.begin(), header.end());
+    buf.insert(buf.end(), inner.begin(), inner.end());
+
+    ensure_THROW(wkbreader.read(buf.data(), buf.size()), geos::io::ParseException);
+}
+
 } // namespace tut
 
diff --git a/tests/unit/io/WKTReaderTest.cpp b/tests/unit/io/WKTReaderTest.cpp
index 59e5f1b25..5da2f3806 100644
--- a/tests/unit/io/WKTReaderTest.cpp
+++ b/tests/unit/io/WKTReaderTest.cpp
@@ -3,6 +3,7 @@
 
 // tut
 #include <tut/tut.hpp>
+#include <tut/tut_macros.hpp>
 // geos
 #include <geos/io/WKTReader.h>
 #include <geos/io/WKTWriter.h>
@@ -162,17 +163,7 @@ template<>
 void object::test<6>
 ()
 {
-    try {
-        wktreader.read("POLYGON( EMPTY, (1 1,2 2,1 2,1 1))");
-        fail("Did not get expected exception");
-    }
-    catch(const geos::util::IllegalArgumentException& ex) {
-        ensure("Got expected exception", true);
-        (void)(ex.what());
-    }
-    catch(...) {
-        fail("Got unexpected exception");
-    }
+    ensure_THROW(wktreader.read("POLYGON( EMPTY, (1 1,2 2,1 2,1 1))"), geos::util::IllegalArgumentException);
 }
 
 // POINT(0 0) http://trac.osgeo.org/geos/ticket/610
@@ -473,4 +464,17 @@ void object::test<24>
     ensure_equals(geom->getNumGeometries(), 3u);
 }
 
+template<>
+template<>
+void object::test<26>
+()
+{
+    set_test_name("ParseException on deeply nested WKT collection avoid stack overflow");
+    std::string wkt;
+    for (int i = 0; i < 200; i++) wkt += "GEOMETRYCOLLECTION(";
+    wkt += "POINT(0 0)";
+    for (int i = 0; i < 200; i++) wkt += ")";
+    ensure_parseexception(wkt);
+}
+
 } // namespace tut

-----------------------------------------------------------------------

Summary of changes:
 capi/geos_ts_c.cpp                                |  1 -
 include/geos/algorithm/distance/DistanceToPoint.h |  3 ++-
 include/geos/io/GeoJSONReader.h                   |  1 +
 include/geos/io/WKBReader.h                       |  2 ++
 include/geos/io/WKTReader.h                       |  1 +
 src/io/GeoJSONReader.cpp                          | 16 ++++++++++++++
 src/io/WKBReader.cpp                              |  9 ++++++++
 src/io/WKTReader.cpp                              |  9 ++++++++
 tests/unit/io/GeoJSONReaderTest.cpp               | 17 +++++++++++++++
 tests/unit/io/WKBReaderTest.cpp                   | 21 ++++++++++++++++++
 tests/unit/io/WKTReaderTest.cpp                   | 26 +++++++++++++----------
 11 files changed, 93 insertions(+), 13 deletions(-)


hooks/post-receive
-- 
GEOS


More information about the geos-commits mailing list