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