-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
limit recursion depth to prevent stack overflow in 2-arg promote_type
#57517
base: master
Are you sure you want to change the base?
limit recursion depth to prevent stack overflow in 2-arg promote_type
#57517
Conversation
57dbdeb
to
69e5d32
Compare
what is the advantage over #57507 ? this seems to add quite a bit more complexity and includes scary things like hardcoded constants |
This PR prevents a much wider class of stack overflows. In particular, #57507 doesn't fix #13193 (see the test here for an example), while this PR should.
Usually I avoid hardcoded constants as best as I can, but in this case there's no way to guarantee (local) termination without hardcoding a cutoff. Another approach would be to stick with recursion, but limit the depth, however in any case it definitely seems necessary to have some kind of hardcoded limit.
|
9e1f5dc
to
4b7bb02
Compare
This is how the behavior for #13193 looks with this PR, due to the limited recursion depth: julia> struct SIQuantity{T<:Number} <: Number; end
julia> Base.promote_rule(::Type{SIQuantity{T}}, ::Type{SIQuantity{S}}) where {T, S} = SIQuantity{promote_type(T,S)}
julia> Base.promote_rule(::Type{SIQuantity{T}}, ::Type{S}) where {T, S<:Number} = SIQuantity{promote_type(T,S)}
julia> struct Interval{T<:Number} <: Number; end
julia> Base.promote_rule(::Type{Interval{T}}, ::Type{Interval{S}}) where {T, S} = Interval{promote_type(T,S)}
julia> Base.promote_rule(::Type{Interval{T}}, ::Type{S}) where {T, S<:Number} = Interval{promote_type(T,S)}
julia> promote_type(Interval{Int}, SIQuantity{Int})
ERROR: ArgumentError: `promote_type`: recursion depth limit reached, giving up; check for faulty/conflicting/missing `promote_rule` methods
Stacktrace:
[1] _promote_type_binary(::Type, ::Type, ::Tuple{})
@ Base ./promotion.jl:308
[2] promote_result(::Type, ::Type, ::Type{Interval{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Interval{Int64}}}}}}}}}}}}, ::Type{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Int64}}}}}}}}}}}}, l::Tuple{})
@ Base ./promotion.jl:360
[3] _promote_type_binary(::Type{Interval{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Int64}}}}}}}}}}}, ::Type{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Interval{Int64}}}}}}}}}}}, recursion_depth_limit::Tuple{Nothing})
@ Base ./promotion.jl:325
[4] promote_result(::Type, ::Type, ::Type{Interval{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Int64}}}}}}}}}}}, ::Type{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Interval{Int64}}}}}}}}}}}, l::Tuple{Nothing})
@ Base ./promotion.jl:360
[5] _promote_type_binary(::Type{Interval{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Interval{Int64}}}}}}}}}}, ::Type{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Int64}}}}}}}}}}, recursion_depth_limit::Tuple{Nothing, Nothing})
@ Base ./promotion.jl:325
[6] promote_result(::Type, ::Type, ::Type{Interval{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Interval{Int64}}}}}}}}}}, ::Type{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Int64}}}}}}}}}}, l::Tuple{Nothing, Nothing})
@ Base ./promotion.jl:360
[7] _promote_type_binary
@ ./promotion.jl:325 [inlined]
[8] promote_result(::Type, ::Type, ::Type{Interval{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Int64}}}}}}}}}, ::Type{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Interval{Int64}}}}}}}}}, l::Tuple{Nothing, Nothing, Nothing})
@ Base ./promotion.jl:360
[9] _promote_type_binary
@ ./promotion.jl:325 [inlined]
[10] promote_result(::Type, ::Type, ::Type{Interval{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Interval{Int64}}}}}}}}, ::Type{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Int64}}}}}}}}, l::NTuple{4, Nothing})
@ Base ./promotion.jl:360
[11] _promote_type_binary
@ ./promotion.jl:325 [inlined]
[12] promote_result(::Type, ::Type, ::Type{Interval{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Int64}}}}}}}, ::Type{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Interval{Int64}}}}}}}, l::NTuple{5, Nothing})
@ Base ./promotion.jl:360
[13] _promote_type_binary
@ ./promotion.jl:325 [inlined]
[14] promote_result(::Type, ::Type, ::Type{Interval{SIQuantity{Interval{SIQuantity{Interval{Int64}}}}}}, ::Type{SIQuantity{Interval{SIQuantity{Interval{SIQuantity{Int64}}}}}}, l::NTuple{6, Nothing})
@ Base ./promotion.jl:360
[15] _promote_type_binary
@ ./promotion.jl:325 [inlined]
[16] promote_result(::Type, ::Type, ::Type{Interval{SIQuantity{Interval{SIQuantity{Int64}}}}}, ::Type{SIQuantity{Interval{SIQuantity{Interval{Int64}}}}}, l::NTuple{7, Nothing})
@ Base ./promotion.jl:360
[17] _promote_type_binary
@ ./promotion.jl:325 [inlined]
[18] promote_result(::Type, ::Type, ::Type{Interval{SIQuantity{Interval{Int64}}}}, ::Type{SIQuantity{Interval{SIQuantity{Int64}}}}, l::NTuple{8, Nothing})
@ Base ./promotion.jl:360
[19] _promote_type_binary
@ ./promotion.jl:325 [inlined]
[20] promote_result(::Type, ::Type, ::Type{Interval{SIQuantity{Int64}}}, ::Type{SIQuantity{Interval{Int64}}}, l::NTuple{9, Nothing})
@ Base ./promotion.jl:360
[21] _promote_type_binary
@ ./promotion.jl:325 [inlined]
[22] promote_type(::Type{Interval{Int64}}, ::Type{SIQuantity{Int64}})
@ Base ./promotion.jl:340
[23] top-level scope
@ REPL[7]:1 |
not sure if I'm 100% qualified to review beyond stylistic things but as far as I can tell I like this approach it might make sense to run the benchmarks? I know this shouldn't change anything but |
promote_type
promote_type
Question for triage: does limiting the recursion depth seem like an acceptable minor change? It's a bugfix, as it prevents stack overflows, but hypothetically some user code could break if it relied on an awkward chain of NB: an earlier version of this PR tried to do away with recursion altogether in favor of a loop, however that resulted in inference regressions. NB: setting the recursion depth limit higher breaks bootstrap for some reason, so currently it's not possible to set the limit higher. |
Don't use recursion, delete the `promote_result` function. Use loop known to terminate, because of a hardcoded limit on the iteration count. Closes JuliaLang#57507, this PR encompasses the fixes from that PR and is more comprehensive. Fixes JuliaLang#13193
6236d3f
to
fac1ce7
Compare
@nanosoldier |
Limit recursion depth to prevent stack overflows.
Fixes #13193