Skip to content

Commit 67a2c0d

Browse files
committed
Make unvetted size throw an error for arrays with non-1 indexing
1 parent 6e7b519 commit 67a2c0d

18 files changed

+220
-100
lines changed

base/abstractarray.jl

Lines changed: 123 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,116 @@ end
2323

2424
size{T,N}(t::AbstractArray{T,N}, d) = d <= N ? size(t)[d] : 1
2525
size{N}(x, d1::Integer, d2::Integer, dx::Vararg{Integer, N}) = (size(x, d1), size(x, d2), ntuple(k->size(x, dx[k]), Val{N})...)
26+
27+
# trait to indicate "vetted" usage for indexing that may not start at 1
28+
"""
29+
IndicesSafety
30+
31+
is an abstract-trait intended to help migrate code that assumes that
32+
array indexing starts with 1.
33+
34+
See `@safeindices`, `SafeIndices`, and `UnsafeIndices` for more information.
35+
36+
"""
37+
abstract IndicesSafety
38+
"""
39+
SafeIndices()
40+
41+
is a trait-value whose purpose is to help migrate functions to a form
42+
safe for arrays that have indexing that does not necessarily start
43+
with 1. For example, a great deal of "legacy" code uses `for i =
44+
1:size(A,d)` to iterate over dimension `d`, but this usage assumes
45+
that indexing starts with 1. (One should use `for i in indices(A, d)`
46+
instead.)
47+
48+
To help discover code that makes such assumptions, `size(A, d)` should
49+
throw an error when passed an array `A` with non-1 indexing.
50+
`SafeIndices()` can then be used to mark a call as having been
51+
"vetted" for its correctness. For example,
52+
53+
size(SafeIndices(), A, d)
54+
55+
should return the size of `A` along dimension `d` even in cases where
56+
`A` uses non-1 indexing.
57+
58+
See also @safeindices, UnsafeIndices, and IndicesSafety.
59+
"""
60+
immutable SafeIndices <: IndicesSafety end
61+
"""
62+
UnsafeIndices()
63+
64+
is used as a default value that can be used to make a call "brittle"
65+
for arrays whose indices may not start with 1. See `@safeindices` or
66+
`SafeIndices` for more information. Example:
67+
68+
trailingsize(A, n) = trailingsize(UnsafeIndices(), A, n)
69+
function trailingsize(s::IndicesSafety, A, n)
70+
sz = size(s, A, n)
71+
for i = n+1:ndims(A)
72+
sz *= size(s, A, i)
73+
end
74+
sz
75+
end
76+
77+
would make `trailingsize` by-default unsafe for non-1 arrays, forcing
78+
the user to make the call as `trailingsize(SafeIndices(), A, n)` if
79+
s/he is certain that the usage is safe.
80+
"""
81+
immutable UnsafeIndices <: IndicesSafety end
82+
83+
"""
84+
@safeindices ex
85+
86+
87+
Marks `ex` as being safe for arrays that have indexing that does not
88+
start at 1. Functions such as `size` throw errors on such arrays,
89+
unless such calls have been wrapped in `@safeindices`.
90+
91+
Internally, this macro simply rewrites such calls as
92+
`size(Base.SafeIndices(), A)`.
93+
94+
Example:
95+
96+
@safeindices function foo(args...)
97+
body
98+
end
99+
100+
will annotate all of `foo`'s calls to `length` and `size` with
101+
`SafeIndices`.
102+
"""
103+
macro safeindices(ex)
104+
esc(_safeindices(ex))
105+
end
106+
107+
function _safeindices(ex::Expr)
108+
if ex.head == :call
109+
f = ex.args[1]
110+
if f == :size || f == :length
111+
return Expr(:call, f, :(Base.SafeIndices()), ex.args[2:end]...)
112+
end
113+
end
114+
return Expr(ex.head, map(_safeindices, ex.args)...)
115+
end
116+
117+
_safeindices(arg) = arg
118+
119+
# The default is that size is safe, but array types that use non-1
120+
# indexing should specialize this to make it unsafe without
121+
# SafeIndices().
122+
size( ::IndicesSafety, A) = size(A)
123+
size( ::IndicesSafety, A::AbstractArray, d) = size(A,d) # fixme
124+
# size(s::IndicesSafety, A::AbstractArray, d) = d <= ndims(A) ? size(s, A)[d] : 1
125+
size{N}(s::IndicesSafety, A::AbstractArray, d1::Integer, d2::Integer, dx::Vararg{Integer, N}) =
126+
(size(s, A, d1), size(s, A, d2), ntuple(k->size(s, A, dx[k]), Val{N})...)
127+
26128
"""
27129
indices(A, d)
28130
29131
Returns the valid range of indices for array `A` along dimension `d`.
30132
"""
31133
function indices(A::AbstractArray, d)
32134
@_inline_meta
33-
1:size(A,d)
135+
1:size(SafeIndices(),A,d)
34136
end
35137
"""
36138
indices(A)
@@ -61,15 +163,17 @@ is `indices(A, 1)`.
61163
Calling this function is the "safe" way to write algorithms that
62164
exploit linear indexing.
63165
"""
64-
linearindices(A) = 1:length(A)
166+
linearindices(A) = 1:length(SafeIndices(), A)
65167
linearindices(A::AbstractVector) = indices1(A)
66168
eltype{T}(::Type{AbstractArray{T}}) = T
67169
eltype{T,N}(::Type{AbstractArray{T,N}}) = T
68170
elsize{T}(::AbstractArray{T}) = sizeof(T)
69171
ndims{T,N}(::AbstractArray{T,N}) = N
70172
ndims{T,N}(::Type{AbstractArray{T,N}}) = N
71173
ndims{T<:AbstractArray}(::Type{T}) = ndims(supertype(T))
72-
length(t::AbstractArray) = prod(size(t))::Int
174+
length(s::IndicesSafety, t::AbstractArray) = prod(size(s,t))
175+
length(s::IndicesSafety, t) = length(t)
176+
length(t::AbstractArray) = length(UnsafeIndices(), t)
73177
endof(a::AbstractArray) = length(a)
74178
first(a::AbstractArray) = a[first(eachindex(a))]
75179

@@ -120,13 +224,14 @@ function isassigned(a::AbstractArray, i::Int...)
120224
end
121225

122226
# used to compute "end" for last index
123-
function trailingsize(A, n)
124-
s = 1
227+
function trailingsize(s::IndicesSafety, A, n)
228+
sz = 1
125229
for i=n:ndims(A)
126-
s *= size(A,i)
230+
sz *= size(s,A,i)
127231
end
128-
return s
232+
return sz
129233
end
234+
trailingsize(A, n) = trailingsize(UnsafeIndices(), A, n)
130235

131236
## Traits for array types ##
132237

@@ -178,21 +283,22 @@ start at something different from 1), it is equivalent to `indices(A,
178283
d)`.
179284
"""
180285
shape(a, d) = shape(indicesbehavior(a), a, d)
181-
shape(::IndicesStartAt1, a) = size(a)
182-
shape(::IndicesStartAt1, a, d) = size(a, d)
286+
shape(::IndicesStartAt1, a) = size(SafeIndices(), a)
287+
shape(::IndicesStartAt1, a, d) = size(SafeIndices(), a, d)
183288
shape(::IndicesBehavior, a) = indices(a)
184289
shape(::IndicesBehavior, a, d) = indices(a, d)
185290

186291
## Bounds checking ##
187-
@generated function trailingsize{T,N,n}(A::AbstractArray{T,N}, ::Type{Val{n}})
292+
@generated function trailingsize{T,N,n}(s::IndicesSafety, A::AbstractArray{T,N}, ::Type{Val{n}})
188293
(isa(n, Int) && isa(N, Int)) || error("Must have concrete type")
189294
n > N && return 1
190-
ex = :(size(A, $n))
295+
ex = :(size(s, A, $n))
191296
for m = n+1:N
192-
ex = :($ex * size(A, $m))
297+
ex = :($ex * size(s, A, $m))
193298
end
194299
Expr(:block, Expr(:meta, :inline), ex)
195300
end
301+
trailingsize{n}(A::AbstractArray, ::Type{Val{n}}) = trailingsize(UnsafeIndices(), A, Val{n})
196302

197303
# check along a single dimension
198304
"""
@@ -265,13 +371,9 @@ _chkbnds(A::AbstractArray, ::NTuple{1,Bool}, I::AbstractVector{Bool}) = length(A
265371
_chkbnds(A::AbstractVector, ::NTuple{1,Bool}, I::AbstractArray{Bool}) = length(A) == length(I)
266372
_chkbnds(A::AbstractVector, ::NTuple{1,Bool}, I::AbstractVector{Bool}) = indices(A) == indices(I)
267373
# Linear indexing:
268-
function _chkbnds(A::AbstractVector, ::NTuple{1,Bool}, I)
269-
@_inline_meta
270-
checkindex(Bool, indices1(A), I)
271-
end
272374
function _chkbnds(A::AbstractArray, ::NTuple{1,Bool}, I)
273375
@_inline_meta
274-
checkindex(Bool, 1:length(A), I)
376+
checkindex(Bool, linearindices(A), I)
275377
end
276378
# When all indices have been checked:
277379
_chkbnds{M}(A, checked::NTuple{M,Bool}) = checked[M]
@@ -359,7 +461,7 @@ similar( a::AbstractArray, T::Type, dims::DimsInteger) = similar(a, T, convert
359461
# similar creates an Array by default
360462
similar( a::AbstractArray, T::Type, dims::Dims) = Array(T, dims)
361463

362-
_similar(::IndicesStartAt1, a::AbstractArray, T::Type) = similar(a, T, size(a))
464+
_similar(::IndicesStartAt1, a::AbstractArray, T::Type) = similar(a, T, size(SafeIndices(), a))
363465
_similar(::IndicesBehavior, a::AbstractArray, T::Type) = similar(a, T, indices(a))
364466

365467
"""
@@ -525,7 +627,7 @@ function copy!(::LinearIndexing, dest::AbstractArray, ::LinearSlow, src::Abstrac
525627
end
526628

527629
function copy!(dest::AbstractArray, dstart::Integer, src::AbstractArray)
528-
copy!(dest, dstart, src, first(linearindices(src)), length(src))
630+
copy!(dest, dstart, src, first(linearindices(src)), length(SafeIndices(), src))
529631
end
530632

531633
function copy!(dest::AbstractArray, dstart::Integer, src::AbstractArray, sstart::Integer)
@@ -650,7 +752,7 @@ function _maxlength(A, B, C...)
650752
max(length(A), _maxlength(B, C...))
651753
end
652754

653-
isempty(a::AbstractArray) = (length(a) == 0)
755+
isempty(a::AbstractArray) = (length(SafeIndices(), a) == 0)
654756

655757
## Conversions ##
656758

@@ -1427,7 +1529,7 @@ function mapslices(f, A::AbstractArray, dims::AbstractVector)
14271529
end
14281530
nextra = max(0,length(dims)-ndims(r1))
14291531
if eltype(Rsize) == Int
1430-
Rsize[dims] = [size(r1)..., ntuple(d->1, nextra)...]
1532+
Rsize[dims] = [size(SafeIndices(), r1)..., ntuple(d->1, nextra)...]
14311533
else
14321534
Rsize[dims] = [indices(r1)..., ntuple(d->1:1, nextra)...]
14331535
end

base/abstractarraymath.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ transpose(a::AbstractArray) = error("transpose not implemented for $(typeof(a)).
1111

1212
## Constructors ##
1313

14-
vec(a::AbstractArray) = reshape(a,length(a))
14+
@safeindices vec(a::AbstractArray) = reshape(a,length(a))
1515
vec(a::AbstractVector) = a
1616

1717
_sub(::Tuple{}, ::Tuple{}) = ()
@@ -69,7 +69,7 @@ function flipdim(A::AbstractVector, d::Integer)
6969
reverse(A)
7070
end
7171

72-
function flipdim(A::AbstractArray, d::Integer)
72+
@safeindices function flipdim(A::AbstractArray, d::Integer)
7373
nd = ndims(A)
7474
if d > nd || isempty(A)
7575
return copy(A)
@@ -107,7 +107,7 @@ function circshift{T,N}(a::AbstractArray{T,N}, shiftamts)
107107
end
108108

109109
# Uses K-B-N summation
110-
function cumsum_kbn{T<:AbstractFloat}(v::AbstractVector{T})
110+
@safeindices function cumsum_kbn{T<:AbstractFloat}(v::AbstractVector{T})
111111
r = similar(v)
112112
if isempty(v); return r; end
113113

base/arraymath.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ function ctranspose!(B::AbstractMatrix, A::AbstractVector)
271271
end
272272

273273
const transposebaselength=64
274-
function transpose_f!(f,B::AbstractMatrix,A::AbstractMatrix)
274+
@safeindices function transpose_f!(f,B::AbstractMatrix,A::AbstractMatrix)
275275
indices(B,1) == indices(A,2) && indices(B,2) == indices(A,1) || throw(DimensionMismatch(string(f)))
276276

277277
m, n = size(A)

base/bitarray.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ end
4747

4848
isassigned{N}(B::BitArray{N}, i::Int) = 1 <= i <= length(B)
4949

50+
linearindexing{A<:BitArray}(::Type{A}) = LinearFast()
51+
5052
## aux functions ##
5153

5254
const _msk64 = ~UInt64(0)

base/broadcast.jl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ end
6464
@inline _newindex(out, I, keep::Bool, indexmap...) = _newindex((out..., ifelse(keep, I[1], 1)), tail(I), indexmap...)
6565

6666
newindexer(sz, x::Number) = ()
67-
@inline newindexer(sz, A) = _newindexer(sz, size(A))
67+
@safeindices @inline newindexer(sz, A) = _newindexer(sz, size(A))
6868
@inline _newindexer(sz, szA::Tuple{}) = ()
6969
@inline _newindexer(sz, szA) = (sz[1] == szA[1], _newindexer(tail(sz), tail(szA))...)
7070

@@ -132,7 +132,7 @@ end
132132
end
133133
end
134134

135-
@inline function broadcast!{nargs}(f, B::AbstractArray, As::Vararg{Any,nargs})
135+
@safeindices @inline function broadcast!{nargs}(f, B::AbstractArray, As::Vararg{Any,nargs})
136136
check_broadcast_shape(shape(B), As...)
137137
sz = size(B)
138138
mapindex = map(x->newindexer(sz, x), As)
@@ -175,7 +175,7 @@ end
175175
end
176176
end
177177

178-
function broadcast_t(f, ::Type{Any}, As...)
178+
@safeindices function broadcast_t(f, ::Type{Any}, As...)
179179
shp = broadcast_shape(As...)
180180
iter = CartesianRange(shp)
181181
if isempty(iter)
@@ -218,12 +218,12 @@ end
218218
@inline bitbroadcast(f, As...) = broadcast!(f, allocate_for(BitArray, As, broadcast_shape(As...)), As...)
219219

220220
broadcast_getindex(src::AbstractArray, I::AbstractArray...) = broadcast_getindex!(Array{eltype(src)}(broadcast_shape(I...)), src, I...)
221-
@generated function broadcast_getindex!(dest::AbstractArray, src::AbstractArray, I::AbstractArray...)
221+
@safeindices @generated function broadcast_getindex!(dest::AbstractArray, src::AbstractArray, I::AbstractArray...)
222222
N = length(I)
223223
Isplat = Expr[:(I[$d]) for d = 1:N]
224224
quote
225225
@nexprs $N d->(I_d = I[d])
226-
check_broadcast_shape(size(dest), $(Isplat...)) # unnecessary if this function is never called directly
226+
check_broadcast_shape(shape(dest), $(Isplat...)) # unnecessary if this function is never called directly
227227
checkbounds(src, $(Isplat...))
228228
@nloops $N i dest d->(@nexprs $N k->(j_d_k = size(I_k, d) == 1 ? 1 : i_d)) begin
229229
@nexprs $N k->(@inbounds J_k = @nref $N I_k d->j_d_k)
@@ -233,7 +233,7 @@ broadcast_getindex(src::AbstractArray, I::AbstractArray...) = broadcast_getindex
233233
end
234234
end
235235

236-
@generated function broadcast_setindex!(A::AbstractArray, x, I::AbstractArray...)
236+
@safeindices @generated function broadcast_setindex!(A::AbstractArray, x, I::AbstractArray...)
237237
N = length(I)
238238
Isplat = Expr[:(I[$d]) for d = 1:N]
239239
quote

base/exports.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,7 @@ export
577577
rot180,
578578
rotl90,
579579
rotr90,
580+
@safeindices,
580581
searchsorted,
581582
searchsortedfirst,
582583
searchsortedlast,

0 commit comments

Comments
 (0)