diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2020-09-21 21:16:46 -0700 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2020-09-21 21:16:46 -0700 |
| commit | 0c70bb4fce8b0460f86ed218f54ba31b291f2bfb (patch) | |
| tree | 1e0ad8f9ea76b30e52753a2f25f5f1d18d25896d | |
| parent | afac5d28951cfd913851094649e8b9f2136694ca (diff) | |
| parent | 58ee5f4e61cd9b7a9ba65798e2214efa3753a733 (diff) | |
| download | zig-0c70bb4fce8b0460f86ed218f54ba31b291f2bfb.tar.gz zig-0c70bb4fce8b0460f86ed218f54ba31b291f2bfb.zip | |
Merge remote-tracking branch 'origin/master' into stage2-zig-cc
| -rw-r--r-- | doc/langref.html.in | 2 | ||||
| -rw-r--r-- | lib/std/build.zig | 2 | ||||
| -rw-r--r-- | lib/std/crypto.zig | 24 | ||||
| -rw-r--r-- | lib/std/crypto/pbkdf2.zig | 280 | ||||
| -rw-r--r-- | lib/std/crypto/siphash.zig | 3 | ||||
| -rw-r--r-- | lib/std/fmt.zig | 139 | ||||
| -rw-r--r-- | lib/std/fs.zig | 66 | ||||
| -rw-r--r-- | lib/std/fs/test.zig | 161 | ||||
| -rw-r--r-- | lib/std/heap.zig | 2 | ||||
| -rw-r--r-- | lib/std/os.zig | 3 | ||||
| -rw-r--r-- | lib/std/os/windows.zig | 3 | ||||
| -rw-r--r-- | src/stage1/analyze.cpp | 29 | ||||
| -rw-r--r-- | src/stage1/ir.cpp | 24 | ||||
| -rw-r--r-- | src/translate_c.zig | 2 | ||||
| -rw-r--r-- | test/stage1/behavior/cast.zig | 5 | ||||
| -rw-r--r-- | test/stage1/behavior/type.zig | 42 |
16 files changed, 677 insertions, 110 deletions
diff --git a/doc/langref.html.in b/doc/langref.html.in index 038aadd45f..740984619b 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -9728,7 +9728,7 @@ const c = @cImport({ <li>Does not support Zig-only pointer attributes such as alignment. Use normal {#link|Pointers#} please!</li> </ul> - <p>When a C pointer is pointing to a single struct (not an array), deference the C pointer to + <p>When a C pointer is pointing to a single struct (not an array), dereference the C pointer to access to the struct's fields or member data. That syntax looks like this: </p> <p>{#syntax#}ptr_to_struct.*.struct_member{#endsyntax#}</p> diff --git a/lib/std/build.zig b/lib/std/build.zig index 30ecf98b76..69f44bad32 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -1188,6 +1188,7 @@ pub const LibExeObjStep = struct { emit_llvm_ir: bool = false, emit_asm: bool = false, emit_bin: bool = true, + emit_docs: bool = false, emit_h: bool = false, bundle_compiler_rt: bool, disable_stack_probing: bool, @@ -2033,6 +2034,7 @@ pub const LibExeObjStep = struct { if (self.emit_llvm_ir) try zig_args.append("-femit-llvm-ir"); if (self.emit_asm) try zig_args.append("-femit-asm"); if (!self.emit_bin) try zig_args.append("-fno-emit-bin"); + if (self.emit_docs) try zig_args.append("-femit-docs"); if (self.emit_h) try zig_args.append("-femit-h"); if (self.strip) { diff --git a/lib/std/crypto.zig b/lib/std/crypto.zig index 5de2f13896..3a1ae599a0 100644 --- a/lib/std/crypto.zig +++ b/lib/std/crypto.zig @@ -35,6 +35,15 @@ pub const onetimeauth = struct { pub const Poly1305 = @import("crypto/poly1305.zig").Poly1305; }; +/// A Key Derivation Function (KDF) is intended to turn a weak, human generated password into a +/// strong key, suitable for cryptographic uses. It does this by salting and stretching the +/// password. Salting injects non-secret random data, so that identical passwords will be converted +/// into unique keys. Stretching applies a deliberately slow hashing function to frustrate +/// brute-force guessing. +pub const kdf = struct { + pub const pbkdf2 = @import("crypto/pbkdf2.zig").pbkdf2; +}; + /// Core functions, that should rarely be used directly by applications. pub const core = struct { pub const aes = @import("crypto/aes.zig"); @@ -70,6 +79,20 @@ const std = @import("std.zig"); pub const randomBytes = std.os.getrandom; test "crypto" { + inline for (std.meta.declarations(@This())) |decl| { + switch (decl.data) { + .Type => |t| { + std.meta.refAllDecls(t); + }, + .Var => |v| { + _ = v; + }, + .Fn => |f| { + _ = f; + }, + } + } + _ = @import("crypto/aes.zig"); _ = @import("crypto/blake2.zig"); _ = @import("crypto/blake3.zig"); @@ -77,6 +100,7 @@ test "crypto" { _ = @import("crypto/gimli.zig"); _ = @import("crypto/hmac.zig"); _ = @import("crypto/md5.zig"); + _ = @import("crypto/pbkdf2.zig"); _ = @import("crypto/poly1305.zig"); _ = @import("crypto/sha1.zig"); _ = @import("crypto/sha2.zig"); diff --git a/lib/std/crypto/pbkdf2.zig b/lib/std/crypto/pbkdf2.zig new file mode 100644 index 0000000000..85c8e01105 --- /dev/null +++ b/lib/std/crypto/pbkdf2.zig @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2020 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. + +const std = @import("std"); +const mem = std.mem; +const maxInt = std.math.maxInt; + +// RFC 2898 Section 5.2 +// +// FromSpec: +// +// PBKDF2 applies a pseudorandom function (see Appendix B.1 for an +// example) to derive keys. The length of the derived key is essentially +// unbounded. (However, the maximum effective search space for the +// derived key may be limited by the structure of the underlying +// pseudorandom function. See Appendix B.1 for further discussion.) +// PBKDF2 is recommended for new applications. +// +// PBKDF2 (P, S, c, dkLen) +// +// Options: PRF underlying pseudorandom function (hLen +// denotes the length in octets of the +// pseudorandom function output) +// +// Input: P password, an octet string +// S salt, an octet string +// c iteration count, a positive integer +// dkLen intended length in octets of the derived +// key, a positive integer, at most +// (2^32 - 1) * hLen +// +// Output: DK derived key, a dkLen-octet string + +// Based on Apple's CommonKeyDerivation, based originally on code by Damien Bergamini. + +pub const Pbkdf2Error = error{ + /// At least one round is required + TooFewRounds, + + /// Maximum length of the derived key is `maxInt(u32) * Prf.mac_length` + DerivedKeyTooLong, +}; + +/// Apply PBKDF2 to generate a key from a password. +/// +/// PBKDF2 is defined in RFC 2898, and is a recommendation of NIST SP 800-132. +/// +/// derivedKey: Slice of appropriate size for generated key. Generally 16 or 32 bytes in length. +/// May be uninitialized. All bytes will be overwritten. +/// Maximum size is `maxInt(u32) * Hash.digest_length` +/// It is a programming error to pass buffer longer than the maximum size. +/// +/// password: Arbitrary sequence of bytes of any length, including empty. +/// +/// salt: Arbitrary sequence of bytes of any length, including empty. A common length is 8 bytes. +/// +/// rounds: Iteration count. Must be greater than 0. Common values range from 1,000 to 100,000. +/// Larger iteration counts improve security by increasing the time required to compute +/// the derivedKey. It is common to tune this parameter to achieve approximately 100ms. +/// +/// Prf: Pseudo-random function to use. A common choice is `std.crypto.auth.hmac.HmacSha256`. +pub fn pbkdf2(derivedKey: []u8, password: []const u8, salt: []const u8, rounds: u32, comptime Prf: type) Pbkdf2Error!void { + if (rounds < 1) return error.TooFewRounds; + + const dkLen = derivedKey.len; + const hLen = Prf.mac_length; + comptime std.debug.assert(hLen >= 1); + + // FromSpec: + // + // 1. If dkLen > maxInt(u32) * hLen, output "derived key too long" and + // stop. + // + if (comptime (maxInt(usize) > maxInt(u32) * hLen) and (dkLen > @as(usize, maxInt(u32) * hLen))) { + // If maxInt(usize) is less than `maxInt(u32) * hLen` then dkLen is always inbounds + return error.DerivedKeyTooLong; + } + + // FromSpec: + // + // 2. Let l be the number of hLen-long blocks of bytes in the derived key, + // rounding up, and let r be the number of bytes in the last + // block + // + + // l will not overflow, proof: + // let `L(dkLen, hLen) = (dkLen + hLen - 1) / hLen` + // then `L^-1(l, hLen) = l*hLen - hLen + 1` + // 1) L^-1(maxInt(u32), hLen) <= maxInt(u32)*hLen + // 2) maxInt(u32)*hLen - hLen + 1 <= maxInt(u32)*hLen // subtract maxInt(u32)*hLen + 1 + // 3) -hLen <= -1 // multiply by -1 + // 4) hLen >= 1 + const r_ = dkLen % hLen; + const l = @intCast(u32, (dkLen / hLen) + @as(u1, if (r_ == 0) 0 else 1)); // original: (dkLen + hLen - 1) / hLen + const r = if (r_ == 0) hLen else r_; + + // FromSpec: + // + // 3. For each block of the derived key apply the function F defined + // below to the password P, the salt S, the iteration count c, and + // the block index to compute the block: + // + // T_1 = F (P, S, c, 1) , + // T_2 = F (P, S, c, 2) , + // ... + // T_l = F (P, S, c, l) , + // + // where the function F is defined as the exclusive-or sum of the + // first c iterates of the underlying pseudorandom function PRF + // applied to the password P and the concatenation of the salt S + // and the block index i: + // + // F (P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c + // + // where + // + // U_1 = PRF (P, S || INT (i)) , + // U_2 = PRF (P, U_1) , + // ... + // U_c = PRF (P, U_{c-1}) . + // + // Here, INT (i) is a four-octet encoding of the integer i, most + // significant octet first. + // + // 4. Concatenate the blocks and extract the first dkLen octets to + // produce a derived key DK: + // + // DK = T_1 || T_2 || ... || T_l<0..r-1> + var block: u32 = 0; // Spec limits to u32 + while (block < l) : (block += 1) { + var prevBlock: [hLen]u8 = undefined; + var newBlock: [hLen]u8 = undefined; + + // U_1 = PRF (P, S || INT (i)) + const blockIndex = mem.toBytes(mem.nativeToBig(u32, block + 1)); // Block index starts at 0001 + var ctx = Prf.init(password); + ctx.update(salt); + ctx.update(blockIndex[0..]); + ctx.final(prevBlock[0..]); + + // Choose portion of DK to write into (T_n) and initialize + const offset = block * hLen; + const blockLen = if (block != l - 1) hLen else r; + const dkBlock: []u8 = derivedKey[offset..][0..blockLen]; + mem.copy(u8, dkBlock, prevBlock[0..dkBlock.len]); + + var i: u32 = 1; + while (i < rounds) : (i += 1) { + // U_c = PRF (P, U_{c-1}) + Prf.create(&newBlock, prevBlock[0..], password); + mem.copy(u8, prevBlock[0..], newBlock[0..]); + + // F (P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c + for (dkBlock) |_, j| { + dkBlock[j] ^= newBlock[j]; + } + } + } +} + +const htest = @import("test.zig"); +const HmacSha1 = std.crypto.auth.hmac.HmacSha1; + +// RFC 6070 PBKDF2 HMAC-SHA1 Test Vectors +test "RFC 6070 one iteration" { + const p = "password"; + const s = "salt"; + const c = 1; + const dkLen = 20; + + var derivedKey: [dkLen]u8 = undefined; + + try pbkdf2(&derivedKey, p, s, c, HmacSha1); + + const expected = "0c60c80f961f0e71f3a9b524af6012062fe037a6"; + + htest.assertEqual(expected, derivedKey[0..]); +} + +test "RFC 6070 two iterations" { + const p = "password"; + const s = "salt"; + const c = 2; + const dkLen = 20; + + var derivedKey: [dkLen]u8 = undefined; + + try pbkdf2(&derivedKey, p, s, c, HmacSha1); + + const expected = "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957"; + + htest.assertEqual(expected, derivedKey[0..]); +} + +test "RFC 6070 4096 iterations" { + const p = "password"; + const s = "salt"; + const c = 4096; + const dkLen = 20; + + var derivedKey: [dkLen]u8 = undefined; + + try pbkdf2(&derivedKey, p, s, c, HmacSha1); + + const expected = "4b007901b765489abead49d926f721d065a429c1"; + + htest.assertEqual(expected, derivedKey[0..]); +} + +test "RFC 6070 16,777,216 iterations" { + // These iteration tests are slow so we always skip them. Results have been verified. + if (true) { + return error.SkipZigTest; + } + + const p = "password"; + const s = "salt"; + const c = 16777216; + const dkLen = 20; + + var derivedKey = [_]u8{0} ** dkLen; + + try pbkdf2(&derivedKey, p, s, c, HmacSha1); + + const expected = "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984"; + + htest.assertEqual(expected, derivedKey[0..]); +} + +test "RFC 6070 multi-block salt and password" { + const p = "passwordPASSWORDpassword"; + const s = "saltSALTsaltSALTsaltSALTsaltSALTsalt"; + const c = 4096; + const dkLen = 25; + + var derivedKey: [dkLen]u8 = undefined; + + try pbkdf2(&derivedKey, p, s, c, HmacSha1); + + const expected = "3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038"; + + htest.assertEqual(expected, derivedKey[0..]); +} + +test "RFC 6070 embedded NUL" { + const p = "pass\x00word"; + const s = "sa\x00lt"; + const c = 4096; + const dkLen = 16; + + var derivedKey: [dkLen]u8 = undefined; + + try pbkdf2(&derivedKey, p, s, c, HmacSha1); + + const expected = "56fa6aa75548099dcc37d7f03425e0c3"; + + htest.assertEqual(expected, derivedKey[0..]); +} + +test "Very large dkLen" { + // This test allocates 8GB of memory and is expected to take several hours to run. + if (true) { + return error.SkipZigTest; + } + const p = "password"; + const s = "salt"; + const c = 1; + const dkLen = 1 << 33; + + var derivedKey = try std.testing.allocator.alloc(u8, dkLen); + defer { + std.testing.allocator.free(derivedKey); + } + + try pbkdf2(derivedKey, p, s, c, HmacSha1); + // Just verify this doesn't crash with an overflow +} diff --git a/lib/std/crypto/siphash.zig b/lib/std/crypto/siphash.zig index 26c892fdd7..ae059b2567 100644 --- a/lib/std/crypto/siphash.zig +++ b/lib/std/crypto/siphash.zig @@ -218,8 +218,9 @@ fn SipHash(comptime T: type, comptime c_rounds: usize, comptime d_rounds: usize) } /// Return an authentication tag for the current state + /// Assumes `out` is less than or equal to `mac_length`. pub fn final(self: *Self, out: []u8) void { - std.debug.assert(out.len >= mac_length); + std.debug.assert(out.len <= mac_length); mem.writeIntLittle(T, out[0..mac_length], self.state.final(self.buf[0..self.buf_len])); } diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index 8d31733959..56a1aba217 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -22,7 +22,7 @@ pub const Alignment = enum { pub const FormatOptions = struct { precision: ?usize = null, width: ?usize = null, - alignment: Alignment = .Left, + alignment: Alignment = .Right, fill: u8 = ' ', }; @@ -327,7 +327,7 @@ pub fn formatType( max_depth: usize, ) @TypeOf(writer).Error!void { if (comptime std.mem.eql(u8, fmt, "*")) { - try writer.writeAll(@typeName(@typeInfo(@TypeOf(value)).Pointer.child)); + try writer.writeAll(@typeName(std.meta.Child(@TypeOf(value)))); try writer.writeAll("@"); try formatInt(@ptrToInt(value), 16, false, FormatOptions{}, writer); return; @@ -631,26 +631,22 @@ pub fn formatBuf( writer: anytype, ) !void { const width = options.width orelse buf.len; - var padding = if (width > buf.len) (width - buf.len) else 0; - const pad_byte = [1]u8{options.fill}; + const padding = if (width > buf.len) (width - buf.len) else 0; + switch (options.alignment) { .Left => { try writer.writeAll(buf); - while (padding > 0) : (padding -= 1) { - try writer.writeAll(&pad_byte); - } + try writer.writeByteNTimes(options.fill, padding); }, .Center => { - const padl = padding / 2; - var i: usize = 0; - while (i < padl) : (i += 1) try writer.writeAll(&pad_byte); + const left_padding = padding / 2; + const right_padding = (padding + 1) / 2; + try writer.writeByteNTimes(options.fill, left_padding); try writer.writeAll(buf); - while (i < padding) : (i += 1) try writer.writeAll(&pad_byte); + try writer.writeByteNTimes(options.fill, right_padding); }, .Right => { - while (padding > 0) : (padding -= 1) { - try writer.writeAll(&pad_byte); - } + try writer.writeByteNTimes(options.fill, padding); try writer.writeAll(buf); }, } @@ -941,61 +937,27 @@ pub fn formatInt( options: FormatOptions, writer: anytype, ) !void { + assert(base >= 2); + const int_value = if (@TypeOf(value) == comptime_int) blk: { const Int = math.IntFittingRange(value, value); break :blk @as(Int, value); } else value; - if (@typeInfo(@TypeOf(int_value)).Int.is_signed) { - return formatIntSigned(int_value, base, uppercase, options, writer); - } else { - return formatIntUnsigned(int_value, base, uppercase, options, writer); - } -} + const value_info = @typeInfo(@TypeOf(int_value)).Int; -fn formatIntSigned( - value: anytype, - base: u8, - uppercase: bool, - options: FormatOptions, - writer: anytype, -) !void { - const new_options = FormatOptions{ - .width = if (options.width) |w| (if (w == 0) 0 else w - 1) else null, - .precision = options.precision, - .fill = options.fill, - }; - const bit_count = @typeInfo(@TypeOf(value)).Int.bits; - const Uint = std.meta.Int(false, bit_count); - if (value < 0) { - try writer.writeAll("-"); - const new_value = math.absCast(value); - return formatIntUnsigned(new_value, base, uppercase, new_options, writer); - } else if (options.width == null or options.width.? == 0) { - return formatIntUnsigned(@intCast(Uint, value), base, uppercase, options, writer); - } else { - try writer.writeAll("+"); - const new_value = @intCast(Uint, value); - return formatIntUnsigned(new_value, base, uppercase, new_options, writer); - } -} + // The type must have the same size as `base` or be wider in order for the + // division to work + const min_int_bits = comptime math.max(value_info.bits, 8); + const MinInt = std.meta.Int(false, min_int_bits); -fn formatIntUnsigned( - value: anytype, - base: u8, - uppercase: bool, - options: FormatOptions, - writer: anytype, -) !void { - assert(base >= 2); - const value_info = @typeInfo(@TypeOf(value)).Int; - var buf: [math.max(value_info.bits, 1)]u8 = undefined; - const min_int_bits = comptime math.max(value_info.bits, @typeInfo(@TypeOf(base)).Int.bits); - const MinInt = std.meta.Int(value_info.is_signed, min_int_bits); - var a: MinInt = value; - var index: usize = buf.len; + const abs_value = math.absCast(int_value); + // The worst case in terms of space needed is base 2, plus 1 for the sign + var buf: [1 + math.max(value_info.bits, 1)]u8 = undefined; + var a: MinInt = abs_value; + var index: usize = buf.len; while (true) { const digit = a % base; index -= 1; @@ -1004,25 +966,21 @@ fn formatIntUnsigned( if (a == 0) break; } - const digits_buf = buf[index..]; - const width = options.width orelse 0; - const padding = if (width > digits_buf.len) (width - digits_buf.len) else 0; - - if (padding > index) { - const zero_byte: u8 = options.fill; - var leftover_padding = padding - index; - while (true) { - try writer.writeAll(@as(*const [1]u8, &zero_byte)[0..]); - leftover_padding -= 1; - if (leftover_padding == 0) break; + if (value_info.is_signed) { + if (value < 0) { + // Negative integer + index -= 1; + buf[index] = '-'; + } else if (options.width == null or options.width.? == 0) { + // Positive integer, omit the plus sign + } else { + // Positive integer + index -= 1; + buf[index] = '+'; } - mem.set(u8, buf[0..index], options.fill); - return writer.writeAll(&buf); - } else { - const padded_buf = buf[index - padding ..]; - mem.set(u8, padded_buf[0..padding], options.fill); - return writer.writeAll(padded_buf); } + + return formatBuf(buf[index..], options, writer); } pub fn formatIntBuf(out_buf: []u8, value: anytype, base: u8, uppercase: bool, options: FormatOptions) usize { @@ -1246,6 +1204,10 @@ test "optional" { const value: ?i32 = null; try testFmt("optional: null\n", "optional: {}\n", .{value}); } + { + const value = @intToPtr(?*i32, 0xf000d000); + try testFmt("optional: *i32@f000d000\n", "optional: {*}\n", .{value}); + } } test "error" { @@ -1283,7 +1245,17 @@ test "int.specifier" { test "int.padded" { try testFmt("u8: ' 1'", "u8: '{:4}'", .{@as(u8, 1)}); - try testFmt("u8: 'xxx1'", "u8: '{:x<4}'", .{@as(u8, 1)}); + try testFmt("u8: '1000'", "u8: '{:0<4}'", .{@as(u8, 1)}); + try testFmt("u8: '0001'", "u8: '{:0>4}'", .{@as(u8, 1)}); + try testFmt("u8: '0100'", "u8: '{:0^4}'", .{@as(u8, 1)}); + try testFmt("i8: '-1 '", "i8: '{:<4}'", .{@as(i8, -1)}); + try testFmt("i8: ' -1'", "i8: '{:>4}'", .{@as(i8, -1)}); + try testFmt("i8: ' -1 '", "i8: '{:^4}'", .{@as(i8, -1)}); + try testFmt("i16: '-1234'", "i16: '{:4}'", .{@as(i16, -1234)}); + try testFmt("i16: '+1234'", "i16: '{:4}'", .{@as(i16, 1234)}); + try testFmt("i16: '-12345'", "i16: '{:4}'", .{@as(i16, -12345)}); + try testFmt("i16: '+12345'", "i16: '{:4}'", .{@as(i16, 12345)}); + try testFmt("u16: '12345'", "u16: '{:4}'", .{@as(u16, 12345)}); } test "buffer" { @@ -1329,7 +1301,7 @@ test "slice" { try testFmt("slice: []const u8@deadbeef\n", "slice: {}\n", .{value}); } - try testFmt("buf: Test \n", "buf: {s:5}\n", .{"Test"}); + try testFmt("buf: Test\n", "buf: {s:5}\n", .{"Test"}); try testFmt("buf: Test\n Other text", "buf: {s}\n Other text", .{"Test"}); } @@ -1362,7 +1334,7 @@ test "cstr" { .{@ptrCast([*c]const u8, "Test C")}, ); try testFmt( - "cstr: Test C \n", + "cstr: Test C\n", "cstr: {s:10}\n", .{@ptrCast([*c]const u8, "Test C")}, ); @@ -1805,7 +1777,7 @@ test "vector" { try testFmt("{ true, false, true, false }", "{}", .{vbool}); try testFmt("{ -2, -1, 0, 1 }", "{}", .{vi64}); - try testFmt("{ - 2, - 1, + 0, + 1 }", "{d:5}", .{vi64}); + try testFmt("{ -2, -1, +0, +1 }", "{d:5}", .{vi64}); try testFmt("{ 1000, 2000, 3000, 4000 }", "{}", .{vu64}); try testFmt("{ 3e8, 7d0, bb8, fa0 }", "{x}", .{vu64}); try testFmt("{ 1kB, 2kB, 3kB, 4kB }", "{B}", .{vu64}); @@ -1818,15 +1790,16 @@ test "enum-literal" { test "padding" { try testFmt("Simple", "{}", .{"Simple"}); - try testFmt("true ", "{:10}", .{true}); + try testFmt(" true", "{:10}", .{true}); try testFmt(" true", "{:>10}", .{true}); try testFmt("======true", "{:=>10}", .{true}); try testFmt("true======", "{:=<10}", .{true}); try testFmt(" true ", "{:^10}", .{true}); try testFmt("===true===", "{:=^10}", .{true}); - try testFmt("Minimum width", "{:18} width", .{"Minimum"}); + try testFmt(" Minimum width", "{:18} width", .{"Minimum"}); try testFmt("==================Filled", "{:=>24}", .{"Filled"}); try testFmt(" Centered ", "{:^24}", .{"Centered"}); + try testFmt("-", "{:-^1}", .{""}); } test "decimal float padding" { diff --git a/lib/std/fs.zig b/lib/std/fs.zig index a217fb3e9b..1890d7e136 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -21,10 +21,6 @@ pub const wasi = @import("fs/wasi.zig"); // TODO audit these APIs with respect to Dir and absolute paths -pub const rename = os.rename; -pub const renameZ = os.renameZ; -pub const renameC = @compileError("deprecated: renamed to renameZ"); -pub const renameW = os.renameW; pub const realpath = os.realpath; pub const realpathZ = os.realpathZ; pub const realpathC = @compileError("deprecated: renamed to realpathZ"); @@ -90,7 +86,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: base64_encoder.encode(tmp_path[dirname.len + 1 ..], &rand_buf); if (cwd().symLink(existing_path, tmp_path, .{})) { - return rename(tmp_path, new_path); + return cwd().rename(tmp_path, new_path); } else |err| switch (err) { error.PathAlreadyExists => continue, else => return err, // TODO zig should know this set does not include PathAlreadyExists @@ -255,6 +251,45 @@ pub fn deleteDirAbsoluteW(dir_path: [*:0]const u16) !void { return os.rmdirW(dir_path); } +pub const renameC = @compileError("deprecated: use renameZ, dir.renameZ, or renameAbsoluteZ"); + +/// Same as `Dir.rename` except the paths are absolute. +pub fn renameAbsolute(old_path: []const u8, new_path: []const u8) !void { + assert(path.isAbsolute(old_path)); + assert(path.isAbsolute(new_path)); + return os.rename(old_path, new_path); +} + +/// Same as `renameAbsolute` except the path parameters are null-terminated. +pub fn renameAbsoluteZ(old_path: [*:0]const u8, new_path: [*:0]const u8) !void { + assert(path.isAbsoluteZ(old_path)); + assert(path.isAbsoluteZ(new_path)); + return os.renameZ(old_path, new_path); +} + +/// Same as `renameAbsolute` except the path parameters are WTF-16 and target OS is assumed Windows. +pub fn renameAbsoluteW(old_path: [*:0]const u16, new_path: [*:0]const u16) !void { + assert(path.isAbsoluteWindowsW(old_path)); + assert(path.isAbsoluteWindowsW(new_path)); + return os.renameW(old_path, new_path); +} + +/// Same as `Dir.rename`, except `new_sub_path` is relative to `new_dir` +pub fn rename(old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) !void { + return os.renameat(old_dir.fd, old_sub_path, new_dir.fd, new_sub_path); +} + +/// Same as `rename` except the parameters are null-terminated. +pub fn renameZ(old_dir: Dir, old_sub_path_z: [*:0]const u8, new_dir: Dir, new_sub_path_z: [*:0]const u8) !void { + return os.renameatZ(old_dir.fd, old_sub_path_z, new_dir.fd, new_sub_path_z); +} + +/// Same as `rename` except the parameters are UTF16LE, NT prefixed. +/// This function is Windows-only. +pub fn renameW(old_dir: Dir, old_sub_path_w: []const u16, new_dir: Dir, new_sub_path_w: []const u16) !void { + return os.renameatW(old_dir.fd, old_sub_path_w, new_dir.fd, new_sub_path_w); +} + pub const Dir = struct { fd: os.fd_t, @@ -1338,6 +1373,27 @@ pub const Dir = struct { }; } + pub const RenameError = os.RenameError; + + /// Change the name or location of a file or directory. + /// If new_sub_path already exists, it will be replaced. + /// Renaming a file over an existing directory or a directory + /// over an existing file will fail with `error.IsDir` or `error.NotDir` + pub fn rename(self: Dir, old_sub_path: []const u8, new_sub_path: []const u8) RenameError!void { + return os.renameat(self.fd, old_sub_path, self.fd, new_sub_path); + } + + /// Same as `rename` except the parameters are null-terminated. + pub fn renameZ(self: Dir, old_sub_path_z: [*:0]const u8, new_sub_path_z: [*:0]const u8) RenameError!void { + return os.renameatZ(self.fd, old_sub_path_z, self.fd, new_sub_path_z); + } + + /// Same as `rename` except the parameters are UTF16LE, NT prefixed. + /// This function is Windows-only. + pub fn renameW(self: Dir, old_sub_path_w: []const u16, new_sub_path_w: []const u16) RenameError!void { + return os.renameatW(self.fd, old_sub_path_w, self.fd, new_sub_path_w); + } + /// Creates a symbolic link named `sym_link_path` which contains the string `target_path`. /// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent /// one; the latter case is known as a dangling link. diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index a59bc46245..b3cc1fe569 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -274,6 +274,167 @@ test "file operations on directories" { dir.close(); } +test "Dir.rename files" { + var tmp_dir = tmpDir(.{}); + defer tmp_dir.cleanup(); + + testing.expectError(error.FileNotFound, tmp_dir.dir.rename("missing_file_name", "something_else")); + + // Renaming files + const test_file_name = "test_file"; + const renamed_test_file_name = "test_file_renamed"; + var file = try tmp_dir.dir.createFile(test_file_name, .{ .read = true }); + file.close(); + try tmp_dir.dir.rename(test_file_name, renamed_test_file_name); + + // Ensure the file was renamed + testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(test_file_name, .{})); + file = try tmp_dir.dir.openFile(renamed_test_file_name, .{}); + file.close(); + + // Rename to self succeeds + try tmp_dir.dir.rename(renamed_test_file_name, renamed_test_file_name); + + // Rename to existing file succeeds + var existing_file = try tmp_dir.dir.createFile("existing_file", .{ .read = true }); + existing_file.close(); + try tmp_dir.dir.rename(renamed_test_file_name, "existing_file"); + + testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(renamed_test_file_name, .{})); + file = try tmp_dir.dir.openFile("existing_file", .{}); + file.close(); +} + +test "Dir.rename directories" { + // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364 + if (builtin.os.tag == .windows) return error.SkipZigTest; + + var tmp_dir = tmpDir(.{}); + defer tmp_dir.cleanup(); + + // Renaming directories + try tmp_dir.dir.makeDir("test_dir"); + try tmp_dir.dir.rename("test_dir", "test_dir_renamed"); + + // Ensure the directory was renamed + testing.expectError(error.FileNotFound, tmp_dir.dir.openDir("test_dir", .{})); + var dir = try tmp_dir.dir.openDir("test_dir_renamed", .{}); + + // Put a file in the directory + var file = try dir.createFile("test_file", .{ .read = true }); + file.close(); + dir.close(); + + try tmp_dir.dir.rename("test_dir_renamed", "test_dir_renamed_again"); + + // Ensure the directory was renamed and the file still exists in it + testing.expectError(error.FileNotFound, tmp_dir.dir.openDir("test_dir_renamed", .{})); + dir = try tmp_dir.dir.openDir("test_dir_renamed_again", .{}); + file = try dir.openFile("test_file", .{}); + file.close(); + dir.close(); + + // Try to rename to a non-empty directory now + var target_dir = try tmp_dir.dir.makeOpenPath("non_empty_target_dir", .{}); + file = try target_dir.createFile("filler", .{ .read = true }); + file.close(); + + testing.expectError(error.PathAlreadyExists, tmp_dir.dir.rename("test_dir_renamed_again", "non_empty_target_dir")); + + // Ensure the directory was not renamed + dir = try tmp_dir.dir.openDir("test_dir_renamed_again", .{}); + file = try dir.openFile("test_file", .{}); + file.close(); + dir.close(); +} + +test "Dir.rename file <-> dir" { + // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364 + if (builtin.os.tag == .windows) return error.SkipZigTest; + + var tmp_dir = tmpDir(.{}); + defer tmp_dir.cleanup(); + + var file = try tmp_dir.dir.createFile("test_file", .{ .read = true }); + file.close(); + try tmp_dir.dir.makeDir("test_dir"); + testing.expectError(error.IsDir, tmp_dir.dir.rename("test_file", "test_dir")); + testing.expectError(error.NotDir, tmp_dir.dir.rename("test_dir", "test_file")); +} + +test "rename" { + var tmp_dir1 = tmpDir(.{}); + defer tmp_dir1.cleanup(); + + var tmp_dir2 = tmpDir(.{}); + defer tmp_dir2.cleanup(); + + // Renaming files + const test_file_name = "test_file"; + const renamed_test_file_name = "test_file_renamed"; + var file = try tmp_dir1.dir.createFile(test_file_name, .{ .read = true }); + file.close(); + try fs.rename(tmp_dir1.dir, test_file_name, tmp_dir2.dir, renamed_test_file_name); + + // ensure the file was renamed + testing.expectError(error.FileNotFound, tmp_dir1.dir.openFile(test_file_name, .{})); + file = try tmp_dir2.dir.openFile(renamed_test_file_name, .{}); + file.close(); +} + +test "renameAbsolute" { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + + var tmp_dir = tmpDir(.{}); + defer tmp_dir.cleanup(); + + // Get base abs path + var arena = ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + const allocator = &arena.allocator; + + const base_path = blk: { + const relative_path = try fs.path.join(&arena.allocator, &[_][]const u8{ "zig-cache", "tmp", tmp_dir.sub_path[0..] }); + break :blk try fs.realpathAlloc(&arena.allocator, relative_path); + }; + + testing.expectError(error.FileNotFound, fs.renameAbsolute( + try fs.path.join(allocator, &[_][]const u8{ base_path, "missing_file_name" }), + try fs.path.join(allocator, &[_][]const u8{ base_path, "something_else" }), + )); + + // Renaming files + const test_file_name = "test_file"; + const renamed_test_file_name = "test_file_renamed"; + var file = try tmp_dir.dir.createFile(test_file_name, .{ .read = true }); + file.close(); + try fs.renameAbsolute( + try fs.path.join(allocator, &[_][]const u8{ base_path, test_file_name }), + try fs.path.join(allocator, &[_][]const u8{ base_path, renamed_test_file_name }), + ); + + // ensure the file was renamed + testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(test_file_name, .{})); + file = try tmp_dir.dir.openFile(renamed_test_file_name, .{}); + const stat = try file.stat(); + testing.expect(stat.kind == .File); + file.close(); + + // Renaming directories + const test_dir_name = "test_dir"; + const renamed_test_dir_name = "test_dir_renamed"; + try tmp_dir.dir.makeDir(test_dir_name); + try fs.renameAbsolute( + try fs.path.join(allocator, &[_][]const u8{ base_path, test_dir_name }), + try fs.path.join(allocator, &[_][]const u8{ base_path, renamed_test_dir_name }), + ); + + // ensure the directory was renamed + testing.expectError(error.FileNotFound, tmp_dir.dir.openDir(test_dir_name, .{})); + var dir = try tmp_dir.dir.openDir(renamed_test_dir_name, .{}); + dir.close(); +} + test "openSelfExe" { if (builtin.os.tag == .wasi) return error.SkipZigTest; diff --git a/lib/std/heap.zig b/lib/std/heap.zig index 6db1be539c..16de215cc2 100644 --- a/lib/std/heap.zig +++ b/lib/std/heap.zig @@ -489,7 +489,7 @@ pub const HeapAllocator = switch (builtin.os.tag) { const full_len = os.windows.kernel32.HeapSize(heap_handle, 0, ptr); assert(full_len != std.math.maxInt(usize)); assert(full_len >= amt); - break :init mem.alignBackwardAnyAlign(full_len - (aligned_addr - root_addr), len_align); + break :init mem.alignBackwardAnyAlign(full_len - (aligned_addr - root_addr) - @sizeOf(usize), len_align); }; const buf = @intToPtr([*]u8, aligned_addr)[0..return_len]; getRecordPtr(buf).* = root_addr; diff --git a/lib/std/os.zig b/lib/std/os.zig index bdc746419a..0b09b1f82a 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -1890,7 +1890,7 @@ pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir }); } -const RenameError = error{ +pub const RenameError = error{ /// In WASI, this error may occur when the file descriptor does /// not hold the required rights to rename a resource by path relative to it. AccessDenied, @@ -2107,6 +2107,7 @@ pub fn renameatW( .ACCESS_DENIED => return error.AccessDenied, .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, + .NOT_SAME_DEVICE => return error.RenameAcrossMountPoints, else => return windows.unexpectedStatus(rc), } } diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 405f7438f5..579215cf12 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -830,7 +830,7 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil } } -pub const MoveFileError = error{Unexpected}; +pub const MoveFileError = error{ FileNotFound, Unexpected }; pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) MoveFileError!void { const old_path_w = try sliceToPrefixedFileW(old_path); @@ -841,6 +841,7 @@ pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) Move pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DWORD) MoveFileError!void { if (kernel32.MoveFileExW(old_path, new_path, flags) == 0) { switch (kernel32.GetLastError()) { + .FILE_NOT_FOUND => return error.FileNotFound, else => |err| return unexpectedError(err), } } diff --git a/src/stage1/analyze.cpp b/src/stage1/analyze.cpp index 6c6f198e7a..15c30350d7 100644 --- a/src/stage1/analyze.cpp +++ b/src/stage1/analyze.cpp @@ -3161,30 +3161,29 @@ static Error resolve_union_zero_bits(CodeGen *g, ZigType *union_type) { tag_type->data.enumeration.fields_by_name.init(field_count); tag_type->data.enumeration.decls_scope = union_type->data.unionation.decls_scope; } else if (enum_type_node != nullptr) { - ZigType *enum_type = analyze_type_expr(g, scope, enum_type_node); - if (type_is_invalid(enum_type)) { + tag_type = analyze_type_expr(g, scope, enum_type_node); + } else { + if (decl_node->type == NodeTypeContainerDecl) { + tag_type = nullptr; + } else { + tag_type = union_type->data.unionation.tag_type; + } + } + if (tag_type != nullptr) { + if (type_is_invalid(tag_type)) { union_type->data.unionation.resolve_status = ResolveStatusInvalid; return ErrorSemanticAnalyzeFail; } - if (enum_type->id != ZigTypeIdEnum) { + if (tag_type->id != ZigTypeIdEnum) { union_type->data.unionation.resolve_status = ResolveStatusInvalid; - add_node_error(g, enum_type_node, - buf_sprintf("expected enum tag type, found '%s'", buf_ptr(&enum_type->name))); + add_node_error(g, enum_type_node != nullptr ? enum_type_node : decl_node, + buf_sprintf("expected enum tag type, found '%s'", buf_ptr(&tag_type->name))); return ErrorSemanticAnalyzeFail; } - if ((err = type_resolve(g, enum_type, ResolveStatusAlignmentKnown))) { + if ((err = type_resolve(g, tag_type, ResolveStatusAlignmentKnown))) { assert(g->errors.length != 0); return err; } - tag_type = enum_type; - } else { - if (decl_node->type == NodeTypeContainerDecl) { - tag_type = nullptr; - } else { - tag_type = union_type->data.unionation.tag_type; - } - } - if (tag_type != nullptr) { covered_enum_fields = heap::c_allocator.allocate<bool>(tag_type->data.enumeration.src_field_count); } union_type->data.unionation.tag_type = tag_type; diff --git a/src/stage1/ir.cpp b/src/stage1/ir.cpp index 50076f9a86..f0546f9e01 100644 --- a/src/stage1/ir.cpp +++ b/src/stage1/ir.cpp @@ -63,6 +63,7 @@ enum ConstCastResultId { ConstCastResultIdPointerChild, ConstCastResultIdSliceChild, ConstCastResultIdOptionalChild, + ConstCastResultIdOptionalShape, ConstCastResultIdErrorUnionPayload, ConstCastResultIdErrorUnionErrorSet, ConstCastResultIdFnAlign, @@ -11946,8 +11947,22 @@ static ConstCastOnly types_match_const_cast_only(IrAnalyze *ira, ZigType *wanted } } - // maybe + // optional types if (wanted_type->id == ZigTypeIdOptional && actual_type->id == ZigTypeIdOptional) { + // Consider the case where the wanted type is ??[*]T and the actual one + // is ?[*]T, we cannot turn the former into the latter even though the + // child types are compatible (?[*]T and [*]T are both represented as a + // pointer). The extra level of indirection in ??[*]T means it's + // represented as a regular, fat, optional type and, as a consequence, + // has a different shape than the one of ?[*]T. + if ((wanted_ptr_type != nullptr) != (actual_ptr_type != nullptr)) { + // The use of type_mismatch is intentional + result.id = ConstCastResultIdOptionalShape; + result.data.type_mismatch = heap::c_allocator.allocate_nonzero<ConstCastTypeMismatch>(1); + result.data.type_mismatch->wanted_type = wanted_type; + result.data.type_mismatch->actual_type = actual_type; + return result; + } ConstCastOnly child = types_match_const_cast_only(ira, wanted_type->data.maybe.child_type, actual_type->data.maybe.child_type, source_node, wanted_is_mutable); if (child.id == ConstCastResultIdInvalid) @@ -14549,6 +14564,13 @@ static void report_recursive_error(IrAnalyze *ira, AstNode *source_node, ConstCa report_recursive_error(ira, source_node, &cast_result->data.optional->child, msg); break; } + case ConstCastResultIdOptionalShape: { + add_error_note(ira->codegen, parent_msg, source_node, + buf_sprintf("optional type child '%s' cannot cast into optional type '%s'", + buf_ptr(&cast_result->data.type_mismatch->actual_type->name), + buf_ptr(&cast_result->data.type_mismatch->wanted_type->name))); + break; + } case ConstCastResultIdErrorUnionErrorSet: { ErrorMsg *msg = add_error_note(ira->codegen, parent_msg, source_node, buf_sprintf("error set '%s' cannot cast into error set '%s'", diff --git a/src/translate_c.zig b/src/translate_c.zig index c5c5231ca2..66b5752930 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -2032,7 +2032,7 @@ fn escapeChar(c: u8, char_buf: *[4]u8) []const u8 { // Handle the remaining escapes Zig doesn't support by turning them // into their respective hex representation else => if (std.ascii.isCntrl(c)) - std.fmt.bufPrint(char_buf, "\\x{x:0<2}", .{c}) catch unreachable + std.fmt.bufPrint(char_buf, "\\x{x:0>2}", .{c}) catch unreachable else std.fmt.bufPrint(char_buf, "{c}", .{c}) catch unreachable, }; diff --git a/test/stage1/behavior/cast.zig b/test/stage1/behavior/cast.zig index 4b678cd2db..ce0d16d1a0 100644 --- a/test/stage1/behavior/cast.zig +++ b/test/stage1/behavior/cast.zig @@ -849,3 +849,8 @@ test "comptime float casts" { expect(b == 2); expect(@TypeOf(b) == comptime_int); } + +test "cast from ?[*]T to ??[*]T" { + const a: ??[*]u8 = @as(?[*]u8, null); + expect(a != null and a.? == null); +} diff --git a/test/stage1/behavior/type.zig b/test/stage1/behavior/type.zig index eac76c9f98..38d23175d0 100644 --- a/test/stage1/behavior/type.zig +++ b/test/stage1/behavior/type.zig @@ -374,3 +374,45 @@ test "Type.Union" { tagged = .{ .unsigned = 1 }; testing.expectEqual(Tag.unsigned, tagged); } + +test "Type.Union from Type.Enum" { + const Tag = @Type(.{ + .Enum = .{ + .layout = .Auto, + .tag_type = u0, + .fields = &[_]TypeInfo.EnumField{ + .{ .name = "working_as_expected", .value = 0 }, + }, + .decls = &[_]TypeInfo.Declaration{}, + .is_exhaustive = true, + }, + }); + const T = @Type(.{ + .Union = .{ + .layout = .Auto, + .tag_type = Tag, + .fields = &[_]TypeInfo.UnionField{ + .{ .name = "working_as_expected", .field_type = u32 }, + }, + .decls = &[_]TypeInfo.Declaration{}, + }, + }); + _ = T; + _ = @typeInfo(T).Union; +} + +test "Type.Union from regular enum" { + const E = enum { working_as_expected = 0 }; + const T = @Type(.{ + .Union = .{ + .layout = .Auto, + .tag_type = E, + .fields = &[_]TypeInfo.UnionField{ + .{ .name = "working_as_expected", .field_type = u32 }, + }, + .decls = &[_]TypeInfo.Declaration{}, + }, + }); + _ = T; + _ = @typeInfo(T).Union; +} |
