| // Copyright 2019 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package main |
| |
| import ( |
| "strings" |
| ) |
| |
| // Returns whether the flag turns on 'invasive' sanitizers. These are sanitizers incompatible with |
| // things like FORTIFY, since they require meaningful runtime support, intercept libc calls, etc. |
| func isInvasiveSanitizerFlag(flag string) bool { |
| // There are a few valid spellings here: |
| // -fsanitize=${sanitizer_list}, which enables the given sanitizers |
| // -fsanitize-trap=${sanitizer_list}, which specifies sanitizer behavior _if_ these |
| // sanitizers are already enabled. |
| // -fsanitize-recover=${sanitizer_list}, which also specifies sanitizer behavior _if_ |
| // these sanitizers are already enabled. |
| // -fsanitize-ignorelist=/path/to/file, which designates a config file for sanitizers. |
| // |
| // All we care about is the first one, since that's what actually enables sanitizers. Clang |
| // does not accept a `-fsanitize ${sanitizer_list}` spelling of this flag. |
| fsanitize := "-fsanitize=" |
| if !strings.HasPrefix(flag, fsanitize) { |
| return false |
| } |
| |
| sanitizers := flag[len(fsanitize):] |
| if sanitizers == "" { |
| return false |
| } |
| |
| for _, sanitizer := range strings.Split(sanitizers, ",") { |
| // Keep an allowlist of sanitizers known to not cause issues. |
| switch sanitizer { |
| case "alignment", "array-bounds", "bool", "bounds", "builtin", "enum", |
| "float-cast-overflow", "integer-divide-by-zero", "local-bounds", |
| "nullability", "nullability-arg", "nullability-assign", |
| "nullability-return", "null", "return", "returns-nonnull-attribute", |
| "shift-base", "shift-exponent", "shift", "unreachable", "vla-bound": |
| // These sanitizers are lightweight. Ignore them. |
| default: |
| return true |
| } |
| } |
| return false |
| } |
| |
| func processSanitizerFlags(builder *commandBuilder) { |
| hasSanitizeFlags := false |
| // TODO: This doesn't take -fno-sanitize flags into account. This doesn't seem to be an |
| // issue in practice. |
| for _, arg := range builder.args { |
| if arg.fromUser && isInvasiveSanitizerFlag(arg.value) { |
| hasSanitizeFlags = true |
| break |
| } |
| } |
| |
| if !hasSanitizeFlags { |
| return |
| } |
| |
| // Flags not supported by sanitizers (ASan etc.) |
| unsupportedSanitizerFlags := map[string]bool{ |
| "-D_FORTIFY_SOURCE=1": true, |
| "-D_FORTIFY_SOURCE=2": true, |
| "-Wl,--no-undefined": true, |
| "-Wl,-z,defs": true, |
| } |
| |
| builder.transformArgs(func(arg builderArg) string { |
| // TODO: This is a bug in the old wrapper to not filter |
| // non user args for gcc. Fix this once we don't compare to the old wrapper anymore. |
| linkerDefinedFlag := ",-z,defs" |
| if builder.target.compilerType != gccType || arg.fromUser { |
| if unsupportedSanitizerFlags[arg.value] { |
| return "" |
| } else if strings.Contains(arg.value, linkerDefinedFlag) { |
| return strings.ReplaceAll(arg.value, linkerDefinedFlag, "") |
| } |
| } |
| return arg.value |
| }) |
| |
| builder.filterArgPairs(func(arg1, arg2 builderArg) bool { |
| return !(arg1.value == "-Wl,-z" && arg2.value == "-Wl,defs") |
| }) |
| } |