-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
limit recursion depth to prevent stack overflow in 2-arg promote_type
#57517
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
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. |
6236d3f
to
fac1ce7
Compare
@nanosoldier |
0bc1c59
to
d2e0fb2
Compare
The package evaluation job you requested has completed - possible new issues were detected. Report summary❗ Packages that crashed1 packages crashed only on the current version.
12 packages crashed on the previous version too. ✖ Packages that failed38 packages failed only on the current version.
1054 packages failed on the previous version too. ✔ Packages that passed tests23 packages passed tests only on the current version.
5334 packages passed tests on the previous version too. ~ Packages that at least loaded9 packages successfully loaded only on the current version.
2968 packages successfully loaded on the previous version too. ➖ Packages that were skipped altogether2 packages were skipped only on the current version.
908 packages were skipped on the previous version too. |
The pkgeval seems OK, it doesn't seem like any packages hit the recursion depth limit. That said, it might be good to repeat the pkgeval once the bpart-related changes are more polished. |
PolynomialRings failure looks real (link that will go stale) |
PolynomialRings is buggy, doesn't have much to do with this PR specifically: tkluck/PolynomialRings.jl#10 EDIT: the EDIT: the current version of this PR will again break PolynomialRings.jl, unless it gets broken for an unrelated reason by #58720 first. But, again, that's the fault of PolynomialRings.jl. |
This reverts commit 66336e0.
If this is fine with the test suite and PkgEval, we can raise it back for even greater assurance that there will be no breakage in practice.
Ideally this shouldn't matter, however it's an easy fix for the test suite of MaxPlus.jl, which expects this ordering of the calls in an overly-strict `test_throws` test.
Will remove them in a follow-up PR. Just adding them back to make this PR easier to review.
In preparation for replacing recursion with metaprogramming.
In particular, this enables more calls to be constant folded.
9b1e379
to
011d7a1
Compare
The registered package in question will be broken for an unrelated reason now anyway by PR JuliaLang#58720, so there's no sense in trying to protect it any more. In any case, it doesn't make sense to hold back the language just to protect a single broken package.
Replace the recursive application of
promote_rule
by a harcoded (with metapogramming) sequence of steps, up to a limited depth.Now that recursion is gone,
promote_rule
can constant fold more often.The simplest cases of conflicting
promote_rule
definitions are detected even before hitting the depth limit, as in #57507. This is just to provide better UX, because of the more precise error message in that case and the shorter stack trace.Fixes #13193
Closes #57507