diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index 421d2e72f1b5..632982e812e3 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -1364,20 +1364,20 @@ fn usage(b: *std.Build, out_stream: anytype) !void { ); } -fn nextArg(args: [][:0]const u8, idx: *usize) ?[:0]const u8 { +fn nextArg(args: []const [:0]const u8, idx: *usize) ?[:0]const u8 { if (idx.* >= args.len) return null; defer idx.* += 1; return args[idx.*]; } -fn nextArgOrFatal(args: [][:0]const u8, idx: *usize) [:0]const u8 { +fn nextArgOrFatal(args: []const [:0]const u8, idx: *usize) [:0]const u8 { return nextArg(args, idx) orelse { std.debug.print("expected argument after '{s}'\n access the help menu with 'zig build -h'\n", .{args[idx.* - 1]}); process.exit(1); }; } -fn argsRest(args: [][:0]const u8, idx: usize) ?[][:0]const u8 { +fn argsRest(args: []const [:0]const u8, idx: usize) ?[]const [:0]const u8 { if (idx >= args.len) return null; return args[idx..]; } diff --git a/lib/std/c/darwin.zig b/lib/std/c/darwin.zig index ef128beaebe0..355ea8b11c15 100644 --- a/lib/std/c/darwin.zig +++ b/lib/std/c/darwin.zig @@ -1232,16 +1232,16 @@ pub extern "c" fn posix_spawn( path: [*:0]const u8, actions: ?*const posix_spawn_file_actions_t, attr: ?*const posix_spawnattr_t, - argv: [*:null]?[*:0]const u8, - env: [*:null]?[*:0]const u8, + argv: [*:null]const ?[*:0]const u8, + env: [*:null]const ?[*:0]const u8, ) c_int; pub extern "c" fn posix_spawnp( pid: *pid_t, path: [*:0]const u8, actions: ?*const posix_spawn_file_actions_t, attr: ?*const posix_spawnattr_t, - argv: [*:null]?[*:0]const u8, - env: [*:null]?[*:0]const u8, + argv: [*:null]const ?[*:0]const u8, + env: [*:null]const ?[*:0]const u8, ) c_int; pub const E = enum(u16) { diff --git a/src/DarwinPosixSpawn.zig b/src/DarwinPosixSpawn.zig index eaca94424175..aaf2df6ec349 100644 --- a/src/DarwinPosixSpawn.zig +++ b/src/DarwinPosixSpawn.zig @@ -161,8 +161,8 @@ pub fn spawn( path: []const u8, actions: ?Actions, attr: ?Attr, - argv: [*:null]?[*:0]const u8, - envp: [*:null]?[*:0]const u8, + argv: [*:null]const ?[*:0]const u8, + envp: [*:null]const ?[*:0]const u8, ) Error!std.c.pid_t { const posix_path = try std.posix.toPosixPath(path); return spawnZ(&posix_path, actions, attr, argv, envp); @@ -172,8 +172,8 @@ pub fn spawnZ( path: [*:0]const u8, actions: ?Actions, attr: ?Attr, - argv: [*:null]?[*:0]const u8, - envp: [*:null]?[*:0]const u8, + argv: [*:null]const ?[*:0]const u8, + envp: [*:null]const ?[*:0]const u8, ) Error!std.c.pid_t { var pid: std.c.pid_t = undefined; switch (errno(std.c.posix_spawn( diff --git a/src/Sema.zig b/src/Sema.zig index 5bee91f0474d..d4829e8b3128 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -30797,7 +30797,8 @@ const InMemoryCoercionResult = union(enum) { ptr_addrspace: AddressSpace, ptr_sentinel: Sentinel, ptr_size: Size, - ptr_qualifiers: Qualifiers, + ptr_const: Pair, + ptr_volatile: Pair, ptr_allowzero: Pair, ptr_bit_range: BitRange, ptr_alignment: AlignPair, @@ -30861,13 +30862,6 @@ const InMemoryCoercionResult = union(enum) { wanted: std.builtin.Type.Pointer.Size, }; - const Qualifiers = struct { - actual_const: bool, - wanted_const: bool, - actual_volatile: bool, - wanted_volatile: bool, - }; - const AddressSpace = struct { actual: std.builtin.AddressSpace, wanted: std.builtin.AddressSpace, @@ -31067,16 +31061,6 @@ const InMemoryCoercionResult = union(enum) { try sema.errNote(src, msg, "a {s} pointer cannot cast into a {s} pointer", .{ pointerSizeString(size.actual), pointerSizeString(size.wanted) }); break; }, - .ptr_qualifiers => |qualifiers| { - const ok_const = !qualifiers.actual_const or qualifiers.wanted_const; - const ok_volatile = !qualifiers.actual_volatile or qualifiers.wanted_volatile; - if (!ok_const) { - try sema.errNote(src, msg, "cast discards const qualifier", .{}); - } else if (!ok_volatile) { - try sema.errNote(src, msg, "cast discards volatile qualifier", .{}); - } - break; - }, .ptr_allowzero => |pair| { const wanted_allow_zero = pair.wanted.ptrAllowsZero(pt.zcu); const actual_allow_zero = pair.actual.ptrAllowsZero(pt.zcu); @@ -31085,8 +31069,32 @@ const InMemoryCoercionResult = union(enum) { pair.actual.fmt(pt), pair.wanted.fmt(pt), }); } else { - try sema.errNote(src, msg, "mutable '{}' allows illegal null values stored to type '{}'", .{ - pair.actual.fmt(pt), pair.wanted.fmt(pt), + try sema.errNote(src, msg, "mutable '{}' would allow illegal null values stored to type '{}'", .{ + pair.wanted.fmt(pt), pair.actual.fmt(pt), + }); + } + break; + }, + .ptr_const => |pair| { + const wanted_const = pair.wanted.isConstPtr(pt.zcu); + const actual_const = pair.actual.isConstPtr(pt.zcu); + if (actual_const and !wanted_const) { + try sema.errNote(src, msg, "cast discards const qualifier", .{}); + } else { + try sema.errNote(src, msg, "mutable '{}' would allow illegal const pointers stored to type '{}'", .{ + pair.wanted.fmt(pt), pair.actual.fmt(pt), + }); + } + break; + }, + .ptr_volatile => |pair| { + const wanted_volatile = pair.wanted.isVolatilePtr(pt.zcu); + const actual_volatile = pair.actual.isVolatilePtr(pt.zcu); + if (actual_volatile and !wanted_volatile) { + try sema.errNote(src, msg, "cast discards volatile qualifier", .{}); + } else { + try sema.errNote(src, msg, "mutable '{}' would allow illegal volatile pointers stored to type '{}'", .{ + pair.wanted.fmt(pt), pair.actual.fmt(pt), }); } break; @@ -31136,20 +31144,22 @@ fn pointerSizeString(size: std.builtin.Type.Pointer.Size) []const u8 { }; } -/// If pointers have the same representation in runtime memory, a bitcast AIR instruction -/// may be used for the coercion. -/// * `const` attribute can be gained -/// * `volatile` attribute can be gained -/// * `allowzero` attribute can be gained (whether from explicit attribute, C pointer, or optional pointer) but only if !dest_is_mut -/// * alignment can be decreased -/// * bit offset attributes must match exactly -/// * `*`/`[*]` must match exactly, but `[*c]` matches either one -/// * sentinel-terminated pointers can coerce into `[*]` +/// If types `A` and `B` have identical representations in runtime memory, they are considered +/// "in-memory coercible". This is a subset of normal coercions. Not only can `A` coerce to `B`, but +/// also, coercions can happen through pointers. For instance, `*const A` can coerce to `*const B`. +/// +/// If this function is called, the coercion must be applied, or a compile error emitted if `.ok` +/// is not returned. This is because this function may modify inferred error sets to make a +/// coercion possible, even if `.ok` is not returned. pub fn coerceInMemoryAllowed( sema: *Sema, block: *Block, dest_ty: Type, src_ty: Type, + /// If `true`, this query comes from an attempted coercion of the form `*Src` -> `*Dest`, where + /// both pointers are mutable. If this coercion is allowed, one could store to the `*Dest` and + /// load from the `*Src` to effectively perform an in-memory coercion from `Dest` to `Src`. + /// Therefore, when `dest_is_mut`, the in-memory coercion must be valid in *both directions*. dest_is_mut: bool, target: std.Target, dest_src: LazySrcLoc, @@ -31224,7 +31234,7 @@ pub fn coerceInMemoryAllowed( // Functions if (dest_tag == .@"fn" and src_tag == .@"fn") { - return try sema.coerceInMemoryAllowedFns(block, dest_ty, src_ty, target, dest_src, src_src); + return try sema.coerceInMemoryAllowedFns(block, dest_ty, src_ty, dest_is_mut, target, dest_src, src_src); } // Error Unions @@ -31233,7 +31243,7 @@ pub fn coerceInMemoryAllowed( const src_payload = src_ty.errorUnionPayload(zcu); const child = try sema.coerceInMemoryAllowed(block, dest_payload, src_payload, dest_is_mut, target, dest_src, src_src, null); if (child != .ok) { - return InMemoryCoercionResult{ .error_union_payload = .{ + return .{ .error_union_payload = .{ .child = try child.dupe(sema.arena), .actual = src_payload, .wanted = dest_payload, @@ -31244,7 +31254,11 @@ pub fn coerceInMemoryAllowed( // Error Sets if (dest_tag == .error_set and src_tag == .error_set) { - return try sema.coerceInMemoryAllowedErrorSets(block, dest_ty, src_ty, dest_src, src_src); + const res1 = try sema.coerceInMemoryAllowedErrorSets(block, dest_ty, src_ty, dest_src, src_src); + if (!dest_is_mut or res1 != .ok) return res1; + // src -> dest is okay, but `dest_is_mut`, so it needs to be allowed in the other direction. + const res2 = try sema.coerceInMemoryAllowedErrorSets(block, src_ty, dest_ty, src_src, dest_src); + return res2; } // Arrays @@ -31252,7 +31266,7 @@ pub fn coerceInMemoryAllowed( const dest_info = dest_ty.arrayInfo(zcu); const src_info = src_ty.arrayInfo(zcu); if (dest_info.len != src_info.len) { - return InMemoryCoercionResult{ .array_len = .{ + return .{ .array_len = .{ .actual = src_info.len, .wanted = dest_info.len, } }; @@ -31263,7 +31277,7 @@ pub fn coerceInMemoryAllowed( .ok => {}, .no_match => return child, else => { - return InMemoryCoercionResult{ .array_elem = .{ + return .{ .array_elem = .{ .child = try child.dupe(sema.arena), .actual = src_info.elem_type, .wanted = dest_info.elem_type, @@ -31279,7 +31293,7 @@ pub fn coerceInMemoryAllowed( zcu, )); if (!ok_sent) { - return InMemoryCoercionResult{ .array_sentinel = .{ + return .{ .array_sentinel = .{ .actual = src_info.sentinel orelse Value.@"unreachable", .wanted = dest_info.sentinel orelse Value.@"unreachable", .ty = dest_info.elem_type, @@ -31293,7 +31307,7 @@ pub fn coerceInMemoryAllowed( const dest_len = dest_ty.vectorLen(zcu); const src_len = src_ty.vectorLen(zcu); if (dest_len != src_len) { - return InMemoryCoercionResult{ .vector_len = .{ + return .{ .vector_len = .{ .actual = src_len, .wanted = dest_len, } }; @@ -31303,7 +31317,7 @@ pub fn coerceInMemoryAllowed( const src_elem_ty = src_ty.scalarType(zcu); const child = try sema.coerceInMemoryAllowed(block, dest_elem_ty, src_elem_ty, dest_is_mut, target, dest_src, src_src, null); if (child != .ok) { - return InMemoryCoercionResult{ .vector_elem = .{ + return .{ .vector_elem = .{ .child = try child.dupe(sema.arena), .actual = src_elem_ty, .wanted = dest_elem_ty, @@ -31320,7 +31334,7 @@ pub fn coerceInMemoryAllowed( const dest_len = dest_ty.arrayLen(zcu); const src_len = src_ty.arrayLen(zcu); if (dest_len != src_len) { - return InMemoryCoercionResult{ .array_len = .{ + return .{ .array_len = .{ .actual = src_len, .wanted = dest_len, } }; @@ -31330,7 +31344,7 @@ pub fn coerceInMemoryAllowed( const src_elem_ty = src_ty.childType(zcu); const child = try sema.coerceInMemoryAllowed(block, dest_elem_ty, src_elem_ty, dest_is_mut, target, dest_src, src_src, null); if (child != .ok) { - return InMemoryCoercionResult{ .array_elem = .{ + return .{ .array_elem = .{ .child = try child.dupe(sema.arena), .actual = src_elem_ty, .wanted = dest_elem_ty, @@ -31340,7 +31354,7 @@ pub fn coerceInMemoryAllowed( if (dest_tag == .array) { const dest_info = dest_ty.arrayInfo(zcu); if (dest_info.sentinel != null) { - return InMemoryCoercionResult{ .array_sentinel = .{ + return .{ .array_sentinel = .{ .actual = Value.@"unreachable", .wanted = dest_info.sentinel.?, .ty = dest_info.elem_type, @@ -31360,7 +31374,7 @@ pub fn coerceInMemoryAllowed( // Optionals if (dest_tag == .optional and src_tag == .optional) { if ((maybe_dest_ptr_ty != null) != (maybe_src_ptr_ty != null)) { - return InMemoryCoercionResult{ .optional_shape = .{ + return .{ .optional_shape = .{ .actual = src_ty, .wanted = dest_ty, } }; @@ -31370,7 +31384,7 @@ pub fn coerceInMemoryAllowed( const child = try sema.coerceInMemoryAllowed(block, dest_child_type, src_child_type, dest_is_mut, target, dest_src, src_src, null); if (child != .ok) { - return InMemoryCoercionResult{ .optional_child = .{ + return .{ .optional_child = .{ .child = try child.dupe(sema.arena), .actual = src_child_type, .wanted = dest_child_type, @@ -31382,7 +31396,6 @@ pub fn coerceInMemoryAllowed( // Tuples (with in-memory-coercible fields) if (dest_ty.isTuple(zcu) and src_ty.isTuple(zcu)) tuple: { - if (dest_ty.containerLayout(zcu) != src_ty.containerLayout(zcu)) break :tuple; if (dest_ty.structFieldCount(zcu) != src_ty.structFieldCount(zcu)) break :tuple; const field_count = dest_ty.structFieldCount(zcu); for (0..field_count) |field_idx| { @@ -31396,7 +31409,7 @@ pub fn coerceInMemoryAllowed( return .ok; } - return InMemoryCoercionResult{ .no_match = .{ + return .{ .no_match = .{ .actual = dest_ty, .wanted = src_ty, } }; @@ -31505,6 +31518,8 @@ fn coerceInMemoryAllowedFns( block: *Block, dest_ty: Type, src_ty: Type, + /// If set, the coercion must be valid in both directions. + dest_is_mut: bool, target: std.Target, dest_src: LazySrcLoc, src_src: LazySrcLoc, @@ -31525,40 +31540,49 @@ fn coerceInMemoryAllowedFns( return InMemoryCoercionResult{ .fn_generic = dest_info.is_generic }; } - if (!callconvCoerceAllowed(target, src_info.cc, dest_info.cc)) { + const callconv_ok = callconvCoerceAllowed(target, src_info.cc, dest_info.cc) and + (!dest_is_mut or callconvCoerceAllowed(target, dest_info.cc, src_info.cc)); + + if (!callconv_ok) { return .{ .fn_cc = .{ .actual = src_info.cc, .wanted = dest_info.cc, } }; } - switch (src_info.return_type) { - .noreturn_type, .generic_poison_type => {}, - else => { - const dest_return_type = Type.fromInterned(dest_info.return_type); - const src_return_type = Type.fromInterned(src_info.return_type); - const rt = try sema.coerceInMemoryAllowed(block, dest_return_type, src_return_type, false, target, dest_src, src_src, null); - if (rt != .ok) { - return InMemoryCoercionResult{ .fn_return_type = .{ - .child = try rt.dupe(sema.arena), - .actual = src_return_type, - .wanted = dest_return_type, - } }; - } - }, + if (!switch (src_info.return_type) { + .generic_poison_type => true, + .noreturn_type => !dest_is_mut, + else => false, + }) { + const rt = try sema.coerceInMemoryAllowed( + block, + .fromInterned(dest_info.return_type), + .fromInterned(src_info.return_type), + dest_is_mut, + target, + dest_src, + src_src, + null, + ); + if (rt != .ok) return .{ .fn_return_type = .{ + .child = try rt.dupe(sema.arena), + .actual = .fromInterned(src_info.return_type), + .wanted = .fromInterned(dest_info.return_type), + } }; } } const params_len = params_len: { if (dest_info.param_types.len != src_info.param_types.len) { - return InMemoryCoercionResult{ .fn_param_count = .{ + return .{ .fn_param_count = .{ .actual = src_info.param_types.len, .wanted = dest_info.param_types.len, } }; } if (dest_info.noalias_bits != src_info.noalias_bits) { - return InMemoryCoercionResult{ .fn_param_noalias = .{ + return .{ .fn_param_noalias = .{ .actual = src_info.noalias_bits, .wanted = dest_info.noalias_bits, } }; @@ -31568,14 +31592,15 @@ fn coerceInMemoryAllowedFns( }; for (0..params_len) |param_i| { - const dest_param_ty = Type.fromInterned(dest_info.param_types.get(ip)[param_i]); - const src_param_ty = Type.fromInterned(src_info.param_types.get(ip)[param_i]); + const dest_param_ty: Type = .fromInterned(dest_info.param_types.get(ip)[param_i]); + const src_param_ty: Type = .fromInterned(src_info.param_types.get(ip)[param_i]); - const param_i_small: u5 = @intCast(param_i); - if (dest_info.paramIsComptime(param_i_small) != src_info.paramIsComptime(param_i_small)) { - return InMemoryCoercionResult{ .fn_param_comptime = .{ + const src_is_comptime = src_info.paramIsComptime(@intCast(param_i)); + const dest_is_comptime = dest_info.paramIsComptime(@intCast(param_i)); + if (src_is_comptime != dest_is_comptime) { + return .{ .fn_param_comptime = .{ .index = param_i, - .wanted = dest_info.paramIsComptime(param_i_small), + .wanted = dest_is_comptime, } }; } @@ -31583,9 +31608,9 @@ fn coerceInMemoryAllowedFns( .generic_poison_type => {}, else => { // Note: Cast direction is reversed here. - const param = try sema.coerceInMemoryAllowed(block, src_param_ty, dest_param_ty, false, target, dest_src, src_src, null); + const param = try sema.coerceInMemoryAllowed(block, src_param_ty, dest_param_ty, dest_is_mut, target, dest_src, src_src, null); if (param != .ok) { - return InMemoryCoercionResult{ .fn_param = .{ + return .{ .fn_param = .{ .child = try param.dupe(sema.arena), .actual = src_param_ty, .wanted = dest_param_ty, @@ -31644,6 +31669,7 @@ fn coerceInMemoryAllowedPtrs( src_ty: Type, dest_ptr_ty: Type, src_ptr_ty: Type, + /// If set, the coercion must be valid in both directions. dest_is_mut: bool, target: std.Target, dest_src: LazySrcLoc, @@ -31663,21 +31689,34 @@ fn coerceInMemoryAllowedPtrs( } }; } - const ok_cv_qualifiers = - (!src_info.flags.is_const or dest_info.flags.is_const) and - (!src_info.flags.is_volatile or dest_info.flags.is_volatile); + const ok_const = src_info.flags.is_const == dest_info.flags.is_const or + (!dest_is_mut and dest_info.flags.is_const); - if (!ok_cv_qualifiers) { - return InMemoryCoercionResult{ .ptr_qualifiers = .{ - .actual_const = src_info.flags.is_const, - .wanted_const = dest_info.flags.is_const, - .actual_volatile = src_info.flags.is_volatile, - .wanted_volatile = dest_info.flags.is_volatile, - } }; - } + if (!ok_const) return .{ .ptr_const = .{ + .actual = src_ty, + .wanted = dest_ty, + } }; + + const ok_volatile = src_info.flags.is_volatile == dest_info.flags.is_volatile or + (!dest_is_mut and dest_info.flags.is_volatile); + + if (!ok_volatile) return .{ .ptr_volatile = .{ + .actual = src_ty, + .wanted = dest_ty, + } }; + + const dest_allowzero = dest_ty.ptrAllowsZero(zcu); + const src_allowzero = src_ty.ptrAllowsZero(zcu); + const ok_allowzero = src_allowzero == dest_allowzero or + (!dest_is_mut and dest_allowzero); + + if (!ok_allowzero) return .{ .ptr_allowzero = .{ + .actual = src_ty, + .wanted = dest_ty, + } }; if (dest_info.flags.address_space != src_info.flags.address_space) { - return InMemoryCoercionResult{ .ptr_addrspace = .{ + return .{ .ptr_addrspace = .{ .actual = src_info.flags.address_space, .wanted = dest_info.flags.address_space, } }; @@ -31685,8 +31724,28 @@ fn coerceInMemoryAllowedPtrs( const dest_child = Type.fromInterned(dest_info.child); const src_child = Type.fromInterned(src_info.child); - const child = try sema.coerceInMemoryAllowed(block, dest_child, src_child, !dest_info.flags.is_const, target, dest_src, src_src, null); - if (child != .ok) allow: { + const child = try sema.coerceInMemoryAllowed( + block, + dest_child, + src_child, + // We must also include `dest_is_mut`. + // Otherwise, this code is valid: + // + // const b: B = ...; + // var pa: *const A = undefined; + // const ppa: **const A = &pa; + // const ppb: **const B = ppa; // <-- this is what that allows + // ppb.* = &b; + // const a: A = pa.*; + // + // ...effectively performing an in-memory coercion from B to A. + dest_is_mut or !dest_info.flags.is_const, + target, + dest_src, + src_src, + null, + ); + if (child != .ok and !dest_is_mut) allow: { // As a special case, we also allow coercing `*[n:s]T` to `*[n]T`, akin to dropping the sentinel from a slice. // `*[n:s]T` cannot coerce in memory to `*[n]T` since they have different sizes. if (src_child.zigTypeTag(zcu) == .array and dest_child.zigTypeTag(zcu) == .array and @@ -31695,30 +31754,17 @@ fn coerceInMemoryAllowedPtrs( { break :allow; } - return InMemoryCoercionResult{ .ptr_child = .{ + return .{ .ptr_child = .{ .child = try child.dupe(sema.arena), - .actual = Type.fromInterned(src_info.child), - .wanted = Type.fromInterned(dest_info.child), - } }; - } - - const dest_allow_zero = dest_ty.ptrAllowsZero(zcu); - const src_allow_zero = src_ty.ptrAllowsZero(zcu); - - const ok_allows_zero = (dest_allow_zero and - (src_allow_zero or !dest_is_mut)) or - (!dest_allow_zero and !src_allow_zero); - if (!ok_allows_zero) { - return InMemoryCoercionResult{ .ptr_allowzero = .{ - .actual = src_ty, - .wanted = dest_ty, + .actual = .fromInterned(src_info.child), + .wanted = .fromInterned(dest_info.child), } }; } if (src_info.packed_offset.host_size != dest_info.packed_offset.host_size or src_info.packed_offset.bit_offset != dest_info.packed_offset.bit_offset) { - return InMemoryCoercionResult{ .ptr_bit_range = .{ + return .{ .ptr_bit_range = .{ .actual_host = src_info.packed_offset.host_size, .wanted_host = dest_info.packed_offset.host_size, .actual_offset = src_info.packed_offset.bit_offset, @@ -31726,11 +31772,20 @@ fn coerceInMemoryAllowedPtrs( } }; } - const ok_sent = dest_info.sentinel == .none or src_info.flags.size == .C or - (src_info.sentinel != .none and - dest_info.sentinel == try zcu.intern_pool.getCoerced(sema.gpa, pt.tid, src_info.sentinel, dest_info.child)); - if (!ok_sent) { - return InMemoryCoercionResult{ .ptr_sentinel = .{ + const sentinel_ok = ok: { + const ss = src_info.sentinel; + const ds = dest_info.sentinel; + if (ss == .none and ds == .none) break :ok true; + if (ss != .none and ds != .none) { + if (ds == try zcu.intern_pool.getCoerced(sema.gpa, pt.tid, ss, dest_info.child)) break :ok true; + } + if (src_info.flags.size == .C) break :ok true; + if (!dest_is_mut and dest_info.sentinel == .none) break :ok true; + break :ok false; + }; + + if (!sentinel_ok) { + return .{ .ptr_sentinel = .{ .actual = switch (src_info.sentinel) { .none => Value.@"unreachable", else => Value.fromInterned(src_info.sentinel), @@ -31760,7 +31815,7 @@ fn coerceInMemoryAllowedPtrs( else try Type.fromInterned(dest_info.child).abiAlignmentSema(pt); - if (dest_align.compare(.gt, src_align)) { + if (dest_align.compare(if (dest_is_mut) .neq else .gt, src_align)) { return InMemoryCoercionResult{ .ptr_alignment = .{ .actual = src_align, .wanted = dest_align, @@ -32252,19 +32307,23 @@ fn checkPtrAttributes(sema: *Sema, dest_ty: Type, inst_ty: Type, in_memory_resul (Type.fromInterned(inst_info.child).arrayLen(zcu) == 0 and dest_info.sentinel == .none and dest_info.flags.size != .C and dest_info.flags.size != .Many))) or (Type.fromInterned(inst_info.child).isTuple(zcu) and Type.fromInterned(inst_info.child).structFieldCount(zcu) == 0); - const ok_cv_qualifiers = - ((!inst_info.flags.is_const or dest_info.flags.is_const) or len0) and - (!inst_info.flags.is_volatile or dest_info.flags.is_volatile); - - if (!ok_cv_qualifiers) { - in_memory_result.* = .{ .ptr_qualifiers = .{ - .actual_const = inst_info.flags.is_const, - .wanted_const = dest_info.flags.is_const, - .actual_volatile = inst_info.flags.is_volatile, - .wanted_volatile = dest_info.flags.is_volatile, + const ok_const = (!inst_info.flags.is_const or dest_info.flags.is_const) or len0; + const ok_volatile = !inst_info.flags.is_volatile or dest_info.flags.is_volatile; + if (!ok_const) { + in_memory_result.* = .{ .ptr_const = .{ + .actual = inst_ty, + .wanted = dest_ty, } }; return false; } + if (!ok_volatile) { + in_memory_result.* = .{ .ptr_volatile = .{ + .actual = inst_ty, + .wanted = dest_ty, + } }; + return false; + } + if (dest_info.flags.address_space != inst_info.flags.address_space) { in_memory_result.* = .{ .ptr_addrspace = .{ .actual = inst_info.flags.address_space, @@ -35441,11 +35500,11 @@ fn resolvePeerTypesInner( .peer_idx_b = i, } }; // ty -> cur_ty - if (.ok == try sema.coerceInMemoryAllowedFns(block, cur_ty, ty, target, src, src)) { + if (.ok == try sema.coerceInMemoryAllowedFns(block, cur_ty, ty, false, target, src, src)) { continue; } // cur_ty -> ty - if (.ok == try sema.coerceInMemoryAllowedFns(block, ty, cur_ty, target, src, src)) { + if (.ok == try sema.coerceInMemoryAllowedFns(block, ty, cur_ty, false, target, src, src)) { opt_cur_ty = ty; continue; } @@ -35834,12 +35893,12 @@ fn resolvePairInMemoryCoercible(sema: *Sema, block: *Block, src: LazySrcLoc, ty_ const target = sema.pt.zcu.getTarget(); // ty_b -> ty_a - if (.ok == try sema.coerceInMemoryAllowed(block, ty_a, ty_b, true, target, src, src, null)) { + if (.ok == try sema.coerceInMemoryAllowed(block, ty_a, ty_b, false, target, src, src, null)) { return ty_a; } // ty_a -> ty_b - if (.ok == try sema.coerceInMemoryAllowed(block, ty_b, ty_a, true, target, src, src, null)) { + if (.ok == try sema.coerceInMemoryAllowed(block, ty_b, ty_a, false, target, src, src, null)) { return ty_b; } diff --git a/src/clang.zig b/src/clang.zig index 904eca3c3f49..8b909cd077a0 100644 --- a/src/clang.zig +++ b/src/clang.zig @@ -2224,8 +2224,8 @@ pub const ErrorMsg = extern struct { pub const LoadFromCommandLine = ZigClangLoadFromCommandLine; extern fn ZigClangLoadFromCommandLine( - args_begin: [*]?[*]const u8, - args_end: [*]?[*]const u8, + args_begin: [*]?[*:0]const u8, + args_end: [*]?[*:0]const u8, errors_ptr: *[*]ErrorMsg, errors_len: *usize, resources_path: [*:0]const u8, diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 41975a048d0d..14065b2ff5c4 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -997,7 +997,7 @@ fn markRelocsDirtyByAddress(coff: *Coff, addr: u32) void { } } -fn resolveRelocs(coff: *Coff, atom_index: Atom.Index, relocs: []*const Relocation, code: []u8, image_base: u64) void { +fn resolveRelocs(coff: *Coff, atom_index: Atom.Index, relocs: []const *const Relocation, code: []u8, image_base: u64) void { log.debug("relocating '{s}'", .{coff.getAtom(atom_index).getName(coff)}); for (relocs) |reloc| { reloc.resolve(atom_index, code, image_base, coff); diff --git a/src/translate_c.zig b/src/translate_c.zig index 6faffde7e215..64a673456a06 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -79,8 +79,8 @@ pub const Context = struct { pub fn translate( gpa: mem.Allocator, - args_begin: [*]?[*]const u8, - args_end: [*]?[*]const u8, + args_begin: [*]?[*:0]const u8, + args_end: [*]?[*:0]const u8, errors: *std.zig.ErrorBundle, resources_path: [*:0]const u8, ) !std.zig.Ast { diff --git a/test/cases/compile_errors/implicit_cast_between_C_pointer_and_Zig_pointer-bad_const-align-child.zig b/test/cases/compile_errors/implicit_cast_between_C_pointer_and_Zig_pointer-bad_const-align-child.zig index d30db5733d8e..46974e5f1eb2 100644 --- a/test/cases/compile_errors/implicit_cast_between_C_pointer_and_Zig_pointer-bad_const-align-child.zig +++ b/test/cases/compile_errors/implicit_cast_between_C_pointer_and_Zig_pointer-bad_const-align-child.zig @@ -38,7 +38,7 @@ export fn f() void { // :8:18: error: expected type '*u8', found '[*c]const u8' // :8:18: note: cast discards const qualifier // :13:19: error: expected type '*u32', found '[*c]u8' -// :13:19: note: pointer type child 'u8' cannot cast into pointer type child 'u32' +// :13:19: note: '[*c]u8' could have null values which are illegal in type '*u32' // :18:22: error: expected type '[*c]u32', found '*align(1) u32' // :18:22: note: pointer alignment '1' cannot cast into pointer alignment '4' // :23:21: error: expected type '[*c]u8', found '*const u8' diff --git a/test/cases/compile_errors/implicit_casting_C_pointers_which_would_mess_up_null_semantics.zig b/test/cases/compile_errors/implicit_casting_C_pointers_which_would_mess_up_null_semantics.zig index ed3c402ef927..02a463a260ce 100644 --- a/test/cases/compile_errors/implicit_casting_C_pointers_which_would_mess_up_null_semantics.zig +++ b/test/cases/compile_errors/implicit_casting_C_pointers_which_would_mess_up_null_semantics.zig @@ -13,7 +13,7 @@ export fn entry2() void { var slice: []u8 = &buf; var opt_many_ptr: [*]u8 = slice.ptr; var ptr_opt_many_ptr = &opt_many_ptr; - var c_ptr: [*c][*c]const u8 = ptr_opt_many_ptr; + var c_ptr: [*c][*c]u8 = ptr_opt_many_ptr; _ = &slice; _ = &ptr_opt_many_ptr; _ = &c_ptr; @@ -24,8 +24,7 @@ export fn entry2() void { // target=native // // :6:24: error: expected type '*const [*]const u8', found '[*c]const [*c]const u8' -// :6:24: note: pointer type child '[*c]const u8' cannot cast into pointer type child '[*]const u8' -// :6:24: note: '[*c]const u8' could have null values which are illegal in type '[*]const u8' -// :16:35: error: expected type '[*c][*c]const u8', found '*[*]u8' -// :16:35: note: pointer type child '[*]u8' cannot cast into pointer type child '[*c]const u8' -// :16:35: note: mutable '[*]u8' allows illegal null values stored to type '[*c]const u8' +// :6:24: note: '[*c]const [*c]const u8' could have null values which are illegal in type '*const [*]const u8' +// :16:29: error: expected type '[*c][*c]u8', found '*[*]u8' +// :16:29: note: pointer type child '[*]u8' cannot cast into pointer type child '[*c]u8' +// :16:29: note: mutable '[*c]u8' would allow illegal null values stored to type '[*]u8' diff --git a/test/cases/compile_errors/invalid_pointer_coercions.zig b/test/cases/compile_errors/invalid_pointer_coercions.zig new file mode 100644 index 000000000000..7e38032baa51 --- /dev/null +++ b/test/cases/compile_errors/invalid_pointer_coercions.zig @@ -0,0 +1,77 @@ +//! This file contains pointer coercions which are invalid because the element types are only +//! in-memory coercible *in one direction*. When casting a mutable pointer, the element type +//! must coerce in both directions for the pointer to coerce. Otherwise, you could do something +//! like this, where `A` coerces to `B` but not vice-versa: +//! +//! ``` +//! var x: A = undefined; +//! const p: *B = &x; // `*A` -> `*B` +//! p.* = some_b; +//! const some_b_as_a = x; +//! ``` + +export fn error_set_to_larger() void { + var x: error{Foo} = undefined; + _ = @as(*const error{ Foo, Bar }, &x); // this is ok + _ = @as(*error{ Foo, Bar }, &x); // compile error +} + +export fn error_set_to_anyerror() void { + var x: error{Foo} = undefined; + _ = @as(*const anyerror, &x); // this is ok + _ = @as(*anyerror, &x); // compile error +} + +export fn error_union_to_anyerror_union() void { + var x: error{Foo}!u32 = undefined; + _ = @as(*const anyerror!u32, &x); // this is ok + _ = @as(*anyerror!u32, &x); // compile error +} + +export fn ptr_to_const_ptr() void { + var x: *u32 = undefined; + _ = @as(*const *const u32, &x); // this is ok + _ = @as(**const u32, &x); // compile error +} + +export fn ptr_to_allowzero_ptr() void { + var x: *u32 = undefined; + _ = @as(*const *allowzero u32, &x); // this is ok + _ = @as(**allowzero u32, &x); // compile error +} + +export fn ptr_to_volatile_ptr() void { + var x: *u32 = undefined; + _ = @as(*const *volatile u32, &x); // this is ok + _ = @as(**volatile u32, &x); // compile error +} + +export fn ptr_to_underaligned_ptr() void { + var x: *u32 = undefined; + _ = @as(*const *align(1) u32, &x); // this is ok + _ = @as(**align(1) u32, &x); // compile error +} + +// error +// +// :16:33: error: expected type '*error{Foo,Bar}', found '*error{Foo}' +// :16:33: note: pointer type child 'error{Foo}' cannot cast into pointer type child 'error{Foo,Bar}' +// :16:33: note: 'error.Bar' not a member of destination error set +// :22:24: error: expected type '*anyerror', found '*error{Foo}' +// :22:24: note: pointer type child 'error{Foo}' cannot cast into pointer type child 'anyerror' +// :22:24: note: global error set cannot cast into a smaller set +// :28:28: error: expected type '*anyerror!u32', found '*error{Foo}!u32' +// :28:28: note: pointer type child 'error{Foo}!u32' cannot cast into pointer type child 'anyerror!u32' +// :28:28: note: global error set cannot cast into a smaller set +// :34:26: error: expected type '**const u32', found '**u32' +// :34:26: note: pointer type child '*u32' cannot cast into pointer type child '*const u32' +// :34:26: note: mutable '*const u32' would allow illegal const pointers stored to type '*u32' +// :40:30: error: expected type '**allowzero u32', found '**u32' +// :40:30: note: pointer type child '*u32' cannot cast into pointer type child '*allowzero u32' +// :40:30: note: mutable '*allowzero u32' would allow illegal null values stored to type '*u32' +// :46:29: error: expected type '**volatile u32', found '**u32' +// :46:29: note: pointer type child '*u32' cannot cast into pointer type child '*volatile u32' +// :46:29: note: mutable '*volatile u32' would allow illegal volatile pointers stored to type '*u32' +// :52:29: error: expected type '**align(1) u32', found '**u32' +// :52:29: note: pointer type child '*u32' cannot cast into pointer type child '*align(1) u32' +// :52:29: note: pointer alignment '4' cannot cast into pointer alignment '1' diff --git a/test/cases/compile_errors/use_implicit_casts_to_assign_null_to_non-nullable_pointer.zig b/test/cases/compile_errors/use_implicit_casts_to_assign_null_to_non-nullable_pointer.zig index d3b7e45f185a..fe56feeadd17 100644 --- a/test/cases/compile_errors/use_implicit_casts_to_assign_null_to_non-nullable_pointer.zig +++ b/test/cases/compile_errors/use_implicit_casts_to_assign_null_to_non-nullable_pointer.zig @@ -12,4 +12,4 @@ export fn entry() void { // // :4:24: error: expected type '*?*i32', found '**i32' // :4:24: note: pointer type child '*i32' cannot cast into pointer type child '?*i32' -// :4:24: note: mutable '*i32' allows illegal null values stored to type '?*i32' +// :4:24: note: mutable '?*i32' would allow illegal null values stored to type '*i32'