diff options
| author | Alex Rønne Petersen <alex@alexrp.com> | 2025-04-16 02:44:55 +0200 |
|---|---|---|
| committer | Alex Rønne Petersen <alex@alexrp.com> | 2025-04-26 22:54:34 +0200 |
| commit | b3537d0f4adeff824348a4918b495976ae230731 (patch) | |
| tree | 4635a2febd416352452e5b909afad138db43b601 /src/main.zig | |
| parent | 23440fbb99a501fac9cfb6af85c6303732cf0b06 (diff) | |
| download | zig-b3537d0f4adeff824348a4918b495976ae230731.tar.gz zig-b3537d0f4adeff824348a4918b495976ae230731.zip | |
compiler: Allow configuring UBSan mode at the module level.
* Accept -fsanitize-c=trap|full in addition to the existing form.
* Accept -f(no-)sanitize-trap=undefined in zig cc.
* Change type of std.Build.Module.sanitize_c to std.zig.SanitizeC.
* Add some missing Compilation.Config fields to the cache.
Closes #23216.
Diffstat (limited to 'src/main.zig')
| -rw-r--r-- | src/main.zig | 80 |
1 files changed, 75 insertions, 5 deletions
diff --git a/src/main.zig b/src/main.zig index 8899354cd7..f05208324f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -526,7 +526,9 @@ const usage_build_generic = \\ -fno-stack-protector Disable stack protection in safe builds \\ -fvalgrind Include valgrind client requests in release builds \\ -fno-valgrind Omit valgrind client requests in debug builds - \\ -fsanitize-c Enable C undefined behavior detection in unsafe builds + \\ -fsanitize-c[=mode] Enable C undefined behavior detection in unsafe builds + \\ trap Insert trap instructions on undefined behavior + \\ full (Default) Insert runtime calls on undefined behavior \\ -fno-sanitize-c Disable C undefined behavior detection in safe builds \\ -fsanitize-thread Enable Thread Sanitizer \\ -fno-sanitize-thread Disable Thread Sanitizer @@ -1464,9 +1466,18 @@ fn buildOutputType( } else if (mem.eql(u8, arg, "-fno-omit-frame-pointer")) { mod_opts.omit_frame_pointer = false; } else if (mem.eql(u8, arg, "-fsanitize-c")) { - mod_opts.sanitize_c = true; + mod_opts.sanitize_c = .full; + } else if (mem.startsWith(u8, arg, "-fsanitize-c=")) { + const mode = arg["-fsanitize-c=".len..]; + if (mem.eql(u8, mode, "trap")) { + mod_opts.sanitize_c = .trap; + } else if (mem.eql(u8, mode, "full")) { + mod_opts.sanitize_c = .full; + } else { + fatal("Invalid -fsanitize-c mode: '{s}'. Must be 'trap' or 'full'.", .{mode}); + } } else if (mem.eql(u8, arg, "-fno-sanitize-c")) { - mod_opts.sanitize_c = false; + mod_opts.sanitize_c = .off; } else if (mem.eql(u8, arg, "-fvalgrind")) { mod_opts.valgrind = true; } else if (mem.eql(u8, arg, "-fno-valgrind")) { @@ -2236,7 +2247,7 @@ fn buildOutputType( var recognized_any = false; while (san_it.next()) |sub_arg| { if (mem.eql(u8, sub_arg, "undefined")) { - mod_opts.sanitize_c = enable; + mod_opts.sanitize_c = if (enable) .full else .off; recognized_any = true; } else if (mem.eql(u8, sub_arg, "thread")) { mod_opts.sanitize_thread = enable; @@ -2250,6 +2261,49 @@ fn buildOutputType( try cc_argv.appendSlice(arena, it.other_args); } }, + .sanitize_trap, .no_sanitize_trap => |t| { + const enable = t == .sanitize_trap; + var san_it = std.mem.splitScalar(u8, it.only_arg, ','); + var recognized_any = false; + while (san_it.next()) |sub_arg| { + // This logic doesn't match Clang 1:1, but it's probably good enough, and avoids + // significantly complicating the resolution of the options. + if (mem.eql(u8, sub_arg, "undefined")) { + if (mod_opts.sanitize_c) |sc| switch (sc) { + .off => if (enable) { + mod_opts.sanitize_c = .trap; + }, + .trap => if (!enable) { + mod_opts.sanitize_c = .full; + }, + .full => if (enable) { + mod_opts.sanitize_c = .trap; + }, + } else { + if (enable) { + mod_opts.sanitize_c = .trap; + } else { + // This means we were passed `-fno-sanitize-trap=undefined` and nothing else. In + // this case, ideally, we should use whatever value `sanitize_c` resolves to by + // default, except change `trap` to `full`. However, we don't yet know what + // `sanitize_c` will resolve to! So we either have to pick `off` or `full`. + // + // `full` has the potential to be problematic if `optimize_mode` turns out to + // be `ReleaseFast`/`ReleaseSmall` because the user will get a slower and larger + // binary than expected. On the other hand, if `optimize_mode` turns out to be + // `Debug`/`ReleaseSafe`, `off` would mean UBSan would unexpectedly be disabled. + // + // `off` seems very slightly less bad, so let's go with that. + mod_opts.sanitize_c = .off; + } + } + recognized_any = true; + } + } + if (!recognized_any) { + try cc_argv.appendSlice(arena, it.other_args); + } + }, .linker_script => linker_script = it.only_arg, .verbose => { verbose_link = true; @@ -2766,7 +2820,7 @@ fn buildOutputType( } if (mod_opts.sanitize_c) |wsc| { - if (wsc and mod_opts.optimize_mode == .ReleaseFast) { + if (wsc != .off and mod_opts.optimize_mode == .ReleaseFast) { mod_opts.optimize_mode = .ReleaseSafe; } } @@ -2915,6 +2969,13 @@ fn buildOutputType( create_module.opts.any_non_single_threaded = true; if (mod_opts.sanitize_thread == true) create_module.opts.any_sanitize_thread = true; + if (mod_opts.sanitize_c) |sc| switch (sc) { + .off => {}, + .trap => if (create_module.opts.any_sanitize_c == .off) { + create_module.opts.any_sanitize_c = .trap; + }, + .full => create_module.opts.any_sanitize_c = .full, + }; if (mod_opts.fuzz == true) create_module.opts.any_fuzz = true; if (mod_opts.unwind_tables) |uwt| switch (uwt) { @@ -5941,6 +6002,8 @@ pub const ClangArgIterator = struct { gdwarf64, sanitize, no_sanitize, + sanitize_trap, + no_sanitize_trap, linker_script, dry_run, verbose, @@ -7728,6 +7791,13 @@ fn handleModArg( create_module.opts.any_non_single_threaded = true; if (mod_opts.sanitize_thread == true) create_module.opts.any_sanitize_thread = true; + if (mod_opts.sanitize_c) |sc| switch (sc) { + .off => {}, + .trap => if (create_module.opts.any_sanitize_c == .off) { + create_module.opts.any_sanitize_c = .trap; + }, + .full => create_module.opts.any_sanitize_c = .full, + }; if (mod_opts.fuzz == true) create_module.opts.any_fuzz = true; if (mod_opts.unwind_tables) |uwt| switch (uwt) { |
