Skip to content

Commit

Permalink
Properly guard UpsilonNode unboxed store
Browse files Browse the repository at this point in the history
In #51852, we are coercing a boxed `Union{@NamedTuple{progress::String}, @NamedTuple{progress::Float64}}`
to `@NamedTuple{progress::String}` via convert_julia_type. This results in a jl_cgval_t that has
a Vboxed that points to a boxed `@NamedTuple{progress::Float64}` but with a `@NamedTuple{progress::String}`
type tag that the up upsilonnode code then tries to unbox into the typed PhiC slot. This ends up treating
the Float64 as a pointer and crashing in GC. Avoid this by adding a runtime check that the converted value
is actually compatible (we already had this kind of check for the non-boxed cases) and then making the
unboxing runtime-conditional on the type of the union matching the type of the phic.

Fixes #51852
  • Loading branch information
Keno committed Oct 26, 2023
1 parent f8294a8 commit e1d6fd8
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 71 deletions.
82 changes: 43 additions & 39 deletions base/compiler/abstractinterpretation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2223,31 +2223,31 @@ end

function abstract_eval_special_value(interp::AbstractInterpreter, @nospecialize(e), vtypes::Union{VarTable,Nothing}, sv::AbsIntState)
if isa(e, QuoteNode)
return Const(e.value)
return RTEffects(Const(e.value), EFFECTS_TOTAL)
elseif isa(e, SSAValue)
return abstract_eval_ssavalue(e, sv)
return RTEffects(abstract_eval_ssavalue(e, sv), EFFECTS_TOTAL)
elseif isa(e, SlotNumber)
effects = EFFECTS_THROWS
if vtypes !== nothing
vtyp = vtypes[slot_id(e)]
if vtyp.undef
merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; nothrow=false))
if !vtyp.undef
effects = EFFECTS_TOTAL
end
return vtyp.typ
return RTEffects(vtyp.typ, effects)
end
merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; nothrow=false))
return Any
return RTEffects(Any, effects)
elseif isa(e, Argument)
if vtypes !== nothing
return vtypes[slot_id(e)].typ
return RTEffects(vtypes[slot_id(e)].typ, EFFECTS_TOTAL)
else
@assert isa(sv, IRInterpretationState)
return sv.ir.argtypes[e.n] # TODO frame_argtypes(sv)[e.n] and remove the assertion
return RTEffects(sv.ir.argtypes[e.n], EFFECTS_TOTAL) # TODO frame_argtypes(sv)[e.n] and remove the assertion
end
elseif isa(e, GlobalRef)
return abstract_eval_globalref(interp, e, sv)
end

return Const(e)
return RTEffects(Const(e), EFFECTS_TOTAL)
end

function abstract_eval_value_expr(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, sv::AbsIntState)
Expand Down Expand Up @@ -2281,8 +2281,9 @@ function abstract_eval_value(interp::AbstractInterpreter, @nospecialize(e), vtyp
if isa(e, Expr)
return abstract_eval_value_expr(interp, e, vtypes, sv)
else
typ = abstract_eval_special_value(interp, e, vtypes, sv)
return collect_limitations!(typ, sv)
(;rt, effects) = abstract_eval_special_value(interp, e, vtypes, sv)
merge_effects!(interp, sv, effects)
return collect_limitations!(rt, sv)
end
end

Expand Down Expand Up @@ -2608,7 +2609,9 @@ function abstract_eval_phi(interp::AbstractInterpreter, phi::PhiNode, vtypes::Un
for i in 1:length(phi.values)
isassigned(phi.values, i) || continue
val = phi.values[i]
rt = tmerge(typeinf_lattice(interp), rt, abstract_eval_special_value(interp, val, vtypes, sv))
# N.B.: Phi arguments are restricted to not have effects, so we can drop
# them here safely.
rt = tmerge(typeinf_lattice(interp), rt, abstract_eval_special_value(interp, val, vtypes, sv).rt)
end
return rt
end
Expand All @@ -2624,53 +2627,55 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e),
add_curr_ssaflag!(sv, IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW)
return abstract_eval_phi(interp, e, vtypes, sv)
end
return abstract_eval_special_value(interp, e, vtypes, sv)
end
(; rt, effects) = abstract_eval_statement_expr(interp, e, vtypes, sv)
if effects.noub === NOUB_IF_NOINBOUNDS
if !iszero(get_curr_ssaflag(sv) & IR_FLAG_INBOUNDS)
effects = Effects(effects; noub=ALWAYS_FALSE)
elseif !propagate_inbounds(sv)
# The callee read our inbounds flag, but unless we propagate inbounds,
# we ourselves don't read our parent's inbounds.
effects = Effects(effects; noub=ALWAYS_TRUE)
(; rt, effects) = abstract_eval_special_value(interp, e, vtypes, sv)
else
(; rt, effects) = abstract_eval_statement_expr(interp, e, vtypes, sv)
if effects.noub === NOUB_IF_NOINBOUNDS
if !iszero(get_curr_ssaflag(sv) & IR_FLAG_INBOUNDS)
effects = Effects(effects; noub=ALWAYS_FALSE)
elseif !propagate_inbounds(sv)
# The callee read our inbounds flag, but unless we propagate inbounds,
# we ourselves don't read our parent's inbounds.
effects = Effects(effects; noub=ALWAYS_TRUE)
end
end
e = e::Expr
@assert !isa(rt, TypeVar) "unhandled TypeVar"
rt = maybe_singleton_const(rt)
if !isempty(sv.pclimitations)
if rt isa Const || rt === Union{}
empty!(sv.pclimitations)
else
rt = LimitedAccuracy(rt, sv.pclimitations)
sv.pclimitations = IdSet{InferenceState}()
end
end
end
# N.B.: This only applies to the effects of the statement itself.
# It is possible for arguments (GlobalRef/:static_parameter) to throw,
# but these will be recomputed during SSA construction later.
set_curr_ssaflag!(sv, flags_for_effects(effects), IR_FLAGS_EFFECTS)
merge_effects!(interp, sv, effects)
e = e::Expr
@assert !isa(rt, TypeVar) "unhandled TypeVar"
rt = maybe_singleton_const(rt)
if !isempty(sv.pclimitations)
if rt isa Const || rt === Union{}
empty!(sv.pclimitations)
else
rt = LimitedAccuracy(rt, sv.pclimitations)
sv.pclimitations = IdSet{InferenceState}()
end
end

return rt
end

function isdefined_globalref(g::GlobalRef)
return ccall(:jl_globalref_boundp, Cint, (Any,), g) != 0
end

function abstract_eval_globalref(g::GlobalRef)
function abstract_eval_globalref_type(g::GlobalRef)
if isdefined_globalref(g) && isconst(g)
return Const(ccall(:jl_get_globalref_value, Any, (Any,), g))
end
ty = ccall(:jl_get_binding_type, Any, (Any, Any), g.mod, g.name)
ty === nothing && return Any
return ty
end
abstract_eval_global(M::Module, s::Symbol) = abstract_eval_globalref(GlobalRef(M, s))
abstract_eval_global(M::Module, s::Symbol) = abstract_eval_globalref_type(GlobalRef(M, s))

function abstract_eval_globalref(interp::AbstractInterpreter, g::GlobalRef, sv::AbsIntState)
rt = abstract_eval_globalref(g)
rt = abstract_eval_globalref_type(g)
consistent = inaccessiblememonly = ALWAYS_FALSE
nothrow = false
if isa(rt, Const)
Expand All @@ -2685,8 +2690,7 @@ function abstract_eval_globalref(interp::AbstractInterpreter, g::GlobalRef, sv::
consistent = inaccessiblememonly = ALWAYS_TRUE
rt = Union{}
end
merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; consistent, nothrow, inaccessiblememonly))
return rt
return RTEffects(rt, Effects(EFFECTS_TOTAL; consistent, nothrow, inaccessiblememonly))
end

function handle_global_assignment!(interp::AbstractInterpreter, frame::InferenceState, lhs::GlobalRef, @nospecialize(newty))
Expand Down
2 changes: 1 addition & 1 deletion base/compiler/optimize.jl
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ function argextype(
elseif isa(x, QuoteNode)
return Const(x.value)
elseif isa(x, GlobalRef)
return abstract_eval_globalref(x)
return abstract_eval_globalref_type(x)
elseif isa(x, PhiNode)
return Any
elseif isa(x, PiNode)
Expand Down
2 changes: 1 addition & 1 deletion base/compiler/ssair/slot2ssa.jl
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ function typ_for_val(@nospecialize(x), ci::CodeInfo, ir::IRCode, idx::Int, slott
end
return (ci.ssavaluetypes::Vector{Any})[idx]
end
isa(x, GlobalRef) && return abstract_eval_globalref(x)
isa(x, GlobalRef) && return abstract_eval_globalref_type(x)
isa(x, SSAValue) && return (ci.ssavaluetypes::Vector{Any})[x.id]
isa(x, Argument) && return slottypes[x.n]
isa(x, NewSSAValue) && return types(ir)[new_to_regular(x, length(ir.stmts))]
Expand Down
2 changes: 1 addition & 1 deletion base/compiler/utilities.jl
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ function is_throw_call(e::Expr)
if e.head === :call
f = e.args[1]
if isa(f, GlobalRef)
ff = abstract_eval_globalref(f)
ff = abstract_eval_globalref_type(f)
if isa(ff, Const) && ff.val === Core.throw
return true
end
Expand Down
3 changes: 3 additions & 0 deletions src/cgutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1385,6 +1385,9 @@ static void null_pointer_check(jl_codectx_t &ctx, Value *v, Value **nullcheck =
template<typename Func>
static Value *emit_guarded_test(jl_codectx_t &ctx, Value *ifnot, Value *defval, Func &&func)
{
if (!ifnot) {
return func();
}
if (auto Cond = dyn_cast<ConstantInt>(ifnot)) {
if (Cond->isZero())
return defval;
Expand Down
67 changes: 39 additions & 28 deletions src/codegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2318,29 +2318,28 @@ static jl_cgval_t convert_julia_type(jl_codectx_t &ctx, const jl_cgval_t &v, jl_
return ghostValue(ctx, typ);
Value *new_tindex = NULL;
if (jl_is_concrete_type(typ)) {
if (v.TIndex && !jl_is_pointerfree(typ)) {
// discovered that this union-split type must actually be isboxed
if (v.Vboxed) {
return jl_cgval_t(v.Vboxed, true, typ, NULL, best_tbaa(ctx.tbaa(), typ));
}
else {
// type mismatch: there weren't any boxed values in the union
if (skip)
*skip = ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 1);
else
CreateTrap(ctx.builder);
return jl_cgval_t();
}
}
if (jl_is_concrete_type(v.typ)) {
if (jl_is_concrete_type(typ)) {
// type mismatch: changing from one leaftype to another
if (skip)
*skip = ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 1);
else
CreateTrap(ctx.builder);
return jl_cgval_t();
// type mismatch: changing from one leaftype to another
if (skip)
*skip = ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 1);
else
CreateTrap(ctx.builder);
return jl_cgval_t();
}
bool mustbox_union = v.TIndex && !jl_is_pointerfree(typ);
if (v.Vboxed && (v.isboxed || mustbox_union)) {
if (skip) {
*skip = ctx.builder.CreateNot(emit_exactly_isa(ctx, v, (jl_datatype_t*)typ));
}
return jl_cgval_t(v.Vboxed, true, typ, NULL, best_tbaa(ctx.tbaa(), typ));
}
if (mustbox_union) {
// type mismatch: there weren't any boxed values in the union
if (skip)
*skip = ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 1);
else
CreateTrap(ctx.builder);
return jl_cgval_t();
}
}
else {
Expand Down Expand Up @@ -5194,9 +5193,13 @@ static void emit_varinfo_assign(jl_codectx_t &ctx, jl_varinfo_t &vi, jl_cgval_t
// If allow_mismatch is set, type mismatches will not result in traps.
// This is used for upsilon nodes, where the destination can have a narrower
// type than the store, if inference determines that the store is never read.
Value *dummy = NULL;
Value **skip = allow_mismatch ? &dummy : NULL;
rval_info = convert_julia_type(ctx, rval_info, slot_type, skip);
Value *skip = NULL;
rval_info = convert_julia_type(ctx, rval_info, slot_type, &skip);
if (!allow_mismatch && skip) {
CreateTrap(ctx.builder);
return;
}

if (rval_info.typ == jl_bottom_type)
return;

Expand Down Expand Up @@ -5241,8 +5244,13 @@ static void emit_varinfo_assign(jl_codectx_t &ctx, jl_varinfo_t &vi, jl_cgval_t

// store unboxed variables
if (!vi.boxroot || (vi.pTIndex && rval_info.TIndex)) {
emit_vi_assignment_unboxed(ctx, vi, isboxed, rval_info);
emit_guarded_test(ctx, skip ? ctx.builder.CreateNot(skip) : nullptr, nullptr, [&]{
emit_vi_assignment_unboxed(ctx, vi, isboxed, rval_info);
return nullptr;
});
}

return;
}

static void emit_assignment(jl_codectx_t &ctx, jl_value_t *l, jl_value_t *r, ssize_t ssaval)
Expand All @@ -5254,7 +5262,8 @@ static void emit_assignment(jl_codectx_t &ctx, jl_value_t *l, jl_value_t *r, ssi
int sl = jl_slot_number(l) - 1;
// it's a local variable
jl_varinfo_t &vi = ctx.slots[sl];
return emit_varinfo_assign(ctx, vi, rval_info, l);
emit_varinfo_assign(ctx, vi, rval_info, l);
return;
}

jl_module_t *mod;
Expand Down Expand Up @@ -5285,15 +5294,17 @@ static void emit_upsilonnode(jl_codectx_t &ctx, ssize_t phic, jl_value_t *val)
// upsilon node is not dynamically observed.
if (val) {
jl_cgval_t rval_info = emit_expr(ctx, val);
if (rval_info.typ == jl_bottom_type)
if (rval_info.typ == jl_bottom_type) {
// as a special case, PhiC nodes are allowed to use undefined
// values, since they are just copy operations, so we need to
// ignore the store (it will not by dynamically observed), while
// normally, for any other operation result, we'd assume this store
// was unreachable and dead
val = NULL;
else
}
else {
emit_varinfo_assign(ctx, vi, rval_info, NULL, true);
}
}
if (!val) {
if (vi.boxroot) {
Expand Down
2 changes: 1 addition & 1 deletion stdlib/REPL/src/REPLCompletions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@ function CC.abstract_eval_globalref(interp::REPLInterpreter, g::GlobalRef,
if CC.isdefined_globalref(g)
return Const(ccall(:jl_get_globalref_value, Any, (Any,), g))
end
return Union{}
return RTEffects(Union{}, EFFECTS_THROWS)
end
return @invoke CC.abstract_eval_globalref(interp::CC.AbstractInterpreter, g::GlobalRef,
sv::CC.InferenceState)
Expand Down
78 changes: 78 additions & 0 deletions test/compiler/inference.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5353,3 +5353,81 @@ function phic_type3()
end
@test Base.return_types(phic_type3) |> only === Union{Int, Float64}
@test phic_type3() === 2

# Issue #51852
function phic_type4()
a = (;progress = "a")
try
may_error(false)
let b = Base.inferencebarrier(true) ? (;progress = 1.0) : a
a = b
end
catch
end
GC.gc()
return a
end
@test Base.return_types(phic_type4) |> only === Union{@NamedTuple{progress::Float64}, @NamedTuple{progress::String}}
@test phic_type4() === (;progress = 1.0)

function phic_type5()
a = (;progress = "a")
try
vals = (a, (progress=1.0,))
may_error(false)
a = vals[Base.inferencebarrier(false) ? 1 : 2]
catch
end
GC.gc()
return a
end
@test Base.return_types(phic_type5) |> only === Union{@NamedTuple{progress::Float64}, @NamedTuple{progress::String}}
@test phic_type5() === (;progress = 1.0)

function phic_type6()
a = Base.inferencebarrier(true) ? (;progress = "a") : (;progress = Ref{Any}(0))
try
may_error(false)
let b = Base.inferencebarrier(true) ? (;progress = 1.0) : a
a = b
end
catch
end
GC.gc()
return a
end
@test Base.return_types(phic_type6) |> only === Union{@NamedTuple{progress::Float64}, @NamedTuple{progress::Base.RefValue{Any}}, @NamedTuple{progress::String}}
@test phic_type6() === (;progress = 1.0)

function phic_type7()
a = Base.inferencebarrier(true) ? (;progress = "a") : (;progress = Ref{Any}(0))
try
vals = (a, (progress=1.0,))
may_error(false)
a = vals[Base.inferencebarrier(false) ? 1 : 2]
catch
end
GC.gc()
return a
end
@test Base.return_types(phic_type7) |> only === Union{@NamedTuple{progress::Float64}, @NamedTuple{progress::Base.RefValue{Any}}, @NamedTuple{progress::String}}
@test phic_type7() === (;progress = 1.0)

function phic_type8()
local a
try
may_error(true)
a = Base.inferencebarrier(1)
catch
end

try
a = 2
may_error(true)
catch
end
GC.gc()
return a
end
@test Base.return_types(phic_type8) |> only === Int
@test phic_type8() === 2

0 comments on commit e1d6fd8

Please sign in to comment.