xref: /aosp_15_r20/external/toolchain-utils/compiler_wrapper/sanitizer_flags.go (revision 760c253c1ed00ce9abd48f8546f08516e57485fe)
1*760c253cSXin Li// Copyright 2019 The ChromiumOS Authors
2*760c253cSXin Li// Use of this source code is governed by a BSD-style license that can be
3*760c253cSXin Li// found in the LICENSE file.
4*760c253cSXin Li
5*760c253cSXin Lipackage main
6*760c253cSXin Li
7*760c253cSXin Liimport (
8*760c253cSXin Li	"strings"
9*760c253cSXin Li)
10*760c253cSXin Li
11*760c253cSXin Li// Returns whether the flag turns on 'invasive' sanitizers. These are sanitizers incompatible with
12*760c253cSXin Li// things like FORTIFY, since they require meaningful runtime support, intercept libc calls, etc.
13*760c253cSXin Lifunc isInvasiveSanitizerFlag(flag string) bool {
14*760c253cSXin Li	// There are a few valid spellings here:
15*760c253cSXin Li	//   -fsanitize=${sanitizer_list}, which enables the given sanitizers
16*760c253cSXin Li	//   -fsanitize-trap=${sanitizer_list}, which specifies sanitizer behavior _if_ these
17*760c253cSXin Li	//     sanitizers are already enabled.
18*760c253cSXin Li	//   -fsanitize-recover=${sanitizer_list}, which also specifies sanitizer behavior _if_
19*760c253cSXin Li	//     these sanitizers are already enabled.
20*760c253cSXin Li	//   -fsanitize-ignorelist=/path/to/file, which designates a config file for sanitizers.
21*760c253cSXin Li	//
22*760c253cSXin Li	// All we care about is the first one, since that's what actually enables sanitizers. Clang
23*760c253cSXin Li	// does not accept a `-fsanitize ${sanitizer_list}` spelling of this flag.
24*760c253cSXin Li	fsanitize := "-fsanitize="
25*760c253cSXin Li	if !strings.HasPrefix(flag, fsanitize) {
26*760c253cSXin Li		return false
27*760c253cSXin Li	}
28*760c253cSXin Li
29*760c253cSXin Li	sanitizers := flag[len(fsanitize):]
30*760c253cSXin Li	if sanitizers == "" {
31*760c253cSXin Li		return false
32*760c253cSXin Li	}
33*760c253cSXin Li
34*760c253cSXin Li	for _, sanitizer := range strings.Split(sanitizers, ",") {
35*760c253cSXin Li		// Keep an allowlist of sanitizers known to not cause issues.
36*760c253cSXin Li		switch sanitizer {
37*760c253cSXin Li		case "alignment", "array-bounds", "bool", "bounds", "builtin", "enum",
38*760c253cSXin Li			"float-cast-overflow", "integer-divide-by-zero", "local-bounds",
39*760c253cSXin Li			"nullability", "nullability-arg", "nullability-assign",
40*760c253cSXin Li			"nullability-return", "null", "return", "returns-nonnull-attribute",
41*760c253cSXin Li			"shift-base", "shift-exponent", "shift", "unreachable", "vla-bound":
42*760c253cSXin Li			// These sanitizers are lightweight. Ignore them.
43*760c253cSXin Li		default:
44*760c253cSXin Li			return true
45*760c253cSXin Li		}
46*760c253cSXin Li	}
47*760c253cSXin Li	return false
48*760c253cSXin Li}
49*760c253cSXin Li
50*760c253cSXin Li// Returns whether the flag given enables FORTIFY. Notably, this should return false if a flag
51*760c253cSXin Li// disables FORTIFY.
52*760c253cSXin Lifunc isFortifyEnableFlag(flag string) bool {
53*760c253cSXin Li	prefix := "-D_FORTIFY_SOURCE="
54*760c253cSXin Li	// At the time of writing, -D_FORTIFY_SOURCE has the valid values 0, 1, 2, and 3. Seems
55*760c253cSXin Li	// unlikely to go past 9, so don't handle past 9.
56*760c253cSXin Li	return strings.HasPrefix(flag, prefix) && len(flag) == len(prefix)+1 && flag[len(prefix)] != '0'
57*760c253cSXin Li}
58*760c253cSXin Li
59*760c253cSXin Lifunc processSanitizerFlags(builder *commandBuilder) {
60*760c253cSXin Li	hasSanitizeFlags := false
61*760c253cSXin Li	// TODO: This doesn't take -fno-sanitize flags into account. This doesn't seem to be an
62*760c253cSXin Li	// issue in practice.
63*760c253cSXin Li	for _, arg := range builder.args {
64*760c253cSXin Li		if arg.fromUser && isInvasiveSanitizerFlag(arg.value) {
65*760c253cSXin Li			hasSanitizeFlags = true
66*760c253cSXin Li			break
67*760c253cSXin Li		}
68*760c253cSXin Li	}
69*760c253cSXin Li
70*760c253cSXin Li	if !hasSanitizeFlags {
71*760c253cSXin Li		return
72*760c253cSXin Li	}
73*760c253cSXin Li
74*760c253cSXin Li	// Flags not supported by sanitizers (ASan etc.)
75*760c253cSXin Li	unsupportedSanitizerFlags := map[string]bool{
76*760c253cSXin Li		"-Wl,--no-undefined": true,
77*760c253cSXin Li		"-Wl,-z,defs":        true,
78*760c253cSXin Li	}
79*760c253cSXin Li
80*760c253cSXin Li	builder.transformArgs(func(arg builderArg) string {
81*760c253cSXin Li		// TODO: This is a bug in the old wrapper to not filter
82*760c253cSXin Li		// non user args for gcc. Fix this once we don't compare to the old wrapper anymore.
83*760c253cSXin Li		linkerDefinedFlag := ",-z,defs"
84*760c253cSXin Li		if builder.target.compilerType != gccType || arg.fromUser {
85*760c253cSXin Li			if unsupportedSanitizerFlags[arg.value] || isFortifyEnableFlag(arg.value) {
86*760c253cSXin Li				return ""
87*760c253cSXin Li			}
88*760c253cSXin Li			if strings.Contains(arg.value, linkerDefinedFlag) {
89*760c253cSXin Li				return strings.ReplaceAll(arg.value, linkerDefinedFlag, "")
90*760c253cSXin Li			}
91*760c253cSXin Li		}
92*760c253cSXin Li		return arg.value
93*760c253cSXin Li	})
94*760c253cSXin Li
95*760c253cSXin Li	builder.filterArgPairs(func(arg1, arg2 builderArg) bool {
96*760c253cSXin Li		return !(arg1.value == "-Wl,-z" && arg2.value == "-Wl,defs")
97*760c253cSXin Li	})
98*760c253cSXin Li}
99