Skip to content

Commit 958ab85

Browse files
giordanonsajko
andauthored
Avoid integer overflow when constructing arrays (#26)
* Avoid integer overflow when constructing arrays * reimplement `checked_dims` instead of using Julia internals `Core.checked_dims` isn't public. * Test more constructors * weird codecov fix * One more constructor test * weird codecov fix --------- Co-authored-by: Neven Sajko <s@purelymail.com>
1 parent 02f5218 commit 958ab85

File tree

2 files changed

+47
-1
lines changed

2 files changed

+47
-1
lines changed

src/FixedSizeArrays.jl

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const FixedSizeVector{T} = FixedSizeArray{T,1}
1414
const FixedSizeMatrix{T} = FixedSizeArray{T,2}
1515

1616
function FixedSizeArray{T,N}(::UndefInitializer, size::NTuple{N,Int}) where {T,N}
17-
FixedSizeArray{T,N}(Memory{T}(undef, prod(size)), size)
17+
FixedSizeArray{T,N}(Memory{T}(undef, checked_dims(size)), size)
1818
end
1919
function FixedSizeArray{T,N}(::UndefInitializer, size::Vararg{Int,N}) where {T,N}
2020
FixedSizeArray{T,N}(undef, size)
@@ -38,6 +38,24 @@ end
3838

3939
Base.isassigned(a::FixedSizeArray, i::Int) = isassigned(a.mem, i)
4040

41+
# safe product of a tuple of integers, for calculating dimensions size
42+
43+
checked_dims_impl(a::Int, ::Tuple{}) = a
44+
function checked_dims_impl(a::Int, t::Tuple{Int,Vararg{Int,N}}) where {N}
45+
b = first(t)
46+
(m, o) = Base.Checked.mul_with_overflow(a, b)
47+
o && throw(ArgumentError("array dimensions too great, can't represent length"))
48+
r = Base.tail(t)::NTuple{N,Int}
49+
checked_dims_impl(m, r)::Int
50+
end
51+
52+
checked_dims(::Tuple{}) = 1
53+
function checked_dims(t::Tuple{Int,Vararg{Int,N}}) where {N}
54+
a = first(t)
55+
r = Base.tail(t)::NTuple{N,Int}
56+
checked_dims_impl(a, r)::Int
57+
end
58+
4159
# broadcasting
4260

4361
function Base.BroadcastStyle(::Type{<:FixedSizeArray})

test/runtests.jl

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,39 @@ using Test
22
using FixedSizeArrays
33
import Aqua
44

5+
const checked_dims = FixedSizeArrays.checked_dims
6+
7+
function allocated(f::F, args::Vararg{Any,N}) where {F,N}
8+
@allocated f(args...)
9+
end
10+
511
@testset "FixedSizeArrays" begin
612
@testset "Aqua.jl" begin
713
Aqua.test_all(FixedSizeArrays)
814
end
915

16+
@testset "Constructors" begin
17+
@test FixedSizeArray{Float64,0}(undef) isa FixedSizeArray{Float64,0}
18+
@test FixedSizeArray{Float64,0}(undef, ()) isa FixedSizeArray{Float64,0}
19+
@test_throws ArgumentError FixedSizeArray{Float64,1}(undef, typemin(Int))
20+
@test_throws ArgumentError FixedSizeArray{Float64,2}(undef, typemax(Int), typemax(Int))
21+
@test_throws ArgumentError FixedSizeArray{Float64,3}(undef, typemax(Int), typemax(Int), 2)
22+
@test_throws ArgumentError FixedSizeArray{Float64,4}(undef, typemax(Int), typemax(Int), 2, 4)
23+
end
24+
25+
@testset "safe computation of length from dimensions size" begin
26+
@test isone(checked_dims(()))
27+
for n 0:30
28+
t = Tuple(1:n)
29+
if 20 < n
30+
@test_throws ArgumentError checked_dims(t)
31+
else
32+
@test factorial(n) == prod(t) == @inferred checked_dims(t)
33+
@test iszero(allocated(checked_dims, t))
34+
end
35+
end
36+
end
37+
1038
@testset "FixedSizeVector" begin
1139
v = FixedSizeVector{Float64}(undef, 3)
1240
@test length(v) == 3

0 commit comments

Comments
 (0)