[SCM] PostGIS branch stable-3.6 updated. 3.6.4-87-gff2d01624

git at osgeo.org git at osgeo.org
Thu Jul 23 13:00:21 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 "PostGIS".

The branch, stable-3.6 has been updated
       via  ff2d0162461490e432f286511d1c05270bd32bbe (commit)
       via  85794db0f2c33e5154b1b1a580ab388ebb145b73 (commit)
      from  ebf8f43a55c86b1f6b1fab1ccac1ca04c262404a (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 ff2d0162461490e432f286511d1c05270bd32bbe
Merge: ebf8f43a5 85794db0f
Author: Darafei Praliaskouski <komzpa at gmail.com>
Date:   Thu Jul 23 13:00:19 2026 -0700

    Merge pull request 'Fix 32-bit topology and coverage crashes on stable 3.6' (!493) from Komzpa/postgis:pr/fix-stable36-coverage-32bit-crash into stable-3.6
    
    Reviewed-on: https://gitea.osgeo.org/postgis/postgis/pulls/493


commit 85794db0f2c33e5154b1b1a580ab388ebb145b73
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Sat Mar 7 15:00:33 2026 +0400

    Fix 32-bit topology and coverage crashes
    
    Two CI failures on i386 came from hard-coded Datum and stack struct assumptions that only break on 32-bit builds.
    
    In coverage_window_calculation(), the simplifyBoundary argument is declared as boolean in SQL, but the C code decoded it with DatumGetFloat8(). On 32-bit PostgreSQL float8 is pass-by-reference, so reading a bool Datum that way can dereference garbage and crash the backend while running coverage.sql. Decode the argument with DatumGetBool() instead.
    
    In the topology SPI backend, several callbacks unconditionally decoded topology primitive IDs with DatumGetInt64() and one face callback always built an INT8[] argument. Topology schemas still default to INT4 IDs unless usesLargeIDs is enabled, so those assumptions are wrong on 32-bit and caused addnode.sql to segfault or raise unsupported byval length errors. Load usesLargeIDs from topology.topology, decode tuple values using their actual column type, and build the face ID array with the matching integer width.
    
    Also zero-initialize temporary LWT_ISO_EDGE stack structs on edge split paths so callback update/select filters do not carry uninitialized fields into backend operations.
    
    (cherry picked from commit 14152529d3f3008be89dbc76505521e819ae78d5)

diff --git a/NEWS b/NEWS
index e98e0e104..2fe473c21 100644
--- a/NEWS
+++ b/NEWS
@@ -40,6 +40,8 @@ PostGIS 3.6.5
   with standard_conforming_strings off (Darafei Praliaskouski)
 - GH-1160, [topology] Preserve overlay intersection boundaries when
   noding lines with GEOS main (Darafei Praliaskouski)
+- GT-493, Fix 32-bit topology and coverage crashes
+  (Darafei Praliaskouski)
 
 
 PostGIS 3.6.4
diff --git a/liblwgeom/topo/lwgeom_topo.c b/liblwgeom/topo/lwgeom_topo.c
index 3e5f5b1ee..9050e100d 100644
--- a/liblwgeom/topo/lwgeom_topo.c
+++ b/liblwgeom/topo/lwgeom_topo.c
@@ -1056,8 +1056,8 @@ lwt_ModEdgeSplit( LWT_TOPOLOGY* topo, LWT_ELEMID edge,
   LWCOLLECTION *split_col;
   const LWGEOM *oldedge_geom;
   const LWGEOM *newedge_geom;
-  LWT_ISO_EDGE newedge1;
-  LWT_ISO_EDGE seledge, updedge, excedge;
+  LWT_ISO_EDGE newedge1 = {0};
+  LWT_ISO_EDGE seledge = {0}, updedge = {0}, excedge = {0};
   int ret;
 
   split_col = _lwt_EdgeSplit( topo, edge, pt, skipISOChecks, &oldedge );
@@ -1213,8 +1213,8 @@ lwt_NewEdgesSplit( LWT_TOPOLOGY* topo, LWT_ELEMID edge,
   LWCOLLECTION *split_col;
   const LWGEOM *oldedge_geom;
   const LWGEOM *newedge_geom;
-  LWT_ISO_EDGE newedges[2];
-  LWT_ISO_EDGE seledge, updedge;
+  LWT_ISO_EDGE newedges[2] = {{0}};
+  LWT_ISO_EDGE seledge = {0}, updedge = {0};
   int ret;
 
   split_col = _lwt_EdgeSplit( topo, edge, pt, skipISOChecks, &oldedge );
@@ -5453,10 +5453,9 @@ _lwt_SnapEdgeToExistingNode(
     replacedBy[n] = existingEdgeId;
   }
 
-
-  LWT_ISO_EDGE updatedEdge;
+  LWT_ISO_EDGE updatedEdge = {0};
   int updateFlags;
-  LWT_ISO_EDGE selEdge;
+  LWT_ISO_EDGE selEdge = {0};
 
   if ( ( replacedBy[0] != 0 && replacedBy[1] == 0 ) ||
        ( replacedBy[1] != 0 && replacedBy[0] == 0 ) )
@@ -5971,7 +5970,7 @@ _lwt_SnapEdgeToExistingNode(
     /* Neither sides of the snapped edge collapsed to an existing edge */
 
     /* New edge is the outgoing one, by design */
-    LWT_ISO_EDGE newEdge;
+    LWT_ISO_EDGE newEdge = {0};
     newEdge.edge_id = lwt_be_getNextEdgeId( topo );
     if ( newEdge.edge_id == -1 ) {
       PGTOPO_BE_ERROR();
diff --git a/postgis/lwgeom_window.c b/postgis/lwgeom_window.c
index 90af7a867..a6c8ebd04 100644
--- a/postgis/lwgeom_window.c
+++ b/postgis/lwgeom_window.c
@@ -700,7 +700,9 @@ coverage_window_calculation(PG_FUNCTION_ARGS, int mode)
 			if (!isnull) tolerance = DatumGetFloat8(d);
 
 			d = WinGetFuncArgCurrent(winobj, 2, &isnull);
-			if (!isnull) simplifyBoundary = DatumGetFloat8(d);
+			/* The third SQL argument is boolean, which matters on 32-bit builds. */
+			if (!isnull)
+				simplifyBoundary = DatumGetBool(d);
 
 			/* GEOSCoverageSimplifyVW is "preserveBoundary" so we invert simplifyBoundary */
 			output = GEOSCoverageSimplifyVW(input, tolerance, !simplifyBoundary);
diff --git a/topology/postgis_topology.c b/topology/postgis_topology.c
index f97343213..88b0613ed 100644
--- a/topology/postgis_topology.c
+++ b/topology/postgis_topology.c
@@ -85,6 +85,7 @@ struct LWT_BE_TOPOLOGY_T
   int32_t srid;
   double precision;
   int hasZ;
+  bool usesLargeIDs;
   Oid geometryOID;
 };
 
@@ -173,8 +174,8 @@ cb_loadTopologyByName(const LWT_BE_DATA* be, const char *name)
 
   argtypes[0] = CSTRINGOID;
   sql =
-    "SELECT id,srid,precision,null::geometry "
-    "FROM topology.topology WHERE name = $1::varchar";
+      "SELECT id,srid,precision,null::geometry,useslargeids "
+      "FROM topology.topology WHERE name = $1::varchar";
   if ( ! plan ) /* prepare on first call */
   {
     plan = SPI_prepare(sql, 1, argtypes);
@@ -219,6 +220,7 @@ cb_loadTopologyByName(const LWT_BE_DATA* be, const char *name)
   topo->be_data = (LWT_BE_DATA *)be; /* const cast.. */
   topo->name = pstrdup(name);
   topo->hasZ = 0;
+  topo->usesLargeIDs = false;
 
   dat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull);
   if ( isnull )
@@ -255,6 +257,12 @@ cb_loadTopologyByName(const LWT_BE_DATA* be, const char *name)
     topo->precision = DatumGetFloat8(dat);
   }
 
+  dat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 5, &isnull);
+  if (!isnull)
+  {
+	  topo->usesLargeIDs = DatumGetBool(dat);
+  }
+
   /* we're dynamically querying geometry type here */
   topo->geometryOID = TupleDescAttr(SPI_tuptable->tupdesc, 3)->atttypid;
 
@@ -659,6 +667,31 @@ addFaceValues(StringInfo str, LWT_ISO_FACE *face, int32_t srid)
   }
 }
 
+static int64
+tuple_get_int64(TupleDesc desc, int col, Datum dat)
+{
+	Oid typid = TupleDescAttr(desc, col - 1)->atttypid;
+
+	/*
+	 * Topology still defaults to INT4 primitive IDs unless usesLargeIDs is
+	 * requested, so SPI tuple readers must honor the actual column type here.
+	 */
+	switch (typid)
+	{
+	case INT2OID:
+		return DatumGetInt16(dat);
+	case INT4OID:
+		return DatumGetInt32(dat);
+	case INT8OID:
+		return DatumGetInt64(dat);
+	case OIDOID:
+		return DatumGetObjectId(dat);
+	default:
+		lwpgerror("Unexpected integer type %u for column %d", typid, col);
+		return -1;
+	}
+}
+
 static void
 fillEdgeFields(LWT_ISO_EDGE* edge, HeapTuple row, TupleDesc rowdesc, int fields)
 {
@@ -682,11 +715,13 @@ fillEdgeFields(LWT_ISO_EDGE* edge, HeapTuple row, TupleDesc rowdesc, int fields)
     }
     else
     {
-      val = DatumGetInt64(dat);
-      POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (edge_id)"
-                     " has int64 val of %" LWTFMT_ELEMID,
-                     colno, val);
-      edge->edge_id = val;
+	    val = tuple_get_int64(rowdesc, colno, dat);
+	    POSTGIS_DEBUGF(2,
+			   "fillEdgeFields: colno%d (edge_id)"
+			   " has int64 val of %" LWTFMT_ELEMID,
+			   colno,
+			   val);
+	    edge->edge_id = val;
     }
 
   }
@@ -700,10 +735,13 @@ fillEdgeFields(LWT_ISO_EDGE* edge, HeapTuple row, TupleDesc rowdesc, int fields)
     }
     else
     {
-      val = DatumGetInt64(dat);
-      POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (start_node)"
-                     " has int64 val of %" LWTFMT_ELEMID, colno, val);
-      edge->start_node = val;
+	    val = tuple_get_int64(rowdesc, colno, dat);
+	    POSTGIS_DEBUGF(2,
+			   "fillEdgeFields: colno%d (start_node)"
+			   " has int64 val of %" LWTFMT_ELEMID,
+			   colno,
+			   val);
+	    edge->start_node = val;
     }
   }
   if ( fields & LWT_COL_EDGE_END_NODE )
@@ -716,10 +754,13 @@ fillEdgeFields(LWT_ISO_EDGE* edge, HeapTuple row, TupleDesc rowdesc, int fields)
     }
     else
     {
-      val = DatumGetInt64(dat);
-      POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (end_node)"
-                     " has int64 val of %" LWTFMT_ELEMID, colno, val);
-      edge->end_node = val;
+	    val = tuple_get_int64(rowdesc, colno, dat);
+	    POSTGIS_DEBUGF(2,
+			   "fillEdgeFields: colno%d (end_node)"
+			   " has int64 val of %" LWTFMT_ELEMID,
+			   colno,
+			   val);
+	    edge->end_node = val;
     }
   }
   if ( fields & LWT_COL_EDGE_FACE_LEFT )
@@ -732,10 +773,13 @@ fillEdgeFields(LWT_ISO_EDGE* edge, HeapTuple row, TupleDesc rowdesc, int fields)
     }
     else
     {
-      val = DatumGetInt64(dat);
-      POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (face_left)"
-                     " has int64 val of %" LWTFMT_ELEMID, colno, val);
-      edge->face_left = val;
+	    val = tuple_get_int64(rowdesc, colno, dat);
+	    POSTGIS_DEBUGF(2,
+			   "fillEdgeFields: colno%d (face_left)"
+			   " has int64 val of %" LWTFMT_ELEMID,
+			   colno,
+			   val);
+	    edge->face_left = val;
     }
   }
   if ( fields & LWT_COL_EDGE_FACE_RIGHT )
@@ -748,10 +792,13 @@ fillEdgeFields(LWT_ISO_EDGE* edge, HeapTuple row, TupleDesc rowdesc, int fields)
     }
     else
     {
-      val = DatumGetInt64(dat);
-      POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (face_right)"
-                     " has int64 val of %" LWTFMT_ELEMID, colno, val);
-      edge->face_right = val;
+	    val = tuple_get_int64(rowdesc, colno, dat);
+	    POSTGIS_DEBUGF(2,
+			   "fillEdgeFields: colno%d (face_right)"
+			   " has int64 val of %" LWTFMT_ELEMID,
+			   colno,
+			   val);
+	    edge->face_right = val;
     }
   }
   if ( fields & LWT_COL_EDGE_NEXT_LEFT )
@@ -764,10 +811,13 @@ fillEdgeFields(LWT_ISO_EDGE* edge, HeapTuple row, TupleDesc rowdesc, int fields)
     }
     else
     {
-      val = DatumGetInt64(dat);
-      POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (next_left)"
-                     " has int64 val of %" LWTFMT_ELEMID, colno, val);
-      edge->next_left = val;
+	    val = tuple_get_int64(rowdesc, colno, dat);
+	    POSTGIS_DEBUGF(2,
+			   "fillEdgeFields: colno%d (next_left)"
+			   " has int64 val of %" LWTFMT_ELEMID,
+			   colno,
+			   val);
+	    edge->next_left = val;
     }
   }
   if ( fields & LWT_COL_EDGE_NEXT_RIGHT )
@@ -780,10 +830,13 @@ fillEdgeFields(LWT_ISO_EDGE* edge, HeapTuple row, TupleDesc rowdesc, int fields)
     }
     else
     {
-      val = DatumGetInt64(dat);
-      POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (next_right)"
-                     " has int64 val of %" LWTFMT_ELEMID, colno, val);
-      edge->next_right = val;
+	    val = tuple_get_int64(rowdesc, colno, dat);
+	    POSTGIS_DEBUGF(2,
+			   "fillEdgeFields: colno%d (next_right)"
+			   " has int64 val of %" LWTFMT_ELEMID,
+			   colno,
+			   val);
+	    edge->next_right = val;
     }
   }
   if ( fields & LWT_COL_EDGE_GEOM )
@@ -823,13 +876,14 @@ fillNodeFields(LWT_ISO_NODE* node, HeapTuple row, TupleDesc rowdesc, int fields)
   if ( fields & LWT_COL_NODE_NODE_ID )
   {
     dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
-    node->node_id = DatumGetInt64(dat);
+    node->node_id = tuple_get_int64(rowdesc, colno, dat);
   }
   if ( fields & LWT_COL_NODE_CONTAINING_FACE )
   {
     dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
     if ( isnull ) node->containing_face = -1;
-    else node->containing_face = DatumGetInt64(dat);
+    else
+	    node->containing_face = tuple_get_int64(rowdesc, colno, dat);
   }
   if ( fields & LWT_COL_NODE_GEOM )
   {
@@ -863,7 +917,7 @@ fillFaceFields(LWT_ISO_FACE* face, HeapTuple row, TupleDesc rowdesc, int fields)
   if ( fields & LWT_COL_FACE_FACE_ID )
   {
     dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
-    face->face_id = DatumGetInt64(dat);
+    face->face_id = tuple_get_int64(rowdesc, colno, dat);
   }
   if ( fields & LWT_COL_FACE_MBR )
   {
@@ -916,7 +970,7 @@ getNotNullInt64( HeapTuple row, TupleDesc desc, int col, int64 *val )
   bool isnull;
   Datum dat = SPI_getbinval( row, desc, col, &isnull );
   if ( isnull ) return 0;
-  *val = DatumGetInt64(dat);
+  *val = tuple_get_int64(desc, col, dat);
   return 1;
 }
 
@@ -1051,12 +1105,24 @@ cb_getEdgeByFace(const LWT_BE_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t *n
   Datum *datum_ids;
   Datum values[2];
   Oid argtypes[2];
+  Oid idtype;
+  Oid idarraytype;
   int nargs = 1;
   GSERIALIZED *gser = NULL;
+  int16 typlen;
+  bool typbyval;
+  char typalign;
 
+  /* Match the callback argument type to the topology schema's chosen ID width. */
+  idtype = topo->usesLargeIDs ? INT8OID : INT4OID;
+  idarraytype = topo->usesLargeIDs ? INT8ARRAYOID : INT4ARRAYOID;
+  get_typlenbyvalalign(idtype, &typlen, &typbyval, &typalign);
   datum_ids = palloc(sizeof(Datum)*(*numelems));
-  for (i=0; i<*numelems; ++i) datum_ids[i] = Int64GetDatum(ids[i]);
-  array_ids = construct_array(datum_ids, *numelems, INT8OID, 8, true, 's');
+  for (i = 0; i < *numelems; ++i)
+  {
+	  datum_ids[i] = topo->usesLargeIDs ? Int64GetDatum(ids[i]) : Int32GetDatum(ids[i]);
+  }
+  array_ids = construct_array(datum_ids, *numelems, idtype, typlen, typbyval, typalign);
 
   initStringInfo(sql);
   appendStringInfoString(sql, "SELECT ");
@@ -1067,7 +1133,7 @@ cb_getEdgeByFace(const LWT_BE_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t *n
                    topo->name);
 
   values[0] = PointerGetDatum(array_ids);
-  argtypes[0] = INT8ARRAYOID;
+  argtypes[0] = idarraytype;
 
   if ( box )
   {
@@ -1249,7 +1315,7 @@ cb_getRingEdges(const LWT_BE_TOPOLOGY *topo, LWT_ELEMID edge, uint64_t *numelems
       *numelems = UINT64_MAX;
       return NULL;
     }
-    val = DatumGetInt64(dat);
+    val = tuple_get_int64(rowdesc, 1, dat);
     edges[i] = val;
     POSTGIS_DEBUGF(1, "Component " UINT64_FORMAT " in ring of edge %" LWTFMT_ELEMID " is edge %d", i, edge, val);
 
@@ -1271,7 +1337,7 @@ cb_getRingEdges(const LWT_BE_TOPOLOGY *topo, LWT_ELEMID edge, uint64_t *numelems
         *numelems = UINT64_MAX;
         return NULL;
       }
-      nextedge = DatumGetInt64(dat);
+      nextedge = tuple_get_int64(rowdesc, sidecol, dat);
       POSTGIS_DEBUGF(1, "Last component in ring of edge %"
                         LWTFMT_ELEMID " (%d) has next_%s_edge %d",
                         edge, val, sidetext, nextedge);

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

Summary of changes:
 NEWS                         |   2 +
 liblwgeom/topo/lwgeom_topo.c |  15 ++---
 postgis/lwgeom_window.c      |   4 +-
 topology/postgis_topology.c  | 148 +++++++++++++++++++++++++++++++------------
 4 files changed, 119 insertions(+), 50 deletions(-)


hooks/post-receive
-- 
PostGIS


More information about the postgis-tickets mailing list