Replace -sanitize with -fsanitize and add -fsanitize-coverage. (#2149)

This reworks the sanitizer interface to be identical with Clang's.
What's left to do is pass the correct runtime libraries to the linker.
This commit is contained in:
Johan Engelen 2017-06-15 17:19:21 +02:00 committed by kinke
parent b82d87d6ee
commit 22b1accb59
14 changed files with 388 additions and 37 deletions

View file

@ -334,6 +334,7 @@ file(GLOB IR_HDR ir/*.h)
set(DRV_SRC
driver/cache.cpp
driver/cl_options.cpp
driver/cl_options_sanitizers.cpp
driver/codegenerator.cpp
driver/configfile.cpp
driver/dcomputecodegenerator.cpp
@ -352,6 +353,7 @@ set(DRV_HDR
driver/cache.h
driver/cache_pruning.h
driver/cl_options.h
driver/cl_options_sanitizers.h
driver/codegenerator.h
driver/configfile.h
driver/dcomputecodegenerator.h

View file

@ -32,6 +32,7 @@
#include "ddmd/errors.h"
#include "driver/cache_pruning.h"
#include "driver/cl_options.h"
#include "driver/cl_options_sanitizers.h"
#include "driver/ldc-version.h"
#include "gen/logger.h"
#include "gen/optimizer.h"
@ -300,6 +301,7 @@ void outputIR2ObjRelevantCmdlineArgs(llvm::raw_ostream &hash_os) {
// the possibility of different default settings on different platforms (while
// sharing the cache).
outputOptimizationSettings(hash_os);
opts::outputSanitizerSettings(hash_os);
hash_os << opts::mCPU;
for (auto &attr : opts::mAttrs) {
hash_os << attr;

View file

@ -0,0 +1,183 @@
//===-- cl_options_sanitizers.cpp -------------------------------*- C++ -*-===//
//
// LDC the LLVM D compiler
//
// This file is distributed under the BSD-style LDC license. See the LICENSE
// file for details.
//
//===----------------------------------------------------------------------===//
//
// Creates and handles the -fsanitize=... and -fsanitize-coverage=...
// commandline options.
//
//===----------------------------------------------------------------------===//
#include "driver/cl_options_sanitizers.h"
#include "ddmd/errors.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/Support/raw_ostream.h"
namespace {
using namespace opts;
cl::list<std::string> fSanitize(
"fsanitize", cl::CommaSeparated,
cl::desc("Turn on runtime checks for various forms of undefined or "
"suspicious behavior."),
cl::value_desc("checks"));
#ifdef ENABLE_COVERAGE_SANITIZER
cl::list<std::string> fSanitizeCoverage(
"fsanitize-coverage", cl::CommaSeparated,
cl::desc("Specify the type of coverage instrumentation for -fsanitize"),
cl::value_desc("type"));
llvm::SanitizerCoverageOptions sanitizerCoverageOptions;
#endif
// Parse sanitizer name passed on commandline and return the corresponding
// sanitizer bits.
SanitizerCheck parseSanitizerName(llvm::StringRef name) {
SanitizerCheck parsedValue =
llvm::StringSwitch<SanitizerCheck>(name)
.Case("address", AddressSanitizer)
.Case("fuzzer", FuzzSanitizer)
.Case("memory", MemorySanitizer)
.Case("thread", ThreadSanitizer)
.Default(NoneSanitizer);
if (parsedValue == NoneSanitizer) {
error(Loc(), "Unrecognized -fsanitize value '%s'.", name.str().c_str());
}
return parsedValue;
}
SanitizerBits parseFSanitizeCmdlineParameter() {
SanitizerBits retval = 0;
for (const auto &name : fSanitize) {
SanitizerCheck check = parseSanitizerName(name);
retval |= SanitizerBits(check);
}
return retval;
}
#ifdef ENABLE_COVERAGE_SANITIZER
void parseFSanitizeCoverageParameter(llvm::StringRef name,
llvm::SanitizerCoverageOptions &opts) {
if (name == "func") {
opts.CoverageType = std::max(opts.CoverageType,
llvm::SanitizerCoverageOptions::SCK_Function);
} else if (name == "bb") {
opts.CoverageType =
std::max(opts.CoverageType, llvm::SanitizerCoverageOptions::SCK_BB);
} else if (name == "edge") {
opts.CoverageType =
std::max(opts.CoverageType, llvm::SanitizerCoverageOptions::SCK_Edge);
} else if (name == "indirect-calls") {
opts.IndirectCalls = true;
} else if (name == "trace-bb") {
opts.TraceBB = true;
}
else if (name == "trace-cmp") {
opts.TraceCmp = true;
}
else if (name == "trace-div") {
opts.TraceDiv = true;
}
else if (name == "trace-gep") {
opts.TraceGep = true;
}
else if (name == "8bit-counters") {
opts.Use8bitCounters = true;
}
else if (name == "trace-pc") {
opts.TracePC = true;
}
else if (name == "trace-pc-guard") {
opts.TracePCGuard = true;
}
#if LDC_LLVM_VER >= 500
else if (name == "no-prune") {
opts.NoPrune = true;
}
#endif
else {
error(Loc(), "Unrecognized -fsanitize-coverage option '%s'.", name.str().c_str());
}
}
void parseFSanitizeCoverageCmdlineParameter(llvm::SanitizerCoverageOptions &opts) {
for (const auto &name : fSanitizeCoverage) {
// Enable CoverageSanitizer when one or more -fsanitize-coverage parameters are passed
enabledSanitizers |= CoverageSanitizer;
parseFSanitizeCoverageParameter(name, opts);
}
}
#endif
} // anonymous namespace
namespace opts {
SanitizerBits enabledSanitizers = 0;
void initializeSanitizerOptionsFromCmdline()
{
enabledSanitizers |= parseFSanitizeCmdlineParameter();
#ifdef ENABLE_COVERAGE_SANITIZER
auto &sancovOpts = sanitizerCoverageOptions;
// The Fuzz sanitizer implies -fsanitize-coverage=trace-pc-guard,indirect-calls,trace-cmp
if (isSanitizerEnabled(FuzzSanitizer)) {
enabledSanitizers |= CoverageSanitizer;
sancovOpts.TracePCGuard = true;
sancovOpts.IndirectCalls = true;
sancovOpts.TraceCmp = true;
}
parseFSanitizeCoverageCmdlineParameter(sancovOpts);
// trace-pc and trace-pc-guard without specifying the insertion type implies
// edge
if ((sancovOpts.CoverageType == llvm::SanitizerCoverageOptions::SCK_None) &&
(sancovOpts.TracePC || sancovOpts.TracePCGuard)) {
sancovOpts.CoverageType = llvm::SanitizerCoverageOptions::SCK_Edge;
}
#endif
}
#ifdef ENABLE_COVERAGE_SANITIZER
llvm::SanitizerCoverageOptions getSanitizerCoverageOptions() {
return sanitizerCoverageOptions;
}
#endif
// Output to `hash_os` all optimization settings that influence object code
// output and that are not observable in the IR before running LLVM passes. This
// is used to calculate the hash use for caching that uniquely identifies the
// object file output.
void outputSanitizerSettings(llvm::raw_ostream &hash_os) {
hash_os << SanitizerBits(enabledSanitizers);
#ifdef ENABLE_COVERAGE_SANITIZER
hash_os << sanitizerCoverageOptions.CoverageType;
hash_os << sanitizerCoverageOptions.IndirectCalls;
hash_os << sanitizerCoverageOptions.TraceBB;
hash_os << sanitizerCoverageOptions.TraceCmp;
hash_os << sanitizerCoverageOptions.TraceDiv;
hash_os << sanitizerCoverageOptions.TraceGep;
hash_os << sanitizerCoverageOptions.Use8bitCounters;
hash_os << sanitizerCoverageOptions.TracePC;
hash_os << sanitizerCoverageOptions.TracePCGuard;
#if LDC_LLVM_VER >= 500
hash_os << sanitizerCoverageOptions.NoPrune;
#endif
#endif
}
} // namespace opts

View file

@ -0,0 +1,59 @@
//=== driver/cl_options_sanitizers.h - LDC command line options -*- C++ -*-===//
//
// LDC the LLVM D compiler
//
// This file is distributed under the BSD-style LDC license. See the LICENSE
// file for details.
//
//===----------------------------------------------------------------------===//
//
// Deals with -fsanitize=...
//
//===----------------------------------------------------------------------===//
#ifndef LDC_DRIVER_CL_OPTIONS_SANITIZERS_H
#define LDC_DRIVER_CL_OPTIONS_SANITIZERS_H
#include "gen/cl_helpers.h"
#include "llvm/Transforms/Instrumentation.h"
#if LDC_LLVM_VER >= 400
// Enable coverage sanitizer options from LLVM 4.0 to simplify our code: earlier
// versions do not have all options available.
#define ENABLE_COVERAGE_SANITIZER
#endif
namespace llvm {
class raw_ostream;
}
namespace opts {
namespace cl = llvm::cl;
typedef unsigned SanitizerBits;
enum SanitizerCheck : SanitizerBits {
NoneSanitizer = 0,
AddressSanitizer = 1 << 0,
FuzzSanitizer = 1 << 1,
MemorySanitizer = 1 << 2,
ThreadSanitizer = 1 << 3,
CoverageSanitizer = 1 << 4
};
extern SanitizerBits enabledSanitizers;
inline bool isAnySanitizerEnabled() { return enabledSanitizers; }
inline bool isSanitizerEnabled(SanitizerCheck san) {
return enabledSanitizers & san;
}
void initializeSanitizerOptionsFromCmdline();
#ifdef ENABLE_COVERAGE_SANITIZER
llvm::SanitizerCoverageOptions getSanitizerCoverageOptions();
#endif
void outputSanitizerSettings(llvm::raw_ostream &hash_os);
} // namespace opts
#endif // LDC_DRIVER_CL_OPTIONS_SANITIZERS_H

View file

@ -9,6 +9,7 @@
#include "errors.h"
#include "driver/cl_options.h"
#include "driver/cl_options_sanitizers.h"
#include "driver/exe_path.h"
#include "driver/tool.h"
#include "gen/irstate.h"
@ -242,13 +243,17 @@ void ArgsBuilder::build(llvm::StringRef outputPath,
// Requires clang.
void ArgsBuilder::addSanitizers() {
if (opts::sanitize == opts::AddressSanitizer) {
// TODO: instead of this, we should link with our own sanitizer libraries
// because LDC's LLVM version could be different from the system clang.
if (opts::isSanitizerEnabled(opts::AddressSanitizer)) {
args.push_back("-fsanitize=address");
}
if (opts::sanitize == opts::MemorySanitizer) {
if (opts::isSanitizerEnabled(opts::MemorySanitizer)) {
args.push_back("-fsanitize=memory");
}
if (opts::sanitize == opts::ThreadSanitizer) {
if (opts::isSanitizerEnabled(opts::ThreadSanitizer)) {
args.push_back("-fsanitize=thread");
}
}

View file

@ -21,6 +21,7 @@
#include "ddmd/target.h"
#include "driver/cache.h"
#include "driver/cl_options.h"
#include "driver/cl_options_sanitizers.h"
#include "driver/codegenerator.h"
#include "driver/configfile.h"
#include "driver/dcomputecodegenerator.h"
@ -458,6 +459,8 @@ void parseCommandLine(int argc, char **argv, Strings &sourceFiles,
}
#endif
initializeSanitizerOptionsFromCmdline();
processVersions(debugArgs, "debug", DebugCondition::setGlobalLevel,
DebugCondition::addGlobalIdent);
processVersions(versions, "version", VersionCondition::setGlobalLevel,
@ -930,16 +933,17 @@ void registerPredefinedVersions() {
VersionCondition::addPredefinedGlobalIdent("D_ObjectiveC");
}
// Pass sanitizer arguments to linker. Requires clang.
if (opts::sanitize == opts::AddressSanitizer) {
// Define sanitizer versions.
if (opts::isSanitizerEnabled(opts::AddressSanitizer)) {
VersionCondition::addPredefinedGlobalIdent("LDC_AddressSanitizer");
}
if (opts::sanitize == opts::MemorySanitizer) {
if (opts::isSanitizerEnabled(opts::CoverageSanitizer)) {
VersionCondition::addPredefinedGlobalIdent("LDC_CoverageSanitizer");
}
if (opts::isSanitizerEnabled(opts::MemorySanitizer)) {
VersionCondition::addPredefinedGlobalIdent("LDC_MemorySanitizer");
}
if (opts::sanitize == opts::ThreadSanitizer) {
if (opts::isSanitizerEnabled(opts::ThreadSanitizer)) {
VersionCondition::addPredefinedGlobalIdent("LDC_ThreadSanitizer");
}

View file

@ -19,6 +19,7 @@
#include "statement.h"
#include "template.h"
#include "driver/cl_options.h"
#include "driver/cl_options_sanitizers.h"
#include "gen/abi.h"
#include "gen/arrays.h"
#include "gen/classes.h"
@ -945,17 +946,17 @@ void DtoDefineFunction(FuncDeclaration *fd, bool linkageAvailableExternally) {
if (gABI->needsUnwindTables()) {
func->addFnAttr(LLAttribute::UWTable);
}
if (opts::sanitize != opts::None) {
if (opts::isAnySanitizerEnabled()) {
// Set the required sanitizer attribute.
if (opts::sanitize == opts::AddressSanitizer) {
if (opts::isSanitizerEnabled(opts::AddressSanitizer)) {
func->addFnAttr(LLAttribute::SanitizeAddress);
}
if (opts::sanitize == opts::MemorySanitizer) {
if (opts::isSanitizerEnabled(opts::MemorySanitizer)) {
func->addFnAttr(LLAttribute::SanitizeMemory);
}
if (opts::sanitize == opts::ThreadSanitizer) {
if (opts::isSanitizerEnabled(opts::ThreadSanitizer)) {
func->addFnAttr(LLAttribute::SanitizeThread);
}
}

View file

@ -8,11 +8,13 @@
//===----------------------------------------------------------------------===//
#include "gen/optimizer.h"
#include "errors.h"
#include "gen/cl_helpers.h"
#include "gen/logger.h"
#include "gen/passes/Passes.h"
#include "driver/cl_options.h"
#include "driver/cl_options_sanitizers.h"
#include "driver/targetmachine.h"
#include "llvm/LinkAllPasses.h"
#if LDC_LLVM_VER >= 307
@ -98,14 +100,6 @@ static cl::opt<bool> stripDebug(
"strip-debug", cl::ZeroOrMore,
cl::desc("Strip symbolic debug information before optimization"));
cl::opt<opts::SanitizerCheck> opts::sanitize(
"sanitize", cl::desc("Enable runtime instrumentation for bug detection"),
cl::init(opts::None),
clEnumValues(clEnumValN(opts::AddressSanitizer, "address", "Memory errors"),
clEnumValN(opts::MemorySanitizer, "memory", "Memory errors"),
clEnumValN(opts::ThreadSanitizer, "thread",
"Race detection")));
static cl::opt<bool> disableLoopUnrolling(
"disable-loop-unrolling", cl::ZeroOrMore,
cl::desc("Disable loop unrolling in all relevant passes"));
@ -213,6 +207,14 @@ static void addThreadSanitizerPass(const PassManagerBuilder &Builder,
PM.add(createThreadSanitizerPass());
}
static void addSanitizerCoveragePass(const PassManagerBuilder &Builder,
legacy::PassManagerBase &PM) {
#ifdef ENABLE_COVERAGE_SANITIZER
PM.add(
createSanitizerCoverageModulePass(opts::getSanitizerCoverageOptions()));
#endif
}
// Adds PGO instrumentation generation and use passes.
static void addPGOPasses(legacy::PassManagerBase &mpm, unsigned optLevel) {
#if LDC_WITH_PGO
@ -303,27 +305,34 @@ static void addOptimizationPasses(PassManagerBase &mpm,
builder.SLPVectorize =
disableSLPVectorization ? false : optLevel > 1 && sizeLevel < 2;
if (opts::sanitize == opts::AddressSanitizer) {
if (opts::isSanitizerEnabled(opts::AddressSanitizer)) {
builder.addExtension(PassManagerBuilder::EP_OptimizerLast,
addAddressSanitizerPasses);
builder.addExtension(PassManagerBuilder::EP_EnabledOnOptLevel0,
addAddressSanitizerPasses);
}
if (opts::sanitize == opts::MemorySanitizer) {
if (opts::isSanitizerEnabled(opts::MemorySanitizer)) {
builder.addExtension(PassManagerBuilder::EP_OptimizerLast,
addMemorySanitizerPass);
builder.addExtension(PassManagerBuilder::EP_EnabledOnOptLevel0,
addMemorySanitizerPass);
}
if (opts::sanitize == opts::ThreadSanitizer) {
if (opts::isSanitizerEnabled(opts::ThreadSanitizer)) {
builder.addExtension(PassManagerBuilder::EP_OptimizerLast,
addThreadSanitizerPass);
builder.addExtension(PassManagerBuilder::EP_EnabledOnOptLevel0,
addThreadSanitizerPass);
}
if (opts::isSanitizerEnabled(opts::CoverageSanitizer)) {
builder.addExtension(PassManagerBuilder::EP_OptimizerLast,
addSanitizerCoveragePass);
builder.addExtension(PassManagerBuilder::EP_EnabledOnOptLevel0,
addSanitizerCoveragePass);
}
if (!disableLangSpecificPasses) {
if (!disableSimplifyDruntimeCalls) {
builder.addExtension(PassManagerBuilder::EP_LoopOptimizerEnd,
@ -483,7 +492,6 @@ void outputOptimizationSettings(llvm::raw_ostream &hash_os) {
hash_os << disableGCToStack;
hash_os << unitAtATime;
hash_os << stripDebug;
hash_os << opts::sanitize;
hash_os << disableLoopUnrolling;
hash_os << disableLoopVectorization;
hash_os << disableSLPVectorization;

View file

@ -24,18 +24,6 @@ namespace llvm {
class raw_ostream;
}
namespace opts {
enum SanitizerCheck {
None,
AddressSanitizer,
MemorySanitizer,
ThreadSanitizer
};
extern llvm::cl::opt<SanitizerCheck> sanitize;
}
namespace llvm {
class Module;
}

View file

@ -0,0 +1,14 @@
// Test basic AddressSanitizer functionality
// RUN: %ldc -c -output-ll -g -fsanitize=address -of=%t.ll %s && FileCheck %s < %t.ll
// RUN: %ldc -c -output-ll -g -fsanitize=address,thread -of=%t.tsan.ll %s && FileCheck %s --check-prefix=wTSAN < %t.tsan.ll
void foo() {
// wTSAN: call {{.*}}_tsan_func_entry
// CHECK: call {{.*}}_asan_stack_malloc
// wTSAN: {{(call|invoke)}} {{.*}}_asan_stack_malloc
int[10] i;
// CHECK: call {{.*}}_asan_report_store
// wTSAN: {{(call|invoke)}} {{.*}}_asan_report_store
i[0] = 1;
}

View file

@ -0,0 +1,31 @@
// Test basic coverage sanitizer functionality
// REQUIRES: atleast_llvm400
// RUN: %ldc -c -output-ll -fsanitize-coverage=trace-pc-guard -of=%t.ll %s && FileCheck %s --check-prefix=PCGUARD < %t.ll
// RUN: %ldc -c -output-ll -fsanitize-coverage=trace-pc-guard,trace-cmp -of=%t.cmp.ll %s && FileCheck %s --check-prefix=PCCMP < %t.cmp.ll
// RUN: %ldc -c -output-ll -fsanitize-coverage=trace-pc-guard,func -of=%t.func.ll %s && FileCheck %s --check-prefix=PCFUNC < %t.func.ll
// PCGUARD-LABEL: define{{.*}} @{{.*}}FuzzMe
// PCCMP-LABEL: define{{.*}} @{{.*}}FuzzMe
// PCFUNC-LABEL: define{{.*}} @{{.*}}FuzzMe
bool FuzzMe(const ubyte* data, size_t dataSize)
{
// PCGUARD: call {{.*}}_sanitizer_cov_trace_pc_guard
// PCGUARD-NOT: call {{.*}}_sanitizer_cov_trace_cmp
// PCGUARD: call {{.*}}_sanitizer_cov_trace_pc_guard
// PCGUARD-NOT: call {{.*}}_sanitizer_cov_trace_cmp
// PCCMP: call {{.*}}_sanitizer_cov_trace_pc_guard
// PCCMP: call {{.*}}_sanitizer_cov_trace_cmp
// PCFUNC: call {{.*}}_sanitizer_cov_trace_pc_guard
// PCFUNC-NOT: call {{.*}}_sanitizer_cov_trace_pc_guard
return dataSize >= 3 &&
data[0] == 'F' &&
data[1] == 'U' &&
data[2] == 'Z' &&
data[3] == 'Z'; // :<
// PCFUNC: ret i1
}

View file

@ -0,0 +1,8 @@
// Test sanitizer errors on unrecognized values
// REQUIRES: atleast_llvm400
// RUN: not %ldc -c -fsanitize=poiuyt -fsanitize-coverage=aqswdefr %s 2>&1 | FileCheck %s
// CHECK-DAG: Unrecognized -fsanitize value 'poiuyt'
// CHECK-DAG: Unrecognized -fsanitize-coverage option 'aqswdefr'

View file

@ -0,0 +1,36 @@
// Test basic Fuzz sanitizer functionality
// REQUIRES: atleast_llvm400
// RUN: %ldc -c -output-ll -O3 -fsanitize=fuzzer -of=%t.ll %s && FileCheck %s < %t.ll
// RUN: %ldc -c -output-ll -fsanitize=fuzzer,address -of=%t.asan.ll %s && FileCheck %s --check-prefix=wASAN < %t.asan.ll
// CHECK-LABEL: define{{.*}} @{{.*}}FuzzMe
// wASAN-LABEL: define{{.*}} @{{.*}}FuzzMe
bool FuzzMe(const ubyte* data, size_t dataSize)
{
// CHECK: call {{.*}}_sanitizer_cov_trace_pc_guard
// CHECK: call {{.*}}_sanitizer_cov_trace_cmp
return dataSize >= 3 &&
data[0] == 'F' &&
data[1] == 'U' &&
data[2] == 'Z' &&
data[3] == 'Z'; // :<
}
// CHECK-LABEL: define{{.*}} @{{.*}}allocInt
// wASAN-LABEL: define{{.*}} @{{.*}}allocInt
void allocInt() {
// CHECK: call {{.*}}_sanitizer_cov_trace_pc_guard
// wASAN: call {{.*}}_asan_stack_malloc
int[10] a;
}
// CHECK-LABEL: define{{.*}} @{{.*}}foo
// wASAN-LABEL: define{{.*}} @{{.*}}foo
void foo(int function() a) {
// CHECK: call {{.*}}_sanitizer_cov_trace_pc_guard
// CHECK: call void @__sanitizer_cov_trace_pc_indir
a();
}

View file

@ -0,0 +1,10 @@
// Test basic ThreadSanitizer functionality
// RUN: %ldc -c -output-ll -g -fsanitize=thread -of=%t.ll %s && FileCheck %s < %t.ll
// CHECK-LABEL: define {{.*}}D16fsanitize_thread3foo
int foo(int i) {
// CHECK: call {{.*}}_tsan_func_entry
return i + 1;
// CHECK: ret i
}