authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-09-21 21:16:46-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-09-21 21:16:46-07:00
log0c70bb4fce8b0460f86ed218f54ba31b291f2bfb
tree1e0ad8f9ea76b30e52753a2f25f5f1d18d25896d
parentafac5d28951cfd913851094649e8b9f2136694ca
parent58ee5f4e61cd9b7a9ba65798e2214efa3753a733

Merge remote-tracking branch 'origin/master' into stage2-zig-cc


16 files changed, 677 insertions(+), 110 deletions(-)

doc/langref.html.in+1-1
......@@ -9728,7 +9728,7 @@ const c = @cImport({
97289728 <li>Does not support Zig-only pointer attributes such as alignment. Use normal {#link|Pointers#}
97299729 please!</li>
97309730 </ul>
9731 <p>When a C pointer is pointing to a single struct (not an array), deference the C pointer to
9731 <p>When a C pointer is pointing to a single struct (not an array), dereference the C pointer to
97329732 access to the struct's fields or member data. That syntax looks like
97339733 this: </p>
97349734 <p>{#syntax#}ptr_to_struct.*.struct_member{#endsyntax#}</p>
lib/std/build.zig+2
......@@ -1188,6 +1188,7 @@ pub const LibExeObjStep = struct {
11881188 emit_llvm_ir: bool = false,
11891189 emit_asm: bool = false,
11901190 emit_bin: bool = true,
1191 emit_docs: bool = false,
11911192 emit_h: bool = false,
11921193 bundle_compiler_rt: bool,
11931194 disable_stack_probing: bool,
......@@ -2033,6 +2034,7 @@ pub const LibExeObjStep = struct {
20332034 if (self.emit_llvm_ir) try zig_args.append("-femit-llvm-ir");
20342035 if (self.emit_asm) try zig_args.append("-femit-asm");
20352036 if (!self.emit_bin) try zig_args.append("-fno-emit-bin");
2037 if (self.emit_docs) try zig_args.append("-femit-docs");
20362038 if (self.emit_h) try zig_args.append("-femit-h");
20372039
20382040 if (self.strip) {
lib/std/crypto.zig+24
......@@ -35,6 +35,15 @@ pub const onetimeauth = struct {
3535 pub const Poly1305 = @import("crypto/poly1305.zig").Poly1305;
3636};
3737
38/// A Key Derivation Function (KDF) is intended to turn a weak, human generated password into a
39/// strong key, suitable for cryptographic uses. It does this by salting and stretching the
40/// password. Salting injects non-secret random data, so that identical passwords will be converted
41/// into unique keys. Stretching applies a deliberately slow hashing function to frustrate
42/// brute-force guessing.
43pub const kdf = struct {
44 pub const pbkdf2 = @import("crypto/pbkdf2.zig").pbkdf2;
45};
46
3847/// Core functions, that should rarely be used directly by applications.
3948pub const core = struct {
4049 pub const aes = @import("crypto/aes.zig");
......@@ -70,6 +79,20 @@ const std = @import("std.zig");
7079pub const randomBytes = std.os.getrandom;
7180
7281test "crypto" {
82 inline for (std.meta.declarations(@This())) |decl| {
83 switch (decl.data) {
84 .Type => |t| {
85 std.meta.refAllDecls(t);
86 },
87 .Var => |v| {
88 _ = v;
89 },
90 .Fn => |f| {
91 _ = f;
92 },
93 }
94 }
95
7396 _ = @import("crypto/aes.zig");
7497 _ = @import("crypto/blake2.zig");
7598 _ = @import("crypto/blake3.zig");
......@@ -77,6 +100,7 @@ test "crypto" {
77100 _ = @import("crypto/gimli.zig");
78101 _ = @import("crypto/hmac.zig");
79102 _ = @import("crypto/md5.zig");
103 _ = @import("crypto/pbkdf2.zig");
80104 _ = @import("crypto/poly1305.zig");
81105 _ = @import("crypto/sha1.zig");
82106 _ = @import("crypto/sha2.zig");
lib/std/crypto/pbkdf2.zig created+280
......@@ -0,0 +1,280 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2020 Zig Contributors
3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.
6
7const std = @import("std");
8const mem = std.mem;
9const maxInt = std.math.maxInt;
10
11// RFC 2898 Section 5.2
12//
13// FromSpec:
14//
15// PBKDF2 applies a pseudorandom function (see Appendix B.1 for an
16// example) to derive keys. The length of the derived key is essentially
17// unbounded. (However, the maximum effective search space for the
18// derived key may be limited by the structure of the underlying
19// pseudorandom function. See Appendix B.1 for further discussion.)
20// PBKDF2 is recommended for new applications.
21//
22// PBKDF2 (P, S, c, dkLen)
23//
24// Options: PRF underlying pseudorandom function (hLen
25// denotes the length in octets of the
26// pseudorandom function output)
27//
28// Input: P password, an octet string
29// S salt, an octet string
30// c iteration count, a positive integer
31// dkLen intended length in octets of the derived
32// key, a positive integer, at most
33// (2^32 - 1) * hLen
34//
35// Output: DK derived key, a dkLen-octet string
36
37// Based on Apple's CommonKeyDerivation, based originally on code by Damien Bergamini.
38
39pub const Pbkdf2Error = error{
40 /// At least one round is required
41 TooFewRounds,
42
43 /// Maximum length of the derived key is `maxInt(u32) * Prf.mac_length`
44 DerivedKeyTooLong,
45};
46
47/// Apply PBKDF2 to generate a key from a password.
48///
49/// PBKDF2 is defined in RFC 2898, and is a recommendation of NIST SP 800-132.
50///
51/// derivedKey: Slice of appropriate size for generated key. Generally 16 or 32 bytes in length.
52/// May be uninitialized. All bytes will be overwritten.
53/// Maximum size is `maxInt(u32) * Hash.digest_length`
54/// It is a programming error to pass buffer longer than the maximum size.
55///
56/// password: Arbitrary sequence of bytes of any length, including empty.
57///
58/// salt: Arbitrary sequence of bytes of any length, including empty. A common length is 8 bytes.
59///
60/// rounds: Iteration count. Must be greater than 0. Common values range from 1,000 to 100,000.
61/// Larger iteration counts improve security by increasing the time required to compute
62/// the derivedKey. It is common to tune this parameter to achieve approximately 100ms.
63///
64/// Prf: Pseudo-random function to use. A common choice is `std.crypto.auth.hmac.HmacSha256`.
65pub fn pbkdf2(derivedKey: []u8, password: []const u8, salt: []const u8, rounds: u32, comptime Prf: type) Pbkdf2Error!void {
66 if (rounds < 1) return error.TooFewRounds;
67
68 const dkLen = derivedKey.len;
69 const hLen = Prf.mac_length;
70 comptime std.debug.assert(hLen >= 1);
71
72 // FromSpec:
73 //
74 // 1. If dkLen > maxInt(u32) * hLen, output "derived key too long" and
75 // stop.
76 //
77 if (comptime (maxInt(usize) > maxInt(u32) * hLen) and (dkLen > @as(usize, maxInt(u32) * hLen))) {
78 // If maxInt(usize) is less than `maxInt(u32) * hLen` then dkLen is always inbounds
79 return error.DerivedKeyTooLong;
80 }
81
82 // FromSpec:
83 //
84 // 2. Let l be the number of hLen-long blocks of bytes in the derived key,
85 // rounding up, and let r be the number of bytes in the last
86 // block
87 //
88
89 // l will not overflow, proof:
90 // let `L(dkLen, hLen) = (dkLen + hLen - 1) / hLen`
91 // then `L^-1(l, hLen) = l*hLen - hLen + 1`
92 // 1) L^-1(maxInt(u32), hLen) <= maxInt(u32)*hLen
93 // 2) maxInt(u32)*hLen - hLen + 1 <= maxInt(u32)*hLen // subtract maxInt(u32)*hLen + 1
94 // 3) -hLen <= -1 // multiply by -1
95 // 4) hLen >= 1
96 const r_ = dkLen % hLen;
97 const l = @intCast(u32, (dkLen / hLen) + @as(u1, if (r_ == 0) 0 else 1)); // original: (dkLen + hLen - 1) / hLen
98 const r = if (r_ == 0) hLen else r_;
99
100 // FromSpec:
101 //
102 // 3. For each block of the derived key apply the function F defined
103 // below to the password P, the salt S, the iteration count c, and
104 // the block index to compute the block:
105 //
106 // T_1 = F (P, S, c, 1) ,
107 // T_2 = F (P, S, c, 2) ,
108 // ...
109 // T_l = F (P, S, c, l) ,
110 //
111 // where the function F is defined as the exclusive-or sum of the
112 // first c iterates of the underlying pseudorandom function PRF
113 // applied to the password P and the concatenation of the salt S
114 // and the block index i:
115 //
116 // F (P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c
117 //
118 // where
119 //
120 // U_1 = PRF (P, S || INT (i)) ,
121 // U_2 = PRF (P, U_1) ,
122 // ...
123 // U_c = PRF (P, U_{c-1}) .
124 //
125 // Here, INT (i) is a four-octet encoding of the integer i, most
126 // significant octet first.
127 //
128 // 4. Concatenate the blocks and extract the first dkLen octets to
129 // produce a derived key DK:
130 //
131 // DK = T_1 || T_2 || ... || T_l<0..r-1>
132 var block: u32 = 0; // Spec limits to u32
133 while (block < l) : (block += 1) {
134 var prevBlock: [hLen]u8 = undefined;
135 var newBlock: [hLen]u8 = undefined;
136
137 // U_1 = PRF (P, S || INT (i))
138 const blockIndex = mem.toBytes(mem.nativeToBig(u32, block + 1)); // Block index starts at 0001
139 var ctx = Prf.init(password);
140 ctx.update(salt);
141 ctx.update(blockIndex[0..]);
142 ctx.final(prevBlock[0..]);
143
144 // Choose portion of DK to write into (T_n) and initialize
145 const offset = block * hLen;
146 const blockLen = if (block != l - 1) hLen else r;
147 const dkBlock: []u8 = derivedKey[offset..][0..blockLen];
148 mem.copy(u8, dkBlock, prevBlock[0..dkBlock.len]);
149
150 var i: u32 = 1;
151 while (i < rounds) : (i += 1) {
152 // U_c = PRF (P, U_{c-1})
153 Prf.create(&newBlock, prevBlock[0..], password);
154 mem.copy(u8, prevBlock[0..], newBlock[0..]);
155
156 // F (P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c
157 for (dkBlock) |_, j| {
158 dkBlock[j] ^= newBlock[j];
159 }
160 }
161 }
162}
163
164const htest = @import("test.zig");
165const HmacSha1 = std.crypto.auth.hmac.HmacSha1;
166
167// RFC 6070 PBKDF2 HMAC-SHA1 Test Vectors
168test "RFC 6070 one iteration" {
169 const p = "password";
170 const s = "salt";
171 const c = 1;
172 const dkLen = 20;
173
174 var derivedKey: [dkLen]u8 = undefined;
175
176 try pbkdf2(&derivedKey, p, s, c, HmacSha1);
177
178 const expected = "0c60c80f961f0e71f3a9b524af6012062fe037a6";
179
180 htest.assertEqual(expected, derivedKey[0..]);
181}
182
183test "RFC 6070 two iterations" {
184 const p = "password";
185 const s = "salt";
186 const c = 2;
187 const dkLen = 20;
188
189 var derivedKey: [dkLen]u8 = undefined;
190
191 try pbkdf2(&derivedKey, p, s, c, HmacSha1);
192
193 const expected = "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957";
194
195 htest.assertEqual(expected, derivedKey[0..]);
196}
197
198test "RFC 6070 4096 iterations" {
199 const p = "password";
200 const s = "salt";
201 const c = 4096;
202 const dkLen = 20;
203
204 var derivedKey: [dkLen]u8 = undefined;
205
206 try pbkdf2(&derivedKey, p, s, c, HmacSha1);
207
208 const expected = "4b007901b765489abead49d926f721d065a429c1";
209
210 htest.assertEqual(expected, derivedKey[0..]);
211}
212
213test "RFC 6070 16,777,216 iterations" {
214 // These iteration tests are slow so we always skip them. Results have been verified.
215 if (true) {
216 return error.SkipZigTest;
217 }
218
219 const p = "password";
220 const s = "salt";
221 const c = 16777216;
222 const dkLen = 20;
223
224 var derivedKey = [_]u8{0} ** dkLen;
225
226 try pbkdf2(&derivedKey, p, s, c, HmacSha1);
227
228 const expected = "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984";
229
230 htest.assertEqual(expected, derivedKey[0..]);
231}
232
233test "RFC 6070 multi-block salt and password" {
234 const p = "passwordPASSWORDpassword";
235 const s = "saltSALTsaltSALTsaltSALTsaltSALTsalt";
236 const c = 4096;
237 const dkLen = 25;
238
239 var derivedKey: [dkLen]u8 = undefined;
240
241 try pbkdf2(&derivedKey, p, s, c, HmacSha1);
242
243 const expected = "3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038";
244
245 htest.assertEqual(expected, derivedKey[0..]);
246}
247
248test "RFC 6070 embedded NUL" {
249 const p = "pass\x00word";
250 const s = "sa\x00lt";
251 const c = 4096;
252 const dkLen = 16;
253
254 var derivedKey: [dkLen]u8 = undefined;
255
256 try pbkdf2(&derivedKey, p, s, c, HmacSha1);
257
258 const expected = "56fa6aa75548099dcc37d7f03425e0c3";
259
260 htest.assertEqual(expected, derivedKey[0..]);
261}
262
263test "Very large dkLen" {
264 // This test allocates 8GB of memory and is expected to take several hours to run.
265 if (true) {
266 return error.SkipZigTest;
267 }
268 const p = "password";
269 const s = "salt";
270 const c = 1;
271 const dkLen = 1 << 33;
272
273 var derivedKey = try std.testing.allocator.alloc(u8, dkLen);
274 defer {
275 std.testing.allocator.free(derivedKey);
276 }
277
278 try pbkdf2(derivedKey, p, s, c, HmacSha1);
279 // Just verify this doesn't crash with an overflow
280}
lib/std/crypto/siphash.zig+2-1
......@@ -218,8 +218,9 @@ fn SipHash(comptime T: type, comptime c_rounds: usize, comptime d_rounds: usize)
218218 }
219219
220220 /// Return an authentication tag for the current state
221 /// Assumes `out` is less than or equal to `mac_length`.
221222 pub fn final(self: *Self, out: []u8) void {
222 std.debug.assert(out.len >= mac_length);
223 std.debug.assert(out.len <= mac_length);
223224 mem.writeIntLittle(T, out[0..mac_length], self.state.final(self.buf[0..self.buf_len]));
224225 }
225226
lib/std/fmt.zig+56-83
......@@ -22,7 +22,7 @@ pub const Alignment = enum {
2222pub const FormatOptions = struct {
2323 precision: ?usize = null,
2424 width: ?usize = null,
25 alignment: Alignment = .Left,
25 alignment: Alignment = .Right,
2626 fill: u8 = ' ',
2727};
2828
......@@ -327,7 +327,7 @@ pub fn formatType(
327327 max_depth: usize,
328328) @TypeOf(writer).Error!void {
329329 if (comptime std.mem.eql(u8, fmt, "*")) {
330 try writer.writeAll(@typeName(@typeInfo(@TypeOf(value)).Pointer.child));
330 try writer.writeAll(@typeName(std.meta.Child(@TypeOf(value))));
331331 try writer.writeAll("@");
332332 try formatInt(@ptrToInt(value), 16, false, FormatOptions{}, writer);
333333 return;
......@@ -631,26 +631,22 @@ pub fn formatBuf(
631631 writer: anytype,
632632) !void {
633633 const width = options.width orelse buf.len;
634 var padding = if (width > buf.len) (width - buf.len) else 0;
635 const pad_byte = [1]u8{options.fill};
634 const padding = if (width > buf.len) (width - buf.len) else 0;
635
636636 switch (options.alignment) {
637637 .Left => {
638638 try writer.writeAll(buf);
639 while (padding > 0) : (padding -= 1) {
640 try writer.writeAll(&pad_byte);
641 }
639 try writer.writeByteNTimes(options.fill, padding);
642640 },
643641 .Center => {
644 const padl = padding / 2;
645 var i: usize = 0;
646 while (i < padl) : (i += 1) try writer.writeAll(&pad_byte);
642 const left_padding = padding / 2;
643 const right_padding = (padding + 1) / 2;
644 try writer.writeByteNTimes(options.fill, left_padding);
647645 try writer.writeAll(buf);
648 while (i < padding) : (i += 1) try writer.writeAll(&pad_byte);
646 try writer.writeByteNTimes(options.fill, right_padding);
649647 },
650648 .Right => {
651 while (padding > 0) : (padding -= 1) {
652 try writer.writeAll(&pad_byte);
653 }
649 try writer.writeByteNTimes(options.fill, padding);
654650 try writer.writeAll(buf);
655651 },
656652 }
......@@ -941,61 +937,27 @@ pub fn formatInt(
941937 options: FormatOptions,
942938 writer: anytype,
943939) !void {
940 assert(base >= 2);
941
944942 const int_value = if (@TypeOf(value) == comptime_int) blk: {
945943 const Int = math.IntFittingRange(value, value);
946944 break :blk @as(Int, value);
947945 } else
948946 value;
949947
950 if (@typeInfo(@TypeOf(int_value)).Int.is_signed) {
951 return formatIntSigned(int_value, base, uppercase, options, writer);
952 } else {
953 return formatIntUnsigned(int_value, base, uppercase, options, writer);
954 }
955}
948 const value_info = @typeInfo(@TypeOf(int_value)).Int;
956949
957fn formatIntSigned(
958 value: anytype,
959 base: u8,
960 uppercase: bool,
961 options: FormatOptions,
962 writer: anytype,
963) !void {
964 const new_options = FormatOptions{
965 .width = if (options.width) |w| (if (w == 0) 0 else w - 1) else null,
966 .precision = options.precision,
967 .fill = options.fill,
968 };
969 const bit_count = @typeInfo(@TypeOf(value)).Int.bits;
970 const Uint = std.meta.Int(false, bit_count);
971 if (value < 0) {
972 try writer.writeAll("-");
973 const new_value = math.absCast(value);
974 return formatIntUnsigned(new_value, base, uppercase, new_options, writer);
975 } else if (options.width == null or options.width.? == 0) {
976 return formatIntUnsigned(@intCast(Uint, value), base, uppercase, options, writer);
977 } else {
978 try writer.writeAll("+");
979 const new_value = @intCast(Uint, value);
980 return formatIntUnsigned(new_value, base, uppercase, new_options, writer);
981 }
982}
950 // The type must have the same size as `base` or be wider in order for the
951 // division to work
952 const min_int_bits = comptime math.max(value_info.bits, 8);
953 const MinInt = std.meta.Int(false, min_int_bits);
983954
984fn formatIntUnsigned(
985 value: anytype,
986 base: u8,
987 uppercase: bool,
988 options: FormatOptions,
989 writer: anytype,
990) !void {
991 assert(base >= 2);
992 const value_info = @typeInfo(@TypeOf(value)).Int;
993 var buf: [math.max(value_info.bits, 1)]u8 = undefined;
994 const min_int_bits = comptime math.max(value_info.bits, @typeInfo(@TypeOf(base)).Int.bits);
995 const MinInt = std.meta.Int(value_info.is_signed, min_int_bits);
996 var a: MinInt = value;
997 var index: usize = buf.len;
955 const abs_value = math.absCast(int_value);
956 // The worst case in terms of space needed is base 2, plus 1 for the sign
957 var buf: [1 + math.max(value_info.bits, 1)]u8 = undefined;
998958
959 var a: MinInt = abs_value;
960 var index: usize = buf.len;
999961 while (true) {
1000962 const digit = a % base;
1001963 index -= 1;
......@@ -1004,25 +966,21 @@ fn formatIntUnsigned(
1004966 if (a == 0) break;
1005967 }
1006968
1007 const digits_buf = buf[index..];
1008 const width = options.width orelse 0;
1009 const padding = if (width > digits_buf.len) (width - digits_buf.len) else 0;
1010
1011 if (padding > index) {
1012 const zero_byte: u8 = options.fill;
1013 var leftover_padding = padding - index;
1014 while (true) {
1015 try writer.writeAll(@as(*const [1]u8, &zero_byte)[0..]);
1016 leftover_padding -= 1;
1017 if (leftover_padding == 0) break;
969 if (value_info.is_signed) {
970 if (value < 0) {
971 // Negative integer
972 index -= 1;
973 buf[index] = '-';
974 } else if (options.width == null or options.width.? == 0) {
975 // Positive integer, omit the plus sign
976 } else {
977 // Positive integer
978 index -= 1;
979 buf[index] = '+';
1018980 }
1019 mem.set(u8, buf[0..index], options.fill);
1020 return writer.writeAll(&buf);
1021 } else {
1022 const padded_buf = buf[index - padding ..];
1023 mem.set(u8, padded_buf[0..padding], options.fill);
1024 return writer.writeAll(padded_buf);
1025981 }
982
983 return formatBuf(buf[index..], options, writer);
1026984}
1027985
1028986pub fn formatIntBuf(out_buf: []u8, value: anytype, base: u8, uppercase: bool, options: FormatOptions) usize {
......@@ -1246,6 +1204,10 @@ test "optional" {
12461204 const value: ?i32 = null;
12471205 try testFmt("optional: null\n", "optional: {}\n", .{value});
12481206 }
1207 {
1208 const value = @intToPtr(?*i32, 0xf000d000);
1209 try testFmt("optional: *i32@f000d000\n", "optional: {*}\n", .{value});
1210 }
12491211}
12501212
12511213test "error" {
......@@ -1283,7 +1245,17 @@ test "int.specifier" {
12831245
12841246test "int.padded" {
12851247 try testFmt("u8: ' 1'", "u8: '{:4}'", .{@as(u8, 1)});
1286 try testFmt("u8: 'xxx1'", "u8: '{:x<4}'", .{@as(u8, 1)});
1248 try testFmt("u8: '1000'", "u8: '{:0<4}'", .{@as(u8, 1)});
1249 try testFmt("u8: '0001'", "u8: '{:0>4}'", .{@as(u8, 1)});
1250 try testFmt("u8: '0100'", "u8: '{:0^4}'", .{@as(u8, 1)});
1251 try testFmt("i8: '-1 '", "i8: '{:<4}'", .{@as(i8, -1)});
1252 try testFmt("i8: ' -1'", "i8: '{:>4}'", .{@as(i8, -1)});
1253 try testFmt("i8: ' -1 '", "i8: '{:^4}'", .{@as(i8, -1)});
1254 try testFmt("i16: '-1234'", "i16: '{:4}'", .{@as(i16, -1234)});
1255 try testFmt("i16: '+1234'", "i16: '{:4}'", .{@as(i16, 1234)});
1256 try testFmt("i16: '-12345'", "i16: '{:4}'", .{@as(i16, -12345)});
1257 try testFmt("i16: '+12345'", "i16: '{:4}'", .{@as(i16, 12345)});
1258 try testFmt("u16: '12345'", "u16: '{:4}'", .{@as(u16, 12345)});
12871259}
12881260
12891261test "buffer" {
......@@ -1329,7 +1301,7 @@ test "slice" {
13291301 try testFmt("slice: []const u8@deadbeef\n", "slice: {}\n", .{value});
13301302 }
13311303
1332 try testFmt("buf: Test \n", "buf: {s:5}\n", .{"Test"});
1304 try testFmt("buf: Test\n", "buf: {s:5}\n", .{"Test"});
13331305 try testFmt("buf: Test\n Other text", "buf: {s}\n Other text", .{"Test"});
13341306}
13351307
......@@ -1362,7 +1334,7 @@ test "cstr" {
13621334 .{@ptrCast([*c]const u8, "Test C")},
13631335 );
13641336 try testFmt(
1365 "cstr: Test C \n",
1337 "cstr: Test C\n",
13661338 "cstr: {s:10}\n",
13671339 .{@ptrCast([*c]const u8, "Test C")},
13681340 );
......@@ -1805,7 +1777,7 @@ test "vector" {
18051777
18061778 try testFmt("{ true, false, true, false }", "{}", .{vbool});
18071779 try testFmt("{ -2, -1, 0, 1 }", "{}", .{vi64});
1808 try testFmt("{ - 2, - 1, + 0, + 1 }", "{d:5}", .{vi64});
1780 try testFmt("{ -2, -1, +0, +1 }", "{d:5}", .{vi64});
18091781 try testFmt("{ 1000, 2000, 3000, 4000 }", "{}", .{vu64});
18101782 try testFmt("{ 3e8, 7d0, bb8, fa0 }", "{x}", .{vu64});
18111783 try testFmt("{ 1kB, 2kB, 3kB, 4kB }", "{B}", .{vu64});
......@@ -1818,15 +1790,16 @@ test "enum-literal" {
18181790
18191791test "padding" {
18201792 try testFmt("Simple", "{}", .{"Simple"});
1821 try testFmt("true ", "{:10}", .{true});
1793 try testFmt(" true", "{:10}", .{true});
18221794 try testFmt(" true", "{:>10}", .{true});
18231795 try testFmt("======true", "{:=>10}", .{true});
18241796 try testFmt("true======", "{:=<10}", .{true});
18251797 try testFmt(" true ", "{:^10}", .{true});
18261798 try testFmt("===true===", "{:=^10}", .{true});
1827 try testFmt("Minimum width", "{:18} width", .{"Minimum"});
1799 try testFmt(" Minimum width", "{:18} width", .{"Minimum"});
18281800 try testFmt("==================Filled", "{:=>24}", .{"Filled"});
18291801 try testFmt(" Centered ", "{:^24}", .{"Centered"});
1802 try testFmt("-", "{:-^1}", .{""});
18301803}
18311804
18321805test "decimal float padding" {
lib/std/fs.zig+61-5
......@@ -21,10 +21,6 @@ pub const wasi = @import("fs/wasi.zig");
2121
2222// TODO audit these APIs with respect to Dir and absolute paths
2323
24pub const rename = os.rename;
25pub const renameZ = os.renameZ;
26pub const renameC = @compileError("deprecated: renamed to renameZ");
27pub const renameW = os.renameW;
2824pub const realpath = os.realpath;
2925pub const realpathZ = os.realpathZ;
3026pub const realpathC = @compileError("deprecated: renamed to realpathZ");
......@@ -90,7 +86,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path:
9086 base64_encoder.encode(tmp_path[dirname.len + 1 ..], &rand_buf);
9187
9288 if (cwd().symLink(existing_path, tmp_path, .{})) {
93 return rename(tmp_path, new_path);
89 return cwd().rename(tmp_path, new_path);
9490 } else |err| switch (err) {
9591 error.PathAlreadyExists => continue,
9692 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 {
255251 return os.rmdirW(dir_path);
256252}
257253
254pub const renameC = @compileError("deprecated: use renameZ, dir.renameZ, or renameAbsoluteZ");
255
256/// Same as `Dir.rename` except the paths are absolute.
257pub fn renameAbsolute(old_path: []const u8, new_path: []const u8) !void {
258 assert(path.isAbsolute(old_path));
259 assert(path.isAbsolute(new_path));
260 return os.rename(old_path, new_path);
261}
262
263/// Same as `renameAbsolute` except the path parameters are null-terminated.
264pub fn renameAbsoluteZ(old_path: [*:0]const u8, new_path: [*:0]const u8) !void {
265 assert(path.isAbsoluteZ(old_path));
266 assert(path.isAbsoluteZ(new_path));
267 return os.renameZ(old_path, new_path);
268}
269
270/// Same as `renameAbsolute` except the path parameters are WTF-16 and target OS is assumed Windows.
271pub fn renameAbsoluteW(old_path: [*:0]const u16, new_path: [*:0]const u16) !void {
272 assert(path.isAbsoluteWindowsW(old_path));
273 assert(path.isAbsoluteWindowsW(new_path));
274 return os.renameW(old_path, new_path);
275}
276
277/// Same as `Dir.rename`, except `new_sub_path` is relative to `new_dir`
278pub fn rename(old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) !void {
279 return os.renameat(old_dir.fd, old_sub_path, new_dir.fd, new_sub_path);
280}
281
282/// Same as `rename` except the parameters are null-terminated.
283pub fn renameZ(old_dir: Dir, old_sub_path_z: [*:0]const u8, new_dir: Dir, new_sub_path_z: [*:0]const u8) !void {
284 return os.renameatZ(old_dir.fd, old_sub_path_z, new_dir.fd, new_sub_path_z);
285}
286
287/// Same as `rename` except the parameters are UTF16LE, NT prefixed.
288/// This function is Windows-only.
289pub fn renameW(old_dir: Dir, old_sub_path_w: []const u16, new_dir: Dir, new_sub_path_w: []const u16) !void {
290 return os.renameatW(old_dir.fd, old_sub_path_w, new_dir.fd, new_sub_path_w);
291}
292
258293pub const Dir = struct {
259294 fd: os.fd_t,
260295
......@@ -1338,6 +1373,27 @@ pub const Dir = struct {
13381373 };
13391374 }
13401375
1376 pub const RenameError = os.RenameError;
1377
1378 /// Change the name or location of a file or directory.
1379 /// If new_sub_path already exists, it will be replaced.
1380 /// Renaming a file over an existing directory or a directory
1381 /// over an existing file will fail with `error.IsDir` or `error.NotDir`
1382 pub fn rename(self: Dir, old_sub_path: []const u8, new_sub_path: []const u8) RenameError!void {
1383 return os.renameat(self.fd, old_sub_path, self.fd, new_sub_path);
1384 }
1385
1386 /// Same as `rename` except the parameters are null-terminated.
1387 pub fn renameZ(self: Dir, old_sub_path_z: [*:0]const u8, new_sub_path_z: [*:0]const u8) RenameError!void {
1388 return os.renameatZ(self.fd, old_sub_path_z, self.fd, new_sub_path_z);
1389 }
1390
1391 /// Same as `rename` except the parameters are UTF16LE, NT prefixed.
1392 /// This function is Windows-only.
1393 pub fn renameW(self: Dir, old_sub_path_w: []const u16, new_sub_path_w: []const u16) RenameError!void {
1394 return os.renameatW(self.fd, old_sub_path_w, self.fd, new_sub_path_w);
1395 }
1396
13411397 /// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
13421398 /// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
13431399 /// one; the latter case is known as a dangling link.
lib/std/fs/test.zig+161
......@@ -274,6 +274,167 @@ test "file operations on directories" {
274274 dir.close();
275275}
276276
277test "Dir.rename files" {
278 var tmp_dir = tmpDir(.{});
279 defer tmp_dir.cleanup();
280
281 testing.expectError(error.FileNotFound, tmp_dir.dir.rename("missing_file_name", "something_else"));
282
283 // Renaming files
284 const test_file_name = "test_file";
285 const renamed_test_file_name = "test_file_renamed";
286 var file = try tmp_dir.dir.createFile(test_file_name, .{ .read = true });
287 file.close();
288 try tmp_dir.dir.rename(test_file_name, renamed_test_file_name);
289
290 // Ensure the file was renamed
291 testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(test_file_name, .{}));
292 file = try tmp_dir.dir.openFile(renamed_test_file_name, .{});
293 file.close();
294
295 // Rename to self succeeds
296 try tmp_dir.dir.rename(renamed_test_file_name, renamed_test_file_name);
297
298 // Rename to existing file succeeds
299 var existing_file = try tmp_dir.dir.createFile("existing_file", .{ .read = true });
300 existing_file.close();
301 try tmp_dir.dir.rename(renamed_test_file_name, "existing_file");
302
303 testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(renamed_test_file_name, .{}));
304 file = try tmp_dir.dir.openFile("existing_file", .{});
305 file.close();
306}
307
308test "Dir.rename directories" {
309 // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364
310 if (builtin.os.tag == .windows) return error.SkipZigTest;
311
312 var tmp_dir = tmpDir(.{});
313 defer tmp_dir.cleanup();
314
315 // Renaming directories
316 try tmp_dir.dir.makeDir("test_dir");
317 try tmp_dir.dir.rename("test_dir", "test_dir_renamed");
318
319 // Ensure the directory was renamed
320 testing.expectError(error.FileNotFound, tmp_dir.dir.openDir("test_dir", .{}));
321 var dir = try tmp_dir.dir.openDir("test_dir_renamed", .{});
322
323 // Put a file in the directory
324 var file = try dir.createFile("test_file", .{ .read = true });
325 file.close();
326 dir.close();
327
328 try tmp_dir.dir.rename("test_dir_renamed", "test_dir_renamed_again");
329
330 // Ensure the directory was renamed and the file still exists in it
331 testing.expectError(error.FileNotFound, tmp_dir.dir.openDir("test_dir_renamed", .{}));
332 dir = try tmp_dir.dir.openDir("test_dir_renamed_again", .{});
333 file = try dir.openFile("test_file", .{});
334 file.close();
335 dir.close();
336
337 // Try to rename to a non-empty directory now
338 var target_dir = try tmp_dir.dir.makeOpenPath("non_empty_target_dir", .{});
339 file = try target_dir.createFile("filler", .{ .read = true });
340 file.close();
341
342 testing.expectError(error.PathAlreadyExists, tmp_dir.dir.rename("test_dir_renamed_again", "non_empty_target_dir"));
343
344 // Ensure the directory was not renamed
345 dir = try tmp_dir.dir.openDir("test_dir_renamed_again", .{});
346 file = try dir.openFile("test_file", .{});
347 file.close();
348 dir.close();
349}
350
351test "Dir.rename file <-> dir" {
352 // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364
353 if (builtin.os.tag == .windows) return error.SkipZigTest;
354
355 var tmp_dir = tmpDir(.{});
356 defer tmp_dir.cleanup();
357
358 var file = try tmp_dir.dir.createFile("test_file", .{ .read = true });
359 file.close();
360 try tmp_dir.dir.makeDir("test_dir");
361 testing.expectError(error.IsDir, tmp_dir.dir.rename("test_file", "test_dir"));
362 testing.expectError(error.NotDir, tmp_dir.dir.rename("test_dir", "test_file"));
363}
364
365test "rename" {
366 var tmp_dir1 = tmpDir(.{});
367 defer tmp_dir1.cleanup();
368
369 var tmp_dir2 = tmpDir(.{});
370 defer tmp_dir2.cleanup();
371
372 // Renaming files
373 const test_file_name = "test_file";
374 const renamed_test_file_name = "test_file_renamed";
375 var file = try tmp_dir1.dir.createFile(test_file_name, .{ .read = true });
376 file.close();
377 try fs.rename(tmp_dir1.dir, test_file_name, tmp_dir2.dir, renamed_test_file_name);
378
379 // ensure the file was renamed
380 testing.expectError(error.FileNotFound, tmp_dir1.dir.openFile(test_file_name, .{}));
381 file = try tmp_dir2.dir.openFile(renamed_test_file_name, .{});
382 file.close();
383}
384
385test "renameAbsolute" {
386 if (builtin.os.tag == .wasi) return error.SkipZigTest;
387
388 var tmp_dir = tmpDir(.{});
389 defer tmp_dir.cleanup();
390
391 // Get base abs path
392 var arena = ArenaAllocator.init(testing.allocator);
393 defer arena.deinit();
394 const allocator = &arena.allocator;
395
396 const base_path = blk: {
397 const relative_path = try fs.path.join(&arena.allocator, &[_][]const u8{ "zig-cache", "tmp", tmp_dir.sub_path[0..] });
398 break :blk try fs.realpathAlloc(&arena.allocator, relative_path);
399 };
400
401 testing.expectError(error.FileNotFound, fs.renameAbsolute(
402 try fs.path.join(allocator, &[_][]const u8{ base_path, "missing_file_name" }),
403 try fs.path.join(allocator, &[_][]const u8{ base_path, "something_else" }),
404 ));
405
406 // Renaming files
407 const test_file_name = "test_file";
408 const renamed_test_file_name = "test_file_renamed";
409 var file = try tmp_dir.dir.createFile(test_file_name, .{ .read = true });
410 file.close();
411 try fs.renameAbsolute(
412 try fs.path.join(allocator, &[_][]const u8{ base_path, test_file_name }),
413 try fs.path.join(allocator, &[_][]const u8{ base_path, renamed_test_file_name }),
414 );
415
416 // ensure the file was renamed
417 testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(test_file_name, .{}));
418 file = try tmp_dir.dir.openFile(renamed_test_file_name, .{});
419 const stat = try file.stat();
420 testing.expect(stat.kind == .File);
421 file.close();
422
423 // Renaming directories
424 const test_dir_name = "test_dir";
425 const renamed_test_dir_name = "test_dir_renamed";
426 try tmp_dir.dir.makeDir(test_dir_name);
427 try fs.renameAbsolute(
428 try fs.path.join(allocator, &[_][]const u8{ base_path, test_dir_name }),
429 try fs.path.join(allocator, &[_][]const u8{ base_path, renamed_test_dir_name }),
430 );
431
432 // ensure the directory was renamed
433 testing.expectError(error.FileNotFound, tmp_dir.dir.openDir(test_dir_name, .{}));
434 var dir = try tmp_dir.dir.openDir(renamed_test_dir_name, .{});
435 dir.close();
436}
437
277438test "openSelfExe" {
278439 if (builtin.os.tag == .wasi) return error.SkipZigTest;
279440
lib/std/heap.zig+1-1
......@@ -489,7 +489,7 @@ pub const HeapAllocator = switch (builtin.os.tag) {
489489 const full_len = os.windows.kernel32.HeapSize(heap_handle, 0, ptr);
490490 assert(full_len != std.math.maxInt(usize));
491491 assert(full_len >= amt);
492 break :init mem.alignBackwardAnyAlign(full_len - (aligned_addr - root_addr), len_align);
492 break :init mem.alignBackwardAnyAlign(full_len - (aligned_addr - root_addr) - @sizeOf(usize), len_align);
493493 };
494494 const buf = @intToPtr([*]u8, aligned_addr)[0..return_len];
495495 getRecordPtr(buf).* = root_addr;
lib/std/os.zig+2-1
......@@ -1890,7 +1890,7 @@ pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError
18901890 return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir });
18911891}
18921892
1893const RenameError = error{
1893pub const RenameError = error{
18941894 /// In WASI, this error may occur when the file descriptor does
18951895 /// not hold the required rights to rename a resource by path relative to it.
18961896 AccessDenied,
......@@ -2107,6 +2107,7 @@ pub fn renameatW(
21072107 .ACCESS_DENIED => return error.AccessDenied,
21082108 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
21092109 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
2110 .NOT_SAME_DEVICE => return error.RenameAcrossMountPoints,
21102111 else => return windows.unexpectedStatus(rc),
21112112 }
21122113}
lib/std/os/windows.zig+2-1
......@@ -830,7 +830,7 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil
830830 }
831831}
832832
833pub const MoveFileError = error{Unexpected};
833pub const MoveFileError = error{ FileNotFound, Unexpected };
834834
835835pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) MoveFileError!void {
836836 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
841841pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DWORD) MoveFileError!void {
842842 if (kernel32.MoveFileExW(old_path, new_path, flags) == 0) {
843843 switch (kernel32.GetLastError()) {
844 .FILE_NOT_FOUND => return error.FileNotFound,
844845 else => |err| return unexpectedError(err),
845846 }
846847 }
src/stage1/analyze.cpp+14-15
......@@ -3161,30 +3161,29 @@ static Error resolve_union_zero_bits(CodeGen *g, ZigType *union_type) {
31613161 tag_type->data.enumeration.fields_by_name.init(field_count);
31623162 tag_type->data.enumeration.decls_scope = union_type->data.unionation.decls_scope;
31633163 } else if (enum_type_node != nullptr) {
3164 ZigType *enum_type = analyze_type_expr(g, scope, enum_type_node);
3165 if (type_is_invalid(enum_type)) {
3164 tag_type = analyze_type_expr(g, scope, enum_type_node);
3165 } else {
3166 if (decl_node->type == NodeTypeContainerDecl) {
3167 tag_type = nullptr;
3168 } else {
3169 tag_type = union_type->data.unionation.tag_type;
3170 }
3171 }
3172 if (tag_type != nullptr) {
3173 if (type_is_invalid(tag_type)) {
31663174 union_type->data.unionation.resolve_status = ResolveStatusInvalid;
31673175 return ErrorSemanticAnalyzeFail;
31683176 }
3169 if (enum_type->id != ZigTypeIdEnum) {
3177 if (tag_type->id != ZigTypeIdEnum) {
31703178 union_type->data.unionation.resolve_status = ResolveStatusInvalid;
3171 add_node_error(g, enum_type_node,
3172 buf_sprintf("expected enum tag type, found '%s'", buf_ptr(&enum_type->name)));
3179 add_node_error(g, enum_type_node != nullptr ? enum_type_node : decl_node,
3180 buf_sprintf("expected enum tag type, found '%s'", buf_ptr(&tag_type->name)));
31733181 return ErrorSemanticAnalyzeFail;
31743182 }
3175 if ((err = type_resolve(g, enum_type, ResolveStatusAlignmentKnown))) {
3183 if ((err = type_resolve(g, tag_type, ResolveStatusAlignmentKnown))) {
31763184 assert(g->errors.length != 0);
31773185 return err;
31783186 }
3179 tag_type = enum_type;
3180 } else {
3181 if (decl_node->type == NodeTypeContainerDecl) {
3182 tag_type = nullptr;
3183 } else {
3184 tag_type = union_type->data.unionation.tag_type;
3185 }
3186 }
3187 if (tag_type != nullptr) {
31883187 covered_enum_fields = heap::c_allocator.allocate<bool>(tag_type->data.enumeration.src_field_count);
31893188 }
31903189 union_type->data.unionation.tag_type = tag_type;
src/stage1/ir.cpp+23-1
......@@ -63,6 +63,7 @@ enum ConstCastResultId {
6363 ConstCastResultIdPointerChild,
6464 ConstCastResultIdSliceChild,
6565 ConstCastResultIdOptionalChild,
66 ConstCastResultIdOptionalShape,
6667 ConstCastResultIdErrorUnionPayload,
6768 ConstCastResultIdErrorUnionErrorSet,
6869 ConstCastResultIdFnAlign,
......@@ -11946,8 +11947,22 @@ static ConstCastOnly types_match_const_cast_only(IrAnalyze *ira, ZigType *wanted
1194611947 }
1194711948 }
1194811949
11949 // maybe
11950 // optional types
1195011951 if (wanted_type->id == ZigTypeIdOptional && actual_type->id == ZigTypeIdOptional) {
11952 // Consider the case where the wanted type is ??[*]T and the actual one
11953 // is ?[*]T, we cannot turn the former into the latter even though the
11954 // child types are compatible (?[*]T and [*]T are both represented as a
11955 // pointer). The extra level of indirection in ??[*]T means it's
11956 // represented as a regular, fat, optional type and, as a consequence,
11957 // has a different shape than the one of ?[*]T.
11958 if ((wanted_ptr_type != nullptr) != (actual_ptr_type != nullptr)) {
11959 // The use of type_mismatch is intentional
11960 result.id = ConstCastResultIdOptionalShape;
11961 result.data.type_mismatch = heap::c_allocator.allocate_nonzero<ConstCastTypeMismatch>(1);
11962 result.data.type_mismatch->wanted_type = wanted_type;
11963 result.data.type_mismatch->actual_type = actual_type;
11964 return result;
11965 }
1195111966 ConstCastOnly child = types_match_const_cast_only(ira, wanted_type->data.maybe.child_type,
1195211967 actual_type->data.maybe.child_type, source_node, wanted_is_mutable);
1195311968 if (child.id == ConstCastResultIdInvalid)
......@@ -14549,6 +14564,13 @@ static void report_recursive_error(IrAnalyze *ira, AstNode *source_node, ConstCa
1454914564 report_recursive_error(ira, source_node, &cast_result->data.optional->child, msg);
1455014565 break;
1455114566 }
14567 case ConstCastResultIdOptionalShape: {
14568 add_error_note(ira->codegen, parent_msg, source_node,
14569 buf_sprintf("optional type child '%s' cannot cast into optional type '%s'",
14570 buf_ptr(&cast_result->data.type_mismatch->actual_type->name),
14571 buf_ptr(&cast_result->data.type_mismatch->wanted_type->name)));
14572 break;
14573 }
1455214574 case ConstCastResultIdErrorUnionErrorSet: {
1455314575 ErrorMsg *msg = add_error_note(ira->codegen, parent_msg, source_node,
1455414576 buf_sprintf("error set '%s' cannot cast into error set '%s'",
src/translate_c.zig+1-1
......@@ -2032,7 +2032,7 @@ fn escapeChar(c: u8, char_buf: *[4]u8) []const u8 {
20322032 // Handle the remaining escapes Zig doesn't support by turning them
20332033 // into their respective hex representation
20342034 else => if (std.ascii.isCntrl(c))
2035 std.fmt.bufPrint(char_buf, "\\x{x:0<2}", .{c}) catch unreachable
2035 std.fmt.bufPrint(char_buf, "\\x{x:0>2}", .{c}) catch unreachable
20362036 else
20372037 std.fmt.bufPrint(char_buf, "{c}", .{c}) catch unreachable,
20382038 };
test/stage1/behavior/cast.zig+5
......@@ -849,3 +849,8 @@ test "comptime float casts" {
849849 expect(b == 2);
850850 expect(@TypeOf(b) == comptime_int);
851851}
852
853test "cast from ?[*]T to ??[*]T" {
854 const a: ??[*]u8 = @as(?[*]u8, null);
855 expect(a != null and a.? == null);
856}
test/stage1/behavior/type.zig+42
......@@ -374,3 +374,45 @@ test "Type.Union" {
374374 tagged = .{ .unsigned = 1 };
375375 testing.expectEqual(Tag.unsigned, tagged);
376376}
377
378test "Type.Union from Type.Enum" {
379 const Tag = @Type(.{
380 .Enum = .{
381 .layout = .Auto,
382 .tag_type = u0,
383 .fields = &[_]TypeInfo.EnumField{
384 .{ .name = "working_as_expected", .value = 0 },
385 },
386 .decls = &[_]TypeInfo.Declaration{},
387 .is_exhaustive = true,
388 },
389 });
390 const T = @Type(.{
391 .Union = .{
392 .layout = .Auto,
393 .tag_type = Tag,
394 .fields = &[_]TypeInfo.UnionField{
395 .{ .name = "working_as_expected", .field_type = u32 },
396 },
397 .decls = &[_]TypeInfo.Declaration{},
398 },
399 });
400 _ = T;
401 _ = @typeInfo(T).Union;
402}
403
404test "Type.Union from regular enum" {
405 const E = enum { working_as_expected = 0 };
406 const T = @Type(.{
407 .Union = .{
408 .layout = .Auto,
409 .tag_type = E,
410 .fields = &[_]TypeInfo.UnionField{
411 .{ .name = "working_as_expected", .field_type = u32 },
412 },
413 .decls = &[_]TypeInfo.Declaration{},
414 },
415 });
416 _ = T;
417 _ = @typeInfo(T).Union;
418}