Merge pull request #13693 from Vexu/safety

Safety panic improvements & some bug fixes
This commit is contained in:
Andrew Kelley 2022-11-29 19:59:55 -05:00 committed by GitHub
commit b8473ae7d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 282 additions and 103 deletions

View File

@ -3803,7 +3803,7 @@ test "switch on non-exhaustive enum" {
{#link|Accessing the non-active field|Wrong Union Field Access#} is
safety-checked {#link|Undefined Behavior#}:
</p>
{#code_begin|test_err|inactive union field#}
{#code_begin|test_err|access of union field 'float' while field 'int' is active#}
const Payload = union {
int: i64,
float: f64,

View File

@ -863,10 +863,14 @@ pub fn panicOutOfBounds(index: usize, len: usize) noreturn {
std.debug.panicExtra(null, @returnAddress(), "index out of bounds: index {d}, len {d}", .{ index, len });
}
pub noinline fn returnError(st: *StackTrace) void {
pub fn panicStartGreaterThanEnd(start: usize, end: usize) noreturn {
@setCold(true);
@setRuntimeSafety(false);
addErrRetTraceAddr(st, @returnAddress());
std.debug.panicExtra(null, @returnAddress(), "start index {d} is larger than end index {d}", .{ start, end });
}
pub fn panicInactiveUnionField(active: anytype, wanted: @TypeOf(active)) noreturn {
@setCold(true);
std.debug.panicExtra(null, @returnAddress(), "access of union field '{s}' while field '{s}' is active", .{ @tagName(wanted), @tagName(active) });
}
pub const panic_messages = struct {
@ -887,8 +891,18 @@ pub const panic_messages = struct {
pub const corrupt_switch = "switch on corrupt value";
pub const shift_rhs_too_big = "shift amount is greater than the type size";
pub const invalid_enum_value = "invalid enum value";
pub const sentinel_mismatch = "sentinel mismatch";
pub const unwrap_error = "attempt to unwrap error";
pub const index_out_of_bounds = "index out of bounds";
pub const start_index_greater_than_end = "start index is larger than end index";
};
pub noinline fn returnError(st: *StackTrace) void {
@setCold(true);
@setRuntimeSafety(false);
addErrRetTraceAddr(st, @returnAddress());
}
pub inline fn addErrRetTraceAddr(st: *StackTrace, addr: usize) void {
if (st.index < st.instruction_addresses.len)
st.instruction_addresses[st.index] = addr;

View File

@ -950,13 +950,15 @@ const Parser = struct {
/// / LabeledStatement
/// / SwitchExpr
/// / AssignExpr SEMICOLON
fn parseStatement(p: *Parser) Error!Node.Index {
fn parseStatement(p: *Parser, allow_defer_var: bool) Error!Node.Index {
const comptime_token = p.eatToken(.keyword_comptime);
const var_decl = try p.parseVarDecl();
if (var_decl != 0) {
try p.expectSemicolon(.expected_semi_after_decl, true);
return var_decl;
if (allow_defer_var) {
const var_decl = try p.parseVarDecl();
if (var_decl != 0) {
try p.expectSemicolon(.expected_semi_after_decl, true);
return var_decl;
}
}
if (comptime_token) |token| {
@ -993,7 +995,7 @@ const Parser = struct {
},
});
},
.keyword_defer => return p.addNode(.{
.keyword_defer => if (allow_defer_var) return p.addNode(.{
.tag = .@"defer",
.main_token = p.nextToken(),
.data = .{
@ -1001,7 +1003,7 @@ const Parser = struct {
.rhs = try p.expectBlockExprStatement(),
},
}),
.keyword_errdefer => return p.addNode(.{
.keyword_errdefer => if (allow_defer_var) return p.addNode(.{
.tag = .@"errdefer",
.main_token = p.nextToken(),
.data = .{
@ -1040,8 +1042,8 @@ const Parser = struct {
return null_node;
}
fn expectStatement(p: *Parser) !Node.Index {
const statement = try p.parseStatement();
fn expectStatement(p: *Parser, allow_defer_var: bool) !Node.Index {
const statement = try p.parseStatement(allow_defer_var);
if (statement == 0) {
return p.fail(.expected_statement);
}
@ -1053,7 +1055,7 @@ const Parser = struct {
/// statement, returns 0.
fn expectStatementRecoverable(p: *Parser) Error!Node.Index {
while (true) {
return p.expectStatement() catch |err| switch (err) {
return p.expectStatement(true) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.ParseError => {
p.findNextStmt(); // Try to skip to the next statement.
@ -1114,7 +1116,7 @@ const Parser = struct {
});
};
_ = try p.parsePayload();
const else_expr = try p.expectStatement();
const else_expr = try p.expectStatement(false);
return p.addNode(.{
.tag = .@"if",
.main_token = if_token,
@ -1226,7 +1228,7 @@ const Parser = struct {
.lhs = array_expr,
.rhs = try p.addExtra(Node.If{
.then_expr = then_expr,
.else_expr = try p.expectStatement(),
.else_expr = try p.expectStatement(false),
}),
},
});
@ -1309,7 +1311,7 @@ const Parser = struct {
}
};
_ = try p.parsePayload();
const else_expr = try p.expectStatement();
const else_expr = try p.expectStatement(false);
return p.addNode(.{
.tag = .@"while",
.main_token = while_token,

View File

@ -4233,6 +4233,30 @@ test "zig fmt: remove newlines surrounding doc comment within container decl" {
);
}
test "zig fmt: invalid else branch statement" {
try testError(
\\comptime {
\\ if (true) {} else var a = 0;
\\ if (true) {} else defer {}
\\}
\\comptime {
\\ while (true) {} else var a = 0;
\\ while (true) {} else defer {}
\\}
\\comptime {
\\ for ("") |_| {} else var a = 0;
\\ for ("") |_| {} else defer {}
\\}
, &[_]Error{
.expected_statement,
.expected_statement,
.expected_statement,
.expected_statement,
.expected_statement,
.expected_statement,
});
}
test "zig fmt: anytype struct field" {
try testError(
\\pub const Pointer = struct {

View File

@ -5070,6 +5070,7 @@ fn containerDecl(
try astgen.extra.ensureUnusedCapacity(gpa, decls_slice.len);
astgen.extra.appendSliceAssumeCapacity(decls_slice);
block_scope.unstack();
try gz.addNamespaceCaptures(&namespace);
return rvalue(gz, ri, indexToRef(decl_inst), node);
},

View File

@ -101,6 +101,7 @@ debug_compile_errors: bool,
job_queued_compiler_rt_lib: bool = false,
job_queued_compiler_rt_obj: bool = false,
alloc_failure_occurred: bool = false,
formatted_panics: bool = false,
c_source_files: []const CSourceFile,
clang_argv: []const []const u8,
@ -937,6 +938,7 @@ pub const InitOptions = struct {
use_stage1: ?bool = null,
single_threaded: ?bool = null,
strip: ?bool = null,
formatted_panics: ?bool = null,
rdynamic: bool = false,
function_sections: bool = false,
no_builtin: bool = false,
@ -1457,6 +1459,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
.Debug => @as(u8, 0),
else => @as(u8, 3),
};
const formatted_panics = options.formatted_panics orelse (options.optimize_mode == .Debug);
// We put everything into the cache hash that *cannot be modified
// during an incremental update*. For example, one cannot change the
@ -1551,6 +1554,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
hash.addOptionalBytes(options.test_name_prefix);
hash.add(options.skip_linker_dependencies);
hash.add(options.parent_compilation_link_libc);
hash.add(formatted_panics);
// In the case of incremental cache mode, this `zig_cache_artifact_directory`
// is computed based on a hash of non-linker inputs, and it is where all
@ -1957,6 +1961,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
.owned_link_dir = owned_link_dir,
.color = options.color,
.reference_trace = options.reference_trace,
.formatted_panics = formatted_panics,
.time_report = options.time_report,
.stack_report = options.stack_report,
.unwind_tables = unwind_tables,

View File

@ -667,9 +667,9 @@ pub const Block = struct {
return result_index;
}
fn addUnreachable(block: *Block, src: LazySrcLoc, safety_check: bool) !void {
fn addUnreachable(block: *Block, safety_check: bool) !void {
if (safety_check and block.wantSafety()) {
_ = try block.sema.safetyPanic(block, src, .unreach);
try block.sema.safetyPanic(block, .unreach);
} else {
_ = try block.addNoOp(.unreach);
}
@ -5003,7 +5003,8 @@ fn zirPanic(sema: *Sema, block: *Block, inst: Zir.Inst.Index, force_comptime: bo
if (block.is_comptime or force_comptime) {
return sema.fail(block, src, "encountered @panic at comptime", .{});
}
return sema.panicWithMsg(block, src, msg_inst);
try sema.panicWithMsg(block, src, msg_inst);
return always_noreturn;
}
fn zirLoop(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@ -5390,7 +5391,8 @@ fn zirExport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void
const container_namespace = container_ty.getNamespace().?;
const maybe_index = try sema.lookupInNamespace(block, operand_src, container_namespace, decl_name, false);
break :index_blk maybe_index.?; // AstGen would produce error in case of unidentified name
break :index_blk maybe_index orelse
return sema.failWithBadMemberAccess(block, container_ty, operand_src, decl_name);
} else try sema.lookupIdentifier(block, operand_src, decl_name);
const options = sema.resolveExportOptions(block, .unneeded, extra.options) catch |err| switch (err) {
error.NeededSourceLocation => {
@ -7962,7 +7964,7 @@ fn analyzeErrUnionPayload(
if (safety_check and block.wantSafety() and
!err_union_ty.errorUnionSet().errorSetIsEmpty())
{
try sema.panicUnwrapError(block, src, operand, .unwrap_errunion_err, .is_non_err);
try sema.panicUnwrapError(block, operand, .unwrap_errunion_err, .is_non_err);
}
return block.addTyOp(.unwrap_errunion_payload, payload_ty, operand);
@ -8047,7 +8049,7 @@ fn analyzeErrUnionPayloadPtr(
if (safety_check and block.wantSafety() and
!err_union_ty.errorUnionSet().errorSetIsEmpty())
{
try sema.panicUnwrapError(block, src, operand, .unwrap_errunion_err_ptr, .is_non_err_ptr);
try sema.panicUnwrapError(block, operand, .unwrap_errunion_err_ptr, .is_non_err_ptr);
}
const air_tag: Air.Inst.Tag = if (initializing)
@ -8709,6 +8711,9 @@ fn analyzeParameter(
});
errdefer msg.destroy(sema.gpa);
const src_decl = sema.mod.declPtr(block.src_decl);
try sema.explainWhyTypeIsComptime(block, param_src, msg, param_src.toSrcLoc(src_decl), param.ty);
try sema.addDeclaredHereNote(msg, param.ty);
break :msg msg;
};
@ -9539,7 +9544,7 @@ fn zirSwitchCapture(
.ErrorSet => if (block.switch_else_err_ty) |some| {
return sema.bitCast(block, some, operand, operand_src);
} else {
try block.addUnreachable(operand_src, false);
try block.addUnreachable(false);
return Air.Inst.Ref.unreachable_value;
},
else => return operand,
@ -10972,7 +10977,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
// that it is unreachable.
if (case_block.wantSafety()) {
try sema.zirDbgStmt(&case_block, cond_dbg_node_index);
_ = try sema.safetyPanic(&case_block, src, .corrupt_switch);
try sema.safetyPanic(&case_block, .corrupt_switch);
} else {
_ = try case_block.addNoOp(.unreach);
}
@ -11301,6 +11306,11 @@ fn maybeErrorUnwrap(sema: *Sema, block: *Block, body: []const Zir.Inst.Index, op
const inst_data = sema.code.instructions.items(.data)[inst].@"unreachable";
const src = inst_data.src();
if (!sema.mod.comp.formatted_panics) {
try sema.safetyPanic(block, .unwrap_error);
return true;
}
const panic_fn = try sema.getBuiltin("panicUnwrapError");
const err_return_trace = try sema.getErrorReturnTrace(block);
const args: [2]Air.Inst.Ref = .{ err_return_trace, operand };
@ -12437,7 +12447,7 @@ fn zirNegate(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
else
try sema.resolveInst(.zero);
return sema.analyzeArithmetic(block, .sub, lhs, rhs, src, lhs_src, rhs_src);
return sema.analyzeArithmetic(block, .sub, lhs, rhs, src, lhs_src, rhs_src, true);
}
fn zirNegateWrap(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@ -12460,7 +12470,7 @@ fn zirNegateWrap(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!
else
try sema.resolveInst(.zero);
return sema.analyzeArithmetic(block, .subwrap, lhs, rhs, src, lhs_src, rhs_src);
return sema.analyzeArithmetic(block, .subwrap, lhs, rhs, src, lhs_src, rhs_src, true);
}
fn zirArithmetic(
@ -12480,7 +12490,7 @@ fn zirArithmetic(
const lhs = try sema.resolveInst(extra.lhs);
const rhs = try sema.resolveInst(extra.rhs);
return sema.analyzeArithmetic(block, zir_tag, lhs, rhs, sema.src, lhs_src, rhs_src);
return sema.analyzeArithmetic(block, zir_tag, lhs, rhs, sema.src, lhs_src, rhs_src, true);
}
fn zirDiv(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@ -13776,6 +13786,7 @@ fn analyzeArithmetic(
src: LazySrcLoc,
lhs_src: LazySrcLoc,
rhs_src: LazySrcLoc,
want_safety: bool,
) CompileError!Air.Inst.Ref {
const lhs_ty = sema.typeOf(lhs);
const rhs_ty = sema.typeOf(rhs);
@ -14204,7 +14215,7 @@ fn analyzeArithmetic(
};
try sema.requireRuntimeBlock(block, src, rs.src);
if (block.wantSafety()) {
if (block.wantSafety() and want_safety) {
if (scalar_tag == .Int) {
const maybe_op_ov: ?Air.Inst.Tag = switch (rs.air_tag) {
.add => .add_with_overflow,
@ -16509,7 +16520,7 @@ fn zirUnreachable(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
return sema.fail(block, src, "reached unreachable code", .{});
}
// TODO Add compile error for @optimizeFor occurring too late in a scope.
try block.addUnreachable(src, true);
try block.addUnreachable(true);
return always_noreturn;
}
@ -17603,11 +17614,11 @@ fn zirFieldType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
const extra = sema.code.extraData(Zir.Inst.FieldType, inst_data.payload_index).data;
const ty_src = inst_data.src();
const field_src = inst_data.src();
const field_name_src: LazySrcLoc = .{ .node_offset_field_name = inst_data.src_node };
const aggregate_ty = try sema.resolveType(block, ty_src, extra.container_type);
if (aggregate_ty.tag() == .var_args_param) return sema.addType(aggregate_ty);
const field_name = sema.code.nullTerminatedString(extra.name_start);
return sema.fieldType(block, aggregate_ty, field_name, field_src, ty_src);
return sema.fieldType(block, aggregate_ty, field_name, field_name_src, ty_src);
}
fn fieldType(
@ -22119,12 +22130,15 @@ pub const PanicId = enum {
shr_overflow,
divide_by_zero,
exact_division_remainder,
/// TODO make this call `std.builtin.panicInactiveUnionField`.
inactive_union_field,
integer_part_out_of_bounds,
corrupt_switch,
shift_rhs_too_big,
invalid_enum_value,
sentinel_mismatch,
unwrap_error,
index_out_of_bounds,
start_index_greater_than_end,
};
fn addSafetyCheck(
@ -22149,12 +22163,7 @@ fn addSafetyCheck(
defer fail_block.instructions.deinit(gpa);
// This function doesn't actually need a src location but if
// the panic function interface ever changes passing `.unneeded` here
// will cause confusing panics.
const src = sema.src;
_ = try sema.safetyPanic(&fail_block, src, panic_id);
try sema.safetyPanic(&fail_block, panic_id);
try sema.addSafetyCheckExtra(parent_block, ok, &fail_block);
}
@ -22218,7 +22227,7 @@ fn panicWithMsg(
block: *Block,
src: LazySrcLoc,
msg_inst: Air.Inst.Ref,
) !Zir.Inst.Index {
) !void {
const mod = sema.mod;
const arena = sema.arena;
@ -22229,7 +22238,7 @@ fn panicWithMsg(
// TODO implement this feature in all the backends and then delete this branch
_ = try block.addNoOp(.breakpoint);
_ = try block.addNoOp(.unreach);
return always_noreturn;
return;
}
const panic_fn = try sema.getBuiltin("panic");
const unresolved_stack_trace_ty = try sema.getBuiltinType("StackTrace");
@ -22245,19 +22254,20 @@ fn panicWithMsg(
);
const args: [3]Air.Inst.Ref = .{ msg_inst, null_stack_trace, .null_value };
_ = try sema.analyzeCall(block, panic_fn, src, src, .auto, false, &args, null);
return always_noreturn;
}
fn panicUnwrapError(
sema: *Sema,
parent_block: *Block,
src: LazySrcLoc,
operand: Air.Inst.Ref,
unwrap_err_tag: Air.Inst.Tag,
is_non_err_tag: Air.Inst.Tag,
) !void {
assert(!parent_block.is_comptime);
const ok = try parent_block.addUnOp(is_non_err_tag, operand);
if (!sema.mod.comp.formatted_panics) {
return sema.addSafetyCheck(parent_block, ok, .unwrap_error);
}
const gpa = sema.gpa;
var fail_block: Block = .{
@ -22286,7 +22296,7 @@ fn panicUnwrapError(
const err = try fail_block.addTyOp(unwrap_err_tag, Type.anyerror, operand);
const err_return_trace = try sema.getErrorReturnTrace(&fail_block);
const args: [2]Air.Inst.Ref = .{ err_return_trace, err };
_ = try sema.analyzeCall(&fail_block, panic_fn, src, src, .auto, false, &args, null);
_ = try sema.analyzeCall(&fail_block, panic_fn, sema.src, sema.src, .auto, false, &args, null);
}
}
try sema.addSafetyCheckExtra(parent_block, ok, &fail_block);
@ -22295,49 +22305,49 @@ fn panicUnwrapError(
fn panicIndexOutOfBounds(
sema: *Sema,
parent_block: *Block,
src: LazySrcLoc,
index: Air.Inst.Ref,
len: Air.Inst.Ref,
cmp_op: Air.Inst.Tag,
) !void {
assert(!parent_block.is_comptime);
const ok = try parent_block.addBinOp(cmp_op, index, len);
const gpa = sema.gpa;
var fail_block: Block = .{
.parent = parent_block,
.sema = sema,
.src_decl = parent_block.src_decl,
.namespace = parent_block.namespace,
.wip_capture_scope = parent_block.wip_capture_scope,
.instructions = .{},
.inlining = parent_block.inlining,
.is_comptime = false,
};
defer fail_block.instructions.deinit(gpa);
{
const this_feature_is_implemented_in_the_backend =
sema.mod.comp.bin_file.options.use_llvm;
if (!this_feature_is_implemented_in_the_backend) {
// TODO implement this feature in all the backends and then delete this branch
_ = try fail_block.addNoOp(.breakpoint);
_ = try fail_block.addNoOp(.unreach);
} else {
const panic_fn = try sema.getBuiltin("panicOutOfBounds");
const args: [2]Air.Inst.Ref = .{ index, len };
_ = try sema.analyzeCall(&fail_block, panic_fn, src, src, .auto, false, &args, null);
}
if (!sema.mod.comp.formatted_panics) {
return sema.addSafetyCheck(parent_block, ok, .index_out_of_bounds);
}
try sema.addSafetyCheckExtra(parent_block, ok, &fail_block);
try sema.safetyCheckFormatted(parent_block, ok, "panicOutOfBounds", &.{ index, len });
}
fn panicStartLargerThanEnd(
sema: *Sema,
parent_block: *Block,
start: Air.Inst.Ref,
end: Air.Inst.Ref,
) !void {
assert(!parent_block.is_comptime);
const ok = try parent_block.addBinOp(.cmp_lte, start, end);
if (!sema.mod.comp.formatted_panics) {
return sema.addSafetyCheck(parent_block, ok, .start_index_greater_than_end);
}
try sema.safetyCheckFormatted(parent_block, ok, "panicStartGreaterThanEnd", &.{ start, end });
}
fn panicInactiveUnionField(
sema: *Sema,
parent_block: *Block,
active_tag: Air.Inst.Ref,
wanted_tag: Air.Inst.Ref,
) !void {
assert(!parent_block.is_comptime);
const ok = try parent_block.addBinOp(.cmp_eq, active_tag, wanted_tag);
if (!sema.mod.comp.formatted_panics) {
return sema.addSafetyCheck(parent_block, ok, .inactive_union_field);
}
try sema.safetyCheckFormatted(parent_block, ok, "panicInactiveUnionField", &.{ active_tag, wanted_tag });
}
fn panicSentinelMismatch(
sema: *Sema,
parent_block: *Block,
src: LazySrcLoc,
maybe_sentinel: ?Value,
sentinel_ty: Type,
ptr: Air.Inst.Ref,
@ -22371,9 +22381,24 @@ fn panicSentinelMismatch(
else {
const panic_fn = try sema.getBuiltin("checkNonScalarSentinel");
const args: [2]Air.Inst.Ref = .{ expected_sentinel, actual_sentinel };
_ = try sema.analyzeCall(parent_block, panic_fn, src, src, .auto, false, &args, null);
_ = try sema.analyzeCall(parent_block, panic_fn, sema.src, sema.src, .auto, false, &args, null);
return;
};
if (!sema.mod.comp.formatted_panics) {
return sema.addSafetyCheck(parent_block, ok, .sentinel_mismatch);
}
try sema.safetyCheckFormatted(parent_block, ok, "panicSentinelMismatch", &.{ expected_sentinel, actual_sentinel });
}
fn safetyCheckFormatted(
sema: *Sema,
parent_block: *Block,
ok: Air.Inst.Ref,
func: []const u8,
args: []const Air.Inst.Ref,
) CompileError!void {
assert(sema.mod.comp.formatted_panics);
const gpa = sema.gpa;
var fail_block: Block = .{
@ -22398,9 +22423,8 @@ fn panicSentinelMismatch(
_ = try fail_block.addNoOp(.breakpoint);
_ = try fail_block.addNoOp(.unreach);
} else {
const panic_fn = try sema.getBuiltin("panicSentinelMismatch");
const args: [2]Air.Inst.Ref = .{ expected_sentinel, actual_sentinel };
_ = try sema.analyzeCall(&fail_block, panic_fn, src, src, .auto, false, &args, null);
const panic_fn = try sema.getBuiltin(func);
_ = try sema.analyzeCall(&fail_block, panic_fn, sema.src, sema.src, .auto, false, args, null);
}
}
try sema.addSafetyCheckExtra(parent_block, ok, &fail_block);
@ -22409,19 +22433,18 @@ fn panicSentinelMismatch(
fn safetyPanic(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
panic_id: PanicId,
) CompileError!Zir.Inst.Index {
) CompileError!void {
const panic_messages_ty = try sema.getBuiltinType("panic_messages");
const msg_decl_index = (try sema.namespaceLookup(
block,
src,
sema.src,
panic_messages_ty.getNamespace().?,
@tagName(panic_id),
)).?;
const msg_inst = try sema.analyzeDeclVal(block, src, msg_decl_index);
return sema.panicWithMsg(block, src, msg_inst);
const msg_inst = try sema.analyzeDeclVal(block, sema.src, msg_decl_index);
try sema.panicWithMsg(block, sema.src, msg_inst);
}
fn emitBackwardBranch(sema: *Sema, block: *Block, src: LazySrcLoc) !void {
@ -23423,8 +23446,7 @@ fn unionFieldPtr(
// TODO would it be better if get_union_tag supported pointers to unions?
const union_val = try block.addTyOp(.load, union_ty, union_ptr);
const active_tag = try block.addTyOp(.get_union_tag, union_obj.tag_ty, union_val);
const ok = try block.addBinOp(.cmp_eq, active_tag, wanted_tag);
try sema.addSafetyCheck(block, ok, .inactive_union_field);
try sema.panicInactiveUnionField(block, active_tag, wanted_tag);
}
if (field.ty.zigTypeTag() == .NoReturn) {
_ = try block.addNoOp(.unreach);
@ -23495,8 +23517,7 @@ fn unionFieldVal(
const wanted_tag_val = try Value.Tag.enum_field_index.create(sema.arena, enum_field_index);
const wanted_tag = try sema.addConstant(union_obj.tag_ty, wanted_tag_val);
const active_tag = try block.addTyOp(.get_union_tag, union_obj.tag_ty, union_byval);
const ok = try block.addBinOp(.cmp_eq, active_tag, wanted_tag);
try sema.addSafetyCheck(block, ok, .inactive_union_field);
try sema.panicInactiveUnionField(block, active_tag, wanted_tag);
}
if (field.ty.zigTypeTag() == .NoReturn) {
_ = try block.addNoOp(.unreach);
@ -23807,7 +23828,7 @@ fn elemValArray(
if (maybe_index_val == null) {
const len_inst = try sema.addIntUnsigned(Type.usize, array_len);
const cmp_op: Air.Inst.Tag = if (array_sent != null) .cmp_lte else .cmp_lt;
try sema.panicIndexOutOfBounds(block, elem_index_src, elem_index, len_inst, cmp_op);
try sema.panicIndexOutOfBounds(block, elem_index, len_inst, cmp_op);
}
}
return block.addBinOp(.array_elem_val, array, elem_index);
@ -23868,7 +23889,7 @@ fn elemPtrArray(
if (block.wantSafety() and offset == null) {
const len_inst = try sema.addIntUnsigned(Type.usize, array_len);
const cmp_op: Air.Inst.Tag = if (array_sent) .cmp_lte else .cmp_lt;
try sema.panicIndexOutOfBounds(block, elem_index_src, elem_index, len_inst, cmp_op);
try sema.panicIndexOutOfBounds(block, elem_index, len_inst, cmp_op);
}
return block.addPtrElemPtr(array_ptr, elem_index, elem_ptr_ty);
@ -23924,7 +23945,7 @@ fn elemValSlice(
else
try block.addTyOp(.slice_len, Type.usize, slice);
const cmp_op: Air.Inst.Tag = if (slice_sent) .cmp_lte else .cmp_lt;
try sema.panicIndexOutOfBounds(block, elem_index_src, elem_index, len_inst, cmp_op);
try sema.panicIndexOutOfBounds(block, elem_index, len_inst, cmp_op);
}
try sema.queueFullTypeResolution(sema.typeOf(slice));
return block.addBinOp(.slice_elem_val, slice, elem_index);
@ -23983,7 +24004,7 @@ fn elemPtrSlice(
break :len try block.addTyOp(.slice_len, Type.usize, slice);
};
const cmp_op: Air.Inst.Tag = if (slice_sent) .cmp_lte else .cmp_lt;
try sema.panicIndexOutOfBounds(block, elem_index_src, elem_index, len_inst, cmp_op);
try sema.panicIndexOutOfBounds(block, elem_index, len_inst, cmp_op);
}
return block.addSliceElemPtr(slice, elem_index, elem_ptr_ty);
}
@ -28028,7 +28049,11 @@ fn analyzeSlice(
}
}
const new_len = try sema.analyzeArithmetic(block, .sub, end, start, src, end_src, start_src);
if (block.wantSafety() and !block.is_comptime) {
// requirement: start <= end
try sema.panicStartLargerThanEnd(block, start, end);
}
const new_len = try sema.analyzeArithmetic(block, .sub, end, start, src, end_src, start_src, false);
const opt_new_len_val = try sema.resolveDefinedValue(block, src, new_len);
const new_ptr_ty_info = sema.typeOf(new_ptr).ptrInfo().data;
@ -28063,18 +28088,18 @@ fn analyzeSlice(
const actual_len = if (slice_ty.sentinel() == null)
slice_len_inst
else
try sema.analyzeArithmetic(block, .add, slice_len_inst, .one, src, end_src, end_src);
try sema.analyzeArithmetic(block, .add, slice_len_inst, .one, src, end_src, end_src, true);
const actual_end = if (slice_sentinel != null)
try sema.analyzeArithmetic(block, .add, end, .one, src, end_src, end_src)
try sema.analyzeArithmetic(block, .add, end, .one, src, end_src, end_src, true)
else
end;
try sema.panicIndexOutOfBounds(block, src, actual_end, actual_len, .cmp_lte);
try sema.panicIndexOutOfBounds(block, actual_end, actual_len, .cmp_lte);
}
// requirement: result[new_len] == slice_sentinel
try sema.panicSentinelMismatch(block, src, slice_sentinel, elem_ty, result, new_len);
try sema.panicSentinelMismatch(block, slice_sentinel, elem_ty, result, new_len);
}
return result;
};
@ -28131,18 +28156,18 @@ fn analyzeSlice(
if (slice_ty.sentinel() == null) break :blk slice_len_inst;
// we have to add one because slice lengths don't include the sentinel
break :blk try sema.analyzeArithmetic(block, .add, slice_len_inst, .one, src, end_src, end_src);
break :blk try sema.analyzeArithmetic(block, .add, slice_len_inst, .one, src, end_src, end_src, true);
} else null;
if (opt_len_inst) |len_inst| {
const actual_end = if (slice_sentinel != null)
try sema.analyzeArithmetic(block, .add, end, .one, src, end_src, end_src)
try sema.analyzeArithmetic(block, .add, end, .one, src, end_src, end_src, true)
else
end;
try sema.panicIndexOutOfBounds(block, src, actual_end, len_inst, .cmp_lte);
try sema.panicIndexOutOfBounds(block, actual_end, len_inst, .cmp_lte);
}
// requirement: start <= end
try sema.panicIndexOutOfBounds(block, src, start, end, .cmp_lte);
try sema.panicIndexOutOfBounds(block, start, end, .cmp_lte);
}
const result = try block.addInst(.{
.tag = .slice,
@ -28156,7 +28181,7 @@ fn analyzeSlice(
});
if (block.wantSafety()) {
// requirement: result[new_len] == slice_sentinel
try sema.panicSentinelMismatch(block, src, slice_sentinel, elem_ty, result, new_len);
try sema.panicSentinelMismatch(block, slice_sentinel, elem_ty, result, new_len);
}
return result;
}

View File

@ -9228,6 +9228,21 @@ pub const FuncGen = struct {
const target = self.dg.module.getTarget();
const layout = union_ty.unionGetLayout(target);
const union_obj = union_ty.cast(Type.Payload.Union).?.data;
if (union_obj.layout == .Packed) {
const big_bits = union_ty.bitSize(target);
const int_llvm_ty = self.dg.context.intType(@intCast(c_uint, big_bits));
const field = union_obj.fields.values()[extra.field_index];
const non_int_val = try self.resolveInst(extra.init);
const ty_bit_size = @intCast(u16, field.ty.bitSize(target));
const small_int_ty = self.dg.context.intType(ty_bit_size);
const small_int_val = if (field.ty.isPtrAtRuntime())
self.builder.buildPtrToInt(non_int_val, small_int_ty, "")
else
self.builder.buildBitCast(non_int_val, small_int_ty, "");
return self.builder.buildZExtOrBitCast(small_int_val, int_llvm_ty, "");
}
const tag_int = blk: {
const tag_ty = union_ty.unionTagTypeHypothetical();
const union_field_name = union_obj.fields.keys()[extra.field_index];

View File

@ -406,6 +406,8 @@ const usage_build_generic =
\\ -fno-function-sections All functions go into same section
\\ -fstrip Omit debug symbols
\\ -fno-strip Keep debug symbols
\\ -fformatted-panics Enable formatted safety panics
\\ -fno-formatted-panics Disable formatted safety panics
\\ -ofmt=[mode] Override target object format
\\ elf Executable and Linking Format
\\ c C source code
@ -632,6 +634,7 @@ fn buildOutputType(
var have_version = false;
var compatibility_version: ?std.builtin.Version = null;
var strip: ?bool = null;
var formatted_panics: ?bool = null;
var function_sections = false;
var no_builtin = false;
var watch = false;
@ -1242,6 +1245,10 @@ fn buildOutputType(
strip = true;
} else if (mem.eql(u8, arg, "-fno-strip")) {
strip = false;
} else if (mem.eql(u8, arg, "-fformatted-panics")) {
formatted_panics = true;
} else if (mem.eql(u8, arg, "-fno-formatted-panics")) {
formatted_panics = false;
} else if (mem.eql(u8, arg, "-fsingle-threaded")) {
single_threaded = true;
} else if (mem.eql(u8, arg, "-fno-single-threaded")) {
@ -2938,6 +2945,7 @@ fn buildOutputType(
.stack_size_override = stack_size_override,
.image_base_override = image_base_override,
.strip = strip,
.formatted_panics = formatted_panics,
.single_threaded = single_threaded,
.function_sections = function_sections,
.no_builtin = no_builtin,

View File

@ -116,6 +116,7 @@ test {
_ = @import("behavior/bugs/13171.zig");
_ = @import("behavior/bugs/13285.zig");
_ = @import("behavior/bugs/13435.zig");
_ = @import("behavior/bugs/13664.zig");
_ = @import("behavior/byteswap.zig");
_ = @import("behavior/byval_arg_var.zig");
_ = @import("behavior/call.zig");

View File

@ -1127,3 +1127,14 @@ test "pointer to zero sized global is mutable" {
};
try expect(@TypeOf(&S.thing) == *S.Thing);
}
test "returning an opaque type from a function" {
const S = struct {
fn foo(comptime a: u32) type {
return opaque {
const b = a;
};
}
};
try expect(S.foo(123).b == 123);
}

View File

@ -0,0 +1,27 @@
const std = @import("std");
const builtin = @import("builtin");
const Fields = packed struct {
timestamp: u50,
random_bits: u13,
};
const ID = packed union {
value: u63,
fields: Fields,
};
fn value() i64 {
return 1341;
}
test {
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
const timestamp: i64 = value();
const id = ID{ .fields = Fields{
.timestamp = @intCast(u50, timestamp),
.random_bits = 420,
} };
try std.testing.expect((ID{ .value = id.value }).fields.timestamp == timestamp);
}

View File

@ -21,4 +21,5 @@ pub export fn entry1() void {
// target=native
//
// :3:6: error: parameter of type '*const fn(anytype) void' must be declared comptime
// :3:6: note: function is generic
// :10:34: error: parameter of type 'comptime_int' must be declared comptime

View File

@ -12,9 +12,21 @@ export fn f() void {
_ = a;
}
const Object = struct {
field_1: u32,
field_2: u32,
};
fn dump(_: Object) void {}
pub export fn entry() void {
dump(.{ .field_1 = 123, .field_3 = 456 });
}
// error
// backend=stage2
// target=native
//
// :10:10: error: no field named 'foo' in struct 'tmp.A'
// :1:11: note: struct declared here
// :21:30: error: no field named 'field_3' in struct 'tmp.Object'
// :15:16: note: struct declared here

View File

@ -0,0 +1,10 @@
const S = struct {};
comptime {
@export(S.foo, .{ .name = "foo" });
}
// error
// target=native
//
// :3:14: error: struct 'tmp.S' has no member named 'foo'
// :1:11: note: struct declared here

View File

@ -2,7 +2,7 @@ const std = @import("std");
pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
_ = stack_trace;
if (std.mem.eql(u8, message, "access of inactive union field")) {
if (std.mem.eql(u8, message, "access of union field 'float' while field 'int' is active")) {
std.process.exit(0);
}
std.process.exit(1);

View File

@ -0,0 +1,23 @@
const std = @import("std");
pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
_ = stack_trace;
if (std.mem.eql(u8, message, "start index 10 is larger than end index 1")) {
std.process.exit(0);
}
std.process.exit(1);
}
pub fn main() !void {
var a: usize = 1;
var b: usize = 10;
var buf: [16]u8 = undefined;
const slice = buf[b..a];
_ = slice;
return error.TestFailed;
}
// run
// backend=llvm
// target=native