Skip to content

Commit

Permalink
effects: taint :consistent-cy on allocation/access of uninitialized…
Browse files Browse the repository at this point in the history
… fields
  • Loading branch information
aviatesk committed Jul 20, 2022
1 parent 64e47df commit 0ea76a8
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 13 deletions.
40 changes: 27 additions & 13 deletions base/compiler/abstractinterpretation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1966,14 +1966,16 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e),
end
elseif ehead === :new
t, isexact = instanceof_tfunc(abstract_eval_value(interp, e.args[1], vtypes, sv))
is_nothrow = true
consistent = nothrow = ALWAYS_TRUE
if isconcretedispatch(t)
isimmutable = !ismutabletype(t)
fcount = fieldcount(t)
nargs = length(e.args) - 1
fcount < nargs && @goto always_throw # guard error from `fieldtype`
ats = Vector{Any}(undef, nargs)
local anyrefine = false
local allconst = true
local is_nothrow = true
for i = 1:nargs
at = widenconditional(abstract_eval_value(interp, e.args[i+1], vtypes, sv))
ft = fieldtype(t, i)
Expand All @@ -1990,23 +1992,35 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e),
# For now, don't allow:
# - Const/PartialStruct of mutables
# - partially initialized Const/PartialStruct
if !ismutabletype(t) && fcount == nargs
if allconst
argvals = Vector{Any}(undef, nargs)
for j in 1:nargs
argvals[j] = (ats[j]::Const).val
if fcount == nargs
if isimmutable
if allconst
argvals = Vector{Any}(undef, nargs)
for j in 1:nargs
argvals[j] = (ats[j]::Const).val
end
t = Const(ccall(:jl_new_structv, Any, (Any, Ptr{Cvoid}, UInt32), t, argvals, nargs))
elseif anyrefine
t = PartialStruct(t, ats)
end
t = Const(ccall(:jl_new_structv, Any, (Any, Ptr{Cvoid}, UInt32), t, argvals, nargs))
elseif anyrefine
t = PartialStruct(t, ats)
else
# mutable object isn't `:consistent`, but we can still give the return
# type information a chance to refine this `:consistent`-cy later
consistent = TRISTATE_UNKNOWN
end
elseif any(i::Int -> !is_undefref_fieldtype(fieldtype(t, i)), (nargs+1):fcount)
# allocation with undefined field leads to undefined behavior and should taint `:consistent`-cy
consistent = ALWAYS_FALSE
elseif !isimmutable
consistent = TRISTATE_UNKNOWN
end
if !is_nothrow
nothrow = ALWAYS_FALSE
end
else
is_nothrow = false
consistent = nothrow = ALWAYS_FALSE
end
tristate_merge!(sv, Effects(EFFECTS_TOTAL;
consistent = !ismutabletype(t) ? ALWAYS_TRUE : TRISTATE_UNKNOWN,
nothrow = is_nothrow ? ALWAYS_TRUE : ALWAYS_FALSE))
tristate_merge!(sv, Effects(EFFECTS_TOTAL; consistent, nothrow))
elseif ehead === :splatnew
t, isexact = instanceof_tfunc(abstract_eval_value(interp, e.args[1], vtypes, sv))
is_nothrow = false # TODO: More precision
Expand Down
41 changes: 41 additions & 0 deletions base/compiler/tfuncs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -971,6 +971,43 @@ function _getfield_tfunc(@nospecialize(s00), @nospecialize(name), setfield::Bool
return rewrap_unionall(R, s00)
end

function getfield_notundefined(@nospecialize(obj), @nospecialize(name))
isa(name, Const) || return false
return _getfield_notundefined(widenconst(obj), name)
end
function _getfield_notundefined(@nospecialize(typ0), name::Const)
typ = unwrap_unionall(typ0)
if isa(typ, Union)
return _getfield_notundefined(rewrap_unionall(typ.a, typ0), name) &&
_getfield_notundefined(rewrap_unionall(typ.b, typ0), name)
end
isa(typ, DataType) || return false
name = name.val
if isa(name, Symbol)
fidx = fieldindex(typ, name, false)
fidx === nothing && return true # always throw doesn't account for undefined
elseif isa(name, Int)
fidx = name
else
return false
end
fcnt = fieldcount_noerror(typ)
fcnt === nothing && return false
0 < fidx fcnt || return true # always throw doesn't account for undefined behaviour
ftyp = fieldtype(typ, fidx)
is_undefref_fieldtype(ftyp) && return true
return fidx datatype_min_ninitialized(typ)
end

function is_undefref_fieldtype(@nospecialize ftyp)
if isa(ftyp, Union)
return is_undefref_fieldtype(ftyp.a) &&
is_undefref_fieldtype(ftyp.b)
end
isa(ftyp, DataType) || return false
return !isbitstype(ftyp)
end

function setfield!_tfunc(o, f, v, order)
@nospecialize
if !isvarargtype(order)
Expand Down Expand Up @@ -1816,6 +1853,10 @@ function builtin_effects(f::Builtin, argtypes::Vector{Any}, @nospecialize(rt))
end
s = s::DataType
consistent = !ismutabletype(s) ? ALWAYS_TRUE : ALWAYS_FALSE
# access to undefined field leads to undefined behavior and should taint `:consistent`-cy
if f === Core.getfield && !getfield_notundefined(s, argtypes[2])
consistent = ALWAYS_FALSE
end
if f === Core.getfield && !isvarargtype(argtypes[end]) && getfield_boundscheck(argtypes) !== true
# If we cannot independently prove inboundsness, taint consistency.
# The inbounds-ness assertion requires dynamic reachability, while
Expand Down
40 changes: 40 additions & 0 deletions test/compiler/effects.jl
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,46 @@ function compare_inconsistent(x::T) where T
end
@test !compare_inconsistent(3)

# allocation/access of uninitialized fields should taint the :consistent-cy
import Core.Compiler: Const, getfield_notundefined
for val = (1, :x)
name = Const(val)
@test getfield_notundefined(Base.RefValue{String}, name)
@test !getfield_notundefined(Base.RefValue{Int}, name)
@test getfield_notundefined(Base.RefValue{Integer}, name)
@test !getfield_notundefined(Base.RefValue{<:Integer}, name)
@test !getfield_notundefined(Base.RefValue{Union{Int32,Int64}}, name)
@test !getfield_notundefined(Base.RefValue, name)
@test !getfield_notundefined(Any, name)
end
for val = (0, 2, :y) # throw doesn't account for undefined behavior
name = Const(val)
@test getfield_notundefined(Base.RefValue{String}, name)
@test getfield_notundefined(Base.RefValue{Int}, name)
@test getfield_notundefined(Base.RefValue{Integer}, name)
@test getfield_notundefined(Base.RefValue{<:Integer}, name)
@test getfield_notundefined(Base.RefValue{Union{Int32,Int64}}, name)
@test getfield_notundefined(Base.RefValue, name)
@test !getfield_notundefined(Any, name)
end
# TODO test with Ref once we handle mutability more nicely
struct ImmutableRef{T}
x::T
ImmutableRef{T}() where T = new{T}()
ImmutableRef{T}(x) where T = new{T}(x)
ImmutableRef(x::T) where T = new{T}(x)
end
Base.getindex(x::ImmutableRef) = x.x
@test Base.infer_effects() do
ImmutableRef{Int}()
end |> !Core.Compiler.is_consistent
@test Base.infer_effects() do
ImmutableRef{Int}()[]
end |> !Core.Compiler.is_consistent
@test !fully_eliminated() do
ImmutableRef{Int}()[]
end

# effects propagation for `Core.invoke` calls
# https://github.com/JuliaLang/julia/issues/44763
global x44763::Int = 0
Expand Down

0 comments on commit 0ea76a8

Please sign in to comment.