Skip to content

Commit cc958cc

Browse files
Merge #1276
1276: Fix and test boundscheck in DataLayouts for empty fields r=charleskawczynski a=charleskawczynski Closes #1272. This PR at least fixes the bug (imprecise bounds checks). I plan to open an issue about whether we want empty fields to traverse the space or not-- I see no application where traversing the space would provide useable functionality. However, I could see it incurring unnecessary cost. Co-authored-by: Charles Kawczynski <kawczynski.charles@gmail.com>
2 parents cbb347b + 54ab288 commit cc958cc

File tree

2 files changed

+50
-7
lines changed

2 files changed

+50
-7
lines changed

src/DataLayouts/DataLayouts.jl

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -356,21 +356,23 @@ end
356356
end
357357

358358
@inline function slab(data::IJFH{S, Nij}, h::Integer) where {S, Nij}
359-
@boundscheck (1 <= h <= length(data)) || throw(BoundsError(data, (h,)))
359+
@boundscheck (1 <= h <= size(parent(data), 4)) ||
360+
throw(BoundsError(data, (h,)))
360361
dataview = @inbounds view(parent(data), :, :, :, h)
361362
IJF{S, Nij}(dataview)
362363
end
363364

364365
@inline function slab(data::IJFH{S, Nij}, v::Integer, h::Integer) where {S, Nij}
365-
@boundscheck (v >= 1 && 1 <= h <= length(data)) ||
366+
@boundscheck (v >= 1 && 1 <= h <= size(parent(data), 4)) ||
366367
throw(BoundsError(data, (v, h)))
367368
dataview = @inbounds view(parent(data), :, :, :, h)
368369
IJF{S, Nij}(dataview)
369370
end
370371

371372
@inline function column(data::IJFH{S, Nij}, i, j, h) where {S, Nij}
372-
@boundscheck (1 <= j <= Nij && 1 <= i <= Nij && 1 <= h <= length(data)) ||
373-
throw(BoundsError(data, (i, j, h)))
373+
@boundscheck (
374+
1 <= j <= Nij && 1 <= i <= Nij && 1 <= h <= size(parent(data), 4)
375+
) || throw(BoundsError(data, (i, j, h)))
374376
dataview = @inbounds view(parent(data), i, j, :, h)
375377
DataF{S}(dataview)
376378
end
@@ -449,14 +451,15 @@ function Base.fill!(data::IFH, val)
449451
end
450452

451453
@inline function slab(data::IFH{S, Ni}, h::Integer) where {S, Ni}
452-
@boundscheck (1 <= h <= length(data)) || throw(BoundsError(data, (h,)))
454+
@boundscheck (1 <= h <= size(parent(data), 3)) ||
455+
throw(BoundsError(data, (h,)))
453456
dataview = @inbounds view(parent(data), :, :, h)
454457
IF{S, Ni}(dataview)
455458
end
456459
Base.@propagate_inbounds slab(data::IFH, v::Integer, h::Integer) = slab(data, h)
457460

458461
@inline function column(data::IFH{S, Ni}, i, h) where {S, Ni}
459-
@boundscheck (1 <= h <= length(data) && 1 <= i <= Ni) ||
462+
@boundscheck (1 <= h <= size(parent(data), 3) && 1 <= i <= Ni) ||
460463
throw(BoundsError(data, (i, h)))
461464
dataview = @inbounds view(parent(data), i, :, h)
462465
DataF{S}(dataview)
@@ -969,7 +972,7 @@ end
969972
end
970973

971974
@inline function Base.setindex!(data::VF{S}, val, v::Integer) where {S}
972-
@boundscheck (1 <= v <= length(parent(data))) ||
975+
@boundscheck (1 <= v <= size(parent(data), 1)) ||
973976
throw(BoundsError(data, (v,)))
974977
@inbounds set_struct!(
975978
parent(data),

test/Fields/field.jl

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,46 @@ end
9494
@test axes(point_field) isa Spaces.PointSpace
9595
end
9696

97+
# Requires `--check-bounds=yes`
98+
@testset "Constructing & broadcasting over empty fields" begin
99+
FT = Float32
100+
for space in TU.all_spaces(FT)
101+
f = TU.FieldFromNamedTuple(space, (;))
102+
@. f += f
103+
end
104+
105+
function test_broken_throws(f)
106+
try
107+
@. f += 1
108+
# we want to throw exception, test is broken
109+
@test_broken false
110+
catch
111+
# we want to throw exception, unexpected pass
112+
@test_broken true
113+
end
114+
end
115+
empty_field(space) = TU.FieldFromNamedTuple(space, (;))
116+
117+
# Broadcasting over the wrong size should error
118+
test_broken_throws(empty_field(TU.PointSpace(FT)))
119+
test_broken_throws(empty_field(TU.SpectralElementSpace1D(FT)))
120+
test_broken_throws(empty_field(TU.SpectralElementSpace2D(FT)))
121+
test_broken_throws(empty_field(TU.ColumnCenterFiniteDifferenceSpace(FT)))
122+
test_broken_throws(empty_field(TU.ColumnFaceFiniteDifferenceSpace(FT)))
123+
test_broken_throws(empty_field(TU.SphereSpectralElementSpace(FT)))
124+
test_broken_throws(empty_field(TU.CenterExtrudedFiniteDifferenceSpace(FT)))
125+
test_broken_throws(empty_field(TU.FaceExtrudedFiniteDifferenceSpace(FT)))
126+
127+
# TODO: performance optimization: shouldn't we do
128+
# nothing when broadcasting over empty fields?
129+
# This is otherwise a performance penalty if
130+
# users regularly rely on empty fields. In particular:
131+
# - does iterating over empty fields load data?
132+
# - what is the overhead in iterating over empty fields?
133+
# - what is the use case of anything useful that can be
134+
# done by iterating over empty fields?
135+
end
136+
97137
@testset "Broadcasting interception for tuple-valued fields" begin
98138
n1 = n2 = 1
99139
Nij = 4

0 commit comments

Comments
 (0)