Skip to content

Commit b7085bb

Browse files
Correctness issue, particularly for Julia 1.12 - use after free bug (#39)
Co-authored-by: Anshul Singhvi <anshulsinghvi@gmail.com>
1 parent 9ef9c5d commit b7085bb

File tree

3 files changed

+104
-69
lines changed

3 files changed

+104
-69
lines changed

Project.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ GeoInterface = "cf35fbd7-0cd7-5166-be24-54bfbe79505f"
88
LibSpatialIndex_jll = "00e98e2a-4326-5239-88cb-15dcbe1c18d0"
99

1010
[compat]
11-
Aqua = "0.7"
11+
Aqua = "0.8"
1212
GeoInterface = "1"
1313
LibSpatialIndex_jll = "1.9"
1414
julia = "1.6"
15+
Test = "1"
1516

1617
[extras]
1718
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"

src/LibSpatialIndex.jl

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,13 @@ module LibSpatialIndex
179179
minvalues::Vector{Float64},
180180
maxvalues::Vector{Float64}
181181
)
182-
C.Index_InsertData(rtree.index, Int64(id), pointer(minvalues),
183-
pointer(maxvalues), UInt32(length(minvalues)), Ptr{UInt8}(0), Cint(0)
182+
length(minvalues) == rtree.ndim || throw(DimensionMismatch("Minimum values must have same length as RTree dimensions"))
183+
length(maxvalues) == rtree.ndim || throw(DimensionMismatch("Maximum values must have same length as RTree dimensions"))
184+
185+
# pass C_NULL as data - it should not be dereferenced, but making it null hopefully means if it is
186+
# it will be easier to debug
187+
C.Index_InsertData(rtree.index, Int64(id), minvalues,
188+
maxvalues, UInt32(rtree.ndim), C_NULL, Cint(0)
184189
)
185190
end
186191
function insert!(rtree::RTree, id::Integer, extent::GI.Extent)
@@ -213,10 +218,13 @@ module LibSpatialIndex
213218
minvalues::Vector{Float64},
214219
maxvalues::Vector{Float64}
215220
)
221+
length(minvalues) == rtree.ndim || throw(DimensionMismatch("Minimum values must have same length as RTree dimensions"))
222+
length(maxvalues) == rtree.ndim || throw(DimensionMismatch("Maximum values must have same length as RTree dimensions"))
223+
216224
items = Ref{Ptr{Int64}}()
217225
nresults = Ref{UInt64}()
218-
result = C.Index_Intersects_id(rtree.index, pointer(minvalues),
219-
pointer(maxvalues), UInt32(length(minvalues)), items, nresults
226+
result = C.Index_Intersects_id(rtree.index, minvalues,
227+
maxvalues, UInt32(rtree.ndim), items, nresults
220228
)
221229
_checkresult(result, "Index_Intersects_id: Failed to evaluate")
222230
unsafe_wrap(Array, items[], nresults[])
@@ -260,10 +268,13 @@ module LibSpatialIndex
260268
maxvalues::Vector{Float64},
261269
k::Integer
262270
)
271+
length(minvalues) == rtree.ndim || throw(DimensionMismatch("Minimum values must have same length as RTree dimensions"))
272+
length(maxvalues) == rtree.ndim || throw(DimensionMismatch("Maximum values must have same length as RTree dimensions"))
273+
263274
items = Ref{Ptr{Int64}}()
264275
nresults = Ref{UInt64}(k)
265-
result = C.Index_NearestNeighbors_id(rtree.index, pointer(minvalues),
266-
pointer(maxvalues), UInt32(length(minvalues)), items, nresults)
276+
result = C.Index_NearestNeighbors_id(rtree.index, minvalues,
277+
maxvalues, UInt32(rtree.ndim), items, nresults)
267278
_checkresult(result, "Index_NearestNeighbors_id: Failed to evaluate")
268279
unsafe_wrap(Array, items[], nresults[])
269280
end
@@ -300,5 +311,4 @@ module LibSpatialIndex
300311
end
301312

302313
_not_point_or_ext_error() = throw(ArgumentError("object is not a point, and does not have an extent"))
303-
304-
end # module
314+
end # module

test/runtests.jl

Lines changed: 84 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -4,72 +4,96 @@ import GeoInterface as GI
44
import LibSpatialIndex as SI
55
import Aqua
66

7-
@testset "Simple Tutorial" begin
8-
# based on https://github.com/libspatialindex/libspatialindex/wiki/Simple-Tutorial
9-
println("Testing LibSpatialIndex v$(SI.version())")
10-
props = SI.C.IndexProperty_Create()
11-
SI.C.IndexProperty_SetIndexType(props, SI.C.RT_RTree)
12-
SI.C.IndexProperty_SetIndexStorage(props, SI.C.RT_Memory)
13-
idx = SI.C.Index_Create(props)
14-
SI.C.IndexProperty_Destroy(props)
15-
@test Bool(SI.C.Index_IsValid(idx))
7+
@testset "LibSpatialIndex" begin
8+
@testset "Simple Tutorial" begin
9+
# based on https://github.com/libspatialindex/libspatialindex/wiki/Simple-Tutorial
10+
println("Testing LibSpatialIndex v$(SI.version())")
11+
props = SI.C.IndexProperty_Create()
12+
SI.C.IndexProperty_SetIndexType(props, SI.C.RT_RTree)
13+
SI.C.IndexProperty_SetIndexStorage(props, SI.C.RT_Memory)
14+
idx = SI.C.Index_Create(props)
15+
SI.C.IndexProperty_Destroy(props)
16+
@test Bool(SI.C.Index_IsValid(idx))
1617

17-
# load()
18-
min = [0.5, 0.5]
19-
max = [0.5, 0.5]
20-
SI.C.Index_InsertData(idx, 1, min, max, 2, Ptr{UInt8}(C_NULL), 0)
18+
# load()
19+
min = [0.5, 0.5]
20+
max = [0.5, 0.5]
21+
SI.C.Index_InsertData(idx, 1, min, max, 2, Ptr{UInt8}(C_NULL), 0)
2122

22-
# query()
23-
min = [0.0, 0.0]
24-
max = [1.0, 1.0]
25-
ndims = UInt32(2)
26-
nresults = Ref{UInt64}()
27-
SI.C.Index_Intersects_count(idx, min, max, ndims, nresults)
28-
@test nresults[] == 1
23+
# query()
24+
min = [0.0, 0.0]
25+
max = [1.0, 1.0]
26+
ndims = UInt32(2)
27+
nresults = Ref{UInt64}()
28+
SI.C.Index_Intersects_count(idx, min, max, ndims, nresults)
29+
@test nresults[] == 1
2930

30-
# bounds()
31-
pmins = zeros(2)
32-
pmaxs = zeros(2)
33-
ndims = Ref{UInt32}()
34-
SI.C.Index_GetBounds(idx, pointer_from_objref(pmins), pointer_from_objref(pmaxs), ndims);
35-
@test ndims[] == 2
36-
@test isapprox(pmins, [0.5, 0.5])
37-
@test isapprox(pmaxs, [0.5, 0.5])
38-
end
31+
# bounds()
32+
pmins_p = Ref{Ptr{Float64}}()
33+
pmaxs_p = Ref{Ptr{Float64}}()
34+
ndims = Ref{UInt32}()
3935

40-
@testset "Simple Operations" begin
41-
rtree = SI.RTree(2)
42-
result = SI.insert!(rtree, 1, [0.,0.], [1.,1.])
43-
@test result == SI.C.RT_None
44-
result = SI.insert!(rtree, 2, [0.,0.], [2.,2.])
45-
@test result == SI.C.RT_None
46-
@test SI.intersects(rtree, [0.,0.],[1.,1.]) == [1,2]
47-
@test SI.intersects(rtree, [0.,0.]) == [1,2]
48-
@test SI.intersects(rtree, [2.,2.]) == [2]
49-
@test SI.intersects(rtree, [1.5,1.5],[2.,2.]) == [2]
50-
@test sort(SI.knn(rtree, [2.,2.],[2.,2.], 1)) == [2]
51-
@test sort(SI.knn(rtree, [2.,2.],[2.,2.], 2)) == [1,2]
52-
@test sort(SI.knn(rtree, [2.,2.],[2.,2.], 3)) == [1,2]
53-
@test sort(SI.knn(rtree, [2.,2.], 1)) == [2]
54-
@test sort(SI.knn(rtree, [2.,2.], 2)) == [1,2]
55-
end
36+
SI.C.Index_GetBounds(idx, pmins_p, pmaxs_p, ndims);
5637

57-
@testset "GeoInterface/Extents Operations" begin
58-
rtree = SI.RTree(2)
59-
result = SI.insert!(rtree, 1, GI.Extent(X=(0.0, 1.0), Y=(0.0, 1.0)))
60-
@test result == SI.C.RT_None
38+
@test ndims[] == 2
39+
pmins = unsafe_wrap(Vector{Float64}, pmins_p[], ndims[], own=true)
40+
pmaxs = unsafe_wrap(Vector{Float64}, pmaxs_p[], ndims[], own=true)
6141

62-
polygon = GI.Polygon([GI.LinearRing([(0.0, 0.0), (0.5, 0.0), (2.0, 0.5), (0.0, 2.0), (0.0, 0.0)])])
63-
result = SI.insert!(rtree, 2, polygon)
42+
@test isapprox(pmins, [0.5, 0.5])
43+
@test isapprox(pmaxs, [0.5, 0.5])
44+
end
6445

65-
@test result == SI.C.RT_None
66-
@test SI.intersects(rtree, GI.LineString([(0.0, 0.0), (1.0, 1.0)])) == [1, 2]
67-
@test SI.intersects(rtree, GI.Point(0.0, 0.0)) == [1, 2]
68-
@test SI.intersects(rtree, (X=2.0, Y=2.0)) == [2]
69-
@test sort(SI.knn(rtree, GI.Extent(X=(2.0, 2.0), Y=(2.0, 2.0)), 1)) == [2]
70-
@test sort(SI.knn(rtree, (2.0, 2.0), 1)) == [2]
71-
end
46+
@testset "Simple Operations" begin
47+
rtree = SI.RTree(2)
48+
result = SI.insert!(rtree, 1, [0.,0.], [1.,1.])
49+
@test result == SI.C.RT_None
50+
result = SI.insert!(rtree, 2, [0.,0.], [2.,2.])
51+
@test result == SI.C.RT_None
52+
@test SI.intersects(rtree, [0.,0.],[1.,1.]) == [1,2]
53+
@test SI.intersects(rtree, [0.,0.]) == [1,2]
54+
@test SI.intersects(rtree, [2.,2.]) == [2]
55+
@test SI.intersects(rtree, [1.5,1.5],[2.,2.]) == [2]
56+
@test sort(SI.knn(rtree, [2.,2.],[2.,2.], 1)) == [2]
57+
@test sort(SI.knn(rtree, [2.,2.],[2.,2.], 2)) == [1,2]
58+
@test sort(SI.knn(rtree, [2.,2.],[2.,2.], 3)) == [1,2]
59+
@test sort(SI.knn(rtree, [2.,2.], 1)) == [2]
60+
@test sort(SI.knn(rtree, [2.,2.], 2)) == [1,2]
61+
end
62+
63+
@testset "GeoInterface/Extents Operations" begin
64+
rtree = SI.RTree(2)
65+
result = SI.insert!(rtree, 1, GI.Extent(X=(0.0, 1.0), Y=(0.0, 1.0)))
66+
@test result == SI.C.RT_None
67+
68+
polygon = GI.Polygon([GI.LinearRing([(0.0, 0.0), (0.5, 0.0), (2.0, 0.5), (0.0, 2.0), (0.0, 0.0)])])
69+
result = SI.insert!(rtree, 2, polygon)
70+
71+
@test result == SI.C.RT_None
72+
@test SI.intersects(rtree, GI.LineString([(0.0, 0.0), (1.0, 1.0)])) == [1, 2]
73+
@test SI.intersects(rtree, GI.Point(0.0, 0.0)) == [1, 2]
74+
@test SI.intersects(rtree, (X=2.0, Y=2.0)) == [2]
75+
@test sort(SI.knn(rtree, GI.Extent(X=(2.0, 2.0), Y=(2.0, 2.0)), 1)) == [2]
76+
@test sort(SI.knn(rtree, (2.0, 2.0), 1)) == [2]
77+
end
78+
79+
@testset "Bounds checks" begin
80+
# confirm that bounds checks on the Julia side are working
81+
tree = SI.RTree(2)
82+
@test_throws DimensionMismatch SI.insert!(tree, 0, [0.0], [0.0, 0.1])
83+
@test_throws DimensionMismatch SI.insert!(tree, 0, [0.0, 1.0], [0.1])
84+
# should throw even if they're the same length if they don't match dimensions
85+
@test_throws DimensionMismatch SI.insert!(tree, 0, [0.0, 0.1, 1.1], [0.0, 0.1, 1.1])
86+
87+
@test_throws DimensionMismatch SI.intersects(tree, [0.0], [0.0, 0.1])
88+
@test_throws DimensionMismatch SI.intersects(tree, [0.0, 1.0], [0.1])
89+
@test_throws DimensionMismatch SI.intersects(tree, [0.0, 0.1, 1.1], [0.0, 0.1, 1.1])
90+
91+
@test_throws DimensionMismatch SI.knn(tree, [0.0], [0.0, 0.1], 1)
92+
@test_throws DimensionMismatch SI.knn(tree, [0.0, 1.0], [0.1], 1)
93+
@test_throws DimensionMismatch SI.knn(tree, [0.0, 0.1, 1.1], [0.0, 0.1, 1.1], 1)
94+
end
7295

73-
@testset "Aqua" begin
74-
Aqua.test_all(LibSpatialIndex)
96+
@testset "Aqua" begin
97+
Aqua.test_all(LibSpatialIndex)
98+
end
7599
end

0 commit comments

Comments
 (0)