From a3dfdbd91cd96a2c123bb60b80f8a0abbfdb9579 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Mon, 10 Jan 2022 19:24:26 +0900 Subject: [PATCH] don't capture arrays/tuples in `BoundsError` This commit changes the semantics of `BoundsError` so that it doesn't capture arrays/tuples passed to its constructor. The change wouldn't improve any performance by itself because the `BoundsError` constructor is mostly called on error paths and thus it is opaque to analyses/optimizers when compiling a caller context. Rather it's supposed to be a building block for broadening the possibility of certain memory optimizations in the future such as copy elision for `ImmutableArray` construction and stack allocation of `Array`s, by allowing us to assume the invariant that primitive indexing operations into array/tuple indexing don't escape it so that our escape analyses can achieve further accuracy. Specifically, when `BoundsError` constructor will now compute the "summary" of its `Array`/`Tuple` argument, rather than capturing it for the later inspection. Assuming `Base.summary(x::Array)` or `Base.summary(x::Tuple)` don't escape `x`, we can assume that `BoundsError` doesn't escape `x`. This change won't appear as breaking in most cases since `showerror` will print the exact same error message as before, but obviously this is technically breaking since we can no longer access to the original arrays/tuples by catching `BoundsError`. I'd say this breaking semantic change would be still acceptable, since I think it's enough if we can know size/type of arrays/tuples from `BoundsError` in most cases. As a last note, I didn't change the semantics for arbitrary user objects, since we still don't have a good infrastructure to tell the compiler some primitive assumptions/rules about user type objects anyway. --- base/Base.jl | 3 ++ base/abstractarray.jl | 20 +++++------ base/abstractarraymath.jl | 2 +- base/array.jl | 10 +++--- base/bitarray.jl | 44 ++++++++++++------------- base/boot.jl | 24 +++++++++++--- base/char.jl | 6 ++-- base/combinatorics.jl | 8 ++--- base/compiler/ssair/ir.jl | 24 +++++++------- base/errorshow.jl | 31 ++++++++++++++++- base/essentials.jl | 2 +- base/exports.jl | 1 + base/iobuffer.jl | 2 +- base/meta.jl | 2 +- base/number.jl | 12 +++---- base/reflection.jl | 4 +-- base/regex.jl | 2 +- base/show.jl | 1 + base/strings/basic.jl | 14 ++++---- base/strings/search.jl | 22 ++++++------- base/strings/string.jl | 8 ++--- base/tuple.jl | 9 ++--- doc/src/manual/interfaces.md | 2 +- doc/src/manual/metaprogramming.md | 2 +- src/codegen.cpp | 7 ++++ src/datatype.c | 2 +- src/jl_exported_funcs.inc | 1 + src/julia.h | 3 ++ src/rtutils.c | 39 +++++++++++++++++++--- stdlib/LibGit2/src/status.jl | 2 +- stdlib/LinearAlgebra/src/generic.jl | 4 +-- stdlib/LinearAlgebra/src/lq.jl | 2 +- stdlib/LinearAlgebra/src/matmul.jl | 8 ++--- stdlib/SharedArrays/src/SharedArrays.jl | 2 +- stdlib/Sockets/src/IPAddr.jl | 2 +- test/arrayops.jl | 2 +- test/boundscheck_exec.jl | 2 +- test/core.jl | 34 +++++++++++++++++-- test/errorshow.jl | 2 +- test/sorting.jl | 2 +- 40 files changed, 244 insertions(+), 125 deletions(-) diff --git a/base/Base.jl b/base/Base.jl index cbfa5ede6aef93..09c3128cf2611b 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -57,6 +57,9 @@ modifyproperty!(x, f::Symbol, op, v, order::Symbol=:notatomic) = replaceproperty!(x, f::Symbol, expected, desired, success_order::Symbol=:notatomic, fail_order::Symbol=success_order) = (@inline; Core.replacefield!(x, f, expected, convert(fieldtype(typeof(x), f), desired), success_order, fail_order)) +throw_boundserror(a, i) = (@noinline; throw(BoundsError(a, i))) +throw_boundserror() = (@noinline; throw(BoundsError())) + convert(::Type{Any}, Core.@nospecialize x) = x convert(::Type{T}, x::T) where {T} = x include("coreio.jl") diff --git a/base/abstractarray.jl b/base/abstractarray.jl index 9c3cb23865dffb..cc8b50f9eb491f 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -700,8 +700,6 @@ end checkbounds_indices(::Type{Bool}, IA::Tuple, ::Tuple{}) = (@inline; all(x->length(x)==1, IA)) checkbounds_indices(::Type{Bool}, ::Tuple{}, ::Tuple{}) = true -throw_boundserror(A, I) = (@noinline; throw(BoundsError(A, I))) - # check along a single dimension """ checkindex(Bool, inds::AbstractUnitRange, index) @@ -956,7 +954,7 @@ function copyto!(dest::AbstractArray, dstart::Integer, src, sstart::Integer, n:: if (dstart ∉ inds || dmax ∉ inds) | (sstart < 1) sstart < 1 && throw(ArgumentError(LazyString("source start offset (", sstart,") is < 1"))) - throw(BoundsError(dest, dstart:dmax)) + throw_boundserror(dest, dstart:dmax) end y = iterate(src) for j = 1:(sstart-1) @@ -974,7 +972,7 @@ function copyto!(dest::AbstractArray, dstart::Integer, src, sstart::Integer, n:: y = iterate(src, st) i += 1 end - i <= dmax && throw(BoundsError(dest, i)) + i <= dmax && throw_boundserror(dest, i) return dest end @@ -1027,7 +1025,7 @@ function copyto_unaliased!(deststyle::IndexStyle, dest::AbstractArray, srcstyle: idf, isf = first(destinds), first(srcinds) Δi = idf - isf (checkbounds(Bool, destinds, isf+Δi) & checkbounds(Bool, destinds, last(srcinds)+Δi)) || - throw(BoundsError(dest, srcinds)) + throw_boundserror(dest, srcinds) if deststyle isa IndexLinear if srcstyle isa IndexLinear # Single-index implementation @@ -1067,7 +1065,7 @@ end function copyto!(dest::AbstractArray, dstart::Integer, src::AbstractArray, sstart::Integer) srcinds = LinearIndices(src) - checkbounds(Bool, srcinds, sstart) || throw(BoundsError(src, sstart)) + checkbounds(Bool, srcinds, sstart) || throw_boundserror(src, sstart) copyto!(dest, dstart, src, sstart, last(srcinds)-sstart+1) end @@ -1078,8 +1076,8 @@ function copyto!(dest::AbstractArray, dstart::Integer, n < 0 && throw(ArgumentError(LazyString("tried to copy n=", n," elements, but n should be nonnegative"))) destinds, srcinds = LinearIndices(dest), LinearIndices(src) - (checkbounds(Bool, destinds, dstart) && checkbounds(Bool, destinds, dstart+n-1)) || throw(BoundsError(dest, dstart:dstart+n-1)) - (checkbounds(Bool, srcinds, sstart) && checkbounds(Bool, srcinds, sstart+n-1)) || throw(BoundsError(src, sstart:sstart+n-1)) + (checkbounds(Bool, destinds, dstart) && checkbounds(Bool, destinds, dstart+n-1)) || throw_boundserror(dest, dstart:dstart+n-1) + (checkbounds(Bool, srcinds, sstart) && checkbounds(Bool, srcinds, sstart+n-1)) || throw_boundserror(src, sstart:sstart+n-1) @inbounds for i = 0:(n-1) dest[dstart+i] = src[sstart+i] end @@ -1306,7 +1304,7 @@ _to_subscript_indices(A, J::Tuple, Jrem::Tuple) = J # already bounds-checked, sa _to_subscript_indices(A::AbstractArray{T,N}, I::Vararg{Int,N}) where {T,N} = I _remaining_size(::Tuple{Any}, t::Tuple) = t _remaining_size(h::Tuple, t::Tuple) = (@inline; _remaining_size(tail(h), tail(t))) -_unsafe_ind2sub(::Tuple{}, i) = () # _ind2sub may throw(BoundsError()) in this case +_unsafe_ind2sub(::Tuple{}, i) = () # _ind2sub may throw_boundserror() in this case _unsafe_ind2sub(sz, i) = (@inline; _ind2sub(sz, i)) ## Setindex! is defined similarly. We first dispatch to an internal _setindex! @@ -2667,7 +2665,7 @@ nextL(L, r::Slice) = L*length(r.indices) offsetin(i, l::Integer) = i-1 offsetin(i, r::AbstractUnitRange) = i-first(r) -_ind2sub(::Tuple{}, ind::Integer) = (@inline; ind == 1 ? () : throw(BoundsError())) +_ind2sub(::Tuple{}, ind::Integer) = (@inline; ind == 1 ? () : throw_boundserror()) _ind2sub(dims::DimsInteger, ind::Integer) = (@inline; _ind2sub_recurse(dims, ind-1)) _ind2sub(inds::Indices, ind::Integer) = (@inline; _ind2sub_recurse(inds, ind-1)) _ind2sub(inds::Indices{1}, ind::Integer) = @@ -3163,7 +3161,7 @@ function _keepat!(a::AbstractVector, inds) end function _keepat!(a::AbstractVector, m::AbstractVector{Bool}) - length(m) == length(a) || throw(BoundsError(a, m)) + length(m) == length(a) || throw_boundserror(a, m) j = firstindex(a) for i in eachindex(a, m) @inbounds begin diff --git a/base/abstractarraymath.jl b/base/abstractarraymath.jl index 9690fc0f2e4c4b..f595b90670a94d 100644 --- a/base/abstractarraymath.jl +++ b/base/abstractarraymath.jl @@ -253,7 +253,7 @@ julia> selectdim(A, 2, 3:4) @noinline function _selectdim(A, d, i, idxs) d >= 1 || throw(ArgumentError("dimension must be ≥ 1, got $d")) nd = ndims(A) - d > nd && (i == 1 || throw(BoundsError(A, (ntuple(Returns(Colon()),d-1)..., i)))) + d > nd && (i == 1 || throw_boundserror(A, (ntuple(Returns(Colon()),d-1)..., i))) return view(A, idxs...) end diff --git a/base/array.jl b/base/array.jl index b8ad7e137f25ef..618f26b920b87f 100644 --- a/base/array.jl +++ b/base/array.jl @@ -326,7 +326,7 @@ function _copyto_impl!(dest::Array, doffs::Integer, src::Array, soffs::Integer, n == 0 && return dest n > 0 || _throw_argerror() if soffs < 1 || doffs < 1 || soffs+n-1 > length(src) || doffs+n-1 > length(dest) - throw(BoundsError()) + throw_boundserror() end unsafe_copyto!(dest, doffs, src, soffs, n) return dest @@ -1568,7 +1568,7 @@ function _deleteat!(a::Vector, inds, dltd=Nowhere()) if i < q throw(ArgumentError("indices must be unique and sorted")) else - throw(BoundsError()) + throw_boundserror() end end while q < i @@ -1589,7 +1589,7 @@ end # Simpler and more efficient version for logical indexing function deleteat!(a::Vector, inds::AbstractVector{Bool}) n = length(a) - length(inds) == n || throw(BoundsError(a, inds)) + length(inds) == n || throw_boundserror(a, inds) p = 1 for (q, i) in enumerate(inds) _copy_item!(a, p, q) @@ -1864,9 +1864,9 @@ function reverse!(v::AbstractVector, start::Integer, stop::Integer=lastindex(v)) liv = LinearIndices(v) if n <= s # empty case; ok elseif !(first(liv) ≤ s ≤ last(liv)) - throw(BoundsError(v, s)) + throw_boundserror(v, s) elseif !(first(liv) ≤ n ≤ last(liv)) - throw(BoundsError(v, n)) + throw_boundserror(v, n) end r = n @inbounds for i in s:div(s+n-1, 2) diff --git a/base/bitarray.jl b/base/bitarray.jl index 33e27155720182..ea267fa5b021dd 100644 --- a/base/bitarray.jl +++ b/base/bitarray.jl @@ -434,7 +434,7 @@ function one(x::BitMatrix) end function copyto!(dest::BitArray, src::BitArray) - length(src) > length(dest) && throw(BoundsError(dest, length(dest)+1)) + length(src) > length(dest) && throw_boundserror(dest, length(dest)+1) destc = dest.chunks; srcc = src.chunks nc = min(length(destc), length(srcc)) nc == 0 && return dest @@ -462,15 +462,15 @@ copyto!(dest::BitArray, doffs::Integer, src::Array, soffs::Integer, n::Integer) _copyto_int!(dest, Int(doffs), src, Int(soffs), Int(n)) function _copyto_int!(dest::BitArray, doffs::Int, src::Array, soffs::Int, n::Int) n == 0 && return dest - soffs < 1 && throw(BoundsError(src, soffs)) - doffs < 1 && throw(BoundsError(dest, doffs)) - soffs+n-1 > length(src) && throw(BoundsError(src, length(src)+1)) - doffs+n-1 > length(dest) && throw(BoundsError(dest, length(dest)+1)) + soffs < 1 && throw_boundserror(src, soffs) + doffs < 1 && throw_boundserror(dest, doffs) + soffs+n-1 > length(src) && throw_boundserror(src, length(src)+1) + doffs+n-1 > length(dest) && throw_boundserror(dest, length(dest)+1) return unsafe_copyto!(dest, doffs, src, soffs, n) end function copyto!(dest::BitArray, src::Array) - length(src) > length(dest) && throw(BoundsError(dest, length(dest)+1)) + length(src) > length(dest) && throw_boundserror(dest, length(dest)+1) length(src) == 0 && return dest return unsafe_copyto!(dest, 1, src, 1, length(src)) end @@ -811,7 +811,7 @@ resize!(B::BitVector, n::Integer) = _resize_int!(B, Int(n)) function _resize_int!(B::BitVector, n::Int) n0 = length(B) n == n0 && return B - n >= 0 || throw(BoundsError(B, n)) + n >= 0 || throw_boundserror(B, n) if n < n0 deleteat!(B, n+1:n0) return B @@ -888,7 +888,7 @@ insert!(B::BitVector, i::Integer, item) = _insert_int!(B, Int(i), item) function _insert_int!(B::BitVector, i::Int, item) i = Int(i) n = length(B) - 1 <= i <= n+1 || throw(BoundsError(B, i)) + 1 <= i <= n+1 || throw_boundserror(B, i) item = convert(Bool, item) Bc = B.chunks @@ -950,7 +950,7 @@ function deleteat!(B::BitVector, i::Integer) i isa Bool && depwarn("passing Bool as an index is deprecated", :deleteat!) i = Int(i) n = length(B) - 1 <= i <= n || throw(BoundsError(B, i)) + 1 <= i <= n || throw_boundserror(B, i) return _deleteat!(B, i) end @@ -959,8 +959,8 @@ function deleteat!(B::BitVector, r::AbstractUnitRange{Int}) n = length(B) i_f = first(r) i_l = last(r) - 1 <= i_f || throw(BoundsError(B, i_f)) - i_l <= n || throw(BoundsError(B, n+1)) + 1 <= i_f || throw_boundserror(B, i_f) + i_l <= n || throw_boundserror(B, n+1) Bc = B.chunks new_l = length(B) - length(r) @@ -997,7 +997,7 @@ function deleteat!(B::BitVector, inds) if !(q <= i <= n) i isa Bool && throw(ArgumentError("invalid index $i of type Bool")) i < q && throw(ArgumentError("indices must be unique and sorted")) - throw(BoundsError(B, i)) + throw_boundserror(B, i) end new_l -= 1 if i > q @@ -1023,7 +1023,7 @@ function deleteat!(B::BitVector, inds) end function deleteat!(B::BitVector, inds::AbstractVector{Bool}) - length(inds) == length(B) || throw(BoundsError(B, inds)) + length(inds) == length(B) || throw_boundserror(B, inds) n = new_l = length(B) y = findfirst(inds) @@ -1073,7 +1073,7 @@ function splice!(B::BitVector, i::Integer) i isa Bool && depwarn("passing Bool as an index is deprecated", :splice!) i = Int(i) n = length(B) - 1 <= i <= n || throw(BoundsError(B, i)) + 1 <= i <= n || throw_boundserror(B, i) v = B[i] # TODO: change to a copy if/when subscripting becomes an ArrayView _deleteat!(B, i) @@ -1090,8 +1090,8 @@ end function _splice_int!(B::BitVector, r, ins) n = length(B) i_f, i_l = first(r), last(r) - 1 <= i_f <= n+1 || throw(BoundsError(B, i_f)) - i_l <= n || throw(BoundsError(B, n+1)) + 1 <= i_f <= n+1 || throw_boundserror(B, i_f) + i_l <= n || throw_boundserror(B, n+1) Bins = convert(BitArray, ins) @@ -1471,7 +1471,7 @@ end # returns the index of the next true element, or nothing if all false function findnext(B::BitArray, start::Integer) start = Int(start) - start > 0 || throw(BoundsError(B, start)) + start > 0 || throw_boundserror(B, start) start > length(B) && return nothing unsafe_bitfindnext(B.chunks, start) end @@ -1480,7 +1480,7 @@ end # aux function: same as findnext(~B, start), but performed without temporaries function findnextnot(B::BitArray, start::Int) - start > 0 || throw(BoundsError(B, start)) + start > 0 || throw_boundserror(B, start) start > length(B) && return nothing Bc = B.chunks @@ -1528,7 +1528,7 @@ function _findnext_int(testf::Function, B::BitArray, start::Int) !f0 && f1 && return findnext(B, start) f0 && !f1 && return findnextnot(B, start) - start > 0 || throw(BoundsError(B, start)) + start > 0 || throw_boundserror(B, start) start > length(B) && return nothing f0 && f1 && return start return nothing # last case: !f0 && !f1 @@ -1557,14 +1557,14 @@ end function findprev(B::BitArray, start::Integer) start = Int(start) start > 0 || return nothing - start > length(B) && throw(BoundsError(B, start)) + start > length(B) && throw_boundserror(B, start) unsafe_bitfindprev(B.chunks, start) end function findprevnot(B::BitArray, start::Int) start = Int(start) start > 0 || return nothing - start > length(B) && throw(BoundsError(B, start)) + start > length(B) && throw_boundserror(B, start) Bc = B.chunks @@ -1605,7 +1605,7 @@ function _findprev_int(testf::Function, B::BitArray, start::Int) f0 && !f1 && return findprevnot(B, start) start > 0 || return nothing - start > length(B) && throw(BoundsError(B, start)) + start > length(B) && throw_boundserror(B, start) f0 && f1 && return start return nothing # last case: !f0 && !f1 end diff --git a/base/boot.jl b/base/boot.jl index 90322b69a54d97..191073f301e409 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -269,14 +269,30 @@ end macro inline() Expr(:meta, :inline) end macro noinline() Expr(:meta, :noinline) end - +import .Intrinsics: ult_int +struct Summarized + axes #=::Union{Base.OneTo, Integer}=# + type::Type + function Summarized(a) + isa(a, Array) || return a + if isdefined(Main, :Base) + return new(Main.Base.axes(a), typeof(a)) + else + return new(nothing, typeof(a)) + end + end + Summarized(ax, typ) = new(ax, typ) +end struct BoundsError <: Exception - a::Any + a i::Any BoundsError() = new() - BoundsError(@nospecialize(a)) = (@noinline; new(a)) - BoundsError(@nospecialize(a), i) = (@noinline; new(a,i)) + BoundsError(@nospecialize(a)) = (@noinline; new(Summarized(a))) + BoundsError(@nospecialize(a), i) = (@noinline; new(Summarized(a), i)) + BoundsError(ax, typ::Type) = (@noinline; new(Summarized((ax,), typ))) + BoundsError(ax, typ::Type, i) = (@noinline; new(Summarized((ax,), typ), i)) end + struct DivideError <: Exception end struct OutOfMemoryError <: Exception end struct ReadOnlyMemoryError <: Exception end diff --git a/base/char.jl b/base/char.jl index c8b1c28166bbf9..4f11b83e93edee 100644 --- a/base/char.jl +++ b/base/char.jl @@ -192,7 +192,7 @@ typemax(::Type{Char}) = bitcast(Char, typemax(UInt32)) typemin(::Type{Char}) = bitcast(Char, typemin(UInt32)) size(c::AbstractChar) = () -size(c::AbstractChar, d::Integer) = d < 1 ? throw(BoundsError()) : 1 +size(c::AbstractChar, d::Integer) = d < 1 ? throw_boundserror() : 1 ndims(c::AbstractChar) = 0 ndims(::Type{<:AbstractChar}) = 0 length(c::AbstractChar) = 1 @@ -200,8 +200,8 @@ IteratorSize(::Type{Char}) = HasShape{0}() firstindex(c::AbstractChar) = 1 lastindex(c::AbstractChar) = 1 getindex(c::AbstractChar) = c -getindex(c::AbstractChar, i::Integer) = i == 1 ? c : throw(BoundsError()) -getindex(c::AbstractChar, I::Integer...) = all(x -> x == 1, I) ? c : throw(BoundsError()) +getindex(c::AbstractChar, i::Integer) = i == 1 ? c : throw_boundserror() +getindex(c::AbstractChar, I::Integer...) = all(x -> x == 1, I) ? c : throw_boundserror() first(c::AbstractChar) = c last(c::AbstractChar) = c eltype(::Type{T}) where {T<:AbstractChar} = T diff --git a/base/combinatorics.jl b/base/combinatorics.jl index 2dd69fbce4c42b..c6af637ef088cf 100644 --- a/base/combinatorics.jl +++ b/base/combinatorics.jl @@ -97,8 +97,8 @@ isperm(P::Any32) = _isperm(P) function swapcols!(a::AbstractMatrix, i, j) i == j && return cols = axes(a,2) - @boundscheck i in cols || throw(BoundsError(a, (:,i))) - @boundscheck j in cols || throw(BoundsError(a, (:,j))) + @boundscheck i in cols || throw_boundserror(a, (:,i)) + @boundscheck j in cols || throw_boundserror(a, (:,j)) for k in axes(a,1) @inbounds a[k,i],a[k,j] = a[k,j],a[k,i] end @@ -108,8 +108,8 @@ end function swaprows!(a::AbstractMatrix, i, j) i == j && return rows = axes(a,1) - @boundscheck i in rows || throw(BoundsError(a, (:,i))) - @boundscheck j in rows || throw(BoundsError(a, (:,j))) + @boundscheck i in rows || throw_boundserror(a, (:,i)) + @boundscheck j in rows || throw_boundserror(a, (:,j)) for k in axes(a,2) @inbounds a[i,k],a[j,k] = a[j,k],a[i,k] end diff --git a/base/compiler/ssair/ir.jl b/base/compiler/ssair/ir.jl index 039d557b2e9e8a..cb8d1169ba272b 100644 --- a/base/compiler/ssair/ir.jl +++ b/base/compiler/ssair/ir.jl @@ -413,38 +413,38 @@ function setindex!(x::UseRef, @nospecialize(v)) rhs = stmt.args[2] if isa(rhs, Expr) if is_relevant_expr(rhs) - x.op > length(rhs.args) && throw(BoundsError()) + x.op > length(rhs.args) && throw_boundserror() rhs.args[x.op] = v return v end end - x.op == 1 || throw(BoundsError()) + x.op == 1 || throw_boundserror() stmt.args[2] = v elseif isa(stmt, Expr) # @assert is_relevant_expr(stmt) - x.op > length(stmt.args) && throw(BoundsError()) + x.op > length(stmt.args) && throw_boundserror() stmt.args[x.op] = v elseif isa(stmt, GotoIfNot) - x.op == 1 || throw(BoundsError()) + x.op == 1 || throw_boundserror() x.stmt = GotoIfNot(v, stmt.dest) elseif isa(stmt, ReturnNode) - x.op == 1 || throw(BoundsError()) + x.op == 1 || throw_boundserror() x.stmt = typeof(stmt)(v) elseif isa(stmt, UpsilonNode) - x.op == 1 || throw(BoundsError()) + x.op == 1 || throw_boundserror() x.stmt = typeof(stmt)(v) elseif isa(stmt, PiNode) - x.op == 1 || throw(BoundsError()) + x.op == 1 || throw_boundserror() x.stmt = typeof(stmt)(v, stmt.typ) elseif isa(stmt, PhiNode) - x.op > length(stmt.values) && throw(BoundsError()) - isassigned(stmt.values, x.op) || throw(BoundsError()) + x.op > length(stmt.values) && throw_boundserror() + isassigned(stmt.values, x.op) || throw_boundserror() stmt.values[x.op] = v elseif isa(stmt, PhiCNode) - x.op > length(stmt.values) && throw(BoundsError()) - isassigned(stmt.values, x.op) || throw(BoundsError()) + x.op > length(stmt.values) && throw_boundserror() + isassigned(stmt.values, x.op) || throw_boundserror() stmt.values[x.op] = v else - throw(BoundsError()) + throw_boundserror() end return x end diff --git a/base/errorshow.jl b/base/errorshow.jl index e56a095d832fde..ad35a9787096f2 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -40,7 +40,36 @@ function showerror(io::IO, ex::BoundsError) print(io, "BoundsError") if isdefined(ex, :a) print(io, ": attempt to access ") - summary(io, ex.a) + a = ex.a + if isa(a, Core.Summarized) + typ = a.type + ax = a.axes + if typ <: Tuple + # We could give more info here, since we have a.axes, but this is the current behavior. + print(io, typ) + elseif typ <: AbstractString + ax = ax[1] + print(io, ax, "-codeunit ", typ) + elseif isa(ax, Tuple) + if length(ax) == 1 + ax = ax[1] + isa(ax, OneTo) && (ax = ax.stop) + isa(ax, Int) && print(io, ax, "-element ", typ) + else + for i = 1:length(ax) + if i == length(ax) + print(io, ax[i].stop, " ", typ) + else + print(io, ax[i].stop, "x") + end + end + end + else + print(io, typ) + end + else + summary(io, a) + end if isdefined(ex, :i) print(io, " at index [") if ex.i isa AbstractRange diff --git a/base/essentials.jl b/base/essentials.jl index b837b556ed9107..a096fe143a6909 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -607,7 +607,7 @@ end function getindex(v::SimpleVector, i::Int) @boundscheck if !(1 <= i <= length(v)) - throw(BoundsError(v,i)) + throw_boundserror(v,i) end return ccall(:jl_svec_ref, Any, (Any, Int), v, i - 1) end diff --git a/base/exports.jl b/base/exports.jl index 2d790f16b79861..8ee4f49d463556 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -446,6 +446,7 @@ export sum!, sum, to_indices, + throw_boundserror, vcat, vec, view, diff --git a/base/iobuffer.jl b/base/iobuffer.jl index e08a019d84a2ca..0d8e8170303417 100644 --- a/base/iobuffer.jl +++ b/base/iobuffer.jl @@ -196,7 +196,7 @@ function read_sub(from::GenericIOBuffer, a::AbstractArray{T}, offs, nel) where T require_one_based_indexing(a) from.readable || _throw_not_readable() if offs+nel-1 > length(a) || offs < 1 || nel < 0 - throw(BoundsError()) + throw_boundserror() end if isbitstype(T) && isa(a,Array) nb = UInt(nel * sizeof(T)) diff --git a/base/meta.jl b/base/meta.jl index fcf66a7a787b29..e617c02f289079 100644 --- a/base/meta.jl +++ b/base/meta.jl @@ -193,7 +193,7 @@ end function _parse_string(text::AbstractString, filename::AbstractString, lineno::Integer, index::Integer, options) if index < 1 || index > ncodeunits(text) + 1 - throw(BoundsError(text, index)) + throw_boundserror(text, index) end ex, offset::Int = Core._parse(text, filename, lineno, index-1, options) ex, offset+1 diff --git a/base/number.jl b/base/number.jl index d3bf14d566250f..7ed4e052c76ae1 100644 --- a/base/number.jl +++ b/base/number.jl @@ -78,29 +78,29 @@ false isfinite(x::Number) = iszero(x - x) size(x::Number) = () -size(x::Number, d::Integer) = d < 1 ? throw(BoundsError()) : 1 +size(x::Number, d::Integer) = d < 1 ? throw_boundserror() : 1 axes(x::Number) = () -axes(x::Number, d::Integer) = d < 1 ? throw(BoundsError()) : OneTo(1) +axes(x::Number, d::Integer) = d < 1 ? throw_boundserror() : OneTo(1) eltype(::Type{T}) where {T<:Number} = T ndims(x::Number) = 0 ndims(::Type{<:Number}) = 0 length(x::Number) = 1 firstindex(x::Number) = 1 -firstindex(x::Number, d::Int) = d < 1 ? throw(BoundsError()) : 1 +firstindex(x::Number, d::Int) = d < 1 ? throw_boundserror() : 1 lastindex(x::Number) = 1 -lastindex(x::Number, d::Int) = d < 1 ? throw(BoundsError()) : 1 +lastindex(x::Number, d::Int) = d < 1 ? throw_boundserror() : 1 IteratorSize(::Type{<:Number}) = HasShape{0}() keys(::Number) = OneTo(1) getindex(x::Number) = x function getindex(x::Number, i::Integer) @inline - @boundscheck i == 1 || throw(BoundsError()) + @boundscheck i == 1 || throw_boundserror() x end function getindex(x::Number, I::Integer...) @inline - @boundscheck all(isone, I) || throw(BoundsError()) + @boundscheck all(isone, I) || throw_boundserror() x end get(x::Number, i::Integer, default) = isone(i) ? x : default diff --git a/base/reflection.jl b/base/reflection.jl index 5490cae9511c81..eb5dff8c23ab52 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -164,7 +164,7 @@ end fieldname(t::UnionAll, i::Integer) = fieldname(unwrap_unionall(t), i) fieldname(t::Type{<:Tuple}, i::Integer) = - i < 1 || i > fieldcount(t) ? throw(BoundsError(t, i)) : Int(i) + i < 1 || i > fieldcount(t) ? throw_boundserror(t, i) : Int(i) """ fieldnames(x::DataType) @@ -477,7 +477,7 @@ function getindex(dtfd::DataTypeFieldDesc, i::Int) layout = unsafe_load(layout_ptr) fielddesc_type = (layout.flags >> 1) & 3 nfields = layout.nfields - @boundscheck ((1 <= i <= nfields) || throw(BoundsError(dtfd, i))) + @boundscheck ((1 <= i <= nfields) || throw_boundserror(dtfd, i)) if fielddesc_type == 0 return FieldDesc(unsafe_load(Ptr{FieldDescStorage{UInt8}}(fd_ptr), i)) elseif fielddesc_type == 1 diff --git a/base/regex.jl b/base/regex.jl index ad26c18d4c5817..38d1f6dfbb9f75 100644 --- a/base/regex.jl +++ b/base/regex.jl @@ -409,7 +409,7 @@ findnext(re::Regex, str::Union{String,SubString}, idx::Integer) = _findnext_re(r # TODO: return only start index and update deprecation function _findnext_re(re::Regex, str::Union{String,SubString}, idx::Integer, match_data::Ptr{Cvoid}) if idx > nextind(str,lastindex(str)) - throw(BoundsError()) + throw_boundserror() end opts = re.match_options compile(re) diff --git a/base/show.jl b/base/show.jl index 8359690034c23d..49d59343cf4214 100644 --- a/base/show.jl +++ b/base/show.jl @@ -2792,6 +2792,7 @@ function summary(x) String(take!(io)) end + ## `summary` for AbstractArrays # sizes such as 0-dimensional, 4-dimensional, 2x3 dims2string(d) = isempty(d) ? "0-dimensional" : diff --git a/base/strings/basic.jl b/base/strings/basic.jl index 515b8363116985..0c2d07466bc0b2 100644 --- a/base/strings/basic.jl +++ b/base/strings/basic.jl @@ -205,6 +205,8 @@ end ## bounds checking ## +throw_boundserror(s::AbstractString, i) = (@noinline; throw(BoundsError(length(codeunits(s)), typeof(s), i))) + checkbounds(::Type{Bool}, s::AbstractString, i::Integer) = 1 ≤ i ≤ ncodeunits(s)::Int checkbounds(::Type{Bool}, s::AbstractString, r::AbstractRange{<:Integer}) = @@ -214,7 +216,7 @@ checkbounds(::Type{Bool}, s::AbstractString, I::AbstractArray{<:Real}) = checkbounds(::Type{Bool}, s::AbstractString, I::AbstractArray{<:Integer}) = all(i -> checkbounds(Bool, s, i), I) checkbounds(s::AbstractString, I::Union{Integer,AbstractArray}) = - checkbounds(Bool, s, I) ? nothing : throw(BoundsError(s, I)) + checkbounds(Bool, s, I) ? nothing : throw_boundserror(s, I) ## construction, conversion, promotion ## @@ -388,8 +390,8 @@ length(s::AbstractString) = @inbounds return length(s, 1, ncodeunits(s)::Int) function length(s::AbstractString, i::Int, j::Int) @boundscheck begin - 0 < i ≤ ncodeunits(s)::Int+1 || throw(BoundsError(s, i)) - 0 ≤ j < ncodeunits(s)::Int+1 || throw(BoundsError(s, j)) + 0 < i ≤ ncodeunits(s)::Int+1 || throw_boundserror(s, i) + 0 ≤ j < ncodeunits(s)::Int+1 || throw_boundserror(s, j) end n = 0 for k = i:j @@ -438,7 +440,7 @@ thisind(s::AbstractString, i::Integer) = thisind(s, Int(i)) function thisind(s::AbstractString, i::Int) z = ncodeunits(s)::Int + 1 i == z && return i - @boundscheck 0 ≤ i ≤ z || throw(BoundsError(s, i)) + @boundscheck 0 ≤ i ≤ z || throw_boundserror(s, i) @inbounds while 1 < i && !(isvalid(s, i)::Bool) i -= 1 end @@ -496,7 +498,7 @@ prevind(s::AbstractString, i::Int) = prevind(s, i, 1) function prevind(s::AbstractString, i::Int, n::Int) n < 0 && throw(ArgumentError("n cannot be negative: $n")) z = ncodeunits(s) + 1 - @boundscheck 0 < i ≤ z || throw(BoundsError(s, i)) + @boundscheck 0 < i ≤ z || throw_boundserror(s, i) n == 0 && return thisind(s, i) == i ? i : string_index_err(s, i) while n > 0 && 1 < i @inbounds n -= isvalid(s, i -= 1) @@ -555,7 +557,7 @@ nextind(s::AbstractString, i::Int) = nextind(s, i, 1) function nextind(s::AbstractString, i::Int, n::Int) n < 0 && throw(ArgumentError("n cannot be negative: $n")) z = ncodeunits(s) - @boundscheck 0 ≤ i ≤ z || throw(BoundsError(s, i)) + @boundscheck 0 ≤ i ≤ z || throw_boundserror(s, i) n == 0 && return thisind(s, i) == i ? i : string_index_err(s, i) while n > 0 && i < z @inbounds n -= isvalid(s, i += 1) diff --git a/base/strings/search.jl b/base/strings/search.jl index 938ed8d527d997..5237fd258c8a20 100644 --- a/base/strings/search.jl +++ b/base/strings/search.jl @@ -6,7 +6,7 @@ function findnext(pred::Fix2{<:Union{typeof(isequal),typeof(==)},<:AbstractChar} s::String, i::Integer) if i < 1 || i > sizeof(s) i == sizeof(s) + 1 && return nothing - throw(BoundsError(s, i)) + throw_boundserror(s, i) end @inbounds isvalid(s, i) || string_index_err(s, i) c = pred.x @@ -30,11 +30,11 @@ findnext(::typeof(iszero), a::ByteArray, i::Integer) = nothing_sentinel(_search( function _search(a::Union{String,ByteArray}, b::Union{Int8,UInt8}, i::Integer = 1) if i < 1 - throw(BoundsError(a, i)) + throw_boundserror(a, i) end n = sizeof(a) if i > n - return i == n+1 ? 0 : throw(BoundsError(a, i)) + return i == n+1 ? 0 : throw_boundserror(a, i) end p = pointer(a) q = GC.@preserve a ccall(:memchr, Ptr{UInt8}, (Ptr{UInt8}, Int32, Csize_t), p+i-1, b, n-i+1) @@ -73,11 +73,11 @@ findprev(::typeof(iszero), a::ByteArray, i::Integer) = nothing_sentinel(_rsearch function _rsearch(a::Union{String,ByteArray}, b::Union{Int8,UInt8}, i::Integer = sizeof(a)) if i < 1 - return i == 0 ? 0 : throw(BoundsError(a, i)) + return i == 0 ? 0 : throw_boundserror(a, i) end n = sizeof(a) if i > n - return i == n+1 ? 0 : throw(BoundsError(a, i)) + return i == n+1 ? 0 : throw_boundserror(a, i) end p = pointer(a) q = GC.@preserve a ccall(:memrchr, Ptr{UInt8}, (Ptr{UInt8}, Int32, Csize_t), p, b, i) @@ -152,7 +152,7 @@ findfirst(pattern::AbstractVector{<:Union{Int8,UInt8}}, function findnext(testf::Function, s::AbstractString, i::Integer) i = Int(i) z = ncodeunits(s) + 1 - 1 ≤ i ≤ z || throw(BoundsError(s, i)) + 1 ≤ i ≤ z || throw_boundserror(s, i) @inbounds i == z || isvalid(s, i) || string_index_err(s, i) e = lastindex(s) while i <= e @@ -171,7 +171,7 @@ function _searchindex(s::Union{AbstractString,ByteArray}, x = Iterators.peel(t) if isnothing(x) return 1 <= i <= nextind(s,lastindex(s))::Int ? i : - throw(BoundsError(s, i)) + throw_boundserror(s, i) end t1, trest = x while true @@ -207,7 +207,7 @@ function _searchindex(s::AbstractVector{<:Union{Int8,UInt8}}, n = length(t) m = length(s) i = Int(_i) - sentinel - (i < 1 || i > m+1) && throw(BoundsError(s, _i)) + (i < 1 || i > m+1) && throw_boundserror(s, _i) if n == 0 return 1 <= i <= m+1 ? max(1, i) : sentinel @@ -410,7 +410,7 @@ findlast(ch::AbstractChar, string::AbstractString) = findlast(==(ch), string) function findprev(testf::Function, s::AbstractString, i::Integer) i = Int(i) z = ncodeunits(s) + 1 - 0 ≤ i ≤ z || throw(BoundsError(s, i)) + 0 ≤ i ≤ z || throw_boundserror(s, i) i == z && return nothing @inbounds i == 0 || isvalid(s, i) || string_index_err(s, i) while i >= 1 @@ -425,7 +425,7 @@ function _rsearchindex(s::AbstractString, i::Integer) if isempty(t) return 1 <= i <= nextind(s, lastindex(s))::Int ? i : - throw(BoundsError(s, i)) + throw_boundserror(s, i) end t1, trest = Iterators.peel(Iterators.reverse(t))::NTuple{2,Any} while true @@ -465,7 +465,7 @@ function _rsearchindex(s::AbstractVector{<:Union{Int8,UInt8}}, t::AbstractVector n = length(t) m = length(s) k = Int(_k) - sentinel - k < 0 && throw(BoundsError(s, _k)) + k < 0 && throw_boundserror(s, _k) if n == 0 return 0 <= k <= m ? max(k, 1) : sentinel diff --git a/base/strings/string.jl b/base/strings/string.jl index 70e46b29b546ed..7fb83c01b30e2c 100644 --- a/base/strings/string.jl +++ b/base/strings/string.jl @@ -146,7 +146,7 @@ typemin(::String) = typemin(String) i == 0 && return 0 n = ncodeunits(s) i == n + 1 && return i - @boundscheck between(i, 1, n) || throw(BoundsError(s, i)) + @boundscheck between(i, 1, n) || throw_boundserror(s, i) @inbounds b = codeunit(s, i) (b & 0xc0 == 0x80) & (i-1 > 0) || return i @inbounds b = codeunit(s, i-1) @@ -166,7 +166,7 @@ end @inline function _nextind_str(s, i::Int) i == 0 && return 1 n = ncodeunits(s) - @boundscheck between(i, 1, n) || throw(BoundsError(s, i)) + @boundscheck between(i, 1, n) || throw_boundserror(s, i) @inbounds l = codeunit(s, i) (l < 0x80) | (0xf8 ≤ l) && return i+1 if l < 0xc0 @@ -286,8 +286,8 @@ length(s::String) = length_continued(s, 1, ncodeunits(s), ncodeunits(s)) @inline function length(s::String, i::Int, j::Int) @boundscheck begin - 0 < i ≤ ncodeunits(s)+1 || throw(BoundsError(s, i)) - 0 ≤ j < ncodeunits(s)+1 || throw(BoundsError(s, j)) + 0 < i ≤ ncodeunits(s)+1 || throw_boundserror(s, i) + 0 ≤ j < ncodeunits(s)+1 || throw_boundserror(s, j) end j < i && return 0 @inbounds i, k = thisind(s, i), i diff --git a/base/tuple.jl b/base/tuple.jl index 3b5142d03039d0..8e777ded9cc725 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -26,10 +26,11 @@ firstindex(@nospecialize t::Tuple) = 1 lastindex(@nospecialize t::Tuple) = length(t) size(@nospecialize(t::Tuple), d::Integer) = (d == 1) ? length(t) : throw(ArgumentError("invalid tuple dimension $d")) axes(@nospecialize t::Tuple) = (OneTo(length(t)),) +throw_boundserror(t::Tuple, i) = (@noinline; throw(BoundsError(axes(t), typeof(t), i))) @eval getindex(@nospecialize(t::Tuple), i::Int) = getfield(t, i, $(Expr(:boundscheck))) @eval getindex(@nospecialize(t::Tuple), i::Integer) = getfield(t, convert(Int, i), $(Expr(:boundscheck))) getindex(t::Tuple, r::AbstractArray{<:Any,1}) = (eltype(t)[t[ri] for ri in r]...,) -getindex(t::Tuple, b::AbstractArray{Bool,1}) = length(b) == length(t) ? getindex(t, findall(b)) : throw(BoundsError(t, b)) +getindex(t::Tuple, b::AbstractArray{Bool,1}) = length(b) == length(t) ? getindex(t, findall(b)) : throw_boundserror(t, b) getindex(t::Tuple, c::Colon) = t get(t::Tuple, i::Integer, default) = i in 1:length(t) ? getindex(t, i) : default @@ -50,7 +51,7 @@ true ``` """ function setindex(x::Tuple, v, i::Integer) - @boundscheck 1 <= i <= length(x) || throw(BoundsError(x, i)) + @boundscheck 1 <= i <= length(x) || throw_boundserror(x, i) @inline _setindex(v, i, x...) end @@ -89,12 +90,12 @@ indexed_iterate(t::Tuple, i::Int, state=1) = (@inline; (getfield(t, i), i+1)) indexed_iterate(a::Array, i::Int, state=1) = (@inline; (a[i], i+1)) function indexed_iterate(I, i) x = iterate(I) - x === nothing && throw(BoundsError(I, i)) + x === nothing && throw_boundserror(I, i) x end function indexed_iterate(I, i, state) x = iterate(I, state) - x === nothing && throw(BoundsError(I, i)) + x === nothing && throw_boundserror(I, i) x end diff --git a/doc/src/manual/interfaces.md b/doc/src/manual/interfaces.md index 2b790a6546ff34..7e42054fc86900 100644 --- a/doc/src/manual/interfaces.md +++ b/doc/src/manual/interfaces.md @@ -174,7 +174,7 @@ simply needs to define [`getindex`](@ref): ```jldoctest squaretype julia> function Base.getindex(S::Squares, i::Int) - 1 <= i <= S.count || throw(BoundsError(S, i)) + 1 <= i <= S.count || throw_boundserror(S, i) return i*i end diff --git a/doc/src/manual/metaprogramming.md b/doc/src/manual/metaprogramming.md index a374b9c879e6af..35009abce645ad 100644 --- a/doc/src/manual/metaprogramming.md +++ b/doc/src/manual/metaprogramming.md @@ -1338,7 +1338,7 @@ The same thing can be done using recursion: julia> sub2ind_rec(dims::Tuple{}) = 1; julia> sub2ind_rec(dims::Tuple{}, i1::Integer, I::Integer...) = - i1 == 1 ? sub2ind_rec(dims, I...) : throw(BoundsError()); + i1 == 1 ? sub2ind_rec(dims, I...) : throw_boundserror(); julia> sub2ind_rec(dims::Tuple{Integer, Vararg{Integer}}, i1::Integer) = i1; diff --git a/src/codegen.cpp b/src/codegen.cpp index 80fc52471bb45c..44b513ccf6948e 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -617,6 +617,12 @@ static const auto jlboundserror_func = new JuliaFunction{ {PointerType::get(JuliaType::get_jlvalue_ty(C), AddressSpace::CalleeRooted), getSizeTy(C)}, false); }, get_attrs_noreturn, }; +static const auto jlboundserrorsum_func = new JuliaFunction{ + XSTR(jl_bounds_error_summarized), + [](LLVMContext &C) { return FunctionType::get(getVoidTy(C), + {getSizeTy(C), PointerType::get(JuliaType::get_jlvalue_ty(C), AddressSpace::CalleeRooted), getSizeTy(C)}, false); }, + get_attrs_noreturn, +}; static const auto jlvboundserror_func = new JuliaFunction{ XSTR(jl_bounds_error_tuple_int), [](LLVMContext &C) { return FunctionType::get(getVoidTy(C), @@ -8066,6 +8072,7 @@ static void init_jit_functions(void) add_named_global(jlundefvarerror_func, &jl_undefined_var_error); add_named_global(jlboundserrorv_func, &jl_bounds_error_ints); add_named_global(jlboundserror_func, &jl_bounds_error_int); + add_named_global(jlboundserrorsum_func, &jl_bounds_error_summarized); add_named_global(jlvboundserror_func, &jl_bounds_error_tuple_int); add_named_global(jluboundserror_func, &jl_bounds_error_unboxed_int); add_named_global(jlnew_func, &jl_new_structv); diff --git a/src/datatype.c b/src/datatype.c index e7f1ab22365b80..96386f109a7dfb 100644 --- a/src/datatype.c +++ b/src/datatype.c @@ -1408,7 +1408,7 @@ JL_DLLEXPORT jl_value_t *jl_get_nth_field(jl_value_t *v, size_t i) { jl_datatype_t *st = (jl_datatype_t*)jl_typeof(v); if (i >= jl_datatype_nfields(st)) - jl_bounds_error_int(v, i + 1); + jl_bounds_error_summarized(jl_nfields(v), (jl_value_t *)st, i + 1); size_t offs = jl_field_offset(st, i); if (jl_field_isptr(st, i)) { return jl_atomic_load_relaxed((_Atomic(jl_value_t*)*)((char*)v + offs)); diff --git a/src/jl_exported_funcs.inc b/src/jl_exported_funcs.inc index 09fb4880ae6263..59dea85d89d3be 100644 --- a/src/jl_exported_funcs.inc +++ b/src/jl_exported_funcs.inc @@ -61,6 +61,7 @@ XX(jl_bounds_error) \ XX(jl_bounds_error_int) \ XX(jl_bounds_error_ints) \ + XX(jl_bounds_error_summarized) \ XX(jl_bounds_error_tuple_int) \ XX(jl_bounds_error_unboxed_int) \ XX(jl_bounds_error_v) \ diff --git a/src/julia.h b/src/julia.h index 5e22c262fb47d3..a4a1222ff085e8 100644 --- a/src/julia.h +++ b/src/julia.h @@ -1667,6 +1667,9 @@ JL_DLLEXPORT void JL_NORETURN jl_bounds_error_v(jl_value_t *v JL_MAYBE_UNROOTED, jl_value_t **idxs, size_t nidxs); JL_DLLEXPORT void JL_NORETURN jl_bounds_error_int(jl_value_t *v JL_MAYBE_UNROOTED, size_t i); +JL_DLLEXPORT void JL_NORETURN jl_bounds_error_summarized(size_t nfields JL_MAYBE_UNROOTED, + jl_value_t *ty JL_MAYBE_UNROOTED, + size_t i); JL_DLLEXPORT void JL_NORETURN jl_bounds_error_tuple_int(jl_value_t **v, size_t nv, size_t i); JL_DLLEXPORT void JL_NORETURN jl_bounds_error_unboxed_int(void *v, jl_value_t *vt, size_t i); diff --git a/src/rtutils.c b/src/rtutils.c index b4432d8af3d0cf..492b02c416fbb7 100644 --- a/src/rtutils.c +++ b/src/rtutils.c @@ -139,11 +139,29 @@ JL_DLLEXPORT void JL_NORETURN jl_atomic_error(char *str) // == jl_exceptionf(jl_ jl_throw(jl_new_struct(jl_atomicerror_type, msg)); } +void JL_NORETURN jl_throw_bounds_error(jl_value_t *v, jl_value_t *t) +{ + jl_value_t **args; + JL_GC_PUSHARGS(args, 2); + args[0] = v; + args[1] = t; + jl_throw(jl_apply_generic((jl_value_t*)jl_boundserror_type, args, 2)); +} + +void JL_NORETURN jl_throw_bounds_error_summarized(jl_value_t *axes, jl_value_t *typ, jl_value_t *t) +{ + jl_value_t **args; + JL_GC_PUSHARGS(args, 3); + args[0] = axes; + args[1] = typ; + args[2] = t; + jl_throw(jl_apply_generic((jl_value_t*)jl_boundserror_type, args, 3)); +} JL_DLLEXPORT void JL_NORETURN jl_bounds_error(jl_value_t *v, jl_value_t *t) { JL_GC_PUSH2(&v, &t); // root arguments so the caller doesn't need to - jl_throw(jl_new_struct((jl_datatype_t*)jl_boundserror_type, v, t)); + jl_throw_bounds_error(v, t); } JL_DLLEXPORT void JL_NORETURN jl_bounds_error_v(jl_value_t *v, jl_value_t **idxs, size_t nidxs) @@ -152,7 +170,7 @@ JL_DLLEXPORT void JL_NORETURN jl_bounds_error_v(jl_value_t *v, jl_value_t **idxs // items in idxs are assumed to already be rooted JL_GC_PUSH2(&v, &t); // root v so the caller doesn't need to t = jl_f_tuple(NULL, idxs, nidxs); - jl_throw(jl_new_struct((jl_datatype_t*)jl_boundserror_type, v, t)); + jl_throw_bounds_error(v, t); } JL_DLLEXPORT void JL_NORETURN jl_bounds_error_tuple_int(jl_value_t **v, size_t nv, size_t i) @@ -169,7 +187,7 @@ JL_DLLEXPORT void JL_NORETURN jl_bounds_error_unboxed_int(void *data, jl_value_t JL_GC_PUSH2(&v, &t); v = jl_new_bits(vt, data); t = jl_box_long(i); - jl_throw(jl_new_struct((jl_datatype_t*)jl_boundserror_type, v, t)); + jl_throw_bounds_error(v, t); } JL_DLLEXPORT void JL_NORETURN jl_bounds_error_int(jl_value_t *v JL_MAYBE_UNROOTED, size_t i) @@ -177,7 +195,7 @@ JL_DLLEXPORT void JL_NORETURN jl_bounds_error_int(jl_value_t *v JL_MAYBE_UNROOTE jl_value_t *t = NULL; JL_GC_PUSH2(&v, &t); // root arguments so the caller doesn't need to t = jl_box_long(i); - jl_throw(jl_new_struct((jl_datatype_t*)jl_boundserror_type, v, t)); + jl_throw_bounds_error(v, t); } JL_DLLEXPORT void JL_NORETURN jl_bounds_error_ints(jl_value_t *v JL_MAYBE_UNROOTED, @@ -191,7 +209,18 @@ JL_DLLEXPORT void JL_NORETURN jl_bounds_error_ints(jl_value_t *v JL_MAYBE_UNROOT jl_svecset(t, i, jl_box_long(idxs[i])); } t = jl_f_tuple(NULL, jl_svec_data(t), nidxs); - jl_throw(jl_new_struct((jl_datatype_t*)jl_boundserror_type, v, t)); + jl_throw_bounds_error(v, t); +} + +JL_DLLEXPORT void JL_NORETURN jl_bounds_error_summarized(size_t nfields, + jl_value_t *ty JL_MAYBE_UNROOTED, size_t i) +{ + jl_value_t *nf = NULL; + jl_value_t *t = NULL; + JL_GC_PUSH3(&nf, &ty, &t); + t = jl_box_long(i); + nf = jl_box_long(nfields); + jl_throw_bounds_error_summarized(nf, ty, t); } JL_DLLEXPORT void JL_NORETURN jl_eof_error(void) diff --git a/stdlib/LibGit2/src/status.jl b/stdlib/LibGit2/src/status.jl index cd871681e4ae90..08a1712d26e75c 100644 --- a/stdlib/LibGit2/src/status.jl +++ b/stdlib/LibGit2/src/status.jl @@ -25,7 +25,7 @@ function Base.length(status::GitStatus) end function Base.getindex(status::GitStatus, i::Integer) - 1 <= i <= length(status) || throw(BoundsError()) + 1 <= i <= length(status) || throw_boundserror() ensure_initialized() GC.@preserve status begin entry_ptr = ccall((:git_status_byindex, :libgit2), diff --git a/stdlib/LinearAlgebra/src/generic.jl b/stdlib/LinearAlgebra/src/generic.jl index 676e965652e8f5..4f047e83d7184e 100644 --- a/stdlib/LinearAlgebra/src/generic.jl +++ b/stdlib/LinearAlgebra/src/generic.jl @@ -1419,9 +1419,9 @@ function axpy!(α, x::AbstractArray, rx::AbstractArray{<:Integer}, y::AbstractAr if length(rx) != length(ry) throw(DimensionMismatch("rx has length $(length(rx)), but ry has length $(length(ry))")) elseif !checkindex(Bool, eachindex(IndexLinear(), x), rx) - throw(BoundsError(x, rx)) + throw_boundserror(x, rx) elseif !checkindex(Bool, eachindex(IndexLinear(), y), ry) - throw(BoundsError(y, ry)) + throw_boundserror(y, ry) end for (IY, IX) in zip(eachindex(ry), eachindex(rx)) @inbounds y[ry[IY]] += x[rx[IX]]*α diff --git a/stdlib/LinearAlgebra/src/lq.jl b/stdlib/LinearAlgebra/src/lq.jl index f19df799bb4a7e..eb8536137ec7ae 100644 --- a/stdlib/LinearAlgebra/src/lq.jl +++ b/stdlib/LinearAlgebra/src/lq.jl @@ -181,7 +181,7 @@ function size(Q::LQPackedQ) end function size(Q::LQPackedQ, dim::Integer) if dim < 1 - throw(BoundsError()) + throw_boundserror() elseif dim <= 2 # && 1 <= dim return size(Q.factors, 2) else # 2 < dim diff --git a/stdlib/LinearAlgebra/src/matmul.jl b/stdlib/LinearAlgebra/src/matmul.jl index 80d9872fdca6ec..89fbbfc092bb4f 100644 --- a/stdlib/LinearAlgebra/src/matmul.jl +++ b/stdlib/LinearAlgebra/src/matmul.jl @@ -19,10 +19,10 @@ function dot(x::Vector{T}, rx::AbstractRange{TI}, y::Vector{T}, ry::AbstractRang throw(DimensionMismatch(lazy"length of rx, $(length(rx)), does not equal length of ry, $(length(ry))")) end if minimum(rx) < 1 || maximum(rx) > length(x) - throw(BoundsError(x, rx)) + throw_boundserror(x, rx) end if minimum(ry) < 1 || maximum(ry) > length(y) - throw(BoundsError(y, ry)) + throw_boundserror(y, ry) end GC.@preserve x y BLAS.dot(length(rx), pointer(x)+(first(rx)-1)*sizeof(T), step(rx), pointer(y)+(first(ry)-1)*sizeof(T), step(ry)) end @@ -32,10 +32,10 @@ function dot(x::Vector{T}, rx::AbstractRange{TI}, y::Vector{T}, ry::AbstractRang throw(DimensionMismatch(lazy"length of rx, $(length(rx)), does not equal length of ry, $(length(ry))")) end if minimum(rx) < 1 || maximum(rx) > length(x) - throw(BoundsError(x, rx)) + throw_boundserror(x, rx) end if minimum(ry) < 1 || maximum(ry) > length(y) - throw(BoundsError(y, ry)) + throw_boundserror(y, ry) end GC.@preserve x y BLAS.dotc(length(rx), pointer(x)+(first(rx)-1)*sizeof(T), step(rx), pointer(y)+(first(ry)-1)*sizeof(T), step(ry)) end diff --git a/stdlib/SharedArrays/src/SharedArrays.jl b/stdlib/SharedArrays/src/SharedArrays.jl index a961be4e534b36..50c5edd1e0db9c 100644 --- a/stdlib/SharedArrays/src/SharedArrays.jl +++ b/stdlib/SharedArrays/src/SharedArrays.jl @@ -589,7 +589,7 @@ end copyto!(S::SharedArray, A::Array) = (copyto!(S.s, A); S) function copyto!(S::SharedArray, R::SharedArray) - length(S) == length(R) || throw(BoundsError()) + length(S) == length(R) || throw_boundserror() ps = intersect(procs(S), procs(R)) isempty(ps) && throw(ArgumentError("source and destination arrays don't share any process")) l = length(S) diff --git a/stdlib/Sockets/src/IPAddr.jl b/stdlib/Sockets/src/IPAddr.jl index 1792008620981b..0b866a9ff67a6b 100644 --- a/stdlib/Sockets/src/IPAddr.jl +++ b/stdlib/Sockets/src/IPAddr.jl @@ -112,7 +112,7 @@ print_ipv6_field(io,field::UInt16) = print(io,string(field, base = 16)) print_ipv6_field(io,ip,i) = print_ipv6_field(io,ipv6_field(ip,i)) function ipv6_field(ip::IPv6,i) if i < 0 || i > 7 - throw(BoundsError()) + throw_boundserror() end UInt16((ip.host&(UInt128(0xFFFF)<<(i*16))) >> (i*16)) end diff --git a/test/arrayops.jl b/test/arrayops.jl index b2badb66ce93da..da08c68402384c 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -2907,7 +2907,7 @@ end err = BoundsError(x, (10, "bad index")) showerror(b, err) @test String(take!(b)) == - "BoundsError: attempt to access 2×2 Matrix{Float64} at index [10, \"bad index\"]" + "BoundsError: attempt to access 2-element 2×2 Matrix{Float64} at index [10, \"bad index\"]" end @testset "inference of Union{T,Nothing} arrays 26771" begin diff --git a/test/boundscheck_exec.jl b/test/boundscheck_exec.jl index 71690c55faecad..f35b0731f21c7b 100644 --- a/test/boundscheck_exec.jl +++ b/test/boundscheck_exec.jl @@ -126,7 +126,7 @@ else end # elide a throw -cb(x) = x > 0 || throw(BoundsError()) +cb(x) = x > 0 || throw_boundserror() @inline function B1() y = [1, 2, 3] diff --git a/test/core.jl b/test/core.jl index 6dbd8f3d9fa0ee..03c04edb157717 100644 --- a/test/core.jl +++ b/test/core.jl @@ -2637,8 +2637,16 @@ end # pull request #9534 @test_throws BoundsError((1, 2), 3) begin; a, b, c = 1, 2; end -let a = [] - @test try; a[]; catch ex; (ex::BoundsError).a === a && ex.i == (); end +let a = Any[] + try + a[] + catch ex + x = (ex::BoundsError).a + @test isa(x, Core.Summarized) + @test x.axes === Base.OneTo(0,) + @test x.type === Vector{Any} + @test ex.i == () + end @test_throws BoundsError(a, (1, 2)) a[1, 2] @test_throws BoundsError(a, (10,)) a[10] end @@ -2668,7 +2676,7 @@ let a = IOBuffer() end x9634 = 3 @test_throws BoundsError(1 + 2im, 3) getfield(1 + 2im, x9634) -@test try; throw(BoundsError()); catch ex; !isdefined((ex::BoundsError), :a) && !isdefined((ex::BoundsError), :i); end +@test try; throw_boundserror(); catch ex; !isdefined((ex::BoundsError), :a) && !isdefined((ex::BoundsError), :i); end @test try; throw(BoundsError(Int)); catch ex; (ex::BoundsError).a == Int && !isdefined((ex::BoundsError), :i); end @test_throws BoundsError(Int, typemin(Int)) throw(BoundsError(Int, typemin(Int))) @test_throws BoundsError(Int, (:a,)) throw(BoundsError(Int, (:a,))) @@ -2678,6 +2686,26 @@ f9534h(a, b, c...) = c[a] @test f9534h(4, 2, 3, 4, 5, 6) == 6 @test_throws BoundsError((3, 4, 5, 6), 5) f9534h(5, 2, 3, 4, 5, 6) +@test let # https://github.com/JuliaLang/julia/pull/43738 + # BoundsError should never capture arrays + code = quote + const THROWN_ARRAY = Ref{Any}() + function Base.summary(io::IO, a::Array) + Main.THROWN_ARRAY[] = a + return "bad override" + end + try + a = [] + a[1] + catch err + end + isdefined(THROWN_ARRAY, :x) && println(stderr, "test failed") + end |> string + cmd = `$(Base.julia_cmd()) -e $code` + stderr = IOBuffer() + success(pipeline(Cmd(cmd); stdout=stdout, stderr=stderr)) && isempty(String(take!(stderr))) +end + # issue #7978, comment 332352438 f7978a() = 1 @test_throws BoundsError(1, 2) begin; a, b = f7978a(); end diff --git a/test/errorshow.jl b/test/errorshow.jl index 72a2ebb1e9cbea..ebcb8a66c3574a 100644 --- a/test/errorshow.jl +++ b/test/errorshow.jl @@ -281,7 +281,7 @@ struct TypeWithIntParam{T<:Integer, Vector{T}<:A<:AbstractArray{T}} end struct Bounded # not an AbstractArray bound::Int end -Base.getindex(b::Bounded, i) = checkindex(Bool, 1:b.bound, i) || throw(BoundsError(b, i)) +Base.getindex(b::Bounded, i) = checkindex(Bool, 1:b.bound, i) || throw_boundserror(b, i) Base.summary(io::IO, b::Bounded) = print(io, "$(b.bound)-size Bounded") let undefvar err_str = @except_strbt sqrt(-1) DomainError diff --git a/test/sorting.jl b/test/sorting.jl index 86479eca6cc783..ecbd0b4be51e53 100644 --- a/test/sorting.jl +++ b/test/sorting.jl @@ -250,7 +250,7 @@ struct ConstantRange{T} <: AbstractRange{T} end Base.length(r::ConstantRange) = r.len -Base.getindex(r::ConstantRange, i::Int) = (1 <= i <= r.len || throw(BoundsError(r,i)); r.val) +Base.getindex(r::ConstantRange, i::Int) = (1 <= i <= r.len || throw_boundserror(r,i); r.val) Base.step(r::ConstantRange) = 0 @testset "searchsorted method with ranges which check for zero step range" begin