mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-03-08 02:24:33 +01:00
Sema: harden switch logic against undef IB
Most places where `undefined` was previously (intentionally) passed across function calls now use `Air.Inst.Ref.none` instead to ensure that these `undefined` references don't accidentally outlive the `switch` logic they belong to.
This commit is contained in:
parent
fa988e88ed
commit
c7c4e8d802
1 changed files with 47 additions and 35 deletions
82
src/Sema.zig
82
src/Sema.zig
|
|
@ -10761,7 +10761,7 @@ fn analyzeSwitchBlock(
|
|||
const val, const ref = if (operand_is_ref)
|
||||
.{ try sema.analyzeLoad(block, src, raw_operand, operand_src), raw_operand }
|
||||
else
|
||||
.{ raw_operand, undefined };
|
||||
.{ raw_operand, .none };
|
||||
|
||||
const operand_ty = sema.typeOf(val);
|
||||
const maybe_operand_opv = try sema.typeHasOnePossibleValue(operand_ty);
|
||||
|
|
@ -10785,7 +10785,7 @@ fn analyzeSwitchBlock(
|
|||
const operand_alloc = try block.addTy(.alloc, operand_ptr_ty);
|
||||
_ = try block.addBinOp(.store, operand_alloc, raw_operand);
|
||||
break :alloc operand_alloc;
|
||||
} else undefined;
|
||||
} else .none;
|
||||
break :operand .{ .{ .loop = .{
|
||||
.operand_alloc = operand_alloc,
|
||||
.operand_is_ref = operand_is_ref,
|
||||
|
|
@ -10857,7 +10857,7 @@ fn analyzeSwitchBlock(
|
|||
const new_val, const new_ref = if (operand_is_ref)
|
||||
.{ try sema.analyzeLoad(child_block, src, new_operand, new_operand_src), new_operand }
|
||||
else
|
||||
.{ new_operand, undefined };
|
||||
.{ new_operand, .none };
|
||||
|
||||
const new_cond_ref = if (union_originally)
|
||||
try sema.unionToTag(child_block, item_ty, new_val, src)
|
||||
|
|
@ -10953,7 +10953,7 @@ fn analyzeSwitchBlock(
|
|||
const by_val = try sema.analyzeLoad(block, src, loaded, src);
|
||||
break :load_operand .{ by_val, loaded };
|
||||
} else {
|
||||
break :load_operand .{ loaded, undefined };
|
||||
break :load_operand .{ loaded, .none };
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
@ -11393,33 +11393,31 @@ fn finishSwitchBr(
|
|||
var emit_bb = false;
|
||||
if (has_else and else_case.is_inline) {
|
||||
const else_prong_src = block.src(.{ .node_offset_switch_else_prong = src_node_offset });
|
||||
var error_names: InternPool.NullTerminatedString.Slice = undefined;
|
||||
var min_int: Value = undefined;
|
||||
check_enumerable: {
|
||||
const error_names, const min_int = check_enumerable: {
|
||||
switch (item_ty.zigTypeTag(zcu)) {
|
||||
.@"union" => unreachable,
|
||||
.@"enum" => if (else_is_named_only or
|
||||
!item_ty.isNonexhaustiveEnum(zcu) or union_originally)
|
||||
{
|
||||
try branch_hints.ensureUnusedCapacity(gpa, @intCast(validated_switch.seen_enum_fields.len));
|
||||
break :check_enumerable;
|
||||
break :check_enumerable .{ undefined, undefined };
|
||||
},
|
||||
.error_set => if (!operand_ty.isAnyError(zcu)) {
|
||||
error_names = item_ty.errorSetNames(zcu);
|
||||
const error_names = item_ty.errorSetNames(zcu);
|
||||
try branch_hints.ensureUnusedCapacity(gpa, error_names.len);
|
||||
break :check_enumerable;
|
||||
break :check_enumerable .{ error_names, undefined };
|
||||
},
|
||||
.int => {
|
||||
min_int = try item_ty.minInt(pt, item_ty);
|
||||
break :check_enumerable;
|
||||
const min_int = try item_ty.minInt(pt, item_ty);
|
||||
break :check_enumerable .{ undefined, min_int };
|
||||
},
|
||||
.bool, .void => break :check_enumerable,
|
||||
.bool, .void => break :check_enumerable .{ undefined, undefined },
|
||||
else => {},
|
||||
}
|
||||
return sema.fail(block, else_prong_src, "cannot enumerate values of type '{f}' for 'inline else'", .{
|
||||
item_ty.fmt(pt),
|
||||
});
|
||||
}
|
||||
};
|
||||
var unhandled_it = validated_switch.iterateUnhandledItems(error_names, min_int);
|
||||
while (try unhandled_it.next(sema, item_ty)) |item_val| {
|
||||
cases_len += 1;
|
||||
|
|
@ -11660,7 +11658,7 @@ fn fixupSwitchContinues(
|
|||
operand_is_ref: bool,
|
||||
item_ty: Type,
|
||||
mode: enum { normal, opv },
|
||||
any_non_inline_capture: bool,
|
||||
any_maybe_runtime_capture: bool,
|
||||
merges: *const Block.Merges,
|
||||
) CompileError!void {
|
||||
const pt = sema.pt;
|
||||
|
|
@ -11686,7 +11684,7 @@ fn fixupSwitchContinues(
|
|||
assert(sema.air_instructions.items(.tag)[@intFromEnum(placeholder_inst)] == .br);
|
||||
const new_operand_maybe_ref = sema.air_instructions.items(.data)[@intFromEnum(placeholder_inst)].br.operand;
|
||||
|
||||
if (any_non_inline_capture and mode != .opv) {
|
||||
if (any_maybe_runtime_capture and mode != .opv) {
|
||||
_ = try replacement_block.addBinOp(.store, operand.loop.operand_alloc, new_operand_maybe_ref);
|
||||
}
|
||||
|
||||
|
|
@ -12431,6 +12429,8 @@ fn resolveSwitchBlock(
|
|||
}
|
||||
}
|
||||
|
||||
assert(zir_switch.else_case != null or under_prong != null); // switch exhaustion check wrong
|
||||
|
||||
const else_case = validated_switch.else_case;
|
||||
const else_is_named_only = zir_switch.else_case != null and under_prong != null;
|
||||
|
||||
|
|
@ -12507,8 +12507,8 @@ const SwitchOperand = union(enum) {
|
|||
simple: struct {
|
||||
/// The raw switch operand value. Always defined.
|
||||
by_val: Air.Inst.Ref,
|
||||
/// The switch operand *pointer*. Defined only if there is a prong
|
||||
/// with a by-ref capture.
|
||||
/// The switch operand *pointer*. `none` if there are no prongs with a
|
||||
/// by-ref capture.
|
||||
by_ref: Air.Inst.Ref,
|
||||
/// The switch condition value. For unions, `operand` is the union
|
||||
/// and `cond` is its enum tag value.
|
||||
|
|
@ -12519,7 +12519,7 @@ const SwitchOperand = union(enum) {
|
|||
loop: struct {
|
||||
/// The `alloc` containing the `switch` operand for the active dispatch.
|
||||
/// Each prong must load from this `alloc` to get captures.
|
||||
/// If there are no captures, this may be undefined.
|
||||
/// If there are no captures, this may be `none`.
|
||||
operand_alloc: Air.Inst.Ref,
|
||||
/// Whether `operand_alloc` contains a by-val operand or a by-ref
|
||||
/// operand.
|
||||
|
|
@ -12665,19 +12665,31 @@ fn analyzeSwitchProng(
|
|||
}
|
||||
}
|
||||
|
||||
const operand_val, const operand_ptr = load_operand: {
|
||||
const need_load: bool = need_load: {
|
||||
if (capture == .none and !has_tag_capture) {
|
||||
// No need to load the operand for this prong!
|
||||
break :load_operand .{ undefined, undefined };
|
||||
break :need_load false;
|
||||
}
|
||||
if (kind == .inline_ref and
|
||||
!(capture != .none and operand_ty.zigTypeTag(zcu) == .@"union"))
|
||||
{
|
||||
// We only need to load the operand if there's a union payload capture
|
||||
// since it's always runtime-known; only the tag is comptime-known here.
|
||||
break :load_operand .{ undefined, undefined };
|
||||
if (capture != .none and operand_ty.zigTypeTag(zcu) == .@"union") {
|
||||
// Non-OPV union payload captures are always runtime-known.
|
||||
break :need_load true;
|
||||
}
|
||||
if (kind == .inline_ref) {
|
||||
// `inline_ref` *is* the (comptime-known) capture.
|
||||
break :need_load false;
|
||||
}
|
||||
assert(zir_switch.any_maybe_runtime_capture); // should have caught everything else by now
|
||||
if (capture != .by_ref and
|
||||
kind == .item_refs and kind.item_refs.len == 1)
|
||||
{
|
||||
// Capture is comptime-known because it's the only prong item
|
||||
break :need_load false;
|
||||
}
|
||||
break :need_load true;
|
||||
};
|
||||
|
||||
const operand_val: Air.Inst.Ref, const operand_ptr: Air.Inst.Ref = load_operand: {
|
||||
if (!need_load) break :load_operand .{ .none, .none };
|
||||
switch (operand) {
|
||||
.simple => |s| break :load_operand .{ s.by_val, s.by_ref },
|
||||
.loop => |l| {
|
||||
|
|
@ -12686,7 +12698,7 @@ fn analyzeSwitchProng(
|
|||
const by_val = try sema.analyzeLoad(case_block, operand_src, loaded, operand_src);
|
||||
break :load_operand .{ by_val, loaded };
|
||||
} else {
|
||||
break :load_operand .{ loaded, undefined };
|
||||
break :load_operand .{ loaded, .none };
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -12735,7 +12747,7 @@ fn analyzeSwitchProng(
|
|||
fn analyzeSwitchTagCapture(
|
||||
sema: *Sema,
|
||||
case_block: *Block,
|
||||
/// May be `undefined` if `inline_case_capture` is not `.none`.
|
||||
/// May be `none` if this is an inline capture or if `kind.item_refs.len == 1`.
|
||||
operand_val: Air.Inst.Ref,
|
||||
operand_ty: Type,
|
||||
capture_src: LazySrcLoc,
|
||||
|
|
@ -12768,9 +12780,11 @@ fn analyzeSwitchPayloadCapture(
|
|||
sema: *Sema,
|
||||
case_block: *Block,
|
||||
operand: SwitchOperand,
|
||||
/// May be `undefined` if this is an inline capture and operand is not a union.
|
||||
/// Always has to be not-`none` if this is a union payload capture.
|
||||
/// For non-union captures, this may be `none` if this is an inline capture
|
||||
/// or if `kind.item_refs.len == 1` and capture is by val.
|
||||
operand_val: Air.Inst.Ref,
|
||||
/// May be `undefined` if `capture_by_ref` is `false` or if `operand_val` is also `undefined`.
|
||||
/// May be `none` if `capture_by_ref` is `false` or if `operand_val` is also `none`.
|
||||
operand_ptr: Air.Inst.Ref,
|
||||
operand_ty: Type,
|
||||
operand_src: LazySrcLoc,
|
||||
|
|
@ -12816,8 +12830,6 @@ fn analyzeSwitchPayloadCapture(
|
|||
}
|
||||
}
|
||||
|
||||
const operand_ptr_ty = if (capture_by_ref) sema.typeOf(operand_ptr) else undefined;
|
||||
|
||||
if (kind == .special) {
|
||||
if (capture_by_ref) return operand_ptr;
|
||||
return switch (operand_ty.zigTypeTag(zcu)) {
|
||||
|
|
@ -12894,7 +12906,7 @@ fn analyzeSwitchPayloadCapture(
|
|||
|
||||
// By-reference captures have some further restrictions which make them easier to emit
|
||||
if (capture_by_ref) {
|
||||
const operand_ptr_info = operand_ptr_ty.ptrInfo(zcu);
|
||||
const operand_ptr_info = sema.typeOf(operand_ptr).ptrInfo(zcu);
|
||||
const capture_ptr_ty = resolve: {
|
||||
// By-ref captures of hetereogeneous types are only allowed if all field
|
||||
// pointer types are peer resolvable to each other.
|
||||
|
|
@ -13136,7 +13148,7 @@ fn analyzeSwitchPayloadCapture(
|
|||
if (case_vals.len == 1) {
|
||||
const item_val = sema.resolveConstDefinedValue(case_block, .unneeded, case_vals[0], undefined) catch unreachable;
|
||||
const item_ty = try pt.singleErrorSetType(item_val.getErrorName(zcu).unwrap().?);
|
||||
return sema.bitCast(case_block, item_ty, operand_val, operand_src, null);
|
||||
return sema.bitCast(case_block, item_ty, .fromValue(item_val), operand_src, null);
|
||||
}
|
||||
|
||||
var names: InferredErrorSet.NameMap = .{};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue