mirror of
https://github.com/ziglang/zig.git
synced 2026-02-13 12:59:04 +00:00
Merge pull request #23272 from squeek502/getenvw-optim
Windows: Faster `getenvW` and a standalone environment variable test
This commit is contained in:
commit
08a6c4ca9b
@ -4890,6 +4890,10 @@ pub const RTL_USER_PROCESS_PARAMETERS = extern struct {
|
||||
DllPath: UNICODE_STRING,
|
||||
ImagePathName: UNICODE_STRING,
|
||||
CommandLine: UNICODE_STRING,
|
||||
/// Points to a NUL-terminated sequence of NUL-terminated
|
||||
/// WTF-16 LE encoded `name=value` sequences.
|
||||
/// Example using string literal syntax:
|
||||
/// `"NAME=value\x00foo=bar\x00\x00"`
|
||||
Environment: [*:0]WCHAR,
|
||||
dwX: ULONG,
|
||||
dwY: ULONG,
|
||||
|
||||
@ -419,10 +419,10 @@ pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError
|
||||
}
|
||||
}
|
||||
|
||||
/// On Windows, `key` must be valid UTF-8.
|
||||
/// On Windows, `key` must be valid WTF-8.
|
||||
pub fn hasEnvVarConstant(comptime key: []const u8) bool {
|
||||
if (native_os == .windows) {
|
||||
const key_w = comptime unicode.utf8ToUtf16LeStringLiteral(key);
|
||||
const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key);
|
||||
return getenvW(key_w) != null;
|
||||
} else if (native_os == .wasi and !builtin.link_libc) {
|
||||
@compileError("hasEnvVarConstant is not supported for WASI without libc");
|
||||
@ -431,10 +431,10 @@ pub fn hasEnvVarConstant(comptime key: []const u8) bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// On Windows, `key` must be valid UTF-8.
|
||||
/// On Windows, `key` must be valid WTF-8.
|
||||
pub fn hasNonEmptyEnvVarConstant(comptime key: []const u8) bool {
|
||||
if (native_os == .windows) {
|
||||
const key_w = comptime unicode.utf8ToUtf16LeStringLiteral(key);
|
||||
const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key);
|
||||
const value = getenvW(key_w) orelse return false;
|
||||
return value.len != 0;
|
||||
} else if (native_os == .wasi and !builtin.link_libc) {
|
||||
@ -451,10 +451,10 @@ pub const ParseEnvVarIntError = std.fmt.ParseIntError || error{EnvironmentVariab
|
||||
///
|
||||
/// Since the key is comptime-known, no allocation is needed.
|
||||
///
|
||||
/// On Windows, `key` must be valid UTF-8.
|
||||
/// On Windows, `key` must be valid WTF-8.
|
||||
pub fn parseEnvVarInt(comptime key: []const u8, comptime I: type, base: u8) ParseEnvVarIntError!I {
|
||||
if (native_os == .windows) {
|
||||
const key_w = comptime std.unicode.utf8ToUtf16LeStringLiteral(key);
|
||||
const key_w = comptime std.unicode.wtf8ToWtf16LeStringLiteral(key);
|
||||
const text = getenvW(key_w) orelse return error.EnvironmentVariableNotFound;
|
||||
return std.fmt.parseIntWithGenericCharacter(I, u16, text, base);
|
||||
} else if (native_os == .wasi and !builtin.link_libc) {
|
||||
@ -527,31 +527,33 @@ pub fn getenvW(key: [*:0]const u16) ?[:0]const u16 {
|
||||
@compileError("Windows-only");
|
||||
}
|
||||
const key_slice = mem.sliceTo(key, 0);
|
||||
// '=' anywhere but the start makes this an invalid environment variable name
|
||||
if (key_slice.len > 0 and std.mem.indexOfScalar(u16, key_slice[1..], '=') != null) {
|
||||
return null;
|
||||
}
|
||||
const ptr = windows.peb().ProcessParameters.Environment;
|
||||
var i: usize = 0;
|
||||
while (ptr[i] != 0) {
|
||||
const key_start = i;
|
||||
const key_value = mem.sliceTo(ptr[i..], 0);
|
||||
|
||||
// There are some special environment variables that start with =,
|
||||
// so we need a special case to not treat = as a key/value separator
|
||||
// if it's the first character.
|
||||
// https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
|
||||
if (ptr[key_start] == '=') i += 1;
|
||||
|
||||
while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
|
||||
const this_key = ptr[key_start..i];
|
||||
|
||||
if (ptr[i] == '=') i += 1;
|
||||
|
||||
const value_start = i;
|
||||
while (ptr[i] != 0) : (i += 1) {}
|
||||
const this_value = ptr[value_start..i :0];
|
||||
const equal_search_start: usize = if (key_value[0] == '=') 1 else 0;
|
||||
const equal_index = std.mem.indexOfScalarPos(u16, key_value, equal_search_start, '=') orelse {
|
||||
// This is enforced by CreateProcess.
|
||||
// If violated, CreateProcess will fail with INVALID_PARAMETER.
|
||||
unreachable; // must contain a =
|
||||
};
|
||||
|
||||
const this_key = key_value[0..equal_index];
|
||||
if (windows.eqlIgnoreCaseWTF16(key_slice, this_key)) {
|
||||
return this_value;
|
||||
return key_value[equal_index + 1 ..];
|
||||
}
|
||||
|
||||
i += 1; // skip over null byte
|
||||
// skip past the NUL terminator
|
||||
i += key_value.len + 1;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -2037,7 +2039,8 @@ test createNullDelimitedEnvMap {
|
||||
pub fn createWindowsEnvBlock(allocator: mem.Allocator, env_map: *const EnvMap) ![]u16 {
|
||||
// count bytes needed
|
||||
const max_chars_needed = x: {
|
||||
var max_chars_needed: usize = 4; // 4 for the final 4 null bytes
|
||||
// Only need 2 trailing NUL code units for an empty environment
|
||||
var max_chars_needed: usize = if (env_map.count() == 0) 2 else 1;
|
||||
var it = env_map.iterator();
|
||||
while (it.next()) |pair| {
|
||||
// +1 for '='
|
||||
@ -2061,12 +2064,14 @@ pub fn createWindowsEnvBlock(allocator: mem.Allocator, env_map: *const EnvMap) !
|
||||
}
|
||||
result[i] = 0;
|
||||
i += 1;
|
||||
result[i] = 0;
|
||||
i += 1;
|
||||
result[i] = 0;
|
||||
i += 1;
|
||||
result[i] = 0;
|
||||
i += 1;
|
||||
// An empty environment is a special case that requires a redundant
|
||||
// NUL terminator. CreateProcess will read the second code unit even
|
||||
// though theoretically the first should be enough to recognize that the
|
||||
// environment is empty (see https://nullprogram.com/blog/2023/08/23/)
|
||||
if (env_map.count() == 0) {
|
||||
result[i] = 0;
|
||||
i += 1;
|
||||
}
|
||||
return try allocator.realloc(result, i);
|
||||
}
|
||||
|
||||
|
||||
@ -96,6 +96,9 @@
|
||||
.empty_env = .{
|
||||
.path = "empty_env",
|
||||
},
|
||||
.env_vars = .{
|
||||
.path = "env_vars",
|
||||
},
|
||||
.issue_11595 = .{
|
||||
.path = "issue_11595",
|
||||
},
|
||||
|
||||
33
test/standalone/env_vars/build.zig
Normal file
33
test/standalone/env_vars/build.zig
Normal file
@ -0,0 +1,33 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
const test_step = b.step("test", "Test it");
|
||||
b.default_step = test_step;
|
||||
|
||||
const optimize: std.builtin.OptimizeMode = .Debug;
|
||||
|
||||
const main = b.addExecutable(.{
|
||||
.name = "main",
|
||||
.root_module = b.createModule(.{
|
||||
.root_source_file = b.path("main.zig"),
|
||||
.target = b.graph.host,
|
||||
.optimize = optimize,
|
||||
}),
|
||||
});
|
||||
|
||||
const run = b.addRunArtifact(main);
|
||||
run.clearEnvironment();
|
||||
run.setEnvironmentVariable("FOO", "123");
|
||||
run.setEnvironmentVariable("EQUALS", "ABC=123");
|
||||
run.setEnvironmentVariable("NO_VALUE", "");
|
||||
run.setEnvironmentVariable("КИРиллИЦА", "non-ascii አማርኛ \u{10FFFF}");
|
||||
if (b.graph.host.result.os.tag == .windows) {
|
||||
run.setEnvironmentVariable("=Hidden", "hi");
|
||||
// \xed\xa0\x80 is a WTF-8 encoded unpaired surrogate code point
|
||||
run.setEnvironmentVariable("INVALID_UTF16_\xed\xa0\x80", "\xed\xa0\x80");
|
||||
}
|
||||
run.disable_zig_progress = true;
|
||||
|
||||
test_step.dependOn(&run.step);
|
||||
}
|
||||
173
test/standalone/env_vars/main.zig
Normal file
173
test/standalone/env_vars/main.zig
Normal file
@ -0,0 +1,173 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
// Note: the environment variables under test are set by the build.zig
|
||||
pub fn main() !void {
|
||||
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
|
||||
defer _ = gpa.deinit();
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var arena_state = std.heap.ArenaAllocator.init(allocator);
|
||||
defer arena_state.deinit();
|
||||
const arena = arena_state.allocator();
|
||||
|
||||
// hasNonEmptyEnvVar
|
||||
{
|
||||
try std.testing.expect(try std.process.hasNonEmptyEnvVar(allocator, "FOO"));
|
||||
try std.testing.expect(!(try std.process.hasNonEmptyEnvVar(allocator, "FOO=")));
|
||||
try std.testing.expect(!(try std.process.hasNonEmptyEnvVar(allocator, "FO")));
|
||||
try std.testing.expect(!(try std.process.hasNonEmptyEnvVar(allocator, "FOOO")));
|
||||
if (builtin.os.tag == .windows) {
|
||||
try std.testing.expect(try std.process.hasNonEmptyEnvVar(allocator, "foo"));
|
||||
}
|
||||
try std.testing.expect(try std.process.hasNonEmptyEnvVar(allocator, "EQUALS"));
|
||||
try std.testing.expect(!(try std.process.hasNonEmptyEnvVar(allocator, "EQUALS=ABC")));
|
||||
try std.testing.expect(try std.process.hasNonEmptyEnvVar(allocator, "КИРиллИЦА"));
|
||||
if (builtin.os.tag == .windows) {
|
||||
try std.testing.expect(try std.process.hasNonEmptyEnvVar(allocator, "кирИЛЛица"));
|
||||
}
|
||||
try std.testing.expect(!(try std.process.hasNonEmptyEnvVar(allocator, "NO_VALUE")));
|
||||
try std.testing.expect(!(try std.process.hasNonEmptyEnvVar(allocator, "NOT_SET")));
|
||||
if (builtin.os.tag == .windows) {
|
||||
try std.testing.expect(try std.process.hasNonEmptyEnvVar(allocator, "=HIDDEN"));
|
||||
try std.testing.expect(try std.process.hasNonEmptyEnvVar(allocator, "INVALID_UTF16_\xed\xa0\x80"));
|
||||
}
|
||||
}
|
||||
|
||||
// hasNonEmptyEnvVarContstant
|
||||
{
|
||||
try std.testing.expect(std.process.hasNonEmptyEnvVarConstant("FOO"));
|
||||
try std.testing.expect(!std.process.hasNonEmptyEnvVarConstant("FOO="));
|
||||
try std.testing.expect(!std.process.hasNonEmptyEnvVarConstant("FO"));
|
||||
try std.testing.expect(!std.process.hasNonEmptyEnvVarConstant("FOOO"));
|
||||
if (builtin.os.tag == .windows) {
|
||||
try std.testing.expect(std.process.hasNonEmptyEnvVarConstant("foo"));
|
||||
}
|
||||
try std.testing.expect(std.process.hasNonEmptyEnvVarConstant("EQUALS"));
|
||||
try std.testing.expect(!std.process.hasNonEmptyEnvVarConstant("EQUALS=ABC"));
|
||||
try std.testing.expect(std.process.hasNonEmptyEnvVarConstant("КИРиллИЦА"));
|
||||
if (builtin.os.tag == .windows) {
|
||||
try std.testing.expect(std.process.hasNonEmptyEnvVarConstant("кирИЛЛица"));
|
||||
}
|
||||
try std.testing.expect(!(std.process.hasNonEmptyEnvVarConstant("NO_VALUE")));
|
||||
try std.testing.expect(!(std.process.hasNonEmptyEnvVarConstant("NOT_SET")));
|
||||
if (builtin.os.tag == .windows) {
|
||||
try std.testing.expect(std.process.hasNonEmptyEnvVarConstant("=HIDDEN"));
|
||||
try std.testing.expect(std.process.hasNonEmptyEnvVarConstant("INVALID_UTF16_\xed\xa0\x80"));
|
||||
}
|
||||
}
|
||||
|
||||
// hasEnvVar
|
||||
{
|
||||
try std.testing.expect(try std.process.hasEnvVar(allocator, "FOO"));
|
||||
try std.testing.expect(!(try std.process.hasEnvVar(allocator, "FOO=")));
|
||||
try std.testing.expect(!(try std.process.hasEnvVar(allocator, "FO")));
|
||||
try std.testing.expect(!(try std.process.hasEnvVar(allocator, "FOOO")));
|
||||
if (builtin.os.tag == .windows) {
|
||||
try std.testing.expect(try std.process.hasEnvVar(allocator, "foo"));
|
||||
}
|
||||
try std.testing.expect(try std.process.hasEnvVar(allocator, "EQUALS"));
|
||||
try std.testing.expect(!(try std.process.hasEnvVar(allocator, "EQUALS=ABC")));
|
||||
try std.testing.expect(try std.process.hasEnvVar(allocator, "КИРиллИЦА"));
|
||||
if (builtin.os.tag == .windows) {
|
||||
try std.testing.expect(try std.process.hasEnvVar(allocator, "кирИЛЛица"));
|
||||
}
|
||||
try std.testing.expect(try std.process.hasEnvVar(allocator, "NO_VALUE"));
|
||||
try std.testing.expect(!(try std.process.hasEnvVar(allocator, "NOT_SET")));
|
||||
if (builtin.os.tag == .windows) {
|
||||
try std.testing.expect(try std.process.hasEnvVar(allocator, "=HIDDEN"));
|
||||
try std.testing.expect(try std.process.hasEnvVar(allocator, "INVALID_UTF16_\xed\xa0\x80"));
|
||||
}
|
||||
}
|
||||
|
||||
// hasEnvVarConstant
|
||||
{
|
||||
try std.testing.expect(std.process.hasEnvVarConstant("FOO"));
|
||||
try std.testing.expect(!std.process.hasEnvVarConstant("FOO="));
|
||||
try std.testing.expect(!std.process.hasEnvVarConstant("FO"));
|
||||
try std.testing.expect(!std.process.hasEnvVarConstant("FOOO"));
|
||||
if (builtin.os.tag == .windows) {
|
||||
try std.testing.expect(std.process.hasEnvVarConstant("foo"));
|
||||
}
|
||||
try std.testing.expect(std.process.hasEnvVarConstant("EQUALS"));
|
||||
try std.testing.expect(!std.process.hasEnvVarConstant("EQUALS=ABC"));
|
||||
try std.testing.expect(std.process.hasEnvVarConstant("КИРиллИЦА"));
|
||||
if (builtin.os.tag == .windows) {
|
||||
try std.testing.expect(std.process.hasEnvVarConstant("кирИЛЛица"));
|
||||
}
|
||||
try std.testing.expect(std.process.hasEnvVarConstant("NO_VALUE"));
|
||||
try std.testing.expect(!(std.process.hasEnvVarConstant("NOT_SET")));
|
||||
if (builtin.os.tag == .windows) {
|
||||
try std.testing.expect(std.process.hasEnvVarConstant("=HIDDEN"));
|
||||
try std.testing.expect(std.process.hasEnvVarConstant("INVALID_UTF16_\xed\xa0\x80"));
|
||||
}
|
||||
}
|
||||
|
||||
// getEnvVarOwned
|
||||
{
|
||||
try std.testing.expectEqualSlices(u8, "123", try std.process.getEnvVarOwned(arena, "FOO"));
|
||||
try std.testing.expectError(error.EnvironmentVariableNotFound, std.process.getEnvVarOwned(arena, "FOO="));
|
||||
try std.testing.expectError(error.EnvironmentVariableNotFound, std.process.getEnvVarOwned(arena, "FO"));
|
||||
try std.testing.expectError(error.EnvironmentVariableNotFound, std.process.getEnvVarOwned(arena, "FOOO"));
|
||||
if (builtin.os.tag == .windows) {
|
||||
try std.testing.expectEqualSlices(u8, "123", try std.process.getEnvVarOwned(arena, "foo"));
|
||||
}
|
||||
try std.testing.expectEqualSlices(u8, "ABC=123", try std.process.getEnvVarOwned(arena, "EQUALS"));
|
||||
try std.testing.expectError(error.EnvironmentVariableNotFound, std.process.getEnvVarOwned(arena, "EQUALS=ABC"));
|
||||
try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try std.process.getEnvVarOwned(arena, "КИРиллИЦА"));
|
||||
if (builtin.os.tag == .windows) {
|
||||
try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try std.process.getEnvVarOwned(arena, "кирИЛЛица"));
|
||||
}
|
||||
try std.testing.expectEqualSlices(u8, "", try std.process.getEnvVarOwned(arena, "NO_VALUE"));
|
||||
try std.testing.expectError(error.EnvironmentVariableNotFound, std.process.getEnvVarOwned(arena, "NOT_SET"));
|
||||
if (builtin.os.tag == .windows) {
|
||||
try std.testing.expectEqualSlices(u8, "hi", try std.process.getEnvVarOwned(arena, "=HIDDEN"));
|
||||
try std.testing.expectEqualSlices(u8, "\xed\xa0\x80", try std.process.getEnvVarOwned(arena, "INVALID_UTF16_\xed\xa0\x80"));
|
||||
}
|
||||
}
|
||||
|
||||
// parseEnvVarInt
|
||||
{
|
||||
try std.testing.expectEqual(123, try std.process.parseEnvVarInt("FOO", u32, 10));
|
||||
try std.testing.expectError(error.EnvironmentVariableNotFound, std.process.parseEnvVarInt("FO", u32, 10));
|
||||
try std.testing.expectError(error.EnvironmentVariableNotFound, std.process.parseEnvVarInt("FOOO", u32, 10));
|
||||
try std.testing.expectEqual(0x123, try std.process.parseEnvVarInt("FOO", u32, 16));
|
||||
if (builtin.os.tag == .windows) {
|
||||
try std.testing.expectEqual(123, try std.process.parseEnvVarInt("foo", u32, 10));
|
||||
}
|
||||
try std.testing.expectError(error.InvalidCharacter, std.process.parseEnvVarInt("EQUALS", u32, 10));
|
||||
try std.testing.expectError(error.EnvironmentVariableNotFound, std.process.parseEnvVarInt("EQUALS=ABC", u32, 10));
|
||||
try std.testing.expectError(error.InvalidCharacter, std.process.parseEnvVarInt("КИРиллИЦА", u32, 10));
|
||||
try std.testing.expectError(error.InvalidCharacter, std.process.parseEnvVarInt("NO_VALUE", u32, 10));
|
||||
try std.testing.expectError(error.EnvironmentVariableNotFound, std.process.parseEnvVarInt("NOT_SET", u32, 10));
|
||||
if (builtin.os.tag == .windows) {
|
||||
try std.testing.expectError(error.InvalidCharacter, std.process.parseEnvVarInt("=HIDDEN", u32, 10));
|
||||
try std.testing.expectError(error.InvalidCharacter, std.process.parseEnvVarInt("INVALID_UTF16_\xed\xa0\x80", u32, 10));
|
||||
}
|
||||
}
|
||||
|
||||
// EnvMap
|
||||
{
|
||||
var env_map = try std.process.getEnvMap(allocator);
|
||||
defer env_map.deinit();
|
||||
|
||||
try std.testing.expectEqualSlices(u8, "123", env_map.get("FOO").?);
|
||||
try std.testing.expectEqual(null, env_map.get("FO"));
|
||||
try std.testing.expectEqual(null, env_map.get("FOOO"));
|
||||
if (builtin.os.tag == .windows) {
|
||||
try std.testing.expectEqualSlices(u8, "123", env_map.get("foo").?);
|
||||
}
|
||||
try std.testing.expectEqualSlices(u8, "ABC=123", env_map.get("EQUALS").?);
|
||||
try std.testing.expectEqual(null, env_map.get("EQUALS=ABC"));
|
||||
try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", env_map.get("КИРиллИЦА").?);
|
||||
if (builtin.os.tag == .windows) {
|
||||
try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", env_map.get("кирИЛЛица").?);
|
||||
}
|
||||
try std.testing.expectEqualSlices(u8, "", env_map.get("NO_VALUE").?);
|
||||
try std.testing.expectEqual(null, env_map.get("NOT_SET"));
|
||||
if (builtin.os.tag == .windows) {
|
||||
try std.testing.expectEqualSlices(u8, "hi", env_map.get("=HIDDEN").?);
|
||||
try std.testing.expectEqualSlices(u8, "\xed\xa0\x80", env_map.get("INVALID_UTF16_\xed\xa0\x80").?);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user