Skip to content

Commit 63e7290

Browse files
committed
[Sanitizers] Add Driver/Frontend option to enable sanitizer instrumentation that supports error recovery.
The new option `-sanitize-recover=` takes a list of sanitizers that recovery instrumentation should be enabled for. Currently we only support it for Address Sanitizer. If the option is not specified then the generated instrumentation does not allow error recovery. This option mirrors the `-fsanitize-recover=` option of Clang. We don't enable recoverable instrumentation by default because it may lead to code size blow up (control flow has to be resumable). The motivation behind this change is that today, setting `ASAN_OPTIONS=halt_on_error=0` at runtime doesn't always work. If you compile without the `-sanitize-recover=address` option (equivalent to the current behavior of the swift compiler) then the generated instrumentation doesn't allow for error recovery. What this means is that if you set `ASAN_OPTIONS=halt_on_error=0` at runtime and if an ASan issue is caught via instrumentation then the process will always halt regardless of how `halt_on_error` is set. However, if ASan catches an issue via one of its interceptors (e.g. memcpy) then `the halt_on_error` runtime option is respected. With `-sanitize-recover=address` the generated instrumentation allows for error recovery which means that the `halt_on_error` runtime option is also respected when the ASan issue is caught by instrumentation. ASan's default for `halt_on_error` is true which means this issue only effects people who choose to not use the default behavior. rdar://problem/56346688
1 parent 0e1766e commit 63e7290

File tree

13 files changed

+223
-2
lines changed

13 files changed

+223
-2
lines changed

include/swift/AST/DiagnosticsFrontend.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ ERROR(error_option_requires_sanitizer, none,
7171
"option '%0' requires a sanitizer to be enabled. Use -sanitize= to "
7272
"enable a sanitizer", (StringRef))
7373

74+
WARNING(warning_option_requires_specific_sanitizer, none,
75+
"option '%0' has no effect when '%1' sanitizer is disabled. Use -sanitize=%1 to "
76+
"enable the sanitizer", (StringRef, StringRef))
77+
7478
ERROR(error_option_missing_required_argument, none,
7579
"option '%0' is missing a required argument (%1)", (StringRef, StringRef))
7680

include/swift/AST/IRGenOptions.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ class IRGenOptions {
106106
/// Which sanitizer is turned on.
107107
OptionSet<SanitizerKind> Sanitizers;
108108

109+
/// Which sanitizer(s) have recovery instrumentation enabled.
110+
OptionSet<SanitizerKind> SanitizersWithRecoveryInstrumentation;
111+
109112
/// Path prefixes that should be rewritten in debug info.
110113
PathRemapper DebugPrefixMap;
111114

@@ -239,6 +242,7 @@ class IRGenOptions {
239242
: DWARFVersion(2), OutputKind(IRGenOutputKind::LLVMAssembly),
240243
Verify(true), OptMode(OptimizationMode::NotSet),
241244
Sanitizers(OptionSet<SanitizerKind>()),
245+
SanitizersWithRecoveryInstrumentation(OptionSet<SanitizerKind>()),
242246
DebugInfoLevel(IRGenDebugInfoLevel::None),
243247
DebugInfoFormat(IRGenDebugInfoFormat::None),
244248
DisableClangModuleSkeletonCUs(false), UseJIT(false),

include/swift/Option/Options.td

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -889,6 +889,15 @@ def sanitize_EQ : CommaJoined<["-"], "sanitize=">,
889889
Flags<[FrontendOption, NoInteractiveOption]>, MetaVarName<"<check>">,
890890
HelpText<"Turn on runtime checks for erroneous behavior.">;
891891

892+
def sanitize_recover_EQ
893+
: CommaJoined<["-"], "sanitize-recover=">,
894+
Flags<[FrontendOption, NoInteractiveOption]>,
895+
MetaVarName<"<check>">,
896+
HelpText<"Specify which sanitizer runtime checks (see -sanitize=) will "
897+
"generate instrumentation that allows error recovery. Listed "
898+
"checks should be comma separated. Default behavior is to not "
899+
"allow error recovery.">;
900+
892901
def sanitize_coverage_EQ : CommaJoined<["-"], "sanitize-coverage=">,
893902
Flags<[FrontendOption, NoInteractiveOption]>,
894903
MetaVarName<"<type>">,

include/swift/Option/SanitizerOptions.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ OptionSet<SanitizerKind> parseSanitizerArgValues(
3636
const llvm::Triple &Triple, DiagnosticEngine &Diag,
3737
llvm::function_ref<bool(llvm::StringRef, bool)> sanitizerRuntimeLibExists);
3838

39+
OptionSet<SanitizerKind> parseSanitizerRecoverArgValues(
40+
const llvm::opt::Arg *A, const OptionSet<SanitizerKind> &enabledSanitizers,
41+
DiagnosticEngine &Diags, bool emitWarnings);
42+
3943
/// Parses a -sanitize-coverage= argument's value.
4044
llvm::SanitizerCoverageOptions parseSanitizerCoverageArgValue(
4145
const llvm::opt::Arg *A,

lib/Driver/Driver.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1636,6 +1636,14 @@ void Driver::buildOutputInfo(const ToolChain &TC, const DerivedArgList &Args,
16361636
return TC.sanitizerRuntimeLibExists(Args, sanitizerName, shared);
16371637
});
16381638

1639+
if (const Arg *A = Args.getLastArg(options::OPT_sanitize_recover_EQ)) {
1640+
// Just validate the args. The frontend will parse these again and actually
1641+
// use them. To avoid emitting warnings multiple times we surpress warnings
1642+
// here but not in the frontend.
1643+
(void)parseSanitizerRecoverArgValues(A, OI.SelectedSanitizers, Diags,
1644+
/*emitWarnings=*/false);
1645+
}
1646+
16391647
if (const Arg *A = Args.getLastArg(options::OPT_sanitize_coverage_EQ)) {
16401648

16411649
// Check that the sanitizer coverage flags are supported if supplied.

lib/Driver/ToolChains.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ static void addCommonFrontendArgs(const ToolChain &TC, const OutputInfo &OI,
214214
inputArgs.AddLastArg(arguments, options::OPT_profile_coverage_mapping);
215215
inputArgs.AddLastArg(arguments, options::OPT_warnings_as_errors);
216216
inputArgs.AddLastArg(arguments, options::OPT_sanitize_EQ);
217+
inputArgs.AddLastArg(arguments, options::OPT_sanitize_recover_EQ);
217218
inputArgs.AddLastArg(arguments, options::OPT_sanitize_coverage_EQ);
218219
inputArgs.AddLastArg(arguments, options::OPT_static);
219220
inputArgs.AddLastArg(arguments, options::OPT_swift_version);

lib/Frontend/CompilerInvocation.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -877,6 +877,12 @@ static bool ParseSILArgs(SILOptions &Opts, ArgList &Args,
877877
IRGenOpts.Sanitizers = Opts.Sanitizers;
878878
}
879879

880+
if (const Arg *A = Args.getLastArg(options::OPT_sanitize_recover_EQ)) {
881+
IRGenOpts.SanitizersWithRecoveryInstrumentation =
882+
parseSanitizerRecoverArgValues(A, Opts.Sanitizers, Diags,
883+
/*emitWarnings=*/true);
884+
}
885+
880886
if (auto A = Args.getLastArg(OPT_enable_verify_exclusivity,
881887
OPT_disable_verify_exclusivity)) {
882888
Opts.VerifyExclusivity

lib/IRGen/IRGen.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,14 @@ static void addSwiftMergeFunctionsPass(const PassManagerBuilder &Builder,
126126

127127
static void addAddressSanitizerPasses(const PassManagerBuilder &Builder,
128128
legacy::PassManagerBase &PM) {
129-
PM.add(createAddressSanitizerFunctionPass());
130-
PM.add(createModuleAddressSanitizerLegacyPassPass());
129+
auto &BuilderWrapper =
130+
static_cast<const PassManagerBuilderWrapper &>(Builder);
131+
auto recover =
132+
bool(BuilderWrapper.IRGOpts.SanitizersWithRecoveryInstrumentation &
133+
SanitizerKind::Address);
134+
PM.add(createAddressSanitizerFunctionPass(/*CompileKernel=*/false, recover));
135+
PM.add(createModuleAddressSanitizerLegacyPassPass(/*CompileKernel=*/false,
136+
recover));
131137
}
132138

133139
static void addThreadSanitizerPass(const PassManagerBuilder &Builder,

lib/Option/SanitizerOptions.cpp

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,49 @@ OptionSet<SanitizerKind> swift::parseSanitizerArgValues(
185185
return sanitizerSet;
186186
}
187187

188+
OptionSet<SanitizerKind> swift::parseSanitizerRecoverArgValues(
189+
const llvm::opt::Arg *A, const OptionSet<SanitizerKind> &enabledSanitizers,
190+
DiagnosticEngine &Diags, bool emitWarnings) {
191+
OptionSet<SanitizerKind> sanitizerRecoverSet;
192+
193+
// Find the sanitizer kind.
194+
for (const char *arg : A->getValues()) {
195+
Optional<SanitizerKind> optKind = parse(arg);
196+
197+
// Unrecognized sanitizer option
198+
if (!optKind.hasValue()) {
199+
Diags.diagnose(SourceLoc(), diag::error_unsupported_option_argument,
200+
A->getOption().getPrefixedName(), arg);
201+
continue;
202+
}
203+
SanitizerKind kind = optKind.getValue();
204+
205+
// Only support ASan for now.
206+
if (kind != SanitizerKind::Address) {
207+
Diags.diagnose(SourceLoc(), diag::error_unsupported_option_argument,
208+
A->getOption().getPrefixedName(), arg);
209+
continue;
210+
}
211+
212+
// Check that the sanitizer is enabled.
213+
if (!(enabledSanitizers & kind)) {
214+
SmallString<128> b;
215+
if (emitWarnings) {
216+
Diags.diagnose(SourceLoc(),
217+
diag::warning_option_requires_specific_sanitizer,
218+
(A->getOption().getPrefixedName() + toStringRef(kind))
219+
.toStringRef(b),
220+
toStringRef(kind));
221+
}
222+
continue;
223+
}
224+
225+
sanitizerRecoverSet |= kind;
226+
}
227+
228+
return sanitizerRecoverSet;
229+
}
230+
188231
std::string swift::getSanitizerList(const OptionSet<SanitizerKind> &Set) {
189232
std::string list;
190233
#define SANITIZER(_, kind, name, file) \

test/Driver/sanitize_recover.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// RUN: not %swiftc_driver -driver-print-jobs -sanitize=address -sanitize-recover=foo %s 2>&1 | %FileCheck -check-prefix=SAN_RECOVER_INVALID_ARG %s
2+
// RUN: not %swiftc_driver -driver-print-jobs -sanitize=address -sanitize-recover=thread %s 2>&1 | %FileCheck -check-prefix=SAN_RECOVER_UNSUPPORTED_ARG %s
3+
// RUN: %swiftc_driver -v -sanitize-recover=address %s -o %t 2>&1 | %FileCheck -check-prefix=SAN_RECOVER_MISSING_INSTRUMENTATION_OPTION %s
4+
// RUN: %swiftc_driver -driver-print-jobs -sanitize=address -sanitize-recover=address %s 2>&1 | %FileCheck -check-prefix=ASAN_WITH_RECOVER %s
5+
// RUN: %swiftc_driver -driver-print-jobs -sanitize=address %s 2>&1 | %FileCheck -check-prefix=ASAN_WITHOUT_RECOVER --implicit-check-not='-sanitize-recover=address' %s
6+
7+
// SAN_RECOVER_INVALID_ARG: unsupported argument 'foo' to option '-sanitize-recover='
8+
// SAN_RECOVER_UNSUPPORTED_ARG: unsupported argument 'thread' to option '-sanitize-recover='
9+
10+
// SAN_RECOVER_MISSING_INSTRUMENTATION_OPTION: warning: option '-sanitize-recover=address' has no effect when 'address' sanitizer is disabled. Use -sanitize=address to enable the sanitizer
11+
// SAN_RECOVER_MISSING_INSTRUMENTATION_OPTION-NOT: warning: option '-sanitize-recover=address' has no effect when 'address' sanitizer is disabled. Use -sanitize=address to enable the sanitizer
12+
13+
// ASAN_WITH_RECOVER: swift
14+
// ASAN_WITH_RECOVER-DAG: -sanitize=address
15+
// ASAN_WITH_RECOVER-DAG: -sanitize-recover=address
16+
17+
// ASAN_WITHOUT_RECOVER: swift
18+
// ASAN_WITHOUT_RECOVER: -sanitize=address

0 commit comments

Comments
 (0)