Skip to content

Commit 827f573

Browse files
danliew-appledelcypher
authored andcommitted
Teach the Swift front-end to generate code with
"Sanitizer Coverage" with a new flag ``-sanitize-coverage=``. This flag is analogous to Clang's ``-fsanitize-coverage=``. This instrumentation currently requires ASan or TSan to be enabled because the module pass created by ``createSanitizerCoverageModulePass()`` inserts calls into functions found in compiler-rt's "sanitizer_common". "sanitizer_common" is not shipped as an individual library but instead exists in several of the sanitizer runtime libraries so we have to link with one of them to avoid linking errors. The rationale between adding this feature is to allow experimentation with libFuzzer which currently relies on "Sanitizer Coverage" instrumentation.
1 parent a42b940 commit 827f573

File tree

12 files changed

+229
-14
lines changed

12 files changed

+229
-14
lines changed

include/swift/AST/DiagnosticsFrontend.def

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ ERROR(error_unsupported_opt_for_target, none,
5858
ERROR(error_argument_not_allowed_with, none,
5959
"argument '%0' is not allowed with '%1'", (StringRef, StringRef))
6060

61+
ERROR(error_option_requires_sanitizer, none,
62+
"option '%0' requires a sanitizer to be enabled. Use -sanitize= to enable a"
63+
" sanitizer", (StringRef))
64+
65+
ERROR(error_option_missing_required_argument, none,
66+
"option '%0' is missing a required argument (%1)", (StringRef, StringRef))
67+
6168
ERROR(cannot_open_file,none,
6269
"cannot open file '%0' (%1)", (StringRef, StringRef))
6370
ERROR(cannot_open_serialized_file,none,

include/swift/AST/IRGenOptions.h

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020

2121
#include "swift/AST/LinkLibrary.h"
2222
#include "swift/Basic/Sanitizers.h"
23+
// FIXME: This include is just for llvm::SanitizerCoverageOptions. We should
24+
// split the header upstream so we don't include so much.
25+
#include "llvm/Transforms/Instrumentation.h"
2326
#include <string>
2427
#include <vector>
2528

@@ -159,19 +162,21 @@ class IRGenOptions {
159162
/// List of backend command-line options for -embed-bitcode.
160163
std::vector<uint8_t> CmdArgs;
161164

162-
IRGenOptions() : OutputKind(IRGenOutputKind::LLVMAssembly), Verify(true),
163-
Optimize(false), Sanitize(SanitizerKind::None),
164-
DebugInfoKind(IRGenDebugInfoKind::None),
165-
UseJIT(false), DisableLLVMOptzns(false),
166-
DisableLLVMARCOpts(false), DisableLLVMSLPVectorizer(false),
167-
DisableFPElim(true), Playground(false),
168-
EmitStackPromotionChecks(false), GenerateProfile(false),
169-
PrintInlineTree(false), EmbedMode(IRGenEmbedMode::None),
170-
HasValueNamesSetting(false), ValueNames(false),
171-
EnableReflectionMetadata(true), EnableReflectionNames(true),
172-
UseIncrementalLLVMCodeGen(true), UseSwiftCall(false),
173-
CmdArgs()
174-
{}
165+
/// Which sanitizer coverage is turned on.
166+
llvm::SanitizerCoverageOptions SanitizeCoverage;
167+
168+
IRGenOptions()
169+
: OutputKind(IRGenOutputKind::LLVMAssembly), Verify(true),
170+
Optimize(false), Sanitize(SanitizerKind::None),
171+
DebugInfoKind(IRGenDebugInfoKind::None), UseJIT(false),
172+
DisableLLVMOptzns(false), DisableLLVMARCOpts(false),
173+
DisableLLVMSLPVectorizer(false), DisableFPElim(true), Playground(false),
174+
EmitStackPromotionChecks(false), GenerateProfile(false),
175+
PrintInlineTree(false), EmbedMode(IRGenEmbedMode::None),
176+
HasValueNamesSetting(false), ValueNames(false),
177+
EnableReflectionMetadata(true), EnableReflectionNames(true),
178+
UseIncrementalLLVMCodeGen(true), UseSwiftCall(false), CmdArgs(),
179+
SanitizeCoverage(llvm::SanitizerCoverageOptions()) {}
175180

176181
/// Gets the name of the specified output filename.
177182
/// If multiple files are specified, the last one is returned.

include/swift/Option/Options.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,4 +466,10 @@ def sanitize_EQ : CommaJoined<["-"], "sanitize=">,
466466
Flags<[FrontendOption, NoInteractiveOption]>, MetaVarName<"<check>">,
467467
HelpText<"Turn on runtime checks for erroneous behavior.">;
468468

469+
def sanitize_coverage_EQ : CommaJoined<["-"], "sanitize-coverage=">,
470+
Flags<[FrontendOption, NoInteractiveOption]>,
471+
MetaVarName<"<type>">,
472+
HelpText<"Specify the type of coverage instrumentation for Sanitizers and"
473+
" additional options separated by commas">;
474+
469475
include "FrontendOptions.td"

include/swift/Option/SanitizerOptions.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
#include "swift/Basic/Sanitizers.h"
1717
#include "llvm/ADT/Triple.h"
1818
#include "llvm/Option/Arg.h"
19+
// FIXME: This include is just for llvm::SanitizerCoverageOptions. We should
20+
// split the header upstream so we don't include so much.
21+
#include "llvm/Transforms/Instrumentation.h"
1922

2023
namespace swift {
2124
class DiagnosticEngine;
@@ -27,5 +30,11 @@ class DiagnosticEngine;
2730
SanitizerKind parseSanitizerArgValues(const llvm::opt::Arg *A,
2831
const llvm::Triple &Triple,
2932
DiagnosticEngine &Diag);
33+
34+
/// \brief Parses a -sanitize-coverage= argument's value.
35+
llvm::SanitizerCoverageOptions
36+
parseSanitizerCoverageArgValue(const llvm::opt::Arg *A,
37+
const llvm::Triple &Triple,
38+
DiagnosticEngine &Diag, SanitizerKind sanitizer);
3039
}
3140
#endif // SWIFT_OPTIONS_SANITIZER_OPTIONS_H

lib/Driver/Driver.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,6 +1104,11 @@ void Driver::buildOutputInfo(const ToolChain &TC, const DerivedArgList &Args,
11041104
OI.SelectedSanitizer = SanitizerKind::None;
11051105
if (const Arg *A = Args.getLastArg(options::OPT_sanitize_EQ))
11061106
OI.SelectedSanitizer = parseSanitizerArgValues(A, TC.getTriple(), Diags);
1107+
1108+
// Check that the sanitizer coverage flags are supported if supplied.
1109+
if (const Arg *A = Args.getLastArg(options::OPT_sanitize_coverage_EQ))
1110+
(void)parseSanitizerCoverageArgValue(A, TC.getTriple(), Diags,
1111+
OI.SelectedSanitizer);
11071112
}
11081113

11091114
void Driver::buildActions(const ToolChain &TC,

lib/Driver/ToolChains.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ static void addCommonFrontendArgs(const ToolChain &TC,
131131
inputArgs.AddLastArg(arguments, options::OPT_profile_coverage_mapping);
132132
inputArgs.AddLastArg(arguments, options::OPT_warnings_as_errors);
133133
inputArgs.AddLastArg(arguments, options::OPT_sanitize_EQ);
134+
inputArgs.AddLastArg(arguments, options::OPT_sanitize_coverage_EQ);
134135

135136
// Pass on any build config options
136137
inputArgs.AddAllArgs(arguments, options::OPT_D);

lib/Frontend/CompilerInvocation.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1266,6 +1266,11 @@ static bool ParseIRGenArgs(IRGenOptions &Opts, ArgList &Args,
12661266
Opts.Sanitize = parseSanitizerArgValues(A, Triple, Diags);
12671267
}
12681268

1269+
if (const Arg *A = Args.getLastArg(options::OPT_sanitize_coverage_EQ)) {
1270+
Opts.SanitizeCoverage =
1271+
parseSanitizerCoverageArgValue(A, Triple, Diags, Opts.Sanitize);
1272+
}
1273+
12691274
if (Args.hasArg(OPT_disable_reflection_metadata)) {
12701275
Opts.EnableReflectionMetadata = false;
12711276
Opts.EnableReflectionNames = false;

lib/IRGen/IRGen.cpp

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,16 @@ using namespace swift;
6666
using namespace irgen;
6767
using namespace llvm;
6868

69+
namespace {
70+
// We need this to access IRGenOptions from extension functions
71+
class PassManagerBuilderWrapper : public PassManagerBuilder {
72+
public:
73+
const IRGenOptions &IRGOpts;
74+
PassManagerBuilderWrapper(const IRGenOptions &IRGOpts)
75+
: PassManagerBuilder(), IRGOpts(IRGOpts) {}
76+
};
77+
}
78+
6979
static void addSwiftARCOptPass(const PassManagerBuilder &Builder,
7080
PassManagerBase &PM) {
7181
if (Builder.OptLevel > 0)
@@ -101,6 +111,14 @@ static void addThreadSanitizerPass(const PassManagerBuilder &Builder,
101111
PM.add(createThreadSanitizerPass());
102112
}
103113

114+
static void addSanitizerCoveragePass(const PassManagerBuilder &Builder,
115+
legacy::PassManagerBase &PM) {
116+
const PassManagerBuilderWrapper &BuilderWrapper =
117+
static_cast<const PassManagerBuilderWrapper &>(Builder);
118+
PM.add(createSanitizerCoverageModulePass(
119+
BuilderWrapper.IRGOpts.SanitizeCoverage));
120+
}
121+
104122
std::tuple<llvm::TargetOptions, std::string, std::vector<std::string>>
105123
swift::getIRTargetOptions(IRGenOptions &Opts, ASTContext &Ctx) {
106124
// Things that maybe we should collect from the command line:
@@ -129,7 +147,7 @@ void swift::performLLVMOptimizations(IRGenOptions &Opts, llvm::Module *Module,
129147
SharedTimer timer("LLVM optimization");
130148

131149
// Set up a pipeline.
132-
PassManagerBuilder PMBuilder;
150+
PassManagerBuilderWrapper PMBuilder(Opts);
133151

134152
if (Opts.Optimize && !Opts.DisableLLVMOptzns) {
135153
PMBuilder.OptLevel = 3;
@@ -169,6 +187,15 @@ void swift::performLLVMOptimizations(IRGenOptions &Opts, llvm::Module *Module,
169187
PMBuilder.addExtension(PassManagerBuilder::EP_EnabledOnOptLevel0,
170188
addThreadSanitizerPass);
171189
}
190+
191+
if (Opts.SanitizeCoverage.CoverageType !=
192+
llvm::SanitizerCoverageOptions::SCK_None) {
193+
PMBuilder.addExtension(PassManagerBuilder::EP_OptimizerLast,
194+
addSanitizerCoveragePass);
195+
PMBuilder.addExtension(PassManagerBuilder::EP_EnabledOnOptLevel0,
196+
addSanitizerCoveragePass);
197+
}
198+
172199
PMBuilder.addExtension(PassManagerBuilder::EP_OptimizerLast,
173200
addSwiftMergeFunctionsPass);
174201

lib/Option/SanitizerOptions.cpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,67 @@ static StringRef toStringRef(const SanitizerKind kind) {
3434
llvm_unreachable("Unsupported sanitizer");
3535
}
3636

37+
llvm::SanitizerCoverageOptions swift::parseSanitizerCoverageArgValue(
38+
const llvm::opt::Arg *A, const llvm::Triple &Triple,
39+
DiagnosticEngine &Diags, SanitizerKind sanitizer) {
40+
41+
llvm::SanitizerCoverageOptions opts;
42+
// The coverage names here follow the names used by clang's
43+
// ``-fsanitize-coverage=`` flag.
44+
for (int i = 0, n = A->getNumValues(); i != n; ++i) {
45+
if (opts.CoverageType == llvm::SanitizerCoverageOptions::SCK_None) {
46+
opts.CoverageType =
47+
llvm::StringSwitch<llvm::SanitizerCoverageOptions::Type>(
48+
A->getValue(i))
49+
.Case("func", llvm::SanitizerCoverageOptions::SCK_Function)
50+
.Case("bb", llvm::SanitizerCoverageOptions::SCK_BB)
51+
.Case("edge", llvm::SanitizerCoverageOptions::SCK_Edge)
52+
.Default(llvm::SanitizerCoverageOptions::SCK_None);
53+
if (opts.CoverageType != llvm::SanitizerCoverageOptions::SCK_None)
54+
continue;
55+
}
56+
57+
if (StringRef(A->getValue(i)) == "indirect-calls") {
58+
opts.IndirectCalls = true;
59+
continue;
60+
} else if (StringRef(A->getValue(i)) == "trace-bb") {
61+
opts.TraceBB = true;
62+
continue;
63+
} else if (StringRef(A->getValue(i)) == "trace-cmp") {
64+
opts.TraceCmp = true;
65+
continue;
66+
} else if (StringRef(A->getValue(i)) == "8bit-counters") {
67+
opts.Use8bitCounters = true;
68+
continue;
69+
}
70+
71+
// Argument is not supported.
72+
Diags.diagnose(SourceLoc(), diag::error_unsupported_option_argument,
73+
A->getOption().getPrefixedName(), A->getValue(i));
74+
return llvm::SanitizerCoverageOptions();
75+
}
76+
77+
if (opts.CoverageType == llvm::SanitizerCoverageOptions::SCK_None) {
78+
Diags.diagnose(SourceLoc(), diag::error_option_missing_required_argument,
79+
A->getSpelling(), "\"func\", \"bb\", \"edge\"");
80+
return llvm::SanitizerCoverageOptions();
81+
}
82+
83+
// Running the sanitizer coverage pass will add undefined symbols to
84+
// functions in compiler-rt's "sanitizer_common". "sanitizer_common" isn't
85+
// shipped as a separate library we can link with. However those are defined
86+
// in the various sanitizer runtime libraries so we require that we are
87+
// doing a sanitized build so we pick up the required functions during
88+
// linking.
89+
if (opts.CoverageType != llvm::SanitizerCoverageOptions::SCK_None &&
90+
sanitizer == SanitizerKind::None) {
91+
Diags.diagnose(SourceLoc(), diag::error_option_requires_sanitizer,
92+
A->getSpelling());
93+
return llvm::SanitizerCoverageOptions();
94+
}
95+
return opts;
96+
}
97+
3798
SanitizerKind swift::parseSanitizerArgValues(const llvm::opt::Arg *A,
3899
const llvm::Triple &Triple,
39100
DiagnosticEngine &Diags) {

test/Driver/sanitize_coverage.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// XFAIL: linux
2+
// Different sanitizer coverage types
3+
// RUN: %swiftc_driver -driver-print-jobs -sanitize-coverage=func -sanitize=address %s | FileCheck -check-prefix=SANCOV_FUNC %s
4+
// RUN: %swiftc_driver -driver-print-jobs -sanitize-coverage=bb -sanitize=address %s | FileCheck -check-prefix=SANCOV_BB %s
5+
// RUN: %swiftc_driver -driver-print-jobs -sanitize-coverage=edge -sanitize=address %s | FileCheck -check-prefix=SANCOV_EDGE %s
6+
7+
// Try some options
8+
// RUN: %swiftc_driver -driver-print-jobs -sanitize-coverage=edge,indirect-calls,trace-bb,trace-cmp,8bit-counters -sanitize=address %s | FileCheck -check-prefix=SANCOV_EDGE_WITH_OPTIONS %s
9+
10+
// Invalid command line arguments
11+
// RUN: not %swiftc_driver -driver-print-jobs -sanitize-coverage=func %s 2>&1 | FileCheck -check-prefix=SANCOV_WITHOUT_XSAN %s
12+
// RUN: not %swiftc_driver -driver-print-jobs -sanitize-coverage=bb %s 2>&1 | FileCheck -check-prefix=SANCOV_WITHOUT_XSAN %s
13+
// RUN: not %swiftc_driver -driver-print-jobs -sanitize-coverage=edge %s 2>&1 | FileCheck -check-prefix=SANCOV_WITHOUT_XSAN %s
14+
// RUN: not %swiftc_driver -driver-print-jobs -sanitize=address -sanitize-coverage=unknown %s 2>&1 | FileCheck -check-prefix=SANCOV_BAD_ARG %s
15+
// RUN: not %swiftc_driver -driver-print-jobs -sanitize=address -sanitize-coverage=indirect-calls %s 2>&1 | FileCheck -check-prefix=SANCOV_MISSING_ARG %s
16+
// RUN: not %swiftc_driver -driver-print-jobs -sanitize=address -sanitize-coverage=trace-bb %s 2>&1 | FileCheck -check-prefix=SANCOV_MISSING_ARG %s
17+
// RUN: not %swiftc_driver -driver-print-jobs -sanitize=address -sanitize-coverage=trace-cmp %s 2>&1 | FileCheck -check-prefix=SANCOV_MISSING_ARG %s
18+
// RUN: not %swiftc_driver -driver-print-jobs -sanitize=address -sanitize-coverage=8bit-counters %s 2>&1 | FileCheck -check-prefix=SANCOV_MISSING_ARG %s
19+
20+
// SANCOV_FUNC: swift
21+
// SANCOV_FUNC-DAG: -sanitize-coverage=func
22+
// SANCOV_FUNC-DAG: -sanitize=address
23+
24+
// SANCOV_BB: swift
25+
// SANCOV_BB-DAG: -sanitize-coverage=bb
26+
// SANCOV_BB-DAG: -sanitize=address
27+
28+
// SANCOV_EDGE: swift
29+
// SANCOV_EDGE-DAG: -sanitize-coverage=edge
30+
// SANCOV_EDGE-DAG: -sanitize=address
31+
32+
// SANCOV_EDGE_WITH_OPTIONS: swift
33+
// SANCOV_EDGE_WITH_OPTIONS-DAG: -sanitize-coverage=edge,indirect-calls,trace-bb,trace-cmp,8bit-counters
34+
// SANCOV_EDGE_WITH_OPTIONS-DAG: -sanitize=address
35+
36+
// SANCOV_WITHOUT_XSAN: option '-sanitize-coverage=' requires a sanitizer to be enabled. Use -sanitize= to enable a sanitizer
37+
// SANCOV_BAD_ARG: unsupported argument 'unknown' to option '-sanitize-coverage='
38+
// SANCOV_MISSING_ARG: error: option '-sanitize-coverage=' is missing a required argument ("func", "bb", "edge")

test/IRGen/sanitize_coverage.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// XFAIL: linux
2+
// RUN: %target-swift-frontend -emit-ir -sanitize=address -sanitize-coverage=func %s | FileCheck %s -check-prefix=SANCOV
3+
// RUN: %target-swift-frontend -emit-ir -sanitize=address -sanitize-coverage=bb %s | FileCheck %s -check-prefix=SANCOV
4+
// RUN: %target-swift-frontend -emit-ir -sanitize=address -sanitize-coverage=edge %s | FileCheck %s -check-prefix=SANCOV
5+
// RUN: %target-swift-frontend -emit-ir -sanitize=address -sanitize-coverage=edge,trace-cmp %s | FileCheck %s -check-prefix=SANCOV -check-prefix=SANCOV_TRACE_CMP
6+
// RUN: %target-swift-frontend -emit-ir -sanitize=address -sanitize-coverage=edge,trace-bb %s | FileCheck %s -check-prefix=SANCOV -check-prefix=SANCOV_TRACE_BB
7+
// RUN: %target-swift-frontend -emit-ir -sanitize=address -sanitize-coverage=edge,indirect-calls %s | FileCheck %s -check-prefix=SANCOV -check-prefix=SANCOV_INDIRECT_CALLS
8+
// RUN: %target-swift-frontend -emit-ir -sanitize=address -sanitize-coverage=edge,8bit-counters %s | FileCheck %s -check-prefix=SANCOV -check-prefix=SANCOV_8BIT_COUNTERS
9+
10+
import Darwin
11+
12+
// FIXME: We should have a reliable way of triggering an indirect call in the
13+
// LLVM IR generated from this code.
14+
func test() {
15+
// Use random numbers so the compiler can't constant fold
16+
let x = arc4random()
17+
let y = arc4random()
18+
// Comparison is to trigger insertion of __sanitizer_cov_trace_cmp
19+
let z = x == y
20+
print("\(z)")
21+
}
22+
23+
test()
24+
25+
// FIXME: We need a way to distinguish the different types of coverage instrumentation
26+
// that isn't really fragile. For now just check there's at least one call to the function
27+
// used to increment coverage count at a particular PC.
28+
// SANCOV: call void @__sanitizer_cov
29+
30+
// SANCOV_TRACE_CMP: call void @__sanitizer_cov_trace_cmp
31+
// SANCOV_TRACE_BB: call void @__sanitizer_cov_trace_basic_block
32+
// SANCOV_INDIRECT_CALLS: call void @__sanitizer_cov_indir_call16
33+
// SANCOV_8BIT_COUNTERS: @__sancov_gen_cov_counter
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// RUN: %target-build-swift -sanitize=address -sanitize-coverage=edge %s -o %t_binary
2+
// RUN: rm -rf %t_coverage_dir
3+
// RUN: mkdir %t_coverage_dir
4+
// RUN: ASAN_OPTIONS=abort_on_error=0,coverage=1,coverage_dir=%t_coverage_dir %t_binary
5+
// check the coverage file exists
6+
// RUN: ls %t_coverage_dir/sanitizer_coverage.swift.tmp_binary.*.sancov > /dev/null
7+
8+
// REQUIRES: executable_test
9+
// REQUIRES: asan_runtime
10+
// For now restrict this test to platforms where we know this test will pass
11+
// REQUIRES: CPU=x86_64
12+
// REQUIRES: OS=macosx
13+
14+
func sayHello() {
15+
print("Hello")
16+
}
17+
18+
sayHello()

0 commit comments

Comments
 (0)