Skip to content

Commit 47ed081

Browse files
committed
Properly guard UpsilonNode unboxed store
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
1 parent 072896d commit 47ed081

File tree

11 files changed

+207
-84
lines changed

11 files changed

+207
-84
lines changed

base/compiler/abstractinterpretation.jl

Lines changed: 43 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2230,31 +2230,31 @@ end
22302230

22312231
function abstract_eval_special_value(interp::AbstractInterpreter, @nospecialize(e), vtypes::Union{VarTable,Nothing}, sv::AbsIntState)
22322232
if isa(e, QuoteNode)
2233-
return Const(e.value)
2233+
return RTEffects(Const(e.value), EFFECTS_TOTAL)
22342234
elseif isa(e, SSAValue)
2235-
return abstract_eval_ssavalue(e, sv)
2235+
return RTEffects(abstract_eval_ssavalue(e, sv), EFFECTS_TOTAL)
22362236
elseif isa(e, SlotNumber)
2237+
effects = EFFECTS_THROWS
22372238
if vtypes !== nothing
22382239
vtyp = vtypes[slot_id(e)]
2239-
if vtyp.undef
2240-
merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; nothrow=false))
2240+
if !vtyp.undef
2241+
effects = EFFECTS_TOTAL
22412242
end
2242-
return vtyp.typ
2243+
return RTEffects(vtyp.typ, effects)
22432244
end
2244-
merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; nothrow=false))
2245-
return Any
2245+
return RTEffects(Any, effects)
22462246
elseif isa(e, Argument)
22472247
if vtypes !== nothing
2248-
return vtypes[slot_id(e)].typ
2248+
return RTEffects(vtypes[slot_id(e)].typ, EFFECTS_TOTAL)
22492249
else
22502250
@assert isa(sv, IRInterpretationState)
2251-
return sv.ir.argtypes[e.n] # TODO frame_argtypes(sv)[e.n] and remove the assertion
2251+
return RTEffects(sv.ir.argtypes[e.n], EFFECTS_TOTAL) # TODO frame_argtypes(sv)[e.n] and remove the assertion
22522252
end
22532253
elseif isa(e, GlobalRef)
22542254
return abstract_eval_globalref(interp, e, sv)
22552255
end
22562256

2257-
return Const(e)
2257+
return RTEffects(Const(e), EFFECTS_TOTAL)
22582258
end
22592259

22602260
function abstract_eval_value_expr(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, sv::AbsIntState)
@@ -2288,8 +2288,9 @@ function abstract_eval_value(interp::AbstractInterpreter, @nospecialize(e), vtyp
22882288
if isa(e, Expr)
22892289
return abstract_eval_value_expr(interp, e, vtypes, sv)
22902290
else
2291-
typ = abstract_eval_special_value(interp, e, vtypes, sv)
2292-
return collect_limitations!(typ, sv)
2291+
(;rt, effects) = abstract_eval_special_value(interp, e, vtypes, sv)
2292+
merge_effects!(interp, sv, effects)
2293+
return collect_limitations!(rt, sv)
22932294
end
22942295
end
22952296

@@ -2615,7 +2616,9 @@ function abstract_eval_phi(interp::AbstractInterpreter, phi::PhiNode, vtypes::Un
26152616
for i in 1:length(phi.values)
26162617
isassigned(phi.values, i) || continue
26172618
val = phi.values[i]
2618-
rt = tmerge(typeinf_lattice(interp), rt, abstract_eval_special_value(interp, val, vtypes, sv))
2619+
# N.B.: Phi arguments are restricted to not have effects, so we can drop
2620+
# them here safely.
2621+
rt = tmerge(typeinf_lattice(interp), rt, abstract_eval_special_value(interp, val, vtypes, sv).rt)
26192622
end
26202623
return rt
26212624
end
@@ -2631,53 +2634,55 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e),
26312634
add_curr_ssaflag!(sv, IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW)
26322635
return abstract_eval_phi(interp, e, vtypes, sv)
26332636
end
2634-
return abstract_eval_special_value(interp, e, vtypes, sv)
2635-
end
2636-
(; rt, effects) = abstract_eval_statement_expr(interp, e, vtypes, sv)
2637-
if effects.noub === NOUB_IF_NOINBOUNDS
2638-
if !iszero(get_curr_ssaflag(sv) & IR_FLAG_INBOUNDS)
2639-
effects = Effects(effects; noub=ALWAYS_FALSE)
2640-
elseif !propagate_inbounds(sv)
2641-
# The callee read our inbounds flag, but unless we propagate inbounds,
2642-
# we ourselves don't read our parent's inbounds.
2643-
effects = Effects(effects; noub=ALWAYS_TRUE)
2637+
(; rt, effects) = abstract_eval_special_value(interp, e, vtypes, sv)
2638+
else
2639+
(; rt, effects) = abstract_eval_statement_expr(interp, e, vtypes, sv)
2640+
if effects.noub === NOUB_IF_NOINBOUNDS
2641+
if !iszero(get_curr_ssaflag(sv) & IR_FLAG_INBOUNDS)
2642+
effects = Effects(effects; noub=ALWAYS_FALSE)
2643+
elseif !propagate_inbounds(sv)
2644+
# The callee read our inbounds flag, but unless we propagate inbounds,
2645+
# we ourselves don't read our parent's inbounds.
2646+
effects = Effects(effects; noub=ALWAYS_TRUE)
2647+
end
2648+
end
2649+
e = e::Expr
2650+
@assert !isa(rt, TypeVar) "unhandled TypeVar"
2651+
rt = maybe_singleton_const(rt)
2652+
if !isempty(sv.pclimitations)
2653+
if rt isa Const || rt === Union{}
2654+
empty!(sv.pclimitations)
2655+
else
2656+
rt = LimitedAccuracy(rt, sv.pclimitations)
2657+
sv.pclimitations = IdSet{InferenceState}()
2658+
end
26442659
end
26452660
end
26462661
# N.B.: This only applies to the effects of the statement itself.
26472662
# It is possible for arguments (GlobalRef/:static_parameter) to throw,
26482663
# but these will be recomputed during SSA construction later.
26492664
set_curr_ssaflag!(sv, flags_for_effects(effects), IR_FLAGS_EFFECTS)
26502665
merge_effects!(interp, sv, effects)
2651-
e = e::Expr
2652-
@assert !isa(rt, TypeVar) "unhandled TypeVar"
2653-
rt = maybe_singleton_const(rt)
2654-
if !isempty(sv.pclimitations)
2655-
if rt isa Const || rt === Union{}
2656-
empty!(sv.pclimitations)
2657-
else
2658-
rt = LimitedAccuracy(rt, sv.pclimitations)
2659-
sv.pclimitations = IdSet{InferenceState}()
2660-
end
2661-
end
2666+
26622667
return rt
26632668
end
26642669

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

2669-
function abstract_eval_globalref(g::GlobalRef)
2674+
function abstract_eval_globalref_type(g::GlobalRef)
26702675
if isdefined_globalref(g) && isconst(g)
26712676
return Const(ccall(:jl_get_globalref_value, Any, (Any,), g))
26722677
end
26732678
ty = ccall(:jl_get_binding_type, Any, (Any, Any), g.mod, g.name)
26742679
ty === nothing && return Any
26752680
return ty
26762681
end
2677-
abstract_eval_global(M::Module, s::Symbol) = abstract_eval_globalref(GlobalRef(M, s))
2682+
abstract_eval_global(M::Module, s::Symbol) = abstract_eval_globalref_type(GlobalRef(M, s))
26782683

26792684
function abstract_eval_globalref(interp::AbstractInterpreter, g::GlobalRef, sv::AbsIntState)
2680-
rt = abstract_eval_globalref(g)
2685+
rt = abstract_eval_globalref_type(g)
26812686
consistent = inaccessiblememonly = ALWAYS_FALSE
26822687
nothrow = false
26832688
if isa(rt, Const)
@@ -2692,8 +2697,7 @@ function abstract_eval_globalref(interp::AbstractInterpreter, g::GlobalRef, sv::
26922697
consistent = inaccessiblememonly = ALWAYS_TRUE
26932698
rt = Union{}
26942699
end
2695-
merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; consistent, nothrow, inaccessiblememonly))
2696-
return rt
2700+
return RTEffects(rt, Effects(EFFECTS_TOTAL; consistent, nothrow, inaccessiblememonly))
26972701
end
26982702

26992703
function handle_global_assignment!(interp::AbstractInterpreter, frame::InferenceState, lhs::GlobalRef, @nospecialize(newty))

base/compiler/optimize.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ function argextype(
396396
elseif isa(x, QuoteNode)
397397
return Const(x.value)
398398
elseif isa(x, GlobalRef)
399-
return abstract_eval_globalref(x)
399+
return abstract_eval_globalref_type(x)
400400
elseif isa(x, PhiNode)
401401
return Any
402402
elseif isa(x, PiNode)

base/compiler/ssair/slot2ssa.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ function typ_for_val(@nospecialize(x), ci::CodeInfo, ir::IRCode, idx::Int, slott
207207
end
208208
return (ci.ssavaluetypes::Vector{Any})[idx]
209209
end
210-
isa(x, GlobalRef) && return abstract_eval_globalref(x)
210+
isa(x, GlobalRef) && return abstract_eval_globalref_type(x)
211211
isa(x, SSAValue) && return (ci.ssavaluetypes::Vector{Any})[x.id]
212212
isa(x, Argument) && return slottypes[x.n]
213213
isa(x, NewSSAValue) && return types(ir)[new_to_regular(x, length(ir.stmts))]

base/compiler/utilities.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@ function is_throw_call(e::Expr)
431431
if e.head === :call
432432
f = e.args[1]
433433
if isa(f, GlobalRef)
434-
ff = abstract_eval_globalref(f)
434+
ff = abstract_eval_globalref_type(f)
435435
if isa(ff, Const) && ff.val === Core.throw
436436
return true
437437
end

src/cgutils.cpp

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1406,6 +1406,9 @@ static void null_pointer_check(jl_codectx_t &ctx, Value *v, Value **nullcheck =
14061406
template<typename Func>
14071407
static Value *emit_guarded_test(jl_codectx_t &ctx, Value *ifnot, Value *defval, Func &&func)
14081408
{
1409+
if (!ifnot) {
1410+
return func();
1411+
}
14091412
if (auto Cond = dyn_cast<ConstantInt>(ifnot)) {
14101413
if (Cond->isZero())
14111414
return defval;
@@ -1544,7 +1547,7 @@ static bool can_optimize_isa_union(jl_uniontype_t *type)
15441547
}
15451548

15461549
// a simple case of emit_isa that is obvious not to include a safe-point
1547-
static Value *emit_exactly_isa(jl_codectx_t &ctx, const jl_cgval_t &arg, jl_datatype_t *dt)
1550+
static Value *emit_exactly_isa(jl_codectx_t &ctx, const jl_cgval_t &arg, jl_datatype_t *dt, bool could_be_null=false)
15481551
{
15491552
assert(jl_is_concrete_type((jl_value_t*)dt));
15501553
if (arg.TIndex) {
@@ -1559,6 +1562,10 @@ static Value *emit_exactly_isa(jl_codectx_t &ctx, const jl_cgval_t &arg, jl_data
15591562
else if (arg.Vboxed) {
15601563
// test for (arg.TIndex == UNION_BOX_MARKER && typeof(arg.V) == type)
15611564
Value *isboxed = ctx.builder.CreateICmpEQ(arg.TIndex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), UNION_BOX_MARKER));
1565+
if (could_be_null) {
1566+
isboxed = ctx.builder.CreateAnd(isboxed,
1567+
ctx.builder.CreateNot(null_pointer_cmp(ctx, arg.Vboxed)));
1568+
}
15621569
setName(ctx.emission_context, isboxed, "isboxed");
15631570
BasicBlock *currBB = ctx.builder.GetInsertBlock();
15641571
BasicBlock *isaBB = BasicBlock::Create(ctx.builder.getContext(), "isa", ctx.f);
@@ -1579,9 +1586,16 @@ static Value *emit_exactly_isa(jl_codectx_t &ctx, const jl_cgval_t &arg, jl_data
15791586
return ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 0);
15801587
}
15811588
}
1582-
auto isa = ctx.builder.CreateICmpEQ(emit_typeof(ctx, arg, false, true), emit_tagfrom(ctx, dt));
1583-
setName(ctx.emission_context, isa, "exactly_isa");
1584-
return isa;
1589+
Value *isnull = NULL;
1590+
if (could_be_null && arg.isboxed) {
1591+
isnull = null_pointer_cmp(ctx, arg.Vboxed);
1592+
}
1593+
Constant *Vfalse = ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 0);
1594+
return emit_guarded_test(ctx, isnull, Vfalse, [&]{
1595+
auto isa = ctx.builder.CreateICmpEQ(emit_typeof(ctx, arg, false, true), emit_tagfrom(ctx, dt));
1596+
setName(ctx.emission_context, isa, "exactly_isa");
1597+
return isa;
1598+
});
15851599
}
15861600

15871601
static std::pair<Value*, bool> emit_isa(jl_codectx_t &ctx, const jl_cgval_t &x,

src/codegen.cpp

Lines changed: 49 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1590,8 +1590,17 @@ struct jl_cgval_t {
15901590
// runtime values) if `(TIndex | UNION_BOX_MARKER) != 0`, then `Vboxed == V` (by value).
15911591
// For convenience, we also set this value of isboxed values, in which case
15921592
// it is equal (at compile time) to V.
1593-
// If this is non-NULL, it is always of type `T_prjlvalue`
1593+
1594+
// If this is non-NULL (at compile time), it is always of type `T_prjlvalue`.
1595+
// N.B.: In general we expect this to always be a dereferenceable pointer at runtime.
1596+
// However, there are situations where this value may be a runtime NULL
1597+
// (PhiNodes with undef predecessors or PhiC with undef UpsilonNode).
1598+
// The middle-end arranges appropriate error checks before any use
1599+
// of this value that may read a non-dereferenceable Vboxed, with two
1600+
// exceptions: PhiNode and UpsilonNode arguments which need special
1601+
// handling to account for the possibility that this may be NULL.
15941602
Value *Vboxed;
1603+
15951604
Value *TIndex; // if `V` is an unboxed (tagged) Union described by `typ`, this gives the DataType index (1-based, small int) as an i8
15961605
jl_value_t *constant; // constant value (rooted in linfo.def.roots)
15971606
jl_value_t *typ; // the original type of V, never NULL
@@ -2408,29 +2417,28 @@ static jl_cgval_t convert_julia_type(jl_codectx_t &ctx, const jl_cgval_t &v, jl_
24082417
return ghostValue(ctx, typ);
24092418
Value *new_tindex = NULL;
24102419
if (jl_is_concrete_type(typ)) {
2411-
if (v.TIndex && !jl_is_pointerfree(typ)) {
2412-
// discovered that this union-split type must actually be isboxed
2413-
if (v.Vboxed) {
2414-
return jl_cgval_t(v.Vboxed, true, typ, NULL, best_tbaa(ctx.tbaa(), typ));
2415-
}
2416-
else {
2417-
// type mismatch: there weren't any boxed values in the union
2418-
if (skip)
2419-
*skip = ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 1);
2420-
else
2421-
CreateTrap(ctx.builder);
2422-
return jl_cgval_t();
2423-
}
2424-
}
24252420
if (jl_is_concrete_type(v.typ)) {
2426-
if (jl_is_concrete_type(typ)) {
2427-
// type mismatch: changing from one leaftype to another
2428-
if (skip)
2429-
*skip = ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 1);
2430-
else
2431-
CreateTrap(ctx.builder);
2432-
return jl_cgval_t();
2421+
// type mismatch: changing from one leaftype to another
2422+
if (skip)
2423+
*skip = ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 1);
2424+
else
2425+
CreateTrap(ctx.builder);
2426+
return jl_cgval_t();
2427+
}
2428+
bool mustbox_union = v.TIndex && !jl_is_pointerfree(typ);
2429+
if (v.Vboxed && (v.isboxed || mustbox_union)) {
2430+
if (skip) {
2431+
*skip = ctx.builder.CreateNot(emit_exactly_isa(ctx, v, (jl_datatype_t*)typ, true));
24332432
}
2433+
return jl_cgval_t(v.Vboxed, true, typ, NULL, best_tbaa(ctx.tbaa(), typ));
2434+
}
2435+
if (mustbox_union) {
2436+
// type mismatch: there weren't any boxed values in the union
2437+
if (skip)
2438+
*skip = ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 1);
2439+
else
2440+
CreateTrap(ctx.builder);
2441+
return jl_cgval_t();
24342442
}
24352443
}
24362444
else {
@@ -5302,9 +5310,13 @@ static void emit_varinfo_assign(jl_codectx_t &ctx, jl_varinfo_t &vi, jl_cgval_t
53025310
// If allow_mismatch is set, type mismatches will not result in traps.
53035311
// This is used for upsilon nodes, where the destination can have a narrower
53045312
// type than the store, if inference determines that the store is never read.
5305-
Value *dummy = NULL;
5306-
Value **skip = allow_mismatch ? &dummy : NULL;
5307-
rval_info = convert_julia_type(ctx, rval_info, slot_type, skip);
5313+
Value *skip = NULL;
5314+
rval_info = convert_julia_type(ctx, rval_info, slot_type, &skip);
5315+
if (!allow_mismatch && skip) {
5316+
CreateTrap(ctx.builder);
5317+
return;
5318+
}
5319+
53085320
if (rval_info.typ == jl_bottom_type)
53095321
return;
53105322

@@ -5349,8 +5361,13 @@ static void emit_varinfo_assign(jl_codectx_t &ctx, jl_varinfo_t &vi, jl_cgval_t
53495361

53505362
// store unboxed variables
53515363
if (!vi.boxroot || (vi.pTIndex && rval_info.TIndex)) {
5352-
emit_vi_assignment_unboxed(ctx, vi, isboxed, rval_info);
5364+
emit_guarded_test(ctx, skip ? ctx.builder.CreateNot(skip) : nullptr, nullptr, [&]{
5365+
emit_vi_assignment_unboxed(ctx, vi, isboxed, rval_info);
5366+
return nullptr;
5367+
});
53535368
}
5369+
5370+
return;
53545371
}
53555372

53565373
static void emit_assignment(jl_codectx_t &ctx, jl_value_t *l, jl_value_t *r, ssize_t ssaval)
@@ -5362,7 +5379,8 @@ static void emit_assignment(jl_codectx_t &ctx, jl_value_t *l, jl_value_t *r, ssi
53625379
int sl = jl_slot_number(l) - 1;
53635380
// it's a local variable
53645381
jl_varinfo_t &vi = ctx.slots[sl];
5365-
return emit_varinfo_assign(ctx, vi, rval_info, l);
5382+
emit_varinfo_assign(ctx, vi, rval_info, l);
5383+
return;
53665384
}
53675385

53685386
jl_module_t *mod;
@@ -5393,15 +5411,17 @@ static void emit_upsilonnode(jl_codectx_t &ctx, ssize_t phic, jl_value_t *val)
53935411
// upsilon node is not dynamically observed.
53945412
if (val) {
53955413
jl_cgval_t rval_info = emit_expr(ctx, val);
5396-
if (rval_info.typ == jl_bottom_type)
5414+
if (rval_info.typ == jl_bottom_type) {
53975415
// as a special case, PhiC nodes are allowed to use undefined
53985416
// values, since they are just copy operations, so we need to
53995417
// ignore the store (it will not by dynamically observed), while
54005418
// normally, for any other operation result, we'd assume this store
54015419
// was unreachable and dead
54025420
val = NULL;
5403-
else
5421+
}
5422+
else {
54045423
emit_varinfo_assign(ctx, vi, rval_info, NULL, true);
5424+
}
54055425
}
54065426
if (!val) {
54075427
if (vi.boxroot) {

stdlib/REPL/src/REPLCompletions.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -533,9 +533,9 @@ function CC.abstract_eval_globalref(interp::REPLInterpreter, g::GlobalRef,
533533
sv::CC.InferenceState)
534534
if (interp.limit_aggressive_inference ? is_repl_frame(sv) : is_call_graph_uncached(sv))
535535
if CC.isdefined_globalref(g)
536-
return Const(ccall(:jl_get_globalref_value, Any, (Any,), g))
536+
return CC.RTEffects(Const(ccall(:jl_get_globalref_value, Any, (Any,), g)), CC.EFFECTS_TOTAL)
537537
end
538-
return Union{}
538+
return CC.RTEffects(Union{}, CC.EFFECTS_THROWS)
539539
end
540540
return @invoke CC.abstract_eval_globalref(interp::CC.AbstractInterpreter, g::GlobalRef,
541541
sv::CC.InferenceState)

test/compiler/EscapeAnalysis/EscapeAnalysis.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ end
201201
let # try/catch
202202
result = code_escapes((Any,)) do a
203203
try
204+
println("prevent ConstABI")
204205
nothing
205206
catch err
206207
return a # return escape
@@ -210,6 +211,7 @@ end
210211
end
211212
let result = code_escapes((Any,)) do a
212213
try
214+
println("prevent ConstABI")
213215
nothing
214216
finally
215217
return a # return escape

0 commit comments

Comments
 (0)