Commit 94b9bcd034

Evan Haas <evan@lagerdata.com>
2022-05-06 19:14:31
stdlib: escape backslashes and double quotes in Builder response file
Fixes #11595
1 parent d7f8368
Changed files (5)
lib
test
lib/std/build.zig
@@ -3009,6 +3009,7 @@ pub const LibExeObjStep = struct {
         // Windows has an argument length limit of 32,766 characters, macOS 262,144 and Linux
         // 2,097,152. If our args exceed 30 KiB, we instead write them to a "response file" and
         // pass that to zig, e.g. via 'zig build-lib @args.rsp'
+        // See @file syntax here: https://gcc.gnu.org/onlinedocs/gcc/Overall-Options.html
         var args_length: usize = 0;
         for (zig_args.items) |arg| {
             args_length += arg.len + 1; // +1 to account for null terminator
@@ -3020,9 +3021,33 @@ pub const LibExeObjStep = struct {
             );
             try std.fs.cwd().makePath(args_dir);
 
+            var args_arena = std.heap.ArenaAllocator.init(builder.allocator);
+            defer args_arena.deinit();
+
+            const args_to_escape = zig_args.items[2..];
+            var escaped_args = try ArrayList([]const u8).initCapacity(args_arena.allocator(), args_to_escape.len);
+
+            arg_blk: for (args_to_escape) |arg| {
+                for (arg) |c, arg_idx| {
+                    if (c == '\\' or c == '"') {
+                        // Slow path for arguments that need to be escaped. We'll need to allocate and copy
+                        var escaped = try ArrayList(u8).initCapacity(args_arena.allocator(), arg.len + 1);
+                        const writer = escaped.writer();
+                        writer.writeAll(arg[0..arg_idx]) catch unreachable;
+                        for (arg[arg_idx..]) |to_escape| {
+                            if (to_escape == '\\' or to_escape == '"') try writer.writeByte('\\');
+                            try writer.writeByte(to_escape);
+                        }
+                        escaped_args.appendAssumeCapacity(escaped.items);
+                        continue :arg_blk;
+                    }
+                }
+                escaped_args.appendAssumeCapacity(arg); // no escaping needed so just use original argument
+            }
+
             // Write the args to zig-cache/args/<SHA256 hash of args> to avoid conflicts with
             // other zig build commands running in parallel.
-            const partially_quoted = try std.mem.join(builder.allocator, "\" \"", zig_args.items[2..]);
+            const partially_quoted = try std.mem.join(builder.allocator, "\" \"", escaped_args.items);
             const args = try std.mem.concat(builder.allocator, u8, &[_][]const u8{ "\"", partially_quoted, "\"" });
 
             var args_hash: [Sha256.digest_length]u8 = undefined;
test/standalone/issue_11595/build.zig
@@ -0,0 +1,52 @@
+const std = @import("std");
+const builtin = @import("builtin");
+const Builder = std.build.Builder;
+const CrossTarget = std.zig.CrossTarget;
+
+// TODO integrate this with the std.build executor API
+fn isRunnableTarget(t: CrossTarget) bool {
+    if (t.isNative()) return true;
+
+    return (t.getOsTag() == builtin.os.tag and
+        t.getCpuArch() == builtin.cpu.arch);
+}
+
+pub fn build(b: *Builder) void {
+    const mode = b.standardReleaseOptions();
+    const target = b.standardTargetOptions(.{});
+
+    const exe = b.addExecutable("zigtest", "main.zig");
+    exe.setBuildMode(mode);
+    exe.install();
+
+    const c_sources = [_][]const u8{
+        "test.c",
+    };
+
+    exe.addCSourceFiles(&c_sources, &.{});
+    exe.linkLibC();
+
+    var i: i32 = 0;
+    while (i < 1000) : (i += 1) {
+        exe.defineCMacro("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
+    }
+
+    exe.defineCMacro("FOO", "42");
+    exe.defineCMacro("BAR", "\"BAR\"");
+    exe.defineCMacro("BAZ",
+        \\"\"BAZ\""
+    );
+    exe.defineCMacro("QUX", "\"Q\" \"UX\"");
+    exe.defineCMacro("QUUX", "\"QU\\\"UX\"");
+
+    exe.setTarget(target);
+    b.default_step.dependOn(&exe.step);
+
+    const test_step = b.step("test", "Test the program");
+    if (isRunnableTarget(target)) {
+        const run_cmd = exe.run();
+        test_step.dependOn(&run_cmd.step);
+    } else {
+        test_step.dependOn(&exe.step);
+    }
+}
test/standalone/issue_11595/main.zig
@@ -0,0 +1,5 @@
+extern fn check() c_int;
+
+pub fn main() u8 {
+    return @intCast(u8, check());
+}
test/standalone/issue_11595/test.c
@@ -0,0 +1,10 @@
+ #include <string.h>
+
+int check(void) {
+    if (FOO != 42) return 1;
+    if (strcmp(BAR, "BAR")) return 2;
+    if (strcmp(BAZ, "\"BAZ\"")) return 3;
+    if (strcmp(QUX, "QUX")) return 4;
+    if (strcmp(QUUX, "QU\"UX")) return 5;
+    return 0;
+}
test/standalone.zig
@@ -49,6 +49,7 @@ pub fn addCases(cases: *tests.StandaloneContext) void {
     cases.addBuildFile("test/standalone/issue_7030/build.zig", .{});
     cases.addBuildFile("test/standalone/install_raw_hex/build.zig", .{});
     cases.addBuildFile("test/standalone/issue_9812/build.zig", .{});
+    cases.addBuildFile("test/standalone/issue_11595/build.zig", .{});
     if (builtin.os.tag != .wasi) {
         cases.addBuildFile("test/standalone/load_dynamic_library/build.zig", .{});
     }