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.
This commit is contained in:
Alex Rønne Petersen 2025-04-16 02:44:55 +02:00
parent 23440fbb99
commit b3537d0f4a
15 changed files with 176 additions and 57 deletions

View File

@ -22,7 +22,7 @@ unwind_tables: ?std.builtin.UnwindTables,
single_threaded: ?bool, single_threaded: ?bool,
stack_protector: ?bool, stack_protector: ?bool,
stack_check: ?bool, stack_check: ?bool,
sanitize_c: ?bool, sanitize_c: ?std.zig.SanitizeC,
sanitize_thread: ?bool, sanitize_thread: ?bool,
fuzz: ?bool, fuzz: ?bool,
code_model: std.builtin.CodeModel, code_model: std.builtin.CodeModel,
@ -256,7 +256,7 @@ pub const CreateOptions = struct {
code_model: std.builtin.CodeModel = .default, code_model: std.builtin.CodeModel = .default,
stack_protector: ?bool = null, stack_protector: ?bool = null,
stack_check: ?bool = null, stack_check: ?bool = null,
sanitize_c: ?bool = null, sanitize_c: ?std.zig.SanitizeC = null,
sanitize_thread: ?bool = null, sanitize_thread: ?bool = null,
fuzz: ?bool = null, fuzz: ?bool = null,
/// Whether to emit machine code that integrates with Valgrind. /// Whether to emit machine code that integrates with Valgrind.
@ -559,13 +559,18 @@ pub fn appendZigProcessFlags(
try addFlag(zig_args, m.stack_protector, "-fstack-protector", "-fno-stack-protector"); try addFlag(zig_args, m.stack_protector, "-fstack-protector", "-fno-stack-protector");
try addFlag(zig_args, m.omit_frame_pointer, "-fomit-frame-pointer", "-fno-omit-frame-pointer"); try addFlag(zig_args, m.omit_frame_pointer, "-fomit-frame-pointer", "-fno-omit-frame-pointer");
try addFlag(zig_args, m.error_tracing, "-ferror-tracing", "-fno-error-tracing"); try addFlag(zig_args, m.error_tracing, "-ferror-tracing", "-fno-error-tracing");
try addFlag(zig_args, m.sanitize_c, "-fsanitize-c", "-fno-sanitize-c");
try addFlag(zig_args, m.sanitize_thread, "-fsanitize-thread", "-fno-sanitize-thread"); try addFlag(zig_args, m.sanitize_thread, "-fsanitize-thread", "-fno-sanitize-thread");
try addFlag(zig_args, m.fuzz, "-ffuzz", "-fno-fuzz"); try addFlag(zig_args, m.fuzz, "-ffuzz", "-fno-fuzz");
try addFlag(zig_args, m.valgrind, "-fvalgrind", "-fno-valgrind"); try addFlag(zig_args, m.valgrind, "-fvalgrind", "-fno-valgrind");
try addFlag(zig_args, m.pic, "-fPIC", "-fno-PIC"); try addFlag(zig_args, m.pic, "-fPIC", "-fno-PIC");
try addFlag(zig_args, m.red_zone, "-mred-zone", "-mno-red-zone"); try addFlag(zig_args, m.red_zone, "-mred-zone", "-mno-red-zone");
if (m.sanitize_c) |sc| switch (sc) {
.off => try zig_args.append("-fno-sanitize-c"),
.trap => try zig_args.append("-fsanitize-c=trap"),
.full => try zig_args.append("-fsanitize-c=full"),
};
if (m.dwarf_format) |dwarf_format| { if (m.dwarf_format) |dwarf_format| {
try zig_args.append(switch (dwarf_format) { try zig_args.append(switch (dwarf_format) {
.@"32" => "-gdwarf32", .@"32" => "-gdwarf32",

View File

@ -236,6 +236,12 @@ pub fn binNameAlloc(allocator: Allocator, options: BinNameOptions) error{OutOfMe
} }
} }
pub const SanitizeC = enum {
off,
trap,
full,
};
pub const BuildId = union(enum) { pub const BuildId = union(enum) {
none, none,
fast, fast,

View File

@ -1287,7 +1287,14 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
const any_unwind_tables = options.config.any_unwind_tables or options.root_mod.unwind_tables != .none; const any_unwind_tables = options.config.any_unwind_tables or options.root_mod.unwind_tables != .none;
const any_non_single_threaded = options.config.any_non_single_threaded or !options.root_mod.single_threaded; const any_non_single_threaded = options.config.any_non_single_threaded or !options.root_mod.single_threaded;
const any_sanitize_thread = options.config.any_sanitize_thread or options.root_mod.sanitize_thread; const any_sanitize_thread = options.config.any_sanitize_thread or options.root_mod.sanitize_thread;
const any_sanitize_c = options.config.any_sanitize_c or options.root_mod.sanitize_c; const any_sanitize_c: std.zig.SanitizeC = switch (options.config.any_sanitize_c) {
.off => options.root_mod.sanitize_c,
.trap => if (options.root_mod.sanitize_c == .full)
.full
else
.trap,
.full => .full,
};
const any_fuzz = options.config.any_fuzz or options.root_mod.fuzz; const any_fuzz = options.config.any_fuzz or options.root_mod.fuzz;
const link_eh_frame_hdr = options.link_eh_frame_hdr or any_unwind_tables; const link_eh_frame_hdr = options.link_eh_frame_hdr or any_unwind_tables;
@ -1346,7 +1353,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
// and this reduces unnecessary bloat. // and this reduces unnecessary bloat.
const ubsan_rt_strat: RtStrat = s: { const ubsan_rt_strat: RtStrat = s: {
const is_spirv = options.root_mod.resolved_target.result.cpu.arch.isSpirV(); const is_spirv = options.root_mod.resolved_target.result.cpu.arch.isSpirV();
const want_ubsan_rt = options.want_ubsan_rt orelse (!is_spirv and any_sanitize_c and is_exe_or_dyn_lib); const want_ubsan_rt = options.want_ubsan_rt orelse (!is_spirv and any_sanitize_c == .full and is_exe_or_dyn_lib);
if (!want_ubsan_rt) break :s .none; if (!want_ubsan_rt) break :s .none;
if (options.skip_linker_dependencies) break :s .none; if (options.skip_linker_dependencies) break :s .none;
if (have_zcu) break :s .zcu; if (have_zcu) break :s .zcu;
@ -1418,6 +1425,10 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
cache.hash.add(options.config.lto); cache.hash.add(options.config.lto);
cache.hash.add(options.config.link_mode); cache.hash.add(options.config.link_mode);
cache.hash.add(options.config.any_unwind_tables); cache.hash.add(options.config.any_unwind_tables);
cache.hash.add(options.config.any_non_single_threaded);
cache.hash.add(options.config.any_sanitize_thread);
cache.hash.add(options.config.any_sanitize_c);
cache.hash.add(options.config.any_fuzz);
cache.hash.add(options.function_sections); cache.hash.add(options.function_sections);
cache.hash.add(options.data_sections); cache.hash.add(options.data_sections);
cache.hash.add(link_libc); cache.hash.add(link_libc);
@ -6048,7 +6059,7 @@ pub fn addCCArgs(
{ {
var san_arg: std.ArrayListUnmanaged(u8) = .empty; var san_arg: std.ArrayListUnmanaged(u8) = .empty;
const prefix = "-fsanitize="; const prefix = "-fsanitize=";
if (mod.sanitize_c) { if (mod.sanitize_c != .off) {
if (san_arg.items.len == 0) try san_arg.appendSlice(arena, prefix); if (san_arg.items.len == 0) try san_arg.appendSlice(arena, prefix);
try san_arg.appendSlice(arena, "undefined,"); try san_arg.appendSlice(arena, "undefined,");
} }
@ -6064,9 +6075,12 @@ pub fn addCCArgs(
if (san_arg.pop()) |_| { if (san_arg.pop()) |_| {
try argv.append(san_arg.items); try argv.append(san_arg.items);
// These args have to be added after the `-fsanitize` arg or switch (mod.sanitize_c) {
// they won't take effect. .off => {},
if (mod.sanitize_c) { .trap => {
try argv.append("-fsanitize-trap=undefined");
},
.full => {
// This check requires implementing the Itanium C++ ABI. // This check requires implementing the Itanium C++ ABI.
// We would make it `-fsanitize-trap=vptr`, however this check requires // We would make it `-fsanitize-trap=vptr`, however this check requires
// a full runtime due to the type hashing involved. // a full runtime due to the type hashing involved.
@ -6081,20 +6095,13 @@ pub fn addCCArgs(
// function was called. // function was called.
try argv.append("-fno-sanitize=function"); try argv.append("-fno-sanitize=function");
if (mod.optimize_mode == .ReleaseSafe) {
// It's recommended to use the minimal runtime in production
// environments due to the security implications of the full runtime.
// The minimal runtime doesn't provide much benefit over simply
// trapping, however, so we do that instead.
try argv.append("-fsanitize-trap=undefined");
} else {
// This is necessary because, by default, Clang instructs LLVM to embed // This is necessary because, by default, Clang instructs LLVM to embed
// a COFF link dependency on `libclang_rt.ubsan_standalone.a` when the // a COFF link dependency on `libclang_rt.ubsan_standalone.a` when the
// UBSan runtime is used. // UBSan runtime is used.
if (target.os.tag == .windows) { if (target.os.tag == .windows) {
try argv.append("-fno-rtlib-defaultlib"); try argv.append("-fno-rtlib-defaultlib");
} }
} },
} }
} }
@ -6797,7 +6804,7 @@ pub fn build_crt_file(
.strip = comp.compilerRtStrip(), .strip = comp.compilerRtStrip(),
.stack_check = false, .stack_check = false,
.stack_protector = 0, .stack_protector = 0,
.sanitize_c = false, .sanitize_c = .off,
.sanitize_thread = false, .sanitize_thread = false,
.red_zone = comp.root_mod.red_zone, .red_zone = comp.root_mod.red_zone,
// Some libcs (e.g. musl) are opinionated about -fomit-frame-pointer. // Some libcs (e.g. musl) are opinionated about -fomit-frame-pointer.

View File

@ -32,7 +32,7 @@ any_non_single_threaded: bool,
/// per-Module setting. /// per-Module setting.
any_error_tracing: bool, any_error_tracing: bool,
any_sanitize_thread: bool, any_sanitize_thread: bool,
any_sanitize_c: bool, any_sanitize_c: std.zig.SanitizeC,
any_fuzz: bool, any_fuzz: bool,
pie: bool, pie: bool,
/// If this is true then linker code is responsible for making an LLVM IR /// If this is true then linker code is responsible for making an LLVM IR
@ -86,7 +86,7 @@ pub const Options = struct {
ensure_libcpp_on_non_freestanding: bool = false, ensure_libcpp_on_non_freestanding: bool = false,
any_non_single_threaded: bool = false, any_non_single_threaded: bool = false,
any_sanitize_thread: bool = false, any_sanitize_thread: bool = false,
any_sanitize_c: bool = false, any_sanitize_c: std.zig.SanitizeC = .off,
any_fuzz: bool = false, any_fuzz: bool = false,
any_unwind_tables: bool = false, any_unwind_tables: bool = false,
any_dyn_libs: bool = false, any_dyn_libs: bool = false,

View File

@ -24,7 +24,7 @@ omit_frame_pointer: bool,
stack_check: bool, stack_check: bool,
stack_protector: u32, stack_protector: u32,
red_zone: bool, red_zone: bool,
sanitize_c: bool, sanitize_c: std.zig.SanitizeC,
sanitize_thread: bool, sanitize_thread: bool,
fuzz: bool, fuzz: bool,
unwind_tables: std.builtin.UnwindTables, unwind_tables: std.builtin.UnwindTables,
@ -92,7 +92,7 @@ pub const CreateOptions = struct {
stack_protector: ?u32 = null, stack_protector: ?u32 = null,
red_zone: ?bool = null, red_zone: ?bool = null,
unwind_tables: ?std.builtin.UnwindTables = null, unwind_tables: ?std.builtin.UnwindTables = null,
sanitize_c: ?bool = null, sanitize_c: ?std.zig.SanitizeC = null,
sanitize_thread: ?bool = null, sanitize_thread: ?bool = null,
fuzz: ?bool = null, fuzz: ?bool = null,
structured_cfg: ?bool = null, structured_cfg: ?bool = null,
@ -113,6 +113,7 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Package.Module {
if (options.inherited.fuzz == true) assert(options.global.any_fuzz); if (options.inherited.fuzz == true) assert(options.global.any_fuzz);
if (options.inherited.single_threaded == false) assert(options.global.any_non_single_threaded); if (options.inherited.single_threaded == false) assert(options.global.any_non_single_threaded);
if (options.inherited.unwind_tables) |uwt| if (uwt != .none) assert(options.global.any_unwind_tables); if (options.inherited.unwind_tables) |uwt| if (uwt != .none) assert(options.global.any_unwind_tables);
if (options.inherited.sanitize_c) |sc| if (sc != .off) assert(options.global.any_sanitize_c != .off);
if (options.inherited.error_tracing == true) assert(options.global.any_error_tracing); if (options.inherited.error_tracing == true) assert(options.global.any_error_tracing);
const resolved_target = options.inherited.resolved_target orelse options.parent.?.resolved_target; const resolved_target = options.inherited.resolved_target orelse options.parent.?.resolved_target;
@ -249,10 +250,18 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Package.Module {
.ReleaseFast, .ReleaseSmall => false, .ReleaseFast, .ReleaseSmall => false,
}; };
const sanitize_c = b: { const sanitize_c: std.zig.SanitizeC = b: {
if (options.inherited.sanitize_c) |x| break :b x; if (options.inherited.sanitize_c) |x| break :b x;
if (options.parent) |p| break :b p.sanitize_c; if (options.parent) |p| break :b p.sanitize_c;
break :b is_safe_mode; break :b switch (optimize_mode) {
.Debug => .full,
// It's recommended to use the minimal runtime in production
// environments due to the security implications of the full runtime.
// The minimal runtime doesn't provide much benefit over simply
// trapping, however, so we do that instead.
.ReleaseSafe => .trap,
.ReleaseFast, .ReleaseSmall => .off,
};
}; };
const stack_check = b: { const stack_check = b: {

View File

@ -3682,7 +3682,14 @@ flagpd1("fno-sanitize-stats"),
flagpd1("fno-sanitize-thread-atomics"), flagpd1("fno-sanitize-thread-atomics"),
flagpd1("fno-sanitize-thread-func-entry-exit"), flagpd1("fno-sanitize-thread-func-entry-exit"),
flagpd1("fno-sanitize-thread-memory-access"), flagpd1("fno-sanitize-thread-memory-access"),
flagpd1("fno-sanitize-trap"), .{
.name = "fno-sanitize-trap",
.syntax = .flag,
.zig_equivalent = .no_sanitize_trap,
.pd1 = true,
.pd2 = false,
.psl = false,
},
flagpd1("fno-sanitize-undefined-trap-on-error"), flagpd1("fno-sanitize-undefined-trap-on-error"),
flagpd1("fno-save-main-program"), flagpd1("fno-save-main-program"),
flagpd1("fno-save-optimization-record"), flagpd1("fno-save-optimization-record"),
@ -4024,7 +4031,14 @@ flagpd1("fsanitize-stats"),
flagpd1("fsanitize-thread-atomics"), flagpd1("fsanitize-thread-atomics"),
flagpd1("fsanitize-thread-func-entry-exit"), flagpd1("fsanitize-thread-func-entry-exit"),
flagpd1("fsanitize-thread-memory-access"), flagpd1("fsanitize-thread-memory-access"),
flagpd1("fsanitize-trap"), .{
.name = "fsanitize-trap",
.syntax = .flag,
.zig_equivalent = .sanitize_trap,
.pd1 = true,
.pd2 = false,
.psl = false,
},
flagpd1("fsanitize-undefined-trap-on-error"), flagpd1("fsanitize-undefined-trap-on-error"),
flagpd1("fsave-main-program"), flagpd1("fsave-main-program"),
flagpd1("fsave-optimization-record"), flagpd1("fsave-optimization-record"),
@ -6592,7 +6606,7 @@ joinpd1("fmacro-prefix-map="),
.{ .{
.name = "fno-sanitize-trap=", .name = "fno-sanitize-trap=",
.syntax = .comma_joined, .syntax = .comma_joined,
.zig_equivalent = .other, .zig_equivalent = .no_sanitize_trap,
.pd1 = true, .pd1 = true,
.pd2 = false, .pd2 = false,
.psl = false, .psl = false,
@ -6864,7 +6878,7 @@ joinpd1("frecord-marker="),
.{ .{
.name = "fsanitize-trap=", .name = "fsanitize-trap=",
.syntax = .comma_joined, .syntax = .comma_joined,
.zig_equivalent = .other, .zig_equivalent = .sanitize_trap,
.pd1 = true, .pd1 = true,
.pd2 = false, .pd2 = false,
.psl = false, .psl = false,

View File

@ -1231,7 +1231,7 @@ fn buildSharedLib(
.strip = strip, .strip = strip,
.stack_check = false, .stack_check = false,
.stack_protector = 0, .stack_protector = 0,
.sanitize_c = false, .sanitize_c = .off,
.sanitize_thread = false, .sanitize_thread = false,
.red_zone = comp.root_mod.red_zone, .red_zone = comp.root_mod.red_zone,
.omit_frame_pointer = comp.root_mod.omit_frame_pointer, .omit_frame_pointer = comp.root_mod.omit_frame_pointer,

View File

@ -182,7 +182,7 @@ pub fn buildLibCxx(comp: *Compilation, prog_node: std.Progress.Node) BuildError!
.strip = strip, .strip = strip,
.stack_check = false, .stack_check = false,
.stack_protector = 0, .stack_protector = 0,
.sanitize_c = false, .sanitize_c = .off,
.sanitize_thread = comp.config.any_sanitize_thread, .sanitize_thread = comp.config.any_sanitize_thread,
.red_zone = comp.root_mod.red_zone, .red_zone = comp.root_mod.red_zone,
.omit_frame_pointer = comp.root_mod.omit_frame_pointer, .omit_frame_pointer = comp.root_mod.omit_frame_pointer,
@ -396,7 +396,7 @@ pub fn buildLibCxxAbi(comp: *Compilation, prog_node: std.Progress.Node) BuildErr
.strip = strip, .strip = strip,
.stack_check = false, .stack_check = false,
.stack_protector = 0, .stack_protector = 0,
.sanitize_c = false, .sanitize_c = .off,
.sanitize_thread = comp.config.any_sanitize_thread, .sanitize_thread = comp.config.any_sanitize_thread,
.red_zone = comp.root_mod.red_zone, .red_zone = comp.root_mod.red_zone,
.omit_frame_pointer = comp.root_mod.omit_frame_pointer, .omit_frame_pointer = comp.root_mod.omit_frame_pointer,

View File

@ -95,7 +95,7 @@ pub fn buildTsan(comp: *Compilation, prog_node: std.Progress.Node) BuildError!vo
.strip = strip, .strip = strip,
.stack_check = false, .stack_check = false,
.stack_protector = 0, .stack_protector = 0,
.sanitize_c = false, .sanitize_c = .off,
.sanitize_thread = false, .sanitize_thread = false,
.red_zone = comp.root_mod.red_zone, .red_zone = comp.root_mod.red_zone,
.omit_frame_pointer = optimize_mode != .Debug and !target.os.tag.isDarwin(), .omit_frame_pointer = optimize_mode != .Debug and !target.os.tag.isDarwin(),

View File

@ -64,7 +64,7 @@ pub fn buildStaticLib(comp: *Compilation, prog_node: std.Progress.Node) BuildErr
.red_zone = comp.root_mod.red_zone, .red_zone = comp.root_mod.red_zone,
.omit_frame_pointer = comp.root_mod.omit_frame_pointer, .omit_frame_pointer = comp.root_mod.omit_frame_pointer,
.valgrind = false, .valgrind = false,
.sanitize_c = false, .sanitize_c = .off,
.sanitize_thread = false, .sanitize_thread = false,
// necessary so that libunwind can unwind through its own stack frames // necessary so that libunwind can unwind through its own stack frames
// The old 32-bit x86 variant of SEH doesn't use tables. // The old 32-bit x86 variant of SEH doesn't use tables.

View File

@ -526,7 +526,9 @@ const usage_build_generic =
\\ -fno-stack-protector Disable stack protection in safe builds \\ -fno-stack-protector Disable stack protection in safe builds
\\ -fvalgrind Include valgrind client requests in release builds \\ -fvalgrind Include valgrind client requests in release builds
\\ -fno-valgrind Omit valgrind client requests in debug 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 \\ -fno-sanitize-c Disable C undefined behavior detection in safe builds
\\ -fsanitize-thread Enable Thread Sanitizer \\ -fsanitize-thread Enable Thread Sanitizer
\\ -fno-sanitize-thread Disable Thread Sanitizer \\ -fno-sanitize-thread Disable Thread Sanitizer
@ -1464,9 +1466,18 @@ fn buildOutputType(
} else if (mem.eql(u8, arg, "-fno-omit-frame-pointer")) { } else if (mem.eql(u8, arg, "-fno-omit-frame-pointer")) {
mod_opts.omit_frame_pointer = false; mod_opts.omit_frame_pointer = false;
} else if (mem.eql(u8, arg, "-fsanitize-c")) { } 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")) { } 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")) { } else if (mem.eql(u8, arg, "-fvalgrind")) {
mod_opts.valgrind = true; mod_opts.valgrind = true;
} else if (mem.eql(u8, arg, "-fno-valgrind")) { } else if (mem.eql(u8, arg, "-fno-valgrind")) {
@ -2236,7 +2247,7 @@ fn buildOutputType(
var recognized_any = false; var recognized_any = false;
while (san_it.next()) |sub_arg| { while (san_it.next()) |sub_arg| {
if (mem.eql(u8, sub_arg, "undefined")) { if (mem.eql(u8, sub_arg, "undefined")) {
mod_opts.sanitize_c = enable; mod_opts.sanitize_c = if (enable) .full else .off;
recognized_any = true; recognized_any = true;
} else if (mem.eql(u8, sub_arg, "thread")) { } else if (mem.eql(u8, sub_arg, "thread")) {
mod_opts.sanitize_thread = enable; mod_opts.sanitize_thread = enable;
@ -2250,6 +2261,49 @@ fn buildOutputType(
try cc_argv.appendSlice(arena, it.other_args); 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, .linker_script => linker_script = it.only_arg,
.verbose => { .verbose => {
verbose_link = true; verbose_link = true;
@ -2766,7 +2820,7 @@ fn buildOutputType(
} }
if (mod_opts.sanitize_c) |wsc| { 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; mod_opts.optimize_mode = .ReleaseSafe;
} }
} }
@ -2915,6 +2969,13 @@ fn buildOutputType(
create_module.opts.any_non_single_threaded = true; create_module.opts.any_non_single_threaded = true;
if (mod_opts.sanitize_thread == true) if (mod_opts.sanitize_thread == true)
create_module.opts.any_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) if (mod_opts.fuzz == true)
create_module.opts.any_fuzz = true; create_module.opts.any_fuzz = true;
if (mod_opts.unwind_tables) |uwt| switch (uwt) { if (mod_opts.unwind_tables) |uwt| switch (uwt) {
@ -5941,6 +6002,8 @@ pub const ClangArgIterator = struct {
gdwarf64, gdwarf64,
sanitize, sanitize,
no_sanitize, no_sanitize,
sanitize_trap,
no_sanitize_trap,
linker_script, linker_script,
dry_run, dry_run,
verbose, verbose,
@ -7728,6 +7791,13 @@ fn handleModArg(
create_module.opts.any_non_single_threaded = true; create_module.opts.any_non_single_threaded = true;
if (mod_opts.sanitize_thread == true) if (mod_opts.sanitize_thread == true)
create_module.opts.any_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) if (mod_opts.fuzz == true)
create_module.opts.any_fuzz = true; create_module.opts.any_fuzz = true;
if (mod_opts.unwind_tables) |uwt| switch (uwt) { if (mod_opts.unwind_tables) |uwt| switch (uwt) {

View File

@ -231,7 +231,7 @@ pub fn buildCrtFile(comp: *Compilation, in_crt_file: CrtFile, prog_node: std.Pro
.strip = strip, .strip = strip,
.stack_check = false, .stack_check = false,
.stack_protector = 0, .stack_protector = 0,
.sanitize_c = false, .sanitize_c = .off,
.sanitize_thread = false, .sanitize_thread = false,
.red_zone = comp.root_mod.red_zone, .red_zone = comp.root_mod.red_zone,
.omit_frame_pointer = comp.root_mod.omit_frame_pointer, .omit_frame_pointer = comp.root_mod.omit_frame_pointer,

View File

@ -2052,7 +2052,7 @@ fn testLargeBss(b: *Build, opts: Options) *Step {
exe.linkLibC(); exe.linkLibC();
// Disabled to work around the ELF linker crashing. // Disabled to work around the ELF linker crashing.
// Can be reproduced on a x86_64-linux host by commenting out the line below. // Can be reproduced on a x86_64-linux host by commenting out the line below.
exe.root_module.sanitize_c = false; exe.root_module.sanitize_c = .off;
const run = addRunArtifact(exe); const run = addRunArtifact(exe);
run.expectExitCode(0); run.expectExitCode(0);
@ -3558,7 +3558,7 @@ fn testTlsLargeTbss(b: *Build, opts: Options) *Step {
exe.linkLibC(); exe.linkLibC();
// Disabled to work around the ELF linker crashing. // Disabled to work around the ELF linker crashing.
// Can be reproduced on a x86_64-linux host by commenting out the line below. // Can be reproduced on a x86_64-linux host by commenting out the line below.
exe.root_module.sanitize_c = false; exe.root_module.sanitize_c = .off;
const run = addRunArtifact(exe); const run = addRunArtifact(exe);
run.expectStdOutEqual("3 0 5 0 0 0\n"); run.expectStdOutEqual("3 0 5 0 0 0\n");

View File

@ -25,7 +25,7 @@ pub fn build(b: *std.Build) void {
// We disable UBSAN for these tests as the libc being tested here is // We disable UBSAN for these tests as the libc being tested here is
// so old, it doesn't even support compiling our UBSAN implementation. // so old, it doesn't even support compiling our UBSAN implementation.
exe.bundle_ubsan_rt = false; exe.bundle_ubsan_rt = false;
exe.root_module.sanitize_c = false; exe.root_module.sanitize_c = .off;
exe.root_module.addCSourceFile(.{ .file = b.path("main.c") }); exe.root_module.addCSourceFile(.{ .file = b.path("main.c") });
// TODO: actually test the output // TODO: actually test the output
_ = exe.getEmittedBin(); _ = exe.getEmittedBin();
@ -69,7 +69,7 @@ pub fn build(b: *std.Build) void {
// We disable UBSAN for these tests as the libc being tested here is // We disable UBSAN for these tests as the libc being tested here is
// so old, it doesn't even support compiling our UBSAN implementation. // so old, it doesn't even support compiling our UBSAN implementation.
exe.bundle_ubsan_rt = false; exe.bundle_ubsan_rt = false;
exe.root_module.sanitize_c = false; exe.root_module.sanitize_c = .off;
exe.root_module.addCSourceFile(.{ .file = b.path("glibc_runtime_check.c") }); exe.root_module.addCSourceFile(.{ .file = b.path("glibc_runtime_check.c") });
// Only try running the test if the host glibc is known to be good enough. Ideally, the Zig // Only try running the test if the host glibc is known to be good enough. Ideally, the Zig
@ -172,7 +172,7 @@ pub fn build(b: *std.Build) void {
// We disable UBSAN for these tests as the libc being tested here is // We disable UBSAN for these tests as the libc being tested here is
// so old, it doesn't even support compiling our UBSAN implementation. // so old, it doesn't even support compiling our UBSAN implementation.
exe.bundle_ubsan_rt = false; exe.bundle_ubsan_rt = false;
exe.root_module.sanitize_c = false; exe.root_module.sanitize_c = .off;
// Only try running the test if the host glibc is known to be good enough. Ideally, the Zig // Only try running the test if the host glibc is known to be good enough. Ideally, the Zig
// test runner would be able to check this, but see https://github.com/ziglang/zig/pull/17702#issuecomment-1831310453 // test runner would be able to check this, but see https://github.com/ziglang/zig/pull/17702#issuecomment-1831310453

View File

@ -288,6 +288,14 @@ const known_options = [_]KnownOpt{
.name = "fno-sanitize", .name = "fno-sanitize",
.ident = "no_sanitize", .ident = "no_sanitize",
}, },
.{
.name = "fsanitize-trap",
.ident = "sanitize_trap",
},
.{
.name = "fno-sanitize-trap",
.ident = "no_sanitize_trap",
},
.{ .{
.name = "T", .name = "T",
.ident = "linker_script", .ident = "linker_script",