authorgravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2025-04-16 02:44:55+02:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2025-04-26 22:54:34+02:00
logb3537d0f4adeff824348a4918b495976ae230731
tree4635a2febd416352452e5b909afad138db43b601
parent23440fbb99a501fac9cfb6af85c6303732cf0b06

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.

15 files changed, 176 insertions(+), 57 deletions(-)

lib/std/Build/Module.zig+8-3
...@@ -22,7 +22,7 @@ unwind_tables: ?std.builtin.UnwindTables,...@@ -22,7 +22,7 @@ unwind_tables: ?std.builtin.UnwindTables,
22single_threaded: ?bool,22single_threaded: ?bool,
23stack_protector: ?bool,23stack_protector: ?bool,
24stack_check: ?bool,24stack_check: ?bool,
25sanitize_c: ?bool,25sanitize_c: ?std.zig.SanitizeC,
26sanitize_thread: ?bool,26sanitize_thread: ?bool,
27fuzz: ?bool,27fuzz: ?bool,
28code_model: std.builtin.CodeModel,28code_model: std.builtin.CodeModel,
...@@ -256,7 +256,7 @@ pub const CreateOptions = struct {...@@ -256,7 +256,7 @@ pub const CreateOptions = struct {
256 code_model: std.builtin.CodeModel = .default,256 code_model: std.builtin.CodeModel = .default,
257 stack_protector: ?bool = null,257 stack_protector: ?bool = null,
258 stack_check: ?bool = null,258 stack_check: ?bool = null,
259 sanitize_c: ?bool = null,259 sanitize_c: ?std.zig.SanitizeC = null,
260 sanitize_thread: ?bool = null,260 sanitize_thread: ?bool = null,
261 fuzz: ?bool = null,261 fuzz: ?bool = null,
262 /// Whether to emit machine code that integrates with Valgrind.262 /// Whether to emit machine code that integrates with Valgrind.
...@@ -559,13 +559,18 @@ pub fn appendZigProcessFlags(...@@ -559,13 +559,18 @@ pub fn appendZigProcessFlags(
559 try addFlag(zig_args, m.stack_protector, "-fstack-protector", "-fno-stack-protector");559 try addFlag(zig_args, m.stack_protector, "-fstack-protector", "-fno-stack-protector");
560 try addFlag(zig_args, m.omit_frame_pointer, "-fomit-frame-pointer", "-fno-omit-frame-pointer");560 try addFlag(zig_args, m.omit_frame_pointer, "-fomit-frame-pointer", "-fno-omit-frame-pointer");
561 try addFlag(zig_args, m.error_tracing, "-ferror-tracing", "-fno-error-tracing");561 try addFlag(zig_args, m.error_tracing, "-ferror-tracing", "-fno-error-tracing");
562 try addFlag(zig_args, m.sanitize_c, "-fsanitize-c", "-fno-sanitize-c");
563 try addFlag(zig_args, m.sanitize_thread, "-fsanitize-thread", "-fno-sanitize-thread");562 try addFlag(zig_args, m.sanitize_thread, "-fsanitize-thread", "-fno-sanitize-thread");
564 try addFlag(zig_args, m.fuzz, "-ffuzz", "-fno-fuzz");563 try addFlag(zig_args, m.fuzz, "-ffuzz", "-fno-fuzz");
565 try addFlag(zig_args, m.valgrind, "-fvalgrind", "-fno-valgrind");564 try addFlag(zig_args, m.valgrind, "-fvalgrind", "-fno-valgrind");
566 try addFlag(zig_args, m.pic, "-fPIC", "-fno-PIC");565 try addFlag(zig_args, m.pic, "-fPIC", "-fno-PIC");
567 try addFlag(zig_args, m.red_zone, "-mred-zone", "-mno-red-zone");566 try addFlag(zig_args, m.red_zone, "-mred-zone", "-mno-red-zone");
568567
568 if (m.sanitize_c) |sc| switch (sc) {
569 .off => try zig_args.append("-fno-sanitize-c"),
570 .trap => try zig_args.append("-fsanitize-c=trap"),
571 .full => try zig_args.append("-fsanitize-c=full"),
572 };
573
569 if (m.dwarf_format) |dwarf_format| {574 if (m.dwarf_format) |dwarf_format| {
570 try zig_args.append(switch (dwarf_format) {575 try zig_args.append(switch (dwarf_format) {
571 .@"32" => "-gdwarf32",576 .@"32" => "-gdwarf32",
lib/std/zig.zig+6
...@@ -236,6 +236,12 @@ pub fn binNameAlloc(allocator: Allocator, options: BinNameOptions) error{OutOfMe...@@ -236,6 +236,12 @@ pub fn binNameAlloc(allocator: Allocator, options: BinNameOptions) error{OutOfMe
236 }236 }
237}237}
238238
239pub const SanitizeC = enum {
240 off,
241 trap,
242 full,
243};
244
239pub const BuildId = union(enum) {245pub const BuildId = union(enum) {
240 none,246 none,
241 fast,247 fast,
src/Compilation.zig+35-28
...@@ -1287,7 +1287,14 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil...@@ -1287,7 +1287,14 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
1287 const any_unwind_tables = options.config.any_unwind_tables or options.root_mod.unwind_tables != .none;1287 const any_unwind_tables = options.config.any_unwind_tables or options.root_mod.unwind_tables != .none;
1288 const any_non_single_threaded = options.config.any_non_single_threaded or !options.root_mod.single_threaded;1288 const any_non_single_threaded = options.config.any_non_single_threaded or !options.root_mod.single_threaded;
1289 const any_sanitize_thread = options.config.any_sanitize_thread or options.root_mod.sanitize_thread;1289 const any_sanitize_thread = options.config.any_sanitize_thread or options.root_mod.sanitize_thread;
1290 const any_sanitize_c = options.config.any_sanitize_c or options.root_mod.sanitize_c;1290 const any_sanitize_c: std.zig.SanitizeC = switch (options.config.any_sanitize_c) {
1291 .off => options.root_mod.sanitize_c,
1292 .trap => if (options.root_mod.sanitize_c == .full)
1293 .full
1294 else
1295 .trap,
1296 .full => .full,
1297 };
1291 const any_fuzz = options.config.any_fuzz or options.root_mod.fuzz;1298 const any_fuzz = options.config.any_fuzz or options.root_mod.fuzz;
12921299
1293 const link_eh_frame_hdr = options.link_eh_frame_hdr or any_unwind_tables;1300 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...@@ -1346,7 +1353,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
1346 // and this reduces unnecessary bloat.1353 // and this reduces unnecessary bloat.
1347 const ubsan_rt_strat: RtStrat = s: {1354 const ubsan_rt_strat: RtStrat = s: {
1348 const is_spirv = options.root_mod.resolved_target.result.cpu.arch.isSpirV();1355 const is_spirv = options.root_mod.resolved_target.result.cpu.arch.isSpirV();
1349 const want_ubsan_rt = options.want_ubsan_rt orelse (!is_spirv and any_sanitize_c and is_exe_or_dyn_lib);1356 const want_ubsan_rt = options.want_ubsan_rt orelse (!is_spirv and any_sanitize_c == .full and is_exe_or_dyn_lib);
1350 if (!want_ubsan_rt) break :s .none;1357 if (!want_ubsan_rt) break :s .none;
1351 if (options.skip_linker_dependencies) break :s .none;1358 if (options.skip_linker_dependencies) break :s .none;
1352 if (have_zcu) break :s .zcu;1359 if (have_zcu) break :s .zcu;
...@@ -1418,6 +1425,10 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil...@@ -1418,6 +1425,10 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
1418 cache.hash.add(options.config.lto);1425 cache.hash.add(options.config.lto);
1419 cache.hash.add(options.config.link_mode);1426 cache.hash.add(options.config.link_mode);
1420 cache.hash.add(options.config.any_unwind_tables);1427 cache.hash.add(options.config.any_unwind_tables);
1428 cache.hash.add(options.config.any_non_single_threaded);
1429 cache.hash.add(options.config.any_sanitize_thread);
1430 cache.hash.add(options.config.any_sanitize_c);
1431 cache.hash.add(options.config.any_fuzz);
1421 cache.hash.add(options.function_sections);1432 cache.hash.add(options.function_sections);
1422 cache.hash.add(options.data_sections);1433 cache.hash.add(options.data_sections);
1423 cache.hash.add(link_libc);1434 cache.hash.add(link_libc);
...@@ -6048,7 +6059,7 @@ pub fn addCCArgs(...@@ -6048,7 +6059,7 @@ pub fn addCCArgs(
6048 {6059 {
6049 var san_arg: std.ArrayListUnmanaged(u8) = .empty;6060 var san_arg: std.ArrayListUnmanaged(u8) = .empty;
6050 const prefix = "-fsanitize=";6061 const prefix = "-fsanitize=";
6051 if (mod.sanitize_c) {6062 if (mod.sanitize_c != .off) {
6052 if (san_arg.items.len == 0) try san_arg.appendSlice(arena, prefix);6063 if (san_arg.items.len == 0) try san_arg.appendSlice(arena, prefix);
6053 try san_arg.appendSlice(arena, "undefined,");6064 try san_arg.appendSlice(arena, "undefined,");
6054 }6065 }
...@@ -6064,37 +6075,33 @@ pub fn addCCArgs(...@@ -6064,37 +6075,33 @@ pub fn addCCArgs(
6064 if (san_arg.pop()) |_| {6075 if (san_arg.pop()) |_| {
6065 try argv.append(san_arg.items);6076 try argv.append(san_arg.items);
60666077
6067 // These args have to be added after the `-fsanitize` arg or6078 switch (mod.sanitize_c) {
6068 // they won't take effect.6079 .off => {},
6069 if (mod.sanitize_c) {6080 .trap => {
6070 // This check requires implementing the Itanium C++ ABI.
6071 // We would make it `-fsanitize-trap=vptr`, however this check requires
6072 // a full runtime due to the type hashing involved.
6073 try argv.append("-fno-sanitize=vptr");
6074
6075 // It is very common, and well-defined, for a pointer on one side of a C ABI
6076 // to have a different but compatible element type. Examples include:
6077 // `char*` vs `uint8_t*` on a system with 8-bit bytes
6078 // `const char*` vs `char*`
6079 // `char*` vs `unsigned char*`
6080 // Without this flag, Clang would invoke UBSAN when such an extern
6081 // function was called.
6082 try argv.append("-fno-sanitize=function");
6083
6084 if (mod.optimize_mode == .ReleaseSafe) {
6085 // It's recommended to use the minimal runtime in production
6086 // environments due to the security implications of the full runtime.
6087 // The minimal runtime doesn't provide much benefit over simply
6088 // trapping, however, so we do that instead.
6089 try argv.append("-fsanitize-trap=undefined");6081 try argv.append("-fsanitize-trap=undefined");
6090 } else {6082 },
6083 .full => {
6084 // This check requires implementing the Itanium C++ ABI.
6085 // We would make it `-fsanitize-trap=vptr`, however this check requires
6086 // a full runtime due to the type hashing involved.
6087 try argv.append("-fno-sanitize=vptr");
6088
6089 // It is very common, and well-defined, for a pointer on one side of a C ABI
6090 // to have a different but compatible element type. Examples include:
6091 // `char*` vs `uint8_t*` on a system with 8-bit bytes
6092 // `const char*` vs `char*`
6093 // `char*` vs `unsigned char*`
6094 // Without this flag, Clang would invoke UBSAN when such an extern
6095 // function was called.
6096 try argv.append("-fno-sanitize=function");
6097
6091 // This is necessary because, by default, Clang instructs LLVM to embed6098 // This is necessary because, by default, Clang instructs LLVM to embed
6092 // a COFF link dependency on `libclang_rt.ubsan_standalone.a` when the6099 // a COFF link dependency on `libclang_rt.ubsan_standalone.a` when the
6093 // UBSan runtime is used.6100 // UBSan runtime is used.
6094 if (target.os.tag == .windows) {6101 if (target.os.tag == .windows) {
6095 try argv.append("-fno-rtlib-defaultlib");6102 try argv.append("-fno-rtlib-defaultlib");
6096 }6103 }
6097 }6104 },
6098 }6105 }
6099 }6106 }
61006107
...@@ -6797,7 +6804,7 @@ pub fn build_crt_file(...@@ -6797,7 +6804,7 @@ pub fn build_crt_file(
6797 .strip = comp.compilerRtStrip(),6804 .strip = comp.compilerRtStrip(),
6798 .stack_check = false,6805 .stack_check = false,
6799 .stack_protector = 0,6806 .stack_protector = 0,
6800 .sanitize_c = false,6807 .sanitize_c = .off,
6801 .sanitize_thread = false,6808 .sanitize_thread = false,
6802 .red_zone = comp.root_mod.red_zone,6809 .red_zone = comp.root_mod.red_zone,
6803 // Some libcs (e.g. musl) are opinionated about -fomit-frame-pointer.6810 // Some libcs (e.g. musl) are opinionated about -fomit-frame-pointer.
src/Compilation/Config.zig+2-2
...@@ -32,7 +32,7 @@ any_non_single_threaded: bool,...@@ -32,7 +32,7 @@ any_non_single_threaded: bool,
32/// per-Module setting.32/// per-Module setting.
33any_error_tracing: bool,33any_error_tracing: bool,
34any_sanitize_thread: bool,34any_sanitize_thread: bool,
35any_sanitize_c: bool,35any_sanitize_c: std.zig.SanitizeC,
36any_fuzz: bool,36any_fuzz: bool,
37pie: bool,37pie: bool,
38/// If this is true then linker code is responsible for making an LLVM IR38/// If this is true then linker code is responsible for making an LLVM IR
...@@ -86,7 +86,7 @@ pub const Options = struct {...@@ -86,7 +86,7 @@ pub const Options = struct {
86 ensure_libcpp_on_non_freestanding: bool = false,86 ensure_libcpp_on_non_freestanding: bool = false,
87 any_non_single_threaded: bool = false,87 any_non_single_threaded: bool = false,
88 any_sanitize_thread: bool = false,88 any_sanitize_thread: bool = false,
89 any_sanitize_c: bool = false,89 any_sanitize_c: std.zig.SanitizeC = .off,
90 any_fuzz: bool = false,90 any_fuzz: bool = false,
91 any_unwind_tables: bool = false,91 any_unwind_tables: bool = false,
92 any_dyn_libs: bool = false,92 any_dyn_libs: bool = false,
src/Package/Module.zig+13-4
...@@ -24,7 +24,7 @@ omit_frame_pointer: bool,...@@ -24,7 +24,7 @@ omit_frame_pointer: bool,
24stack_check: bool,24stack_check: bool,
25stack_protector: u32,25stack_protector: u32,
26red_zone: bool,26red_zone: bool,
27sanitize_c: bool,27sanitize_c: std.zig.SanitizeC,
28sanitize_thread: bool,28sanitize_thread: bool,
29fuzz: bool,29fuzz: bool,
30unwind_tables: std.builtin.UnwindTables,30unwind_tables: std.builtin.UnwindTables,
...@@ -92,7 +92,7 @@ pub const CreateOptions = struct {...@@ -92,7 +92,7 @@ pub const CreateOptions = struct {
92 stack_protector: ?u32 = null,92 stack_protector: ?u32 = null,
93 red_zone: ?bool = null,93 red_zone: ?bool = null,
94 unwind_tables: ?std.builtin.UnwindTables = null,94 unwind_tables: ?std.builtin.UnwindTables = null,
95 sanitize_c: ?bool = null,95 sanitize_c: ?std.zig.SanitizeC = null,
96 sanitize_thread: ?bool = null,96 sanitize_thread: ?bool = null,
97 fuzz: ?bool = null,97 fuzz: ?bool = null,
98 structured_cfg: ?bool = null,98 structured_cfg: ?bool = null,
...@@ -113,6 +113,7 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Package.Module {...@@ -113,6 +113,7 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Package.Module {
113 if (options.inherited.fuzz == true) assert(options.global.any_fuzz);113 if (options.inherited.fuzz == true) assert(options.global.any_fuzz);
114 if (options.inherited.single_threaded == false) assert(options.global.any_non_single_threaded);114 if (options.inherited.single_threaded == false) assert(options.global.any_non_single_threaded);
115 if (options.inherited.unwind_tables) |uwt| if (uwt != .none) assert(options.global.any_unwind_tables);115 if (options.inherited.unwind_tables) |uwt| if (uwt != .none) assert(options.global.any_unwind_tables);
116 if (options.inherited.sanitize_c) |sc| if (sc != .off) assert(options.global.any_sanitize_c != .off);
116 if (options.inherited.error_tracing == true) assert(options.global.any_error_tracing);117 if (options.inherited.error_tracing == true) assert(options.global.any_error_tracing);
117118
118 const resolved_target = options.inherited.resolved_target orelse options.parent.?.resolved_target;119 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 {...@@ -249,10 +250,18 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Package.Module {
249 .ReleaseFast, .ReleaseSmall => false,250 .ReleaseFast, .ReleaseSmall => false,
250 };251 };
251252
252 const sanitize_c = b: {253 const sanitize_c: std.zig.SanitizeC = b: {
253 if (options.inherited.sanitize_c) |x| break :b x;254 if (options.inherited.sanitize_c) |x| break :b x;
254 if (options.parent) |p| break :b p.sanitize_c;255 if (options.parent) |p| break :b p.sanitize_c;
255 break :b is_safe_mode;256 break :b switch (optimize_mode) {
257 .Debug => .full,
258 // It's recommended to use the minimal runtime in production
259 // environments due to the security implications of the full runtime.
260 // The minimal runtime doesn't provide much benefit over simply
261 // trapping, however, so we do that instead.
262 .ReleaseSafe => .trap,
263 .ReleaseFast, .ReleaseSmall => .off,
264 };
256 };265 };
257266
258 const stack_check = b: {267 const stack_check = b: {
src/clang_options_data.zig+18-4
...@@ -3682,7 +3682,14 @@ flagpd1("fno-sanitize-stats"),...@@ -3682,7 +3682,14 @@ flagpd1("fno-sanitize-stats"),
3682flagpd1("fno-sanitize-thread-atomics"),3682flagpd1("fno-sanitize-thread-atomics"),
3683flagpd1("fno-sanitize-thread-func-entry-exit"),3683flagpd1("fno-sanitize-thread-func-entry-exit"),
3684flagpd1("fno-sanitize-thread-memory-access"),3684flagpd1("fno-sanitize-thread-memory-access"),
3685flagpd1("fno-sanitize-trap"),3685.{
3686 .name = "fno-sanitize-trap",
3687 .syntax = .flag,
3688 .zig_equivalent = .no_sanitize_trap,
3689 .pd1 = true,
3690 .pd2 = false,
3691 .psl = false,
3692},
3686flagpd1("fno-sanitize-undefined-trap-on-error"),3693flagpd1("fno-sanitize-undefined-trap-on-error"),
3687flagpd1("fno-save-main-program"),3694flagpd1("fno-save-main-program"),
3688flagpd1("fno-save-optimization-record"),3695flagpd1("fno-save-optimization-record"),
...@@ -4024,7 +4031,14 @@ flagpd1("fsanitize-stats"),...@@ -4024,7 +4031,14 @@ flagpd1("fsanitize-stats"),
4024flagpd1("fsanitize-thread-atomics"),4031flagpd1("fsanitize-thread-atomics"),
4025flagpd1("fsanitize-thread-func-entry-exit"),4032flagpd1("fsanitize-thread-func-entry-exit"),
4026flagpd1("fsanitize-thread-memory-access"),4033flagpd1("fsanitize-thread-memory-access"),
4027flagpd1("fsanitize-trap"),4034.{
4035 .name = "fsanitize-trap",
4036 .syntax = .flag,
4037 .zig_equivalent = .sanitize_trap,
4038 .pd1 = true,
4039 .pd2 = false,
4040 .psl = false,
4041},
4028flagpd1("fsanitize-undefined-trap-on-error"),4042flagpd1("fsanitize-undefined-trap-on-error"),
4029flagpd1("fsave-main-program"),4043flagpd1("fsave-main-program"),
4030flagpd1("fsave-optimization-record"),4044flagpd1("fsave-optimization-record"),
...@@ -6592,7 +6606,7 @@ joinpd1("fmacro-prefix-map="),...@@ -6592,7 +6606,7 @@ joinpd1("fmacro-prefix-map="),
6592.{6606.{
6593 .name = "fno-sanitize-trap=",6607 .name = "fno-sanitize-trap=",
6594 .syntax = .comma_joined,6608 .syntax = .comma_joined,
6595 .zig_equivalent = .other,6609 .zig_equivalent = .no_sanitize_trap,
6596 .pd1 = true,6610 .pd1 = true,
6597 .pd2 = false,6611 .pd2 = false,
6598 .psl = false,6612 .psl = false,
...@@ -6864,7 +6878,7 @@ joinpd1("frecord-marker="),...@@ -6864,7 +6878,7 @@ joinpd1("frecord-marker="),
6864.{6878.{
6865 .name = "fsanitize-trap=",6879 .name = "fsanitize-trap=",
6866 .syntax = .comma_joined,6880 .syntax = .comma_joined,
6867 .zig_equivalent = .other,6881 .zig_equivalent = .sanitize_trap,
6868 .pd1 = true,6882 .pd1 = true,
6869 .pd2 = false,6883 .pd2 = false,
6870 .psl = false,6884 .psl = false,
src/glibc.zig+1-1
...@@ -1231,7 +1231,7 @@ fn buildSharedLib(...@@ -1231,7 +1231,7 @@ fn buildSharedLib(
1231 .strip = strip,1231 .strip = strip,
1232 .stack_check = false,1232 .stack_check = false,
1233 .stack_protector = 0,1233 .stack_protector = 0,
1234 .sanitize_c = false,1234 .sanitize_c = .off,
1235 .sanitize_thread = false,1235 .sanitize_thread = false,
1236 .red_zone = comp.root_mod.red_zone,1236 .red_zone = comp.root_mod.red_zone,
1237 .omit_frame_pointer = comp.root_mod.omit_frame_pointer,1237 .omit_frame_pointer = comp.root_mod.omit_frame_pointer,
src/libcxx.zig+2-2
...@@ -182,7 +182,7 @@ pub fn buildLibCxx(comp: *Compilation, prog_node: std.Progress.Node) BuildError!...@@ -182,7 +182,7 @@ pub fn buildLibCxx(comp: *Compilation, prog_node: std.Progress.Node) BuildError!
182 .strip = strip,182 .strip = strip,
183 .stack_check = false,183 .stack_check = false,
184 .stack_protector = 0,184 .stack_protector = 0,
185 .sanitize_c = false,185 .sanitize_c = .off,
186 .sanitize_thread = comp.config.any_sanitize_thread,186 .sanitize_thread = comp.config.any_sanitize_thread,
187 .red_zone = comp.root_mod.red_zone,187 .red_zone = comp.root_mod.red_zone,
188 .omit_frame_pointer = comp.root_mod.omit_frame_pointer,188 .omit_frame_pointer = comp.root_mod.omit_frame_pointer,
...@@ -396,7 +396,7 @@ pub fn buildLibCxxAbi(comp: *Compilation, prog_node: std.Progress.Node) BuildErr...@@ -396,7 +396,7 @@ pub fn buildLibCxxAbi(comp: *Compilation, prog_node: std.Progress.Node) BuildErr
396 .strip = strip,396 .strip = strip,
397 .stack_check = false,397 .stack_check = false,
398 .stack_protector = 0,398 .stack_protector = 0,
399 .sanitize_c = false,399 .sanitize_c = .off,
400 .sanitize_thread = comp.config.any_sanitize_thread,400 .sanitize_thread = comp.config.any_sanitize_thread,
401 .red_zone = comp.root_mod.red_zone,401 .red_zone = comp.root_mod.red_zone,
402 .omit_frame_pointer = comp.root_mod.omit_frame_pointer,402 .omit_frame_pointer = comp.root_mod.omit_frame_pointer,
src/libtsan.zig+1-1
...@@ -95,7 +95,7 @@ pub fn buildTsan(comp: *Compilation, prog_node: std.Progress.Node) BuildError!vo...@@ -95,7 +95,7 @@ pub fn buildTsan(comp: *Compilation, prog_node: std.Progress.Node) BuildError!vo
95 .strip = strip,95 .strip = strip,
96 .stack_check = false,96 .stack_check = false,
97 .stack_protector = 0,97 .stack_protector = 0,
98 .sanitize_c = false,98 .sanitize_c = .off,
99 .sanitize_thread = false,99 .sanitize_thread = false,
100 .red_zone = comp.root_mod.red_zone,100 .red_zone = comp.root_mod.red_zone,
101 .omit_frame_pointer = optimize_mode != .Debug and !target.os.tag.isDarwin(),101 .omit_frame_pointer = optimize_mode != .Debug and !target.os.tag.isDarwin(),
src/libunwind.zig+1-1
...@@ -64,7 +64,7 @@ pub fn buildStaticLib(comp: *Compilation, prog_node: std.Progress.Node) BuildErr...@@ -64,7 +64,7 @@ pub fn buildStaticLib(comp: *Compilation, prog_node: std.Progress.Node) BuildErr
64 .red_zone = comp.root_mod.red_zone,64 .red_zone = comp.root_mod.red_zone,
65 .omit_frame_pointer = comp.root_mod.omit_frame_pointer,65 .omit_frame_pointer = comp.root_mod.omit_frame_pointer,
66 .valgrind = false,66 .valgrind = false,
67 .sanitize_c = false,67 .sanitize_c = .off,
68 .sanitize_thread = false,68 .sanitize_thread = false,
69 // necessary so that libunwind can unwind through its own stack frames69 // necessary so that libunwind can unwind through its own stack frames
70 // The old 32-bit x86 variant of SEH doesn't use tables.70 // The old 32-bit x86 variant of SEH doesn't use tables.
src/main.zig+75-5
...@@ -526,7 +526,9 @@ const usage_build_generic =...@@ -526,7 +526,9 @@ const usage_build_generic =
526 \\ -fno-stack-protector Disable stack protection in safe builds526 \\ -fno-stack-protector Disable stack protection in safe builds
527 \\ -fvalgrind Include valgrind client requests in release builds527 \\ -fvalgrind Include valgrind client requests in release builds
528 \\ -fno-valgrind Omit valgrind client requests in debug builds528 \\ -fno-valgrind Omit valgrind client requests in debug builds
529 \\ -fsanitize-c Enable C undefined behavior detection in unsafe builds529 \\ -fsanitize-c[=mode] Enable C undefined behavior detection in unsafe builds
530 \\ trap Insert trap instructions on undefined behavior
531 \\ full (Default) Insert runtime calls on undefined behavior
530 \\ -fno-sanitize-c Disable C undefined behavior detection in safe builds532 \\ -fno-sanitize-c Disable C undefined behavior detection in safe builds
531 \\ -fsanitize-thread Enable Thread Sanitizer533 \\ -fsanitize-thread Enable Thread Sanitizer
532 \\ -fno-sanitize-thread Disable Thread Sanitizer534 \\ -fno-sanitize-thread Disable Thread Sanitizer
...@@ -1464,9 +1466,18 @@ fn buildOutputType(...@@ -1464,9 +1466,18 @@ fn buildOutputType(
1464 } else if (mem.eql(u8, arg, "-fno-omit-frame-pointer")) {1466 } else if (mem.eql(u8, arg, "-fno-omit-frame-pointer")) {
1465 mod_opts.omit_frame_pointer = false;1467 mod_opts.omit_frame_pointer = false;
1466 } else if (mem.eql(u8, arg, "-fsanitize-c")) {1468 } else if (mem.eql(u8, arg, "-fsanitize-c")) {
1467 mod_opts.sanitize_c = true;1469 mod_opts.sanitize_c = .full;
1470 } else if (mem.startsWith(u8, arg, "-fsanitize-c=")) {
1471 const mode = arg["-fsanitize-c=".len..];
1472 if (mem.eql(u8, mode, "trap")) {
1473 mod_opts.sanitize_c = .trap;
1474 } else if (mem.eql(u8, mode, "full")) {
1475 mod_opts.sanitize_c = .full;
1476 } else {
1477 fatal("Invalid -fsanitize-c mode: '{s}'. Must be 'trap' or 'full'.", .{mode});
1478 }
1468 } else if (mem.eql(u8, arg, "-fno-sanitize-c")) {1479 } else if (mem.eql(u8, arg, "-fno-sanitize-c")) {
1469 mod_opts.sanitize_c = false;1480 mod_opts.sanitize_c = .off;
1470 } else if (mem.eql(u8, arg, "-fvalgrind")) {1481 } else if (mem.eql(u8, arg, "-fvalgrind")) {
1471 mod_opts.valgrind = true;1482 mod_opts.valgrind = true;
1472 } else if (mem.eql(u8, arg, "-fno-valgrind")) {1483 } else if (mem.eql(u8, arg, "-fno-valgrind")) {
...@@ -2236,7 +2247,7 @@ fn buildOutputType(...@@ -2236,7 +2247,7 @@ fn buildOutputType(
2236 var recognized_any = false;2247 var recognized_any = false;
2237 while (san_it.next()) |sub_arg| {2248 while (san_it.next()) |sub_arg| {
2238 if (mem.eql(u8, sub_arg, "undefined")) {2249 if (mem.eql(u8, sub_arg, "undefined")) {
2239 mod_opts.sanitize_c = enable;2250 mod_opts.sanitize_c = if (enable) .full else .off;
2240 recognized_any = true;2251 recognized_any = true;
2241 } else if (mem.eql(u8, sub_arg, "thread")) {2252 } else if (mem.eql(u8, sub_arg, "thread")) {
2242 mod_opts.sanitize_thread = enable;2253 mod_opts.sanitize_thread = enable;
...@@ -2250,6 +2261,49 @@ fn buildOutputType(...@@ -2250,6 +2261,49 @@ fn buildOutputType(
2250 try cc_argv.appendSlice(arena, it.other_args);2261 try cc_argv.appendSlice(arena, it.other_args);
2251 }2262 }
2252 },2263 },
2264 .sanitize_trap, .no_sanitize_trap => |t| {
2265 const enable = t == .sanitize_trap;
2266 var san_it = std.mem.splitScalar(u8, it.only_arg, ',');
2267 var recognized_any = false;
2268 while (san_it.next()) |sub_arg| {
2269 // This logic doesn't match Clang 1:1, but it's probably good enough, and avoids
2270 // significantly complicating the resolution of the options.
2271 if (mem.eql(u8, sub_arg, "undefined")) {
2272 if (mod_opts.sanitize_c) |sc| switch (sc) {
2273 .off => if (enable) {
2274 mod_opts.sanitize_c = .trap;
2275 },
2276 .trap => if (!enable) {
2277 mod_opts.sanitize_c = .full;
2278 },
2279 .full => if (enable) {
2280 mod_opts.sanitize_c = .trap;
2281 },
2282 } else {
2283 if (enable) {
2284 mod_opts.sanitize_c = .trap;
2285 } else {
2286 // This means we were passed `-fno-sanitize-trap=undefined` and nothing else. In
2287 // this case, ideally, we should use whatever value `sanitize_c` resolves to by
2288 // default, except change `trap` to `full`. However, we don't yet know what
2289 // `sanitize_c` will resolve to! So we either have to pick `off` or `full`.
2290 //
2291 // `full` has the potential to be problematic if `optimize_mode` turns out to
2292 // be `ReleaseFast`/`ReleaseSmall` because the user will get a slower and larger
2293 // binary than expected. On the other hand, if `optimize_mode` turns out to be
2294 // `Debug`/`ReleaseSafe`, `off` would mean UBSan would unexpectedly be disabled.
2295 //
2296 // `off` seems very slightly less bad, so let's go with that.
2297 mod_opts.sanitize_c = .off;
2298 }
2299 }
2300 recognized_any = true;
2301 }
2302 }
2303 if (!recognized_any) {
2304 try cc_argv.appendSlice(arena, it.other_args);
2305 }
2306 },
2253 .linker_script => linker_script = it.only_arg,2307 .linker_script => linker_script = it.only_arg,
2254 .verbose => {2308 .verbose => {
2255 verbose_link = true;2309 verbose_link = true;
...@@ -2766,7 +2820,7 @@ fn buildOutputType(...@@ -2766,7 +2820,7 @@ fn buildOutputType(
2766 }2820 }
27672821
2768 if (mod_opts.sanitize_c) |wsc| {2822 if (mod_opts.sanitize_c) |wsc| {
2769 if (wsc and mod_opts.optimize_mode == .ReleaseFast) {2823 if (wsc != .off and mod_opts.optimize_mode == .ReleaseFast) {
2770 mod_opts.optimize_mode = .ReleaseSafe;2824 mod_opts.optimize_mode = .ReleaseSafe;
2771 }2825 }
2772 }2826 }
...@@ -2915,6 +2969,13 @@ fn buildOutputType(...@@ -2915,6 +2969,13 @@ fn buildOutputType(
2915 create_module.opts.any_non_single_threaded = true;2969 create_module.opts.any_non_single_threaded = true;
2916 if (mod_opts.sanitize_thread == true)2970 if (mod_opts.sanitize_thread == true)
2917 create_module.opts.any_sanitize_thread = true;2971 create_module.opts.any_sanitize_thread = true;
2972 if (mod_opts.sanitize_c) |sc| switch (sc) {
2973 .off => {},
2974 .trap => if (create_module.opts.any_sanitize_c == .off) {
2975 create_module.opts.any_sanitize_c = .trap;
2976 },
2977 .full => create_module.opts.any_sanitize_c = .full,
2978 };
2918 if (mod_opts.fuzz == true)2979 if (mod_opts.fuzz == true)
2919 create_module.opts.any_fuzz = true;2980 create_module.opts.any_fuzz = true;
2920 if (mod_opts.unwind_tables) |uwt| switch (uwt) {2981 if (mod_opts.unwind_tables) |uwt| switch (uwt) {
...@@ -5941,6 +6002,8 @@ pub const ClangArgIterator = struct {...@@ -5941,6 +6002,8 @@ pub const ClangArgIterator = struct {
5941 gdwarf64,6002 gdwarf64,
5942 sanitize,6003 sanitize,
5943 no_sanitize,6004 no_sanitize,
6005 sanitize_trap,
6006 no_sanitize_trap,
5944 linker_script,6007 linker_script,
5945 dry_run,6008 dry_run,
5946 verbose,6009 verbose,
...@@ -7728,6 +7791,13 @@ fn handleModArg(...@@ -7728,6 +7791,13 @@ fn handleModArg(
7728 create_module.opts.any_non_single_threaded = true;7791 create_module.opts.any_non_single_threaded = true;
7729 if (mod_opts.sanitize_thread == true)7792 if (mod_opts.sanitize_thread == true)
7730 create_module.opts.any_sanitize_thread = true;7793 create_module.opts.any_sanitize_thread = true;
7794 if (mod_opts.sanitize_c) |sc| switch (sc) {
7795 .off => {},
7796 .trap => if (create_module.opts.any_sanitize_c == .off) {
7797 create_module.opts.any_sanitize_c = .trap;
7798 },
7799 .full => create_module.opts.any_sanitize_c = .full,
7800 };
7731 if (mod_opts.fuzz == true)7801 if (mod_opts.fuzz == true)
7732 create_module.opts.any_fuzz = true;7802 create_module.opts.any_fuzz = true;
7733 if (mod_opts.unwind_tables) |uwt| switch (uwt) {7803 if (mod_opts.unwind_tables) |uwt| switch (uwt) {
src/musl.zig+1-1
...@@ -231,7 +231,7 @@ pub fn buildCrtFile(comp: *Compilation, in_crt_file: CrtFile, prog_node: std.Pro...@@ -231,7 +231,7 @@ pub fn buildCrtFile(comp: *Compilation, in_crt_file: CrtFile, prog_node: std.Pro
231 .strip = strip,231 .strip = strip,
232 .stack_check = false,232 .stack_check = false,
233 .stack_protector = 0,233 .stack_protector = 0,
234 .sanitize_c = false,234 .sanitize_c = .off,
235 .sanitize_thread = false,235 .sanitize_thread = false,
236 .red_zone = comp.root_mod.red_zone,236 .red_zone = comp.root_mod.red_zone,
237 .omit_frame_pointer = comp.root_mod.omit_frame_pointer,237 .omit_frame_pointer = comp.root_mod.omit_frame_pointer,
test/link/elf.zig+2-2
...@@ -2052,7 +2052,7 @@ fn testLargeBss(b: *Build, opts: Options) *Step {...@@ -2052,7 +2052,7 @@ fn testLargeBss(b: *Build, opts: Options) *Step {
2052 exe.linkLibC();2052 exe.linkLibC();
2053 // Disabled to work around the ELF linker crashing.2053 // Disabled to work around the ELF linker crashing.
2054 // Can be reproduced on a x86_64-linux host by commenting out the line below.2054 // Can be reproduced on a x86_64-linux host by commenting out the line below.
2055 exe.root_module.sanitize_c = false;2055 exe.root_module.sanitize_c = .off;
20562056
2057 const run = addRunArtifact(exe);2057 const run = addRunArtifact(exe);
2058 run.expectExitCode(0);2058 run.expectExitCode(0);
...@@ -3558,7 +3558,7 @@ fn testTlsLargeTbss(b: *Build, opts: Options) *Step {...@@ -3558,7 +3558,7 @@ fn testTlsLargeTbss(b: *Build, opts: Options) *Step {
3558 exe.linkLibC();3558 exe.linkLibC();
3559 // Disabled to work around the ELF linker crashing.3559 // Disabled to work around the ELF linker crashing.
3560 // Can be reproduced on a x86_64-linux host by commenting out the line below.3560 // Can be reproduced on a x86_64-linux host by commenting out the line below.
3561 exe.root_module.sanitize_c = false;3561 exe.root_module.sanitize_c = .off;
35623562
3563 const run = addRunArtifact(exe);3563 const run = addRunArtifact(exe);
3564 run.expectStdOutEqual("3 0 5 0 0 0\n");3564 run.expectStdOutEqual("3 0 5 0 0 0\n");
test/link/glibc_compat/build.zig+3-3
...@@ -25,7 +25,7 @@ pub fn build(b: *std.Build) void {...@@ -25,7 +25,7 @@ pub fn build(b: *std.Build) void {
25 // We disable UBSAN for these tests as the libc being tested here is25 // We disable UBSAN for these tests as the libc being tested here is
26 // so old, it doesn't even support compiling our UBSAN implementation.26 // so old, it doesn't even support compiling our UBSAN implementation.
27 exe.bundle_ubsan_rt = false;27 exe.bundle_ubsan_rt = false;
28 exe.root_module.sanitize_c = false;28 exe.root_module.sanitize_c = .off;
29 exe.root_module.addCSourceFile(.{ .file = b.path("main.c") });29 exe.root_module.addCSourceFile(.{ .file = b.path("main.c") });
30 // TODO: actually test the output30 // TODO: actually test the output
31 _ = exe.getEmittedBin();31 _ = exe.getEmittedBin();
...@@ -69,7 +69,7 @@ pub fn build(b: *std.Build) void {...@@ -69,7 +69,7 @@ pub fn build(b: *std.Build) void {
69 // We disable UBSAN for these tests as the libc being tested here is69 // We disable UBSAN for these tests as the libc being tested here is
70 // so old, it doesn't even support compiling our UBSAN implementation.70 // so old, it doesn't even support compiling our UBSAN implementation.
71 exe.bundle_ubsan_rt = false;71 exe.bundle_ubsan_rt = false;
72 exe.root_module.sanitize_c = false;72 exe.root_module.sanitize_c = .off;
73 exe.root_module.addCSourceFile(.{ .file = b.path("glibc_runtime_check.c") });73 exe.root_module.addCSourceFile(.{ .file = b.path("glibc_runtime_check.c") });
7474
75 // Only try running the test if the host glibc is known to be good enough. Ideally, the Zig75 // 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 {...@@ -172,7 +172,7 @@ pub fn build(b: *std.Build) void {
172 // We disable UBSAN for these tests as the libc being tested here is172 // We disable UBSAN for these tests as the libc being tested here is
173 // so old, it doesn't even support compiling our UBSAN implementation.173 // so old, it doesn't even support compiling our UBSAN implementation.
174 exe.bundle_ubsan_rt = false;174 exe.bundle_ubsan_rt = false;
175 exe.root_module.sanitize_c = false;175 exe.root_module.sanitize_c = .off;
176176
177 // Only try running the test if the host glibc is known to be good enough. Ideally, the Zig177 // Only try running the test if the host glibc is known to be good enough. Ideally, the Zig
178 // test runner would be able to check this, but see https://github.com/ziglang/zig/pull/17702#issuecomment-1831310453178 // test runner would be able to check this, but see https://github.com/ziglang/zig/pull/17702#issuecomment-1831310453
tools/update_clang_options.zig+8
...@@ -288,6 +288,14 @@ const known_options = [_]KnownOpt{...@@ -288,6 +288,14 @@ const known_options = [_]KnownOpt{
288 .name = "fno-sanitize",288 .name = "fno-sanitize",
289 .ident = "no_sanitize",289 .ident = "no_sanitize",
290 },290 },
291 .{
292 .name = "fsanitize-trap",
293 .ident = "sanitize_trap",
294 },
295 .{
296 .name = "fno-sanitize-trap",
297 .ident = "no_sanitize_trap",
298 },
291 .{299 .{
292 .name = "T",300 .name = "T",
293 .ident = "linker_script",301 .ident = "linker_script",