From e06a1eeba006199639185d213bf9f42d9b43ece2 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Thu, 30 Dec 2021 16:49:46 +0900 Subject: [PATCH] inference: add error/throwness checks for `setfield!` In a similar spirit to #43587, this commit introduces error check for `setfield!`. We can bail out from inference if we can prove either of: - the object is not mutable type - the object is `Module` object - the value being assigned is incompatible with the declared type of object field This commit also adds the throwness check for `setfield!` (i.e. `setfield!_nothrow`). This throwness check won't be used in the current native compilation pipeline since `setfield!` call can't be eliminated even if we can prove that it never throws. But this throwness check would be used by EscapeAnalysis.jl integration and so I'd like to include it in Base. --- base/compiler/tfuncs.jl | 70 +++++++++++++++++++++++++++++--- test/compiler/inference.jl | 82 +++++++++++++++++++++++++++++++++++++- 2 files changed, 145 insertions(+), 7 deletions(-) diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 5c47ea5e4fde4..a475721bb2074 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -758,7 +758,8 @@ end getfield_tfunc(s00, name, boundscheck_or_order) = (@nospecialize; getfield_tfunc(s00, name)) getfield_tfunc(s00, name, order, boundscheck) = (@nospecialize; getfield_tfunc(s00, name)) -function getfield_tfunc(@nospecialize(s00), @nospecialize(name)) +getfield_tfunc(@nospecialize(s00), @nospecialize(name)) = _getfield_tfunc(s00, name, true) +function _getfield_tfunc(@nospecialize(s00), @nospecialize(name), allow_module::Bool) s = unwrap_unionall(s00) if isa(s, Union) return tmerge(getfield_tfunc(rewrap_unionall(s.a, s00), name), @@ -774,6 +775,7 @@ function getfield_tfunc(@nospecialize(s00), @nospecialize(name)) if isa(name, Const) nv = name.val if isa(sv, Module) + allow_module || return Bottom if isa(nv, Symbol) return abstract_eval_global(sv, nv) end @@ -817,9 +819,8 @@ function getfield_tfunc(@nospecialize(s00), @nospecialize(name)) return Bottom end if s <: Module - if !(Symbol <: widenconst(name)) - return Bottom - end + allow_module || return Bottom + hasintersect(widenconst(name), Symbol) || return Bottom return Any end if s.name === _NAMEDTUPLE_NAME && !isconcretetype(s) @@ -885,8 +886,65 @@ function getfield_tfunc(@nospecialize(s00), @nospecialize(name)) return rewrap_unionall(R, s00) end -setfield!_tfunc(o, f, v, order) = (@nospecialize; v) -setfield!_tfunc(o, f, v) = (@nospecialize; v) +function setfield!_tfunc(o, f, v, order) + @nospecialize + if !isvarargtype(order) + hasintersect(widenconst(order), Symbol) || return Bottom + end + return setfield!_tfunc(o, f, v) +end +function setfield!_tfunc(o, f, v) + @nospecialize + mutability_errorcheck(o) || return Bottom + ft = _getfield_tfunc(o, f, false) + ft === Bottom && return Bottom + hasintersect(widenconst(v), widenconst(ft)) || return Bottom + return v +end +function mutability_errorcheck(@nospecialize obj) + objt0 = widenconst(obj) + objt = unwrap_unionall(objt0) + if isa(objt, Union) + return mutability_errorcheck(rewrap_unionall(objt.a, objt0)) || + mutability_errorcheck(rewrap_unionall(objt.b, objt0)) + elseif isa(objt, DataType) + # Can't say anything about abstract types + isabstracttype(objt) && return true + return ismutabletype(objt) + end + return true +end + +function setfield!_nothrow(argtypes::Vector{Any}) + if length(argtypes) == 4 + order = argtypes[4] + order === Const(:non_atomic) || return false # TODO: this is assuming not atomic + else + length(argtypes) == 3 || return false + end + return setfield!_nothrow(argtypes[1], argtypes[2], argtypes[3]) +end +function setfield!_nothrow(s00, name, v) + @nospecialize + s0 = widenconst(s00) + s = unwrap_unionall(s0) + if isa(s, Union) + return setfield!_nothrow(rewrap_unionall(s.a, s00), name, v) && + setfield!_nothrow(rewrap_unionall(s.b, s00), name, v) + elseif isa(s, DataType) + # Can't say anything about abstract types + isabstracttype(s) && return false + ismutabletype(s) || return false + s.name.atomicfields == C_NULL || return false # TODO: currently we're only testing for ordering == :not_atomic + isa(name, Const) || return false + field = try_compute_fieldidx(s, name.val) + field === nothing && return false + # `try_compute_fieldidx` already check for field index bound. + v_expected = fieldtype(s0, field) + return v ⊑ v_expected + end + return false +end swapfield!_tfunc(o, f, v, order) = (@nospecialize; getfield_tfunc(o, f)) swapfield!_tfunc(o, f, v) = (@nospecialize; getfield_tfunc(o, f)) diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 5803a5aee80d1..53a75960a745b 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -1622,6 +1622,86 @@ import Core.Compiler.getfield_tfunc @test getfield_tfunc(NamedTuple{<:Any, T} where {T <: Tuple{Int, Union{Float64, Missing}}}, Const(:x)) == Union{Missing, Float64, Int} +import Core.Compiler: setfield!_tfunc, setfield!_nothrow, Const +mutable struct XY{X,Y} + x::X + y::Y +end +@test setfield!_tfunc(Base.RefValue{Int}, Const(:x), Int) === Int +@test setfield!_tfunc(Base.RefValue{Int}, Const(:x), Int, Symbol) === Int +@test setfield!_tfunc(Base.RefValue{Int}, Const(1), Int) === Int +@test setfield!_tfunc(Base.RefValue{Int}, Const(1), Int, Symbol) === Int +@test setfield!_tfunc(Base.RefValue{Int}, Int, Int) === Int +@test setfield!_tfunc(Base.RefValue{Any}, Const(:x), Int) === Int +@test setfield!_tfunc(Base.RefValue{Any}, Const(:x), Int, Symbol) === Int +@test setfield!_tfunc(Base.RefValue{Any}, Const(1), Int) === Int +@test setfield!_tfunc(Base.RefValue{Any}, Const(1), Int, Symbol) === Int +@test setfield!_tfunc(Base.RefValue{Any}, Int, Int) === Int +@test setfield!_tfunc(XY{Any,Any}, Const(1), Int) === Int +@test setfield!_tfunc(XY{Any,Any}, Const(2), Float64) === Float64 +@test setfield!_tfunc(XY{Int,Float64}, Const(1), Int) === Int +@test setfield!_tfunc(XY{Int,Float64}, Const(2), Float64) === Float64 +@test setfield!_tfunc(Union{Base.RefValue{Any},Some{Any}}, Const(:x), Int) === Int +@test setfield!_tfunc(Union{Base.RefValue,Some{Any}}, Const(:x), Int) === Int +@test setfield!_tfunc(Union{Base.RefValue{Any},Some{Any}}, Const(1), Int) === Int +@test setfield!_tfunc(Union{Base.RefValue,Some{Any}}, Const(1), Int) === Int +@test setfield!_tfunc(Union{Base.RefValue{Any},Some{Any}}, Symbol, Int) === Int +@test setfield!_tfunc(Union{Base.RefValue,Some{Any}}, Symbol, Int) === Int +@test setfield!_tfunc(Union{Base.RefValue{Any},Some{Any}}, Int, Int) === Int +@test setfield!_tfunc(Union{Base.RefValue,Some{Any}}, Int, Int) === Int +@test setfield!_tfunc(Any, Symbol, Int) === Int +@test setfield!_tfunc(Any, Int, Int) === Int +@test setfield!_tfunc(Any, Any, Int) === Int +@test setfield!_tfunc(Base.RefValue{Int}, Const(:x), Float64) === Union{} +@test setfield!_tfunc(Base.RefValue{Int}, Const(:x), Float64, Symbol) === Union{} +@test setfield!_tfunc(Base.RefValue{Int}, Const(1), Float64) === Union{} +@test setfield!_tfunc(Base.RefValue{Int}, Const(1), Float64, Symbol) === Union{} +@test setfield!_tfunc(Base.RefValue{Int}, Int, Float64) === Union{} +@test setfield!_tfunc(Base.RefValue{Any}, Const(:y), Int) === Union{} +@test setfield!_tfunc(Base.RefValue{Any}, Const(:y), Int, Bool) === Union{} +@test setfield!_tfunc(Base.RefValue{Any}, Const(2), Int) === Union{} +@test setfield!_tfunc(Base.RefValue{Any}, Const(2), Int, Bool) === Union{} +@test setfield!_tfunc(Base.RefValue{Any}, String, Int) === Union{} +@test setfield!_tfunc(Some{Any}, Const(:value), Int) === Union{} +@test setfield!_tfunc(Some, Const(:value), Int) === Union{} +@test setfield!_tfunc(Some{Any}, Const(1), Int) === Union{} +@test setfield!_tfunc(Some, Const(1), Int) === Union{} +@test setfield!_tfunc(Some{Any}, Symbol, Int) === Union{} +@test setfield!_tfunc(Some, Symbol, Int) === Union{} +@test setfield!_tfunc(Some{Any}, Int, Int) === Union{} +@test setfield!_tfunc(Some, Int, Int) === Union{} +@test setfield!_tfunc(Const(@__MODULE__), Const(:v), Int) === Union{} +@test setfield!_tfunc(Const(@__MODULE__), Int, Int) === Union{} +@test setfield!_tfunc(Module, Const(:v), Int) === Union{} +@test setfield!_nothrow(Base.RefValue{Int}, Const(:x), Int) +@test setfield!_nothrow(Base.RefValue{Int}, Const(1), Int) +@test setfield!_nothrow(Base.RefValue{Any}, Const(:x), Int) +@test setfield!_nothrow(Base.RefValue{Any}, Const(1), Int) +@test setfield!_nothrow(XY{Any,Any}, Const(:x), Int) +@test setfield!_nothrow(XY{Any,Any}, Const(:x), Any) +@test setfield!_nothrow(XY{Int,Float64}, Const(:x), Int) +@test !setfield!_nothrow(XY{Int,Float64}, Symbol, Any) +@test !setfield!_nothrow(XY{Int,Float64}, Int, Any) +@test !setfield!_nothrow(Base.RefValue{Int}, Const(:x), Any) +@test !setfield!_nothrow(Base.RefValue{Int}, Const(1), Any) +@test !setfield!_nothrow(Any[Base.RefValue{Any}, Const(:x), Int, Symbol]) +@test !setfield!_nothrow(Base.RefValue{Any}, Symbol, Int) +@test !setfield!_nothrow(Base.RefValue{Any}, Int, Int) +@test !setfield!_nothrow(XY{Int,Float64}, Const(:y), Int) +@test !setfield!_nothrow(XY{Int,Float64}, Symbol, Int) +@test !setfield!_nothrow(XY{Int,Float64}, Int, Int) +@test !setfield!_nothrow(Union{Base.RefValue{Any},Some{Any}}, Const(:x), Int) +@test !setfield!_nothrow(Union{Base.RefValue,Some{Any}}, Const(:x), Int) +@test !setfield!_nothrow(Union{Base.RefValue{Any},Some{Any}}, Const(1), Int) +@test !setfield!_nothrow(Union{Base.RefValue,Some{Any}}, Const(1), Int) +@test !setfield!_nothrow(Union{Base.RefValue{Any},Some{Any}}, Symbol, Int) +@test !setfield!_nothrow(Union{Base.RefValue,Some{Any}}, Symbol, Int) +@test !setfield!_nothrow(Union{Base.RefValue{Any},Some{Any}}, Int, Int) +@test !setfield!_nothrow(Union{Base.RefValue,Some{Any}}, Int, Int) +@test !setfield!_nothrow(Any, Symbol, Int) +@test !setfield!_nothrow(Any, Int, Int) +@test !setfield!_nothrow(Any, Any, Int) + struct Foo_22708 x::Ptr{Foo_22708} end @@ -3164,7 +3244,7 @@ end @test Core.Compiler.return_type(apply26826, Tuple{typeof(===), Any, Vararg}) == Bool @test Core.Compiler.return_type(apply26826, Tuple{typeof(===), Any, Any, Vararg}) == Bool @test Core.Compiler.return_type(apply26826, Tuple{typeof(===), Any, Any, Any, Vararg}) == Union{} -@test Core.Compiler.return_type(apply26826, Tuple{typeof(setfield!), Vararg{Symbol}}) == Symbol +@test Core.Compiler.return_type(apply26826, Tuple{typeof(setfield!), Vararg{Symbol}}) == Union{} @test Core.Compiler.return_type(apply26826, Tuple{typeof(setfield!), Any, Vararg{Symbol}}) == Symbol @test Core.Compiler.return_type(apply26826, Tuple{typeof(setfield!), Any, Symbol, Vararg{Integer}}) == Integer @test Core.Compiler.return_type(apply26826, Tuple{typeof(setfield!), Any, Symbol, Integer, Vararg}) == Integer