Skip to content

Commit b33bc00

Browse files
authored
Disallow overflow for empty ranges in the OffsetArray constructor (#226)
* Disallow overflow in the OffsetArray constructor for empty ranges * Overflow check in summing offsets in the nested OffsetArray constructor
1 parent ac9821e commit b33bc00

File tree

2 files changed

+59
-7
lines changed

2 files changed

+59
-7
lines changed

src/OffsetArrays.jl

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ struct OffsetArray{T,N,AA<:AbstractArray{T,N}} <: AbstractArray{T,N}
117117
end
118118
end
119119

120-
function OffsetArray{T, N, AA}(parent::AA, offsets::NTuple{N, <:Integer}) where {T, N, AA<:AbstractArray{T,N}}
120+
function OffsetArray{T, N, AA}(parent::AA, offsets::NTuple{N, Integer}) where {T, N, AA<:AbstractArray{T,N}}
121121
OffsetArray{T, N, AA}(parent, map(x -> convert(Int, x)::Int, offsets))
122122
end
123123

@@ -135,16 +135,34 @@ Type alias and convenience constructor for two-dimensional [`OffsetArray`](@ref)
135135
"""
136136
const OffsetMatrix{T,AA<:AbstractMatrix{T}} = OffsetArray{T,2,AA}
137137

138-
function overflow_check(r, offset::T) where T
138+
# checks if the offset may be added to the range without overflowing
139+
function overflow_check(r::AbstractUnitRange{T}, offset::T) where T<:Integer
139140
# This gives some performance boost https://github.com/JuliaLang/julia/issues/33273
140-
throw_upper_overflow_error() = throw(OverflowError("Boundary overflow detected: offset should be <= $(typemax(T) - last(r)) for offsets of type $T, received $offset"))
141-
throw_lower_overflow_error() = throw(OverflowError("Boundary overflow detected: offset should be >= $(typemin(T) - first(r)) for offsets of type $T, received $offset"))
141+
throw_upper_overflow_error(val) = throw(OverflowError("offset should be <= $(typemax(T) - val) corresponding to the axis $r, received an offset $offset"))
142+
throw_lower_overflow_error(val) = throw(OverflowError("offset should be >= $(typemin(T) - val) corresponding to the axis $r, received an offset $offset"))
142143

143-
if offset > 0 && last(r) > typemax(T) - offset
144+
# With ranges in the picture, first(r) might not necessarily be < last(r)
145+
# we therefore use the min and max of first(r) and last(r) to check for overflow
146+
firstlast_min, firstlast_max = minmax(first(r), last(r))
147+
148+
if offset > 0 && firstlast_max > typemax(T) - offset
149+
throw_upper_overflow_error(firstlast_max)
150+
elseif offset < 0 && firstlast_min < typemin(T) - offset
151+
throw_lower_overflow_error(firstlast_min)
152+
end
153+
return nothing
154+
end
155+
# checks if the two offsets may be added together without overflowing
156+
function overflow_check(offset1::T, offset2::T) where {T<:Integer}
157+
throw_upper_overflow_error() = throw(OverflowError("offset should be <= $(typemax(eltype(offset1)) - offset1) given a pre-existing offset of $offset1, received an offset $offset2"))
158+
throw_lower_overflow_error() = throw(OverflowError("offset should be >= $(typemin(eltype(offset1)) - offset1) given a pre-existing offset of $offset1, received an offset $offset2"))
159+
160+
if offset1 > 0 && offset2 > typemax(T) - offset1
144161
throw_upper_overflow_error()
145-
elseif offset < 0 && first(r) < typemin(T) - offset
162+
elseif offset1 < 0 && offset2 < typemin(T) - offset1
146163
throw_lower_overflow_error()
147164
end
165+
return nothing
148166
end
149167

150168
# Tuples of integers are treated as offsets
@@ -170,10 +188,16 @@ end
170188
## OffsetArray constructors
171189
for FT in (:OffsetArray, :OffsetVector, :OffsetMatrix)
172190
# Nested OffsetArrays may strip off the wrapper and collate the offsets
173-
@eval @inline function $FT(A::OffsetArray, offsets::Tuple{Vararg{Integer}})
191+
# empty tuples are handled here
192+
@eval @inline function $FT(A::OffsetArray, offsets::Tuple{Vararg{Int}})
174193
_checkindices(A, offsets, "offsets")
194+
# ensure that the offsets may be added together without an overflow
195+
foreach(overflow_check, A.offsets, offsets)
175196
$FT(parent(A), map(+, A.offsets, offsets))
176197
end
198+
@eval @inline function $FT(A::OffsetArray, offsets::Tuple{Integer,Vararg{Integer}})
199+
$FT(A, map(x -> convert(Int, x)::Int, offsets))
200+
end
177201

178202
# In general, indices get converted to AbstractUnitRanges.
179203
# CartesianIndices{N} get converted to N ranges

test/runtests.jl

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,14 @@ Base.convert(::Type{Int}, a::WeirdInteger) = a
321321
@test axes(a) === (IdOffsetRange(Base.OneTo(4), 0), )
322322
end
323323

324+
# nested OffsetVectors
325+
for inds in Any[offsets, offsets_big]
326+
a = OffsetVector{Float64}(undef, inds)
327+
b = OffsetVector(a, inds); b2 = OffsetVector(a, inds...);
328+
@test eltype(b) === eltype(b2) === Float64
329+
@test axes(b, 1) === axes(b2, 1) === IdOffsetRange(Base.OneTo(4), 4)
330+
end
331+
324332
# offset indexing
325333
for inds in offset_axes
326334
# test offsets
@@ -385,6 +393,8 @@ Base.convert(::Type{Int}, a::WeirdInteger) = a
385393
@test_nowarn OffsetArray{Float64, 1, typeof(ao)}(ao, (-1, ))
386394
@test_throws OverflowError OffsetArray{Float64, 1, typeof(ao)}(ao, (-2, )) # inner Constructor
387395
@test_throws OverflowError OffsetArray(ao, (-2, )) # convinient constructor accumulate offsets
396+
@test_throws OverflowError OffsetVector(1:0, typemax(Int))
397+
@test_throws OverflowError OffsetVector(OffsetVector(1:0, 0), typemax(Int))
388398

389399
@testset "OffsetRange" begin
390400
local r = 1:100
@@ -471,6 +481,15 @@ Base.convert(::Type{Int}, a::WeirdInteger) = a
471481
end
472482
@test_throws Union{ArgumentError, ErrorException} OffsetMatrix{Float64}(undef, 2, -2) # only positive numbers works
473483

484+
# nested OffsetMatrices
485+
for inds in Any[offsets, offsets_big]
486+
a = OffsetMatrix{Float64}(undef, inds)
487+
b = OffsetMatrix(a, inds); b2 = OffsetMatrix(a, inds...);
488+
@test eltype(b) === eltype(b2) === Float64
489+
@test axes(b, 1) === axes(b2, 1) === IdOffsetRange(Base.OneTo(4), 4)
490+
@test axes(b, 2) === axes(b2, 2) === IdOffsetRange(Base.OneTo(3), 3)
491+
end
492+
474493
for inds in offset_axes
475494
# test offsets
476495
a = OffsetMatrix{Float64}(undef, inds)
@@ -578,6 +597,15 @@ Base.convert(::Type{Int}, a::WeirdInteger) = a
578597
@test OffsetArray(a, 0:1, :, CartesianIndices(()), 2:3) == OffsetArray(a, 0:1, :, 2:3)
579598
@test OffsetArray(a, 0:1, :, 2:3, CartesianIndices(())) == OffsetArray(a, 0:1, :, 2:3)
580599

600+
# nested OffsetArrays
601+
for offsets in [(1,1,1), big.((1,1,1))]
602+
ob = OffsetArray(oa, offsets); ob2 = OffsetArray(oa, offsets...);
603+
@test eltype(ob) === eltype(ob2) === Float64
604+
@test axes(ob, 1) === axes(ob2, 1) === IdOffsetRange(Base.OneTo(2), 0)
605+
@test axes(ob, 2) === axes(ob2, 2) === IdOffsetRange(Base.OneTo(2), 3)
606+
@test axes(ob, 3) === axes(ob2, 3) === IdOffsetRange(Base.OneTo(2), 2)
607+
end
608+
581609
indices = (-1:1, -7:7, -1:2, -5:5, -1:1, -3:3, -2:2, -1:1)
582610
y = OffsetArray{Float64}(undef, indices...);
583611
@test axes(y) === axes(OffsetArray{Float64}(undef, indices))

0 commit comments

Comments
 (0)