Skip to content

Teach the Swift front-end to generate code with sanitizer coverage #2527

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 28, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions include/swift/AST/DiagnosticsFrontend.def
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ ERROR(error_unsupported_opt_for_target, none,
ERROR(error_argument_not_allowed_with, none,
"argument '%0' is not allowed with '%1'", (StringRef, StringRef))

ERROR(error_option_requires_sanitizer, none,
"option '%0' requires a sanitizer to be enabled. Use -sanitize= to enable a"
" sanitizer", (StringRef))

ERROR(error_option_missing_required_argument, none,
"option '%0' is missing a required argument (%1)", (StringRef, StringRef))

ERROR(cannot_open_file,none,
"cannot open file '%0' (%1)", (StringRef, StringRef))
ERROR(cannot_open_serialized_file,none,
Expand Down
31 changes: 18 additions & 13 deletions include/swift/AST/IRGenOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@

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

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

IRGenOptions() : OutputKind(IRGenOutputKind::LLVMAssembly), Verify(true),
Optimize(false), Sanitize(SanitizerKind::None),
DebugInfoKind(IRGenDebugInfoKind::None),
UseJIT(false), DisableLLVMOptzns(false),
DisableLLVMARCOpts(false), DisableLLVMSLPVectorizer(false),
DisableFPElim(true), Playground(false),
EmitStackPromotionChecks(false), GenerateProfile(false),
PrintInlineTree(false), EmbedMode(IRGenEmbedMode::None),
HasValueNamesSetting(false), ValueNames(false),
EnableReflectionMetadata(true), EnableReflectionNames(true),
UseIncrementalLLVMCodeGen(true), UseSwiftCall(false),
CmdArgs()
{}
/// Which sanitizer coverage is turned on.
llvm::SanitizerCoverageOptions SanitizeCoverage;

IRGenOptions()
: OutputKind(IRGenOutputKind::LLVMAssembly), Verify(true),
Optimize(false), Sanitize(SanitizerKind::None),
DebugInfoKind(IRGenDebugInfoKind::None), UseJIT(false),
DisableLLVMOptzns(false), DisableLLVMARCOpts(false),
DisableLLVMSLPVectorizer(false), DisableFPElim(true), Playground(false),
EmitStackPromotionChecks(false), GenerateProfile(false),
PrintInlineTree(false), EmbedMode(IRGenEmbedMode::None),
HasValueNamesSetting(false), ValueNames(false),
EnableReflectionMetadata(true), EnableReflectionNames(true),
UseIncrementalLLVMCodeGen(true), UseSwiftCall(false), CmdArgs(),
SanitizeCoverage(llvm::SanitizerCoverageOptions()) {}

/// Gets the name of the specified output filename.
/// If multiple files are specified, the last one is returned.
Expand Down
6 changes: 6 additions & 0 deletions include/swift/Option/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -466,4 +466,10 @@ def sanitize_EQ : CommaJoined<["-"], "sanitize=">,
Flags<[FrontendOption, NoInteractiveOption]>, MetaVarName<"<check>">,
HelpText<"Turn on runtime checks for erroneous behavior.">;

def sanitize_coverage_EQ : CommaJoined<["-"], "sanitize-coverage=">,
Flags<[FrontendOption, NoInteractiveOption]>,
MetaVarName<"<type>">,
HelpText<"Specify the type of coverage instrumentation for Sanitizers and"
" additional options separated by commas">;

include "FrontendOptions.td"
9 changes: 9 additions & 0 deletions include/swift/Option/SanitizerOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
#include "swift/Basic/Sanitizers.h"
#include "llvm/ADT/Triple.h"
#include "llvm/Option/Arg.h"
// FIXME: This include is just for llvm::SanitizerCoverageOptions. We should
// split the header upstream so we don't include so much.
#include "llvm/Transforms/Instrumentation.h"

namespace swift {
class DiagnosticEngine;
Expand All @@ -27,5 +30,11 @@ class DiagnosticEngine;
SanitizerKind parseSanitizerArgValues(const llvm::opt::Arg *A,
const llvm::Triple &Triple,
DiagnosticEngine &Diag);

/// \brief Parses a -sanitize-coverage= argument's value.
llvm::SanitizerCoverageOptions
parseSanitizerCoverageArgValue(const llvm::opt::Arg *A,
const llvm::Triple &Triple,
DiagnosticEngine &Diag, SanitizerKind sanitizer);
}
#endif // SWIFT_OPTIONS_SANITIZER_OPTIONS_H
5 changes: 5 additions & 0 deletions lib/Driver/Driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1104,6 +1104,11 @@ void Driver::buildOutputInfo(const ToolChain &TC, const DerivedArgList &Args,
OI.SelectedSanitizer = SanitizerKind::None;
if (const Arg *A = Args.getLastArg(options::OPT_sanitize_EQ))
OI.SelectedSanitizer = parseSanitizerArgValues(A, TC.getTriple(), Diags);

// Check that the sanitizer coverage flags are supported if supplied.
if (const Arg *A = Args.getLastArg(options::OPT_sanitize_coverage_EQ))
(void)parseSanitizerCoverageArgValue(A, TC.getTriple(), Diags,
OI.SelectedSanitizer);
}

void Driver::buildActions(const ToolChain &TC,
Expand Down
1 change: 1 addition & 0 deletions lib/Driver/ToolChains.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ static void addCommonFrontendArgs(const ToolChain &TC,
inputArgs.AddLastArg(arguments, options::OPT_profile_coverage_mapping);
inputArgs.AddLastArg(arguments, options::OPT_warnings_as_errors);
inputArgs.AddLastArg(arguments, options::OPT_sanitize_EQ);
inputArgs.AddLastArg(arguments, options::OPT_sanitize_coverage_EQ);

// Pass on any build config options
inputArgs.AddAllArgs(arguments, options::OPT_D);
Expand Down
5 changes: 5 additions & 0 deletions lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1266,6 +1266,11 @@ static bool ParseIRGenArgs(IRGenOptions &Opts, ArgList &Args,
Opts.Sanitize = parseSanitizerArgValues(A, Triple, Diags);
}

if (const Arg *A = Args.getLastArg(options::OPT_sanitize_coverage_EQ)) {
Opts.SanitizeCoverage =
parseSanitizerCoverageArgValue(A, Triple, Diags, Opts.Sanitize);
}

if (Args.hasArg(OPT_disable_reflection_metadata)) {
Opts.EnableReflectionMetadata = false;
Opts.EnableReflectionNames = false;
Expand Down
29 changes: 28 additions & 1 deletion lib/IRGen/IRGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ using namespace swift;
using namespace irgen;
using namespace llvm;

namespace {
// We need this to access IRGenOptions from extension functions
class PassManagerBuilderWrapper : public PassManagerBuilder {
public:
const IRGenOptions &IRGOpts;
PassManagerBuilderWrapper(const IRGenOptions &IRGOpts)
: PassManagerBuilder(), IRGOpts(IRGOpts) {}
};
}

static void addSwiftARCOptPass(const PassManagerBuilder &Builder,
PassManagerBase &PM) {
if (Builder.OptLevel > 0)
Expand Down Expand Up @@ -101,6 +111,14 @@ static void addThreadSanitizerPass(const PassManagerBuilder &Builder,
PM.add(createThreadSanitizerPass());
}

static void addSanitizerCoveragePass(const PassManagerBuilder &Builder,
legacy::PassManagerBase &PM) {
const PassManagerBuilderWrapper &BuilderWrapper =
static_cast<const PassManagerBuilderWrapper &>(Builder);
PM.add(createSanitizerCoverageModulePass(
BuilderWrapper.IRGOpts.SanitizeCoverage));
}

std::tuple<llvm::TargetOptions, std::string, std::vector<std::string>>
swift::getIRTargetOptions(IRGenOptions &Opts, ASTContext &Ctx) {
// Things that maybe we should collect from the command line:
Expand Down Expand Up @@ -129,7 +147,7 @@ void swift::performLLVMOptimizations(IRGenOptions &Opts, llvm::Module *Module,
SharedTimer timer("LLVM optimization");

// Set up a pipeline.
PassManagerBuilder PMBuilder;
PassManagerBuilderWrapper PMBuilder(Opts);

if (Opts.Optimize && !Opts.DisableLLVMOptzns) {
PMBuilder.OptLevel = 3;
Expand Down Expand Up @@ -169,6 +187,15 @@ void swift::performLLVMOptimizations(IRGenOptions &Opts, llvm::Module *Module,
PMBuilder.addExtension(PassManagerBuilder::EP_EnabledOnOptLevel0,
addThreadSanitizerPass);
}

if (Opts.SanitizeCoverage.CoverageType !=
llvm::SanitizerCoverageOptions::SCK_None) {
PMBuilder.addExtension(PassManagerBuilder::EP_OptimizerLast,
addSanitizerCoveragePass);
PMBuilder.addExtension(PassManagerBuilder::EP_EnabledOnOptLevel0,
addSanitizerCoveragePass);
}

PMBuilder.addExtension(PassManagerBuilder::EP_OptimizerLast,
addSwiftMergeFunctionsPass);

Expand Down
61 changes: 61 additions & 0 deletions lib/Option/SanitizerOptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,67 @@ static StringRef toStringRef(const SanitizerKind kind) {
llvm_unreachable("Unsupported sanitizer");
}

llvm::SanitizerCoverageOptions swift::parseSanitizerCoverageArgValue(
const llvm::opt::Arg *A, const llvm::Triple &Triple,
DiagnosticEngine &Diags, SanitizerKind sanitizer) {

llvm::SanitizerCoverageOptions opts;
// The coverage names here follow the names used by clang's
// ``-fsanitize-coverage=`` flag.
for (int i = 0, n = A->getNumValues(); i != n; ++i) {
if (opts.CoverageType == llvm::SanitizerCoverageOptions::SCK_None) {
opts.CoverageType =
llvm::StringSwitch<llvm::SanitizerCoverageOptions::Type>(
A->getValue(i))
.Case("func", llvm::SanitizerCoverageOptions::SCK_Function)
.Case("bb", llvm::SanitizerCoverageOptions::SCK_BB)
.Case("edge", llvm::SanitizerCoverageOptions::SCK_Edge)
.Default(llvm::SanitizerCoverageOptions::SCK_None);
if (opts.CoverageType != llvm::SanitizerCoverageOptions::SCK_None)
continue;
}

if (StringRef(A->getValue(i)) == "indirect-calls") {
opts.IndirectCalls = true;
continue;
} else if (StringRef(A->getValue(i)) == "trace-bb") {
opts.TraceBB = true;
continue;
} else if (StringRef(A->getValue(i)) == "trace-cmp") {
opts.TraceCmp = true;
continue;
} else if (StringRef(A->getValue(i)) == "8bit-counters") {
opts.Use8bitCounters = true;
continue;
}

// Argument is not supported.
Diags.diagnose(SourceLoc(), diag::error_unsupported_option_argument,
A->getOption().getPrefixedName(), A->getValue(i));
return llvm::SanitizerCoverageOptions();
}

if (opts.CoverageType == llvm::SanitizerCoverageOptions::SCK_None) {
Diags.diagnose(SourceLoc(), diag::error_option_missing_required_argument,
A->getSpelling(), "\"func\", \"bb\", \"edge\"");
return llvm::SanitizerCoverageOptions();
}

// Running the sanitizer coverage pass will add undefined symbols to
// functions in compiler-rt's "sanitizer_common". "sanitizer_common" isn't
// shipped as a separate library we can link with. However those are defined
// in the various sanitizer runtime libraries so we require that we are
// doing a sanitized build so we pick up the required functions during
// linking.
if (opts.CoverageType != llvm::SanitizerCoverageOptions::SCK_None &&
sanitizer == SanitizerKind::None) {
Diags.diagnose(SourceLoc(), diag::error_option_requires_sanitizer,
A->getSpelling());
return llvm::SanitizerCoverageOptions();
}
return opts;
}

SanitizerKind swift::parseSanitizerArgValues(const llvm::opt::Arg *A,
const llvm::Triple &Triple,
DiagnosticEngine &Diags) {
Expand Down
38 changes: 38 additions & 0 deletions test/Driver/sanitize_coverage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// XFAIL: linux
// Different sanitizer coverage types
// RUN: %swiftc_driver -driver-print-jobs -sanitize-coverage=func -sanitize=address %s | FileCheck -check-prefix=SANCOV_FUNC %s
// RUN: %swiftc_driver -driver-print-jobs -sanitize-coverage=bb -sanitize=address %s | FileCheck -check-prefix=SANCOV_BB %s
// RUN: %swiftc_driver -driver-print-jobs -sanitize-coverage=edge -sanitize=address %s | FileCheck -check-prefix=SANCOV_EDGE %s

// Try some options
// 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

// Invalid command line arguments
// RUN: not %swiftc_driver -driver-print-jobs -sanitize-coverage=func %s 2>&1 | FileCheck -check-prefix=SANCOV_WITHOUT_XSAN %s
// RUN: not %swiftc_driver -driver-print-jobs -sanitize-coverage=bb %s 2>&1 | FileCheck -check-prefix=SANCOV_WITHOUT_XSAN %s
// RUN: not %swiftc_driver -driver-print-jobs -sanitize-coverage=edge %s 2>&1 | FileCheck -check-prefix=SANCOV_WITHOUT_XSAN %s
// RUN: not %swiftc_driver -driver-print-jobs -sanitize=address -sanitize-coverage=unknown %s 2>&1 | FileCheck -check-prefix=SANCOV_BAD_ARG %s
// RUN: not %swiftc_driver -driver-print-jobs -sanitize=address -sanitize-coverage=indirect-calls %s 2>&1 | FileCheck -check-prefix=SANCOV_MISSING_ARG %s
// RUN: not %swiftc_driver -driver-print-jobs -sanitize=address -sanitize-coverage=trace-bb %s 2>&1 | FileCheck -check-prefix=SANCOV_MISSING_ARG %s
// RUN: not %swiftc_driver -driver-print-jobs -sanitize=address -sanitize-coverage=trace-cmp %s 2>&1 | FileCheck -check-prefix=SANCOV_MISSING_ARG %s
// RUN: not %swiftc_driver -driver-print-jobs -sanitize=address -sanitize-coverage=8bit-counters %s 2>&1 | FileCheck -check-prefix=SANCOV_MISSING_ARG %s

// SANCOV_FUNC: swift
// SANCOV_FUNC-DAG: -sanitize-coverage=func
// SANCOV_FUNC-DAG: -sanitize=address

// SANCOV_BB: swift
// SANCOV_BB-DAG: -sanitize-coverage=bb
// SANCOV_BB-DAG: -sanitize=address

// SANCOV_EDGE: swift
// SANCOV_EDGE-DAG: -sanitize-coverage=edge
// SANCOV_EDGE-DAG: -sanitize=address

// SANCOV_EDGE_WITH_OPTIONS: swift
// SANCOV_EDGE_WITH_OPTIONS-DAG: -sanitize-coverage=edge,indirect-calls,trace-bb,trace-cmp,8bit-counters
// SANCOV_EDGE_WITH_OPTIONS-DAG: -sanitize=address

// SANCOV_WITHOUT_XSAN: option '-sanitize-coverage=' requires a sanitizer to be enabled. Use -sanitize= to enable a sanitizer
// SANCOV_BAD_ARG: unsupported argument 'unknown' to option '-sanitize-coverage='
// SANCOV_MISSING_ARG: error: option '-sanitize-coverage=' is missing a required argument ("func", "bb", "edge")
33 changes: 33 additions & 0 deletions test/IRGen/sanitize_coverage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// XFAIL: linux
// RUN: %target-swift-frontend -emit-ir -sanitize=address -sanitize-coverage=func %s | FileCheck %s -check-prefix=SANCOV
// RUN: %target-swift-frontend -emit-ir -sanitize=address -sanitize-coverage=bb %s | FileCheck %s -check-prefix=SANCOV
// RUN: %target-swift-frontend -emit-ir -sanitize=address -sanitize-coverage=edge %s | FileCheck %s -check-prefix=SANCOV
// RUN: %target-swift-frontend -emit-ir -sanitize=address -sanitize-coverage=edge,trace-cmp %s | FileCheck %s -check-prefix=SANCOV -check-prefix=SANCOV_TRACE_CMP
// RUN: %target-swift-frontend -emit-ir -sanitize=address -sanitize-coverage=edge,trace-bb %s | FileCheck %s -check-prefix=SANCOV -check-prefix=SANCOV_TRACE_BB
// RUN: %target-swift-frontend -emit-ir -sanitize=address -sanitize-coverage=edge,indirect-calls %s | FileCheck %s -check-prefix=SANCOV -check-prefix=SANCOV_INDIRECT_CALLS
// RUN: %target-swift-frontend -emit-ir -sanitize=address -sanitize-coverage=edge,8bit-counters %s | FileCheck %s -check-prefix=SANCOV -check-prefix=SANCOV_8BIT_COUNTERS

import Darwin

// FIXME: We should have a reliable way of triggering an indirect call in the
// LLVM IR generated from this code.
func test() {
// Use random numbers so the compiler can't constant fold
let x = arc4random()
let y = arc4random()
// Comparison is to trigger insertion of __sanitizer_cov_trace_cmp
let z = x == y
print("\(z)")
}

test()

// FIXME: We need a way to distinguish the different types of coverage instrumentation
// that isn't really fragile. For now just check there's at least one call to the function
// used to increment coverage count at a particular PC.
// SANCOV: call void @__sanitizer_cov

// SANCOV_TRACE_CMP: call void @__sanitizer_cov_trace_cmp
// SANCOV_TRACE_BB: call void @__sanitizer_cov_trace_basic_block
// SANCOV_INDIRECT_CALLS: call void @__sanitizer_cov_indir_call16
// SANCOV_8BIT_COUNTERS: @__sancov_gen_cov_counter
18 changes: 18 additions & 0 deletions test/Sanitizers/sanitizer_coverage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// RUN: %target-build-swift -sanitize=address -sanitize-coverage=edge %s -o %t_binary
// RUN: rm -rf %t_coverage_dir
// RUN: mkdir %t_coverage_dir
// RUN: ASAN_OPTIONS=abort_on_error=0,coverage=1,coverage_dir=%t_coverage_dir %t_binary
// check the coverage file exists
// RUN: ls %t_coverage_dir/sanitizer_coverage.swift.tmp_binary.*.sancov > /dev/null

// REQUIRES: executable_test
// REQUIRES: asan_runtime
// For now restrict this test to platforms where we know this test will pass
// REQUIRES: CPU=x86_64
// REQUIRES: OS=macosx

func sayHello() {
print("Hello")
}

sayHello()