authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-12-15 14:54:57+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-12-16 14:53:54+00:00
logf154cd1fdc748c3f9c1c499347c7c0b26f0d3e19
tree16978eef86ad9d0eefca5d17443f6d40358acff6
parentd12c0bf90911339c8db0741b257eac251b827b2c
signaturelock-open Commit is signed but in an unrecognized format.

Sema: disallow unsafe in-memory coercions

The error messages here aren't amazing yet, but this is an improvement on status quo, because the current behavior allows false negative compile errors, so effectively miscompiles. Resolves: #15874

5 files changed, 263 insertions(+), 128 deletions(-)

src/Sema.zig+179-120
...@@ -30797,7 +30797,8 @@ const InMemoryCoercionResult = union(enum) {...@@ -30797,7 +30797,8 @@ const InMemoryCoercionResult = union(enum) {
30797 ptr_addrspace: AddressSpace,30797 ptr_addrspace: AddressSpace,
30798 ptr_sentinel: Sentinel,30798 ptr_sentinel: Sentinel,
30799 ptr_size: Size,30799 ptr_size: Size,
30800 ptr_qualifiers: Qualifiers,30800 ptr_const: Pair,
30801 ptr_volatile: Pair,
30801 ptr_allowzero: Pair,30802 ptr_allowzero: Pair,
30802 ptr_bit_range: BitRange,30803 ptr_bit_range: BitRange,
30803 ptr_alignment: AlignPair,30804 ptr_alignment: AlignPair,
...@@ -30861,13 +30862,6 @@ const InMemoryCoercionResult = union(enum) {...@@ -30861,13 +30862,6 @@ const InMemoryCoercionResult = union(enum) {
30861 wanted: std.builtin.Type.Pointer.Size,30862 wanted: std.builtin.Type.Pointer.Size,
30862 };30863 };
3086330864
30864 const Qualifiers = struct {
30865 actual_const: bool,
30866 wanted_const: bool,
30867 actual_volatile: bool,
30868 wanted_volatile: bool,
30869 };
30870
30871 const AddressSpace = struct {30865 const AddressSpace = struct {
30872 actual: std.builtin.AddressSpace,30866 actual: std.builtin.AddressSpace,
30873 wanted: std.builtin.AddressSpace,30867 wanted: std.builtin.AddressSpace,
...@@ -31067,16 +31061,6 @@ const InMemoryCoercionResult = union(enum) {...@@ -31067,16 +31061,6 @@ const InMemoryCoercionResult = union(enum) {
31067 try sema.errNote(src, msg, "a {s} pointer cannot cast into a {s} pointer", .{ pointerSizeString(size.actual), pointerSizeString(size.wanted) });31061 try sema.errNote(src, msg, "a {s} pointer cannot cast into a {s} pointer", .{ pointerSizeString(size.actual), pointerSizeString(size.wanted) });
31068 break;31062 break;
31069 },31063 },
31070 .ptr_qualifiers => |qualifiers| {
31071 const ok_const = !qualifiers.actual_const or qualifiers.wanted_const;
31072 const ok_volatile = !qualifiers.actual_volatile or qualifiers.wanted_volatile;
31073 if (!ok_const) {
31074 try sema.errNote(src, msg, "cast discards const qualifier", .{});
31075 } else if (!ok_volatile) {
31076 try sema.errNote(src, msg, "cast discards volatile qualifier", .{});
31077 }
31078 break;
31079 },
31080 .ptr_allowzero => |pair| {31064 .ptr_allowzero => |pair| {
31081 const wanted_allow_zero = pair.wanted.ptrAllowsZero(pt.zcu);31065 const wanted_allow_zero = pair.wanted.ptrAllowsZero(pt.zcu);
31082 const actual_allow_zero = pair.actual.ptrAllowsZero(pt.zcu);31066 const actual_allow_zero = pair.actual.ptrAllowsZero(pt.zcu);
...@@ -31085,8 +31069,32 @@ const InMemoryCoercionResult = union(enum) {...@@ -31085,8 +31069,32 @@ const InMemoryCoercionResult = union(enum) {
31085 pair.actual.fmt(pt), pair.wanted.fmt(pt),31069 pair.actual.fmt(pt), pair.wanted.fmt(pt),
31086 });31070 });
31087 } else {31071 } else {
31088 try sema.errNote(src, msg, "mutable '{}' allows illegal null values stored to type '{}'", .{31072 try sema.errNote(src, msg, "mutable '{}' would allow illegal null values stored to type '{}'", .{
31089 pair.actual.fmt(pt), pair.wanted.fmt(pt),31073 pair.wanted.fmt(pt), pair.actual.fmt(pt),
31074 });
31075 }
31076 break;
31077 },
31078 .ptr_const => |pair| {
31079 const wanted_const = pair.wanted.isConstPtr(pt.zcu);
31080 const actual_const = pair.actual.isConstPtr(pt.zcu);
31081 if (actual_const and !wanted_const) {
31082 try sema.errNote(src, msg, "cast discards const qualifier", .{});
31083 } else {
31084 try sema.errNote(src, msg, "mutable '{}' would allow illegal const pointers stored to type '{}'", .{
31085 pair.wanted.fmt(pt), pair.actual.fmt(pt),
31086 });
31087 }
31088 break;
31089 },
31090 .ptr_volatile => |pair| {
31091 const wanted_volatile = pair.wanted.isVolatilePtr(pt.zcu);
31092 const actual_volatile = pair.actual.isVolatilePtr(pt.zcu);
31093 if (actual_volatile and !wanted_volatile) {
31094 try sema.errNote(src, msg, "cast discards volatile qualifier", .{});
31095 } else {
31096 try sema.errNote(src, msg, "mutable '{}' would allow illegal volatile pointers stored to type '{}'", .{
31097 pair.wanted.fmt(pt), pair.actual.fmt(pt),
31090 });31098 });
31091 }31099 }
31092 break;31100 break;
...@@ -31136,20 +31144,22 @@ fn pointerSizeString(size: std.builtin.Type.Pointer.Size) []const u8 {...@@ -31136,20 +31144,22 @@ fn pointerSizeString(size: std.builtin.Type.Pointer.Size) []const u8 {
31136 };31144 };
31137}31145}
3113831146
31139/// If pointers have the same representation in runtime memory, a bitcast AIR instruction31147/// If types `A` and `B` have identical representations in runtime memory, they are considered
31140/// may be used for the coercion.31148/// "in-memory coercible". This is a subset of normal coercions. Not only can `A` coerce to `B`, but
31141/// * `const` attribute can be gained31149/// also, coercions can happen through pointers. For instance, `*const A` can coerce to `*const B`.
31142/// * `volatile` attribute can be gained31150///
31143/// * `allowzero` attribute can be gained (whether from explicit attribute, C pointer, or optional pointer) but only if !dest_is_mut31151/// If this function is called, the coercion must be applied, or a compile error emitted if `.ok`
31144/// * alignment can be decreased31152/// is not returned. This is because this function may modify inferred error sets to make a
31145/// * bit offset attributes must match exactly31153/// coercion possible, even if `.ok` is not returned.
31146/// * `*`/`[*]` must match exactly, but `[*c]` matches either one
31147/// * sentinel-terminated pointers can coerce into `[*]`
31148pub fn coerceInMemoryAllowed(31154pub fn coerceInMemoryAllowed(
31149 sema: *Sema,31155 sema: *Sema,
31150 block: *Block,31156 block: *Block,
31151 dest_ty: Type,31157 dest_ty: Type,
31152 src_ty: Type,31158 src_ty: Type,
31159 /// If `true`, this query comes from an attempted coercion of the form `*Src` -> `*Dest`, where
31160 /// both pointers are mutable. If this coercion is allowed, one could store to the `*Dest` and
31161 /// load from the `*Src` to effectively perform an in-memory coercion from `Dest` to `Src`.
31162 /// Therefore, when `dest_is_mut`, the in-memory coercion must be valid in *both directions*.
31153 dest_is_mut: bool,31163 dest_is_mut: bool,
31154 target: std.Target,31164 target: std.Target,
31155 dest_src: LazySrcLoc,31165 dest_src: LazySrcLoc,
...@@ -31224,7 +31234,7 @@ pub fn coerceInMemoryAllowed(...@@ -31224,7 +31234,7 @@ pub fn coerceInMemoryAllowed(
3122431234
31225 // Functions31235 // Functions
31226 if (dest_tag == .@"fn" and src_tag == .@"fn") {31236 if (dest_tag == .@"fn" and src_tag == .@"fn") {
31227 return try sema.coerceInMemoryAllowedFns(block, dest_ty, src_ty, target, dest_src, src_src);31237 return try sema.coerceInMemoryAllowedFns(block, dest_ty, src_ty, dest_is_mut, target, dest_src, src_src);
31228 }31238 }
3122931239
31230 // Error Unions31240 // Error Unions
...@@ -31233,7 +31243,7 @@ pub fn coerceInMemoryAllowed(...@@ -31233,7 +31243,7 @@ pub fn coerceInMemoryAllowed(
31233 const src_payload = src_ty.errorUnionPayload(zcu);31243 const src_payload = src_ty.errorUnionPayload(zcu);
31234 const child = try sema.coerceInMemoryAllowed(block, dest_payload, src_payload, dest_is_mut, target, dest_src, src_src, null);31244 const child = try sema.coerceInMemoryAllowed(block, dest_payload, src_payload, dest_is_mut, target, dest_src, src_src, null);
31235 if (child != .ok) {31245 if (child != .ok) {
31236 return InMemoryCoercionResult{ .error_union_payload = .{31246 return .{ .error_union_payload = .{
31237 .child = try child.dupe(sema.arena),31247 .child = try child.dupe(sema.arena),
31238 .actual = src_payload,31248 .actual = src_payload,
31239 .wanted = dest_payload,31249 .wanted = dest_payload,
...@@ -31244,7 +31254,11 @@ pub fn coerceInMemoryAllowed(...@@ -31244,7 +31254,11 @@ pub fn coerceInMemoryAllowed(
3124431254
31245 // Error Sets31255 // Error Sets
31246 if (dest_tag == .error_set and src_tag == .error_set) {31256 if (dest_tag == .error_set and src_tag == .error_set) {
31247 return try sema.coerceInMemoryAllowedErrorSets(block, dest_ty, src_ty, dest_src, src_src);31257 const res1 = try sema.coerceInMemoryAllowedErrorSets(block, dest_ty, src_ty, dest_src, src_src);
31258 if (!dest_is_mut or res1 != .ok) return res1;
31259 // src -> dest is okay, but `dest_is_mut`, so it needs to be allowed in the other direction.
31260 const res2 = try sema.coerceInMemoryAllowedErrorSets(block, src_ty, dest_ty, src_src, dest_src);
31261 return res2;
31248 }31262 }
3124931263
31250 // Arrays31264 // Arrays
...@@ -31252,7 +31266,7 @@ pub fn coerceInMemoryAllowed(...@@ -31252,7 +31266,7 @@ pub fn coerceInMemoryAllowed(
31252 const dest_info = dest_ty.arrayInfo(zcu);31266 const dest_info = dest_ty.arrayInfo(zcu);
31253 const src_info = src_ty.arrayInfo(zcu);31267 const src_info = src_ty.arrayInfo(zcu);
31254 if (dest_info.len != src_info.len) {31268 if (dest_info.len != src_info.len) {
31255 return InMemoryCoercionResult{ .array_len = .{31269 return .{ .array_len = .{
31256 .actual = src_info.len,31270 .actual = src_info.len,
31257 .wanted = dest_info.len,31271 .wanted = dest_info.len,
31258 } };31272 } };
...@@ -31263,7 +31277,7 @@ pub fn coerceInMemoryAllowed(...@@ -31263,7 +31277,7 @@ pub fn coerceInMemoryAllowed(
31263 .ok => {},31277 .ok => {},
31264 .no_match => return child,31278 .no_match => return child,
31265 else => {31279 else => {
31266 return InMemoryCoercionResult{ .array_elem = .{31280 return .{ .array_elem = .{
31267 .child = try child.dupe(sema.arena),31281 .child = try child.dupe(sema.arena),
31268 .actual = src_info.elem_type,31282 .actual = src_info.elem_type,
31269 .wanted = dest_info.elem_type,31283 .wanted = dest_info.elem_type,
...@@ -31279,7 +31293,7 @@ pub fn coerceInMemoryAllowed(...@@ -31279,7 +31293,7 @@ pub fn coerceInMemoryAllowed(
31279 zcu,31293 zcu,
31280 ));31294 ));
31281 if (!ok_sent) {31295 if (!ok_sent) {
31282 return InMemoryCoercionResult{ .array_sentinel = .{31296 return .{ .array_sentinel = .{
31283 .actual = src_info.sentinel orelse Value.@"unreachable",31297 .actual = src_info.sentinel orelse Value.@"unreachable",
31284 .wanted = dest_info.sentinel orelse Value.@"unreachable",31298 .wanted = dest_info.sentinel orelse Value.@"unreachable",
31285 .ty = dest_info.elem_type,31299 .ty = dest_info.elem_type,
...@@ -31293,7 +31307,7 @@ pub fn coerceInMemoryAllowed(...@@ -31293,7 +31307,7 @@ pub fn coerceInMemoryAllowed(
31293 const dest_len = dest_ty.vectorLen(zcu);31307 const dest_len = dest_ty.vectorLen(zcu);
31294 const src_len = src_ty.vectorLen(zcu);31308 const src_len = src_ty.vectorLen(zcu);
31295 if (dest_len != src_len) {31309 if (dest_len != src_len) {
31296 return InMemoryCoercionResult{ .vector_len = .{31310 return .{ .vector_len = .{
31297 .actual = src_len,31311 .actual = src_len,
31298 .wanted = dest_len,31312 .wanted = dest_len,
31299 } };31313 } };
...@@ -31303,7 +31317,7 @@ pub fn coerceInMemoryAllowed(...@@ -31303,7 +31317,7 @@ pub fn coerceInMemoryAllowed(
31303 const src_elem_ty = src_ty.scalarType(zcu);31317 const src_elem_ty = src_ty.scalarType(zcu);
31304 const child = try sema.coerceInMemoryAllowed(block, dest_elem_ty, src_elem_ty, dest_is_mut, target, dest_src, src_src, null);31318 const child = try sema.coerceInMemoryAllowed(block, dest_elem_ty, src_elem_ty, dest_is_mut, target, dest_src, src_src, null);
31305 if (child != .ok) {31319 if (child != .ok) {
31306 return InMemoryCoercionResult{ .vector_elem = .{31320 return .{ .vector_elem = .{
31307 .child = try child.dupe(sema.arena),31321 .child = try child.dupe(sema.arena),
31308 .actual = src_elem_ty,31322 .actual = src_elem_ty,
31309 .wanted = dest_elem_ty,31323 .wanted = dest_elem_ty,
...@@ -31320,7 +31334,7 @@ pub fn coerceInMemoryAllowed(...@@ -31320,7 +31334,7 @@ pub fn coerceInMemoryAllowed(
31320 const dest_len = dest_ty.arrayLen(zcu);31334 const dest_len = dest_ty.arrayLen(zcu);
31321 const src_len = src_ty.arrayLen(zcu);31335 const src_len = src_ty.arrayLen(zcu);
31322 if (dest_len != src_len) {31336 if (dest_len != src_len) {
31323 return InMemoryCoercionResult{ .array_len = .{31337 return .{ .array_len = .{
31324 .actual = src_len,31338 .actual = src_len,
31325 .wanted = dest_len,31339 .wanted = dest_len,
31326 } };31340 } };
...@@ -31330,7 +31344,7 @@ pub fn coerceInMemoryAllowed(...@@ -31330,7 +31344,7 @@ pub fn coerceInMemoryAllowed(
31330 const src_elem_ty = src_ty.childType(zcu);31344 const src_elem_ty = src_ty.childType(zcu);
31331 const child = try sema.coerceInMemoryAllowed(block, dest_elem_ty, src_elem_ty, dest_is_mut, target, dest_src, src_src, null);31345 const child = try sema.coerceInMemoryAllowed(block, dest_elem_ty, src_elem_ty, dest_is_mut, target, dest_src, src_src, null);
31332 if (child != .ok) {31346 if (child != .ok) {
31333 return InMemoryCoercionResult{ .array_elem = .{31347 return .{ .array_elem = .{
31334 .child = try child.dupe(sema.arena),31348 .child = try child.dupe(sema.arena),
31335 .actual = src_elem_ty,31349 .actual = src_elem_ty,
31336 .wanted = dest_elem_ty,31350 .wanted = dest_elem_ty,
...@@ -31340,7 +31354,7 @@ pub fn coerceInMemoryAllowed(...@@ -31340,7 +31354,7 @@ pub fn coerceInMemoryAllowed(
31340 if (dest_tag == .array) {31354 if (dest_tag == .array) {
31341 const dest_info = dest_ty.arrayInfo(zcu);31355 const dest_info = dest_ty.arrayInfo(zcu);
31342 if (dest_info.sentinel != null) {31356 if (dest_info.sentinel != null) {
31343 return InMemoryCoercionResult{ .array_sentinel = .{31357 return .{ .array_sentinel = .{
31344 .actual = Value.@"unreachable",31358 .actual = Value.@"unreachable",
31345 .wanted = dest_info.sentinel.?,31359 .wanted = dest_info.sentinel.?,
31346 .ty = dest_info.elem_type,31360 .ty = dest_info.elem_type,
...@@ -31360,7 +31374,7 @@ pub fn coerceInMemoryAllowed(...@@ -31360,7 +31374,7 @@ pub fn coerceInMemoryAllowed(
31360 // Optionals31374 // Optionals
31361 if (dest_tag == .optional and src_tag == .optional) {31375 if (dest_tag == .optional and src_tag == .optional) {
31362 if ((maybe_dest_ptr_ty != null) != (maybe_src_ptr_ty != null)) {31376 if ((maybe_dest_ptr_ty != null) != (maybe_src_ptr_ty != null)) {
31363 return InMemoryCoercionResult{ .optional_shape = .{31377 return .{ .optional_shape = .{
31364 .actual = src_ty,31378 .actual = src_ty,
31365 .wanted = dest_ty,31379 .wanted = dest_ty,
31366 } };31380 } };
...@@ -31370,7 +31384,7 @@ pub fn coerceInMemoryAllowed(...@@ -31370,7 +31384,7 @@ pub fn coerceInMemoryAllowed(
3137031384
31371 const child = try sema.coerceInMemoryAllowed(block, dest_child_type, src_child_type, dest_is_mut, target, dest_src, src_src, null);31385 const child = try sema.coerceInMemoryAllowed(block, dest_child_type, src_child_type, dest_is_mut, target, dest_src, src_src, null);
31372 if (child != .ok) {31386 if (child != .ok) {
31373 return InMemoryCoercionResult{ .optional_child = .{31387 return .{ .optional_child = .{
31374 .child = try child.dupe(sema.arena),31388 .child = try child.dupe(sema.arena),
31375 .actual = src_child_type,31389 .actual = src_child_type,
31376 .wanted = dest_child_type,31390 .wanted = dest_child_type,
...@@ -31382,7 +31396,6 @@ pub fn coerceInMemoryAllowed(...@@ -31382,7 +31396,6 @@ pub fn coerceInMemoryAllowed(
3138231396
31383 // Tuples (with in-memory-coercible fields)31397 // Tuples (with in-memory-coercible fields)
31384 if (dest_ty.isTuple(zcu) and src_ty.isTuple(zcu)) tuple: {31398 if (dest_ty.isTuple(zcu) and src_ty.isTuple(zcu)) tuple: {
31385 if (dest_ty.containerLayout(zcu) != src_ty.containerLayout(zcu)) break :tuple;
31386 if (dest_ty.structFieldCount(zcu) != src_ty.structFieldCount(zcu)) break :tuple;31399 if (dest_ty.structFieldCount(zcu) != src_ty.structFieldCount(zcu)) break :tuple;
31387 const field_count = dest_ty.structFieldCount(zcu);31400 const field_count = dest_ty.structFieldCount(zcu);
31388 for (0..field_count) |field_idx| {31401 for (0..field_count) |field_idx| {
...@@ -31396,7 +31409,7 @@ pub fn coerceInMemoryAllowed(...@@ -31396,7 +31409,7 @@ pub fn coerceInMemoryAllowed(
31396 return .ok;31409 return .ok;
31397 }31410 }
3139831411
31399 return InMemoryCoercionResult{ .no_match = .{31412 return .{ .no_match = .{
31400 .actual = dest_ty,31413 .actual = dest_ty,
31401 .wanted = src_ty,31414 .wanted = src_ty,
31402 } };31415 } };
...@@ -31505,6 +31518,8 @@ fn coerceInMemoryAllowedFns(...@@ -31505,6 +31518,8 @@ fn coerceInMemoryAllowedFns(
31505 block: *Block,31518 block: *Block,
31506 dest_ty: Type,31519 dest_ty: Type,
31507 src_ty: Type,31520 src_ty: Type,
31521 /// If set, the coercion must be valid in both directions.
31522 dest_is_mut: bool,
31508 target: std.Target,31523 target: std.Target,
31509 dest_src: LazySrcLoc,31524 dest_src: LazySrcLoc,
31510 src_src: LazySrcLoc,31525 src_src: LazySrcLoc,
...@@ -31525,40 +31540,49 @@ fn coerceInMemoryAllowedFns(...@@ -31525,40 +31540,49 @@ fn coerceInMemoryAllowedFns(
31525 return InMemoryCoercionResult{ .fn_generic = dest_info.is_generic };31540 return InMemoryCoercionResult{ .fn_generic = dest_info.is_generic };
31526 }31541 }
3152731542
31528 if (!callconvCoerceAllowed(target, src_info.cc, dest_info.cc)) {31543 const callconv_ok = callconvCoerceAllowed(target, src_info.cc, dest_info.cc) and
31544 (!dest_is_mut or callconvCoerceAllowed(target, dest_info.cc, src_info.cc));
31545
31546 if (!callconv_ok) {
31529 return .{ .fn_cc = .{31547 return .{ .fn_cc = .{
31530 .actual = src_info.cc,31548 .actual = src_info.cc,
31531 .wanted = dest_info.cc,31549 .wanted = dest_info.cc,
31532 } };31550 } };
31533 }31551 }
3153431552
31535 switch (src_info.return_type) {31553 if (!switch (src_info.return_type) {
31536 .noreturn_type, .generic_poison_type => {},31554 .generic_poison_type => true,
31537 else => {31555 .noreturn_type => !dest_is_mut,
31538 const dest_return_type = Type.fromInterned(dest_info.return_type);31556 else => false,
31539 const src_return_type = Type.fromInterned(src_info.return_type);31557 }) {
31540 const rt = try sema.coerceInMemoryAllowed(block, dest_return_type, src_return_type, false, target, dest_src, src_src, null);31558 const rt = try sema.coerceInMemoryAllowed(
31541 if (rt != .ok) {31559 block,
31542 return InMemoryCoercionResult{ .fn_return_type = .{31560 .fromInterned(dest_info.return_type),
31543 .child = try rt.dupe(sema.arena),31561 .fromInterned(src_info.return_type),
31544 .actual = src_return_type,31562 dest_is_mut,
31545 .wanted = dest_return_type,31563 target,
31546 } };31564 dest_src,
31547 }31565 src_src,
31548 },31566 null,
31567 );
31568 if (rt != .ok) return .{ .fn_return_type = .{
31569 .child = try rt.dupe(sema.arena),
31570 .actual = .fromInterned(src_info.return_type),
31571 .wanted = .fromInterned(dest_info.return_type),
31572 } };
31549 }31573 }
31550 }31574 }
3155131575
31552 const params_len = params_len: {31576 const params_len = params_len: {
31553 if (dest_info.param_types.len != src_info.param_types.len) {31577 if (dest_info.param_types.len != src_info.param_types.len) {
31554 return InMemoryCoercionResult{ .fn_param_count = .{31578 return .{ .fn_param_count = .{
31555 .actual = src_info.param_types.len,31579 .actual = src_info.param_types.len,
31556 .wanted = dest_info.param_types.len,31580 .wanted = dest_info.param_types.len,
31557 } };31581 } };
31558 }31582 }
3155931583
31560 if (dest_info.noalias_bits != src_info.noalias_bits) {31584 if (dest_info.noalias_bits != src_info.noalias_bits) {
31561 return InMemoryCoercionResult{ .fn_param_noalias = .{31585 return .{ .fn_param_noalias = .{
31562 .actual = src_info.noalias_bits,31586 .actual = src_info.noalias_bits,
31563 .wanted = dest_info.noalias_bits,31587 .wanted = dest_info.noalias_bits,
31564 } };31588 } };
...@@ -31568,14 +31592,15 @@ fn coerceInMemoryAllowedFns(...@@ -31568,14 +31592,15 @@ fn coerceInMemoryAllowedFns(
31568 };31592 };
3156931593
31570 for (0..params_len) |param_i| {31594 for (0..params_len) |param_i| {
31571 const dest_param_ty = Type.fromInterned(dest_info.param_types.get(ip)[param_i]);31595 const dest_param_ty: Type = .fromInterned(dest_info.param_types.get(ip)[param_i]);
31572 const src_param_ty = Type.fromInterned(src_info.param_types.get(ip)[param_i]);31596 const src_param_ty: Type = .fromInterned(src_info.param_types.get(ip)[param_i]);
3157331597
31574 const param_i_small: u5 = @intCast(param_i);31598 const src_is_comptime = src_info.paramIsComptime(@intCast(param_i));
31575 if (dest_info.paramIsComptime(param_i_small) != src_info.paramIsComptime(param_i_small)) {31599 const dest_is_comptime = dest_info.paramIsComptime(@intCast(param_i));
31576 return InMemoryCoercionResult{ .fn_param_comptime = .{31600 if (src_is_comptime != dest_is_comptime) {
31601 return .{ .fn_param_comptime = .{
31577 .index = param_i,31602 .index = param_i,
31578 .wanted = dest_info.paramIsComptime(param_i_small),31603 .wanted = dest_is_comptime,
31579 } };31604 } };
31580 }31605 }
3158131606
...@@ -31583,9 +31608,9 @@ fn coerceInMemoryAllowedFns(...@@ -31583,9 +31608,9 @@ fn coerceInMemoryAllowedFns(
31583 .generic_poison_type => {},31608 .generic_poison_type => {},
31584 else => {31609 else => {
31585 // Note: Cast direction is reversed here.31610 // Note: Cast direction is reversed here.
31586 const param = try sema.coerceInMemoryAllowed(block, src_param_ty, dest_param_ty, false, target, dest_src, src_src, null);31611 const param = try sema.coerceInMemoryAllowed(block, src_param_ty, dest_param_ty, dest_is_mut, target, dest_src, src_src, null);
31587 if (param != .ok) {31612 if (param != .ok) {
31588 return InMemoryCoercionResult{ .fn_param = .{31613 return .{ .fn_param = .{
31589 .child = try param.dupe(sema.arena),31614 .child = try param.dupe(sema.arena),
31590 .actual = src_param_ty,31615 .actual = src_param_ty,
31591 .wanted = dest_param_ty,31616 .wanted = dest_param_ty,
...@@ -31644,6 +31669,7 @@ fn coerceInMemoryAllowedPtrs(...@@ -31644,6 +31669,7 @@ fn coerceInMemoryAllowedPtrs(
31644 src_ty: Type,31669 src_ty: Type,
31645 dest_ptr_ty: Type,31670 dest_ptr_ty: Type,
31646 src_ptr_ty: Type,31671 src_ptr_ty: Type,
31672 /// If set, the coercion must be valid in both directions.
31647 dest_is_mut: bool,31673 dest_is_mut: bool,
31648 target: std.Target,31674 target: std.Target,
31649 dest_src: LazySrcLoc,31675 dest_src: LazySrcLoc,
...@@ -31663,21 +31689,34 @@ fn coerceInMemoryAllowedPtrs(...@@ -31663,21 +31689,34 @@ fn coerceInMemoryAllowedPtrs(
31663 } };31689 } };
31664 }31690 }
3166531691
31666 const ok_cv_qualifiers =31692 const ok_const = src_info.flags.is_const == dest_info.flags.is_const or
31667 (!src_info.flags.is_const or dest_info.flags.is_const) and31693 (!dest_is_mut and dest_info.flags.is_const);
31668 (!src_info.flags.is_volatile or dest_info.flags.is_volatile);
3166931694
31670 if (!ok_cv_qualifiers) {31695 if (!ok_const) return .{ .ptr_const = .{
31671 return InMemoryCoercionResult{ .ptr_qualifiers = .{31696 .actual = src_ty,
31672 .actual_const = src_info.flags.is_const,31697 .wanted = dest_ty,
31673 .wanted_const = dest_info.flags.is_const,31698 } };
31674 .actual_volatile = src_info.flags.is_volatile,31699
31675 .wanted_volatile = dest_info.flags.is_volatile,31700 const ok_volatile = src_info.flags.is_volatile == dest_info.flags.is_volatile or
31676 } };31701 (!dest_is_mut and dest_info.flags.is_volatile);
31677 }31702
31703 if (!ok_volatile) return .{ .ptr_volatile = .{
31704 .actual = src_ty,
31705 .wanted = dest_ty,
31706 } };
31707
31708 const dest_allowzero = dest_ty.ptrAllowsZero(zcu);
31709 const src_allowzero = src_ty.ptrAllowsZero(zcu);
31710 const ok_allowzero = src_allowzero == dest_allowzero or
31711 (!dest_is_mut and dest_allowzero);
31712
31713 if (!ok_allowzero) return .{ .ptr_allowzero = .{
31714 .actual = src_ty,
31715 .wanted = dest_ty,
31716 } };
3167831717
31679 if (dest_info.flags.address_space != src_info.flags.address_space) {31718 if (dest_info.flags.address_space != src_info.flags.address_space) {
31680 return InMemoryCoercionResult{ .ptr_addrspace = .{31719 return .{ .ptr_addrspace = .{
31681 .actual = src_info.flags.address_space,31720 .actual = src_info.flags.address_space,
31682 .wanted = dest_info.flags.address_space,31721 .wanted = dest_info.flags.address_space,
31683 } };31722 } };
...@@ -31685,8 +31724,28 @@ fn coerceInMemoryAllowedPtrs(...@@ -31685,8 +31724,28 @@ fn coerceInMemoryAllowedPtrs(
3168531724
31686 const dest_child = Type.fromInterned(dest_info.child);31725 const dest_child = Type.fromInterned(dest_info.child);
31687 const src_child = Type.fromInterned(src_info.child);31726 const src_child = Type.fromInterned(src_info.child);
31688 const child = try sema.coerceInMemoryAllowed(block, dest_child, src_child, !dest_info.flags.is_const, target, dest_src, src_src, null);31727 const child = try sema.coerceInMemoryAllowed(
31689 if (child != .ok) allow: {31728 block,
31729 dest_child,
31730 src_child,
31731 // We must also include `dest_is_mut`.
31732 // Otherwise, this code is valid:
31733 //
31734 // const b: B = ...;
31735 // var pa: *const A = undefined;
31736 // const ppa: **const A = &pa;
31737 // const ppb: **const B = ppa; // <-- this is what that allows
31738 // ppb.* = &b;
31739 // const a: A = pa.*;
31740 //
31741 // ...effectively performing an in-memory coercion from B to A.
31742 dest_is_mut or !dest_info.flags.is_const,
31743 target,
31744 dest_src,
31745 src_src,
31746 null,
31747 );
31748 if (child != .ok and !dest_is_mut) allow: {
31690 // As a special case, we also allow coercing `*[n:s]T` to `*[n]T`, akin to dropping the sentinel from a slice.31749 // As a special case, we also allow coercing `*[n:s]T` to `*[n]T`, akin to dropping the sentinel from a slice.
31691 // `*[n:s]T` cannot coerce in memory to `*[n]T` since they have different sizes.31750 // `*[n:s]T` cannot coerce in memory to `*[n]T` since they have different sizes.
31692 if (src_child.zigTypeTag(zcu) == .array and dest_child.zigTypeTag(zcu) == .array and31751 if (src_child.zigTypeTag(zcu) == .array and dest_child.zigTypeTag(zcu) == .array and
...@@ -31695,30 +31754,17 @@ fn coerceInMemoryAllowedPtrs(...@@ -31695,30 +31754,17 @@ fn coerceInMemoryAllowedPtrs(
31695 {31754 {
31696 break :allow;31755 break :allow;
31697 }31756 }
31698 return InMemoryCoercionResult{ .ptr_child = .{31757 return .{ .ptr_child = .{
31699 .child = try child.dupe(sema.arena),31758 .child = try child.dupe(sema.arena),
31700 .actual = Type.fromInterned(src_info.child),31759 .actual = .fromInterned(src_info.child),
31701 .wanted = Type.fromInterned(dest_info.child),31760 .wanted = .fromInterned(dest_info.child),
31702 } };
31703 }
31704
31705 const dest_allow_zero = dest_ty.ptrAllowsZero(zcu);
31706 const src_allow_zero = src_ty.ptrAllowsZero(zcu);
31707
31708 const ok_allows_zero = (dest_allow_zero and
31709 (src_allow_zero or !dest_is_mut)) or
31710 (!dest_allow_zero and !src_allow_zero);
31711 if (!ok_allows_zero) {
31712 return InMemoryCoercionResult{ .ptr_allowzero = .{
31713 .actual = src_ty,
31714 .wanted = dest_ty,
31715 } };31761 } };
31716 }31762 }
3171731763
31718 if (src_info.packed_offset.host_size != dest_info.packed_offset.host_size or31764 if (src_info.packed_offset.host_size != dest_info.packed_offset.host_size or
31719 src_info.packed_offset.bit_offset != dest_info.packed_offset.bit_offset)31765 src_info.packed_offset.bit_offset != dest_info.packed_offset.bit_offset)
31720 {31766 {
31721 return InMemoryCoercionResult{ .ptr_bit_range = .{31767 return .{ .ptr_bit_range = .{
31722 .actual_host = src_info.packed_offset.host_size,31768 .actual_host = src_info.packed_offset.host_size,
31723 .wanted_host = dest_info.packed_offset.host_size,31769 .wanted_host = dest_info.packed_offset.host_size,
31724 .actual_offset = src_info.packed_offset.bit_offset,31770 .actual_offset = src_info.packed_offset.bit_offset,
...@@ -31726,11 +31772,20 @@ fn coerceInMemoryAllowedPtrs(...@@ -31726,11 +31772,20 @@ fn coerceInMemoryAllowedPtrs(
31726 } };31772 } };
31727 }31773 }
3172831774
31729 const ok_sent = dest_info.sentinel == .none or src_info.flags.size == .C or31775 const sentinel_ok = ok: {
31730 (src_info.sentinel != .none and31776 const ss = src_info.sentinel;
31731 dest_info.sentinel == try zcu.intern_pool.getCoerced(sema.gpa, pt.tid, src_info.sentinel, dest_info.child));31777 const ds = dest_info.sentinel;
31732 if (!ok_sent) {31778 if (ss == .none and ds == .none) break :ok true;
31733 return InMemoryCoercionResult{ .ptr_sentinel = .{31779 if (ss != .none and ds != .none) {
31780 if (ds == try zcu.intern_pool.getCoerced(sema.gpa, pt.tid, ss, dest_info.child)) break :ok true;
31781 }
31782 if (src_info.flags.size == .C) break :ok true;
31783 if (!dest_is_mut and dest_info.sentinel == .none) break :ok true;
31784 break :ok false;
31785 };
31786
31787 if (!sentinel_ok) {
31788 return .{ .ptr_sentinel = .{
31734 .actual = switch (src_info.sentinel) {31789 .actual = switch (src_info.sentinel) {
31735 .none => Value.@"unreachable",31790 .none => Value.@"unreachable",
31736 else => Value.fromInterned(src_info.sentinel),31791 else => Value.fromInterned(src_info.sentinel),
...@@ -31760,7 +31815,7 @@ fn coerceInMemoryAllowedPtrs(...@@ -31760,7 +31815,7 @@ fn coerceInMemoryAllowedPtrs(
31760 else31815 else
31761 try Type.fromInterned(dest_info.child).abiAlignmentSema(pt);31816 try Type.fromInterned(dest_info.child).abiAlignmentSema(pt);
3176231817
31763 if (dest_align.compare(.gt, src_align)) {31818 if (dest_align.compare(if (dest_is_mut) .neq else .gt, src_align)) {
31764 return InMemoryCoercionResult{ .ptr_alignment = .{31819 return InMemoryCoercionResult{ .ptr_alignment = .{
31765 .actual = src_align,31820 .actual = src_align,
31766 .wanted = dest_align,31821 .wanted = dest_align,
...@@ -32252,19 +32307,23 @@ fn checkPtrAttributes(sema: *Sema, dest_ty: Type, inst_ty: Type, in_memory_resul...@@ -32252,19 +32307,23 @@ fn checkPtrAttributes(sema: *Sema, dest_ty: Type, inst_ty: Type, in_memory_resul
32252 (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))) or32307 (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
32253 (Type.fromInterned(inst_info.child).isTuple(zcu) and Type.fromInterned(inst_info.child).structFieldCount(zcu) == 0);32308 (Type.fromInterned(inst_info.child).isTuple(zcu) and Type.fromInterned(inst_info.child).structFieldCount(zcu) == 0);
3225432309
32255 const ok_cv_qualifiers =32310 const ok_const = (!inst_info.flags.is_const or dest_info.flags.is_const) or len0;
32256 ((!inst_info.flags.is_const or dest_info.flags.is_const) or len0) and32311 const ok_volatile = !inst_info.flags.is_volatile or dest_info.flags.is_volatile;
32257 (!inst_info.flags.is_volatile or dest_info.flags.is_volatile);32312 if (!ok_const) {
3225832313 in_memory_result.* = .{ .ptr_const = .{
32259 if (!ok_cv_qualifiers) {32314 .actual = inst_ty,
32260 in_memory_result.* = .{ .ptr_qualifiers = .{32315 .wanted = dest_ty,
32261 .actual_const = inst_info.flags.is_const,
32262 .wanted_const = dest_info.flags.is_const,
32263 .actual_volatile = inst_info.flags.is_volatile,
32264 .wanted_volatile = dest_info.flags.is_volatile,
32265 } };32316 } };
32266 return false;32317 return false;
32267 }32318 }
32319 if (!ok_volatile) {
32320 in_memory_result.* = .{ .ptr_volatile = .{
32321 .actual = inst_ty,
32322 .wanted = dest_ty,
32323 } };
32324 return false;
32325 }
32326
32268 if (dest_info.flags.address_space != inst_info.flags.address_space) {32327 if (dest_info.flags.address_space != inst_info.flags.address_space) {
32269 in_memory_result.* = .{ .ptr_addrspace = .{32328 in_memory_result.* = .{ .ptr_addrspace = .{
32270 .actual = inst_info.flags.address_space,32329 .actual = inst_info.flags.address_space,
...@@ -35441,11 +35500,11 @@ fn resolvePeerTypesInner(...@@ -35441,11 +35500,11 @@ fn resolvePeerTypesInner(
35441 .peer_idx_b = i,35500 .peer_idx_b = i,
35442 } };35501 } };
35443 // ty -> cur_ty35502 // ty -> cur_ty
35444 if (.ok == try sema.coerceInMemoryAllowedFns(block, cur_ty, ty, target, src, src)) {35503 if (.ok == try sema.coerceInMemoryAllowedFns(block, cur_ty, ty, false, target, src, src)) {
35445 continue;35504 continue;
35446 }35505 }
35447 // cur_ty -> ty35506 // cur_ty -> ty
35448 if (.ok == try sema.coerceInMemoryAllowedFns(block, ty, cur_ty, target, src, src)) {35507 if (.ok == try sema.coerceInMemoryAllowedFns(block, ty, cur_ty, false, target, src, src)) {
35449 opt_cur_ty = ty;35508 opt_cur_ty = ty;
35450 continue;35509 continue;
35451 }35510 }
...@@ -35834,12 +35893,12 @@ fn resolvePairInMemoryCoercible(sema: *Sema, block: *Block, src: LazySrcLoc, ty_...@@ -35834,12 +35893,12 @@ fn resolvePairInMemoryCoercible(sema: *Sema, block: *Block, src: LazySrcLoc, ty_
35834 const target = sema.pt.zcu.getTarget();35893 const target = sema.pt.zcu.getTarget();
3583535894
35836 // ty_b -> ty_a35895 // ty_b -> ty_a
35837 if (.ok == try sema.coerceInMemoryAllowed(block, ty_a, ty_b, true, target, src, src, null)) {35896 if (.ok == try sema.coerceInMemoryAllowed(block, ty_a, ty_b, false, target, src, src, null)) {
35838 return ty_a;35897 return ty_a;
35839 }35898 }
3584035899
35841 // ty_a -> ty_b35900 // ty_a -> ty_b
35842 if (.ok == try sema.coerceInMemoryAllowed(block, ty_b, ty_a, true, target, src, src, null)) {35901 if (.ok == try sema.coerceInMemoryAllowed(block, ty_b, ty_a, false, target, src, src, null)) {
35843 return ty_b;35902 return ty_b;
35844 }35903 }
3584535904
test/cases/compile_errors/implicit_cast_between_C_pointer_and_Zig_pointer-bad_const-align-child.zig+1-1
...@@ -38,7 +38,7 @@ export fn f() void {...@@ -38,7 +38,7 @@ export fn f() void {
38// :8:18: error: expected type '*u8', found '[*c]const u8'38// :8:18: error: expected type '*u8', found '[*c]const u8'
39// :8:18: note: cast discards const qualifier39// :8:18: note: cast discards const qualifier
40// :13:19: error: expected type '*u32', found '[*c]u8'40// :13:19: error: expected type '*u32', found '[*c]u8'
41// :13:19: note: pointer type child 'u8' cannot cast into pointer type child 'u32'41// :13:19: note: '[*c]u8' could have null values which are illegal in type '*u32'
42// :18:22: error: expected type '[*c]u32', found '*align(1) u32'42// :18:22: error: expected type '[*c]u32', found '*align(1) u32'
43// :18:22: note: pointer alignment '1' cannot cast into pointer alignment '4'43// :18:22: note: pointer alignment '1' cannot cast into pointer alignment '4'
44// :23:21: error: expected type '[*c]u8', found '*const u8'44// :23:21: error: expected type '[*c]u8', found '*const u8'
test/cases/compile_errors/implicit_casting_C_pointers_which_would_mess_up_null_semantics.zig+5-6
...@@ -13,7 +13,7 @@ export fn entry2() void {...@@ -13,7 +13,7 @@ export fn entry2() void {
13 var slice: []u8 = &buf;13 var slice: []u8 = &buf;
14 var opt_many_ptr: [*]u8 = slice.ptr;14 var opt_many_ptr: [*]u8 = slice.ptr;
15 var ptr_opt_many_ptr = &opt_many_ptr;15 var ptr_opt_many_ptr = &opt_many_ptr;
16 var c_ptr: [*c][*c]const u8 = ptr_opt_many_ptr;16 var c_ptr: [*c][*c]u8 = ptr_opt_many_ptr;
17 _ = &slice;17 _ = &slice;
18 _ = &ptr_opt_many_ptr;18 _ = &ptr_opt_many_ptr;
19 _ = &c_ptr;19 _ = &c_ptr;
...@@ -24,8 +24,7 @@ export fn entry2() void {...@@ -24,8 +24,7 @@ export fn entry2() void {
24// target=native24// target=native
25//25//
26// :6:24: error: expected type '*const [*]const u8', found '[*c]const [*c]const u8'26// :6:24: error: expected type '*const [*]const u8', found '[*c]const [*c]const u8'
27// :6:24: note: pointer type child '[*c]const u8' cannot cast into pointer type child '[*]const u8'27// :6:24: note: '[*c]const [*c]const u8' could have null values which are illegal in type '*const [*]const u8'
28// :6:24: note: '[*c]const u8' could have null values which are illegal in type '[*]const u8'28// :16:29: error: expected type '[*c][*c]u8', found '*[*]u8'
29// :16:35: error: expected type '[*c][*c]const u8', found '*[*]u8'29// :16:29: note: pointer type child '[*]u8' cannot cast into pointer type child '[*c]u8'
30// :16:35: note: pointer type child '[*]u8' cannot cast into pointer type child '[*c]const u8'30// :16:29: note: mutable '[*c]u8' would allow illegal null values stored to type '[*]u8'
31// :16:35: note: mutable '[*]u8' allows illegal null values stored to type '[*c]const u8'
test/cases/compile_errors/invalid_pointer_coercions.zig created+77
...@@ -0,0 +1,77 @@
1//! This file contains pointer coercions which are invalid because the element types are only
2//! in-memory coercible *in one direction*. When casting a mutable pointer, the element type
3//! must coerce in both directions for the pointer to coerce. Otherwise, you could do something
4//! like this, where `A` coerces to `B` but not vice-versa:
5//!
6//! ```
7//! var x: A = undefined;
8//! const p: *B = &x; // `*A` -> `*B`
9//! p.* = some_b;
10//! const some_b_as_a = x;
11//! ```
12
13export fn error_set_to_larger() void {
14 var x: error{Foo} = undefined;
15 _ = @as(*const error{ Foo, Bar }, &x); // this is ok
16 _ = @as(*error{ Foo, Bar }, &x); // compile error
17}
18
19export fn error_set_to_anyerror() void {
20 var x: error{Foo} = undefined;
21 _ = @as(*const anyerror, &x); // this is ok
22 _ = @as(*anyerror, &x); // compile error
23}
24
25export fn error_union_to_anyerror_union() void {
26 var x: error{Foo}!u32 = undefined;
27 _ = @as(*const anyerror!u32, &x); // this is ok
28 _ = @as(*anyerror!u32, &x); // compile error
29}
30
31export fn ptr_to_const_ptr() void {
32 var x: *u32 = undefined;
33 _ = @as(*const *const u32, &x); // this is ok
34 _ = @as(**const u32, &x); // compile error
35}
36
37export fn ptr_to_allowzero_ptr() void {
38 var x: *u32 = undefined;
39 _ = @as(*const *allowzero u32, &x); // this is ok
40 _ = @as(**allowzero u32, &x); // compile error
41}
42
43export fn ptr_to_volatile_ptr() void {
44 var x: *u32 = undefined;
45 _ = @as(*const *volatile u32, &x); // this is ok
46 _ = @as(**volatile u32, &x); // compile error
47}
48
49export fn ptr_to_underaligned_ptr() void {
50 var x: *u32 = undefined;
51 _ = @as(*const *align(1) u32, &x); // this is ok
52 _ = @as(**align(1) u32, &x); // compile error
53}
54
55// error
56//
57// :16:33: error: expected type '*error{Foo,Bar}', found '*error{Foo}'
58// :16:33: note: pointer type child 'error{Foo}' cannot cast into pointer type child 'error{Foo,Bar}'
59// :16:33: note: 'error.Bar' not a member of destination error set
60// :22:24: error: expected type '*anyerror', found '*error{Foo}'
61// :22:24: note: pointer type child 'error{Foo}' cannot cast into pointer type child 'anyerror'
62// :22:24: note: global error set cannot cast into a smaller set
63// :28:28: error: expected type '*anyerror!u32', found '*error{Foo}!u32'
64// :28:28: note: pointer type child 'error{Foo}!u32' cannot cast into pointer type child 'anyerror!u32'
65// :28:28: note: global error set cannot cast into a smaller set
66// :34:26: error: expected type '**const u32', found '**u32'
67// :34:26: note: pointer type child '*u32' cannot cast into pointer type child '*const u32'
68// :34:26: note: mutable '*const u32' would allow illegal const pointers stored to type '*u32'
69// :40:30: error: expected type '**allowzero u32', found '**u32'
70// :40:30: note: pointer type child '*u32' cannot cast into pointer type child '*allowzero u32'
71// :40:30: note: mutable '*allowzero u32' would allow illegal null values stored to type '*u32'
72// :46:29: error: expected type '**volatile u32', found '**u32'
73// :46:29: note: pointer type child '*u32' cannot cast into pointer type child '*volatile u32'
74// :46:29: note: mutable '*volatile u32' would allow illegal volatile pointers stored to type '*u32'
75// :52:29: error: expected type '**align(1) u32', found '**u32'
76// :52:29: note: pointer type child '*u32' cannot cast into pointer type child '*align(1) u32'
77// :52:29: note: pointer alignment '4' cannot cast into pointer alignment '1'
test/cases/compile_errors/use_implicit_casts_to_assign_null_to_non-nullable_pointer.zig+1-1
...@@ -12,4 +12,4 @@ export fn entry() void {...@@ -12,4 +12,4 @@ export fn entry() void {
12//12//
13// :4:24: error: expected type '*?*i32', found '**i32'13// :4:24: error: expected type '*?*i32', found '**i32'
14// :4:24: note: pointer type child '*i32' cannot cast into pointer type child '?*i32'14// :4:24: note: pointer type child '*i32' cannot cast into pointer type child '?*i32'
15// :4:24: note: mutable '*i32' allows illegal null values stored to type '?*i32'15// :4:24: note: mutable '?*i32' would allow illegal null values stored to type '*i32'