Skip to content

Commit 83da10b

Browse files
committed
Merge pull request #2527 from delcypher/sanitizer_coverage
Teach the Swift front-end to generate code with sanitizer coverage
2 parents 52de534 + 827f573 commit 83da10b

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)