Opened 18 months ago

Closed 18 months ago

Last modified 18 months ago

#3579 closed defect (fixed)

Backend crash with mixed collections

Reported by: pramsey Owned by: pramsey
Priority: blocker Milestone: PostGIS 2.2.3
Component: postgis Version: 2.2.x
Keywords: Cc:

Description

When called with a collection that include empty sub-components, the back-end may crash

with
        params as (
        select
            11 :: float as sidewalk_offset,
            1 :: float  as epsilon
    ),
        road as (
-- L-shaped road, 10 m
        select 'SRID=3857;LINESTRING(10 0, 0 0, 0 10)' :: geometry as geom
    ),
        sidewalks as (
        select ST_Collect(
                   ST_OffsetCurve(geom, sidewalk_offset),
                   ST_OffsetCurve(geom, -sidewalk_offset)
               ) geom
        from road, params
    )
select
    ST_Intersects(road.geom, sidewalks.geom),
-- should be false
    ST_Intersects(ST_Buffer(road.geom, sidewalk_offset + epsilon), sidewalks.geom) -- should be true
from road, sidewalks, params;

Originally reported on github at https://github.com/postgis/postgis/pull/105

Change History (10)

comment:1 Changed 18 months ago by pramsey

Fixed in 2.2 at r14956

comment:2 Changed 18 months ago by pramsey

Fixed in trunk at r14957

comment:3 Changed 18 months ago by pramsey

Fixed in 2.1 at r14958

comment:4 Changed 18 months ago by pramsey

Fixed in 2.0 at r14959

comment:5 Changed 18 months ago by pramsey

Resolution: fixed
Status: newclosed

comment:6 Changed 18 months ago by strk

Did you find out _what_ was crashing, exactly ? The fix seems to workaround a deeper bug, which would be good to fix. Was it in GEOS or in PostGIS ?

comment:7 Changed 18 months ago by pramsey

I never actually fired up the debugger, just confirmed that the fix worked and didn't regress.

comment:8 Changed 18 months ago by pramsey

Here's a stack-trace. Seems like a pretty common code path (geometry.intersects()). Is it possible we never exercised GEOS w/ collections containing a mixture of full/empty geometry before?

* thread #1: tid = 0x1a7a62, 0x000000010be7a09b libgeos-3.6.0dev.dylib`geos::algorithm::PointLocator::locate(geos::geom::Coordinate const&, geos::geom::LineString const*) [inlined] geos::geom::Coordinate::equals2D(this=<unavailable>, other=0x0000000000000000) const + 5 at Coordinate.inl:52, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
    frame #0: 0x000000010be7a09b libgeos-3.6.0dev.dylib`geos::algorithm::PointLocator::locate(geos::geom::Coordinate const&, geos::geom::LineString const*) [inlined] geos::geom::Coordinate::equals2D(this=<unavailable>, other=0x0000000000000000) const + 5 at Coordinate.inl:52
   49  	INLINE bool
   50  	Coordinate::equals2D(const Coordinate& other) const
   51  	{
-> 52  		if (x != other.x) return false;
   53  		if (y != other.y) return false;
   54  		return true;
   55  	}
(lldb) bt
* thread #1: tid = 0x1a7a62, 0x000000010be7a09b libgeos-3.6.0dev.dylib`geos::algorithm::PointLocator::locate(geos::geom::Coordinate const&, geos::geom::LineString const*) [inlined] geos::geom::Coordinate::equals2D(this=<unavailable>, other=0x0000000000000000) const + 5 at Coordinate.inl:52, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
  * frame #0: 0x000000010be7a09b libgeos-3.6.0dev.dylib`geos::algorithm::PointLocator::locate(geos::geom::Coordinate const&, geos::geom::LineString const*) [inlined] geos::geom::Coordinate::equals2D(this=<unavailable>, other=0x0000000000000000) const + 5 at Coordinate.inl:52
    frame #1: 0x000000010be7a096 libgeos-3.6.0dev.dylib`geos::algorithm::PointLocator::locate(geos::geom::Coordinate const&, geos::geom::LineString const*) [inlined] geos::geom::operator==(b=0x0000000000000000) at Coordinate.inl:123
    frame #2: 0x000000010be7a096 libgeos-3.6.0dev.dylib`geos::algorithm::PointLocator::locate(this=<unavailable>, p=0x00007f9881e022e0, l=<unavailable>) + 54 at PointLocator.cpp:133
    frame #3: 0x000000010be7a412 libgeos-3.6.0dev.dylib`geos::algorithm::PointLocator::computeLocation(this=0x00007fff5dba46f0, p=0x00007f9881e022e0, geom=<unavailable>) + 418 at PointLocator.cpp:82
    frame #4: 0x000000010be7a026 libgeos-3.6.0dev.dylib`geos::algorithm::PointLocator::locate(this=0x00007fff5dba46f0, p=0x00007f9881e022e0, geom=0x00007f9881e01520) + 182 at PointLocator.cpp:55
    frame #5: 0x000000010bef71f4 libgeos-3.6.0dev.dylib`geos::operation::relate::RelateComputer::labelIsolatedNodes() [inlined] geos::operation::relate::RelateComputer::labelIsolatedNode(targetIndex=1) + 31 at RelateComputer.cpp:508
    frame #6: 0x000000010bef71d5 libgeos-3.6.0dev.dylib`geos::operation::relate::RelateComputer::labelIsolatedNodes(this=0x00007fff5dba4678) + 165 at RelateComputer.cpp:499
    frame #7: 0x000000010bef69c6 libgeos-3.6.0dev.dylib`geos::operation::relate::RelateComputer::computeIM(this=0x00007fff5dba4678) + 374 at RelateComputer.cpp:162
    frame #8: 0x000000010bef8276 libgeos-3.6.0dev.dylib`geos::operation::relate::RelateOp::relate(geos::geom::Geometry const*, geos::geom::Geometry const*) [inlined] geos::operation::relate::RelateOp::getIntersectionMatrix() + 8 at RelateOp.cpp:74
    frame #9: 0x000000010bef826e libgeos-3.6.0dev.dylib`geos::operation::relate::RelateOp::relate(a=<unavailable>, b=<unavailable>) + 78 at RelateOp.cpp:42
    frame #10: 0x000000010be83b0d libgeos-3.6.0dev.dylib`geos::geom::Geometry::intersects(this=0x00007f9881e017f0, g=0x00007f9881e01520) const + 333 at Geometry.cpp:344
    frame #11: 0x000000010306d0b5 libgeos_c.1.dylib`::GEOSIntersects_r(extHandle=<unavailable>, g1=<unavailable>, g2=<unavailable>) + 53 at geos_ts_c.cpp:500
    frame #12: 0x0000000102f6682c postgis-2.3.so`geos_intersects(fcinfo=0x00007f98830648a8) + 1628 at lwgeom_geos.c:2418
    frame #13: 0x0000000102f69738 postgis-2.3.so`intersects(fcinfo=0x00007f98830648a8) + 40 at lwgeom_backend_api.c:173
    frame #14: 0x00000001022bb29a postgres`ExecMakeFunctionResultNoSets(fcache=0x00007f9883064838, econtext=0x00007f988305b580, isNull="", isDone=0x0000000000000000) + 346 at execQual.c:2026
    frame #15: 0x00000001022b4d47 postgres`ExecEvalFunc(fcache=0x00007f9883064838, econtext=0x00007f988305b580, isNull="", isDone=0x0000000000000000) + 199 at execQual.c:2417
    frame #16: 0x00000001022b5d16 postgres`ExecEvalAnd(andExpr=0x00007f988305b678, econtext=0x00007f988305b580, isNull="", isDone=0x00007f98830689c0) + 134 at execQual.c:2831
    frame #17: 0x00000001022ba0ef postgres`ExecTargetList(targetlist=0x00007f9883068960, econtext=0x00007f988305b580, values=0x00007f9883068820, isnull="", itemIsDone=0x00007f98830689c0, isDone=0x00007fff5dba4c84) + 175 at execQual.c:5307
    frame #18: 0x00000001022ba005 postgres`ExecProject(projInfo=0x00007f9883068868, isDone=0x00007fff5dba4c84) + 741 at execQual.c:5522
    frame #19: 0x00000001022d8084 postgres`ExecNestLoop(node=0x00007f988305b468) + 1140 at nodeNestloop.c:267
    frame #20: 0x00000001022adeaa postgres`ExecProcNode(node=0x00007f988305b468) + 490 at execProcnode.c:449
    frame #21: 0x00000001022a98d3 postgres`ExecutePlan(estate=0x00007f9883056438, planstate=0x00007f988305b468, operation=CMD_SELECT, sendTuples='\x01', numberTuples=0, direction=ForwardScanDirection, dest=0x00007f988304ac40) + 115 at execMain.c:1490
    frame #22: 0x00000001022a97fa postgres`standard_ExecutorRun(queryDesc=0x00007f988302aa38, direction=ForwardScanDirection, count=0) + 490 at execMain.c:319
    frame #23: 0x00000001022a960a postgres`ExecutorRun(queryDesc=0x00007f988302aa38, direction=ForwardScanDirection, count=0) + 74 at execMain.c:267
    frame #24: 0x00000001024676a4 postgres`PortalRunSelect(portal=0x00007f9883044438, forward='\x01', count=0, dest=0x00007f988304ac40) + 308 at pquery.c:942
    frame #25: 0x0000000102467087 postgres`PortalRun(portal=0x00007f9883044438, count=9223372036854775807, isTopLevel='\x01', dest=0x00007f988304ac40, altdest=0x00007f988304ac40, completionTag="") + 679 at pquery.c:786
    frame #26: 0x0000000102462947 postgres`exec_simple_query(query_string="with\n        params as (\n        select\n            11 :: float as sidewalk_offset,\n            1 :: float  as epsilon\n    ),\n        road as (\n\n        select 'SRID=3857;LINESTRING(10 0, 0 0, 0 10)' :: geometry as geom\n    ),\n        sidewalks as (\n        select ST_Collect(\n                   ST_OffsetCurve(geom, sidewalk_offset),\n                   ST_OffsetCurve(geom, -sidewalk_offset)\n               ) geom\n        from road, params\n    )\nselect\n    ST_Intersects(road.geom, sidewalks.geom),\n\n    ST_Intersects(ST_Buffer(road.geom, sidewalk_offset + epsilon), sidewalks.geom) \nfrom road, sidewalks, params;") + 1239 at postgres.c:1072
    frame #27: 0x0000000102461cb8 postgres`PostgresMain(argc=1, argv=0x00007f98820060d8, dbname="postgis23", username="pramsey") + 2856 at postgres.c:4079
    frame #28: 0x00000001023ca585 postgres`BackendRun(port=0x00007f9881f00c80) + 693 at postmaster.c:4285
    frame #29: 0x00000001023c97c8 postgres`BackendStartup(port=0x00007f9881f00c80) + 408 at postmaster.c:3948
    frame #30: 0x00000001023c8985 postgres`ServerLoop + 597 at postmaster.c:1679
    frame #31: 0x00000001023c6271 postgres`PostmasterMain(argc=3, argv=0x00007f9881c07220) + 5409 at postmaster.c:1287
    frame #32: 0x0000000102305daa postgres`main(argc=3, argv=0x00007f9881c07220) + 746 at main.c:233
    frame #33: 0x00007fff8454b5ad libdyld.dylib`start + 1

comment:9 Changed 18 months ago by strk

Could very well be. Worth a ticket to GEOS

comment:10 Changed 18 months ago by pramsey

Based on the example provided, here's the minimal crasher, which could be tested in GEOS.

 select st_intersects(
 'LINESTRING(10 0, 0 0, 0 10)'::geometry,
 'MULTILINESTRING((10 -1,-1 10),EMPTY)'::geometry
 )

Just having the collection-with-empty structure was not enough to induce a crash, the geometries had to overlap so that the overlay code was started up. Then things went boom.

Note: See TracTickets for help on using tickets.