diff --git a/clang/docs/SanitizerCoverage.rst b/clang/docs/SanitizerCoverage.rst --- a/clang/docs/SanitizerCoverage.rst +++ b/clang/docs/SanitizerCoverage.rst @@ -312,11 +312,17 @@ // for every non-constant array index. void __sanitizer_cov_trace_gep(uintptr_t Idx); -Partially disabling instrumentation -=================================== +Disabling instrumentation with ``__attribute__((no_sanitize("coverage")))`` +=========================================================================== + +It is possible to disable coverage instrumentation for select functions via the +function attribute ``__attribute__((no_sanitize("coverage")))``. + +Disabling instrumentation without source modification +===================================================== It is sometimes useful to tell SanitizerCoverage to instrument only a subset of the -functions in your target. +functions in your target without modifying source files. With ``-fsanitize-coverage-allowlist=allowlist.txt`` and ``-fsanitize-coverage-blocklist=blocklist.txt``, you can specify such a subset through the combination of an allowlist and a blocklist. diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -2897,6 +2897,10 @@ } return Mask; } + + bool hasCoverage() const { + return llvm::is_contained(sanitizers(), "coverage"); + } }]; } diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -2564,12 +2564,17 @@ let Content = [{ Use the ``no_sanitize`` attribute on a function or a global variable declaration to specify that a particular instrumentation or set of -instrumentations should not be applied. The attribute takes a list of -string literals, which have the same meaning as values accepted by the -``-fno-sanitize=`` flag. For example, -``__attribute__((no_sanitize("address", "thread")))`` specifies that -AddressSanitizer and ThreadSanitizer should not be applied to the -function or variable. +instrumentations should not be applied. + +The attribute takes a list of string literals with the following accepted +values: +* all values accepted by ``-fno-sanitize=``; +* ``coverage``, to disable SanitizerCoverage instrumentation. + +For example, ``__attribute__((no_sanitize("address", "thread")))`` specifies +that AddressSanitizer and ThreadSanitizer should not be applied to the function +or variable. Using ``__attribute__((no_sanitize("coverage")))`` specifies that +SanitizerCoverage should not be applied to the function. See :ref:`Controlling Code Generation ` for a full list of supported sanitizer flags. diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp --- a/clang/lib/CodeGen/CodeGenFunction.cpp +++ b/clang/lib/CodeGen/CodeGenFunction.cpp @@ -734,8 +734,10 @@ } while (0); if (D) { - // Apply the no_sanitize* attributes to SanOpts. + bool NoSanitizeCoverage = false; + for (auto Attr : D->specific_attrs()) { + // Apply the no_sanitize* attributes to SanOpts. SanitizerMask mask = Attr->getMask(); SanOpts.Mask &= ~mask; if (mask & SanitizerKind::Address) @@ -746,7 +748,18 @@ SanOpts.set(SanitizerKind::KernelHWAddress, false); if (mask & SanitizerKind::KernelHWAddress) SanOpts.set(SanitizerKind::HWAddress, false); + + // SanitizeCoverage is not handled by SanOpts. + if (Attr->hasCoverage()) + NoSanitizeCoverage = true; } + + bool HaveSanitizeCoverage = + CGM.getCodeGenOpts().SanitizeCoverageType || + CGM.getCodeGenOpts().SanitizeCoverageIndirectCalls || + CGM.getCodeGenOpts().SanitizeCoverageTraceCmp; + if (NoSanitizeCoverage && HaveSanitizeCoverage) + Fn->addFnAttr(llvm::Attribute::NoSanitizeCoverage); } // Apply sanitizer attributes to the function. diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -7274,7 +7274,8 @@ return; if (parseSanitizerValue(SanitizerName, /*AllowGroups=*/true) == - SanitizerMask()) + SanitizerMask() && + SanitizerName != "coverage") S.Diag(LiteralLoc, diag::warn_unknown_sanitizer_ignored) << SanitizerName; else if (isGlobalVar(D) && SanitizerName != "address") S.Diag(D->getLocation(), diag::err_attribute_wrong_decl_type) diff --git a/clang/test/CodeGen/sanitize-coverage.c b/clang/test/CodeGen/sanitize-coverage.c --- a/clang/test/CodeGen/sanitize-coverage.c +++ b/clang/test/CodeGen/sanitize-coverage.c @@ -19,4 +19,52 @@ if (n) x[n] = 42; } + +// CHECK-LABEL: define dso_local void @test_no_sanitize_coverage( +__attribute__((no_sanitize("coverage"))) void test_no_sanitize_coverage(int n) { + // CHECK-NOT: call void @__sanitizer_cov_trace_pc + // CHECK-NOT: call void @__sanitizer_cov_trace_const_cmp + // ASAN-DAG: call void @__asan_report_store + // MSAN-DAG: call void @__msan_warning + // BOUNDS-DAG: call void @__ubsan_handle_out_of_bounds + // TSAN-DAG: call void @__tsan_func_entry + // UBSAN-DAG: call void @__ubsan_handle + if (n) + x[n] = 42; +} + + +// CHECK-LABEL: define dso_local void @test_no_sanitize_combined( +__attribute__((no_sanitize("address", "memory", "thread", "bounds", "undefined", "coverage"))) +void test_no_sanitize_combined(int n) { + // CHECK-NOT: call void @__sanitizer_cov_trace_pc + // CHECK-NOT: call void @__sanitizer_cov_trace_const_cmp + // ASAN-NOT: call void @__asan_report_store + // MSAN-NOT: call void @__msan_warning + // BOUNDS-NOT: call void @__ubsan_handle_out_of_bounds + // TSAN-NOT: call void @__tsan_func_entry + // UBSAN-NOT: call void @__ubsan_handle + if (n) + x[n] = 42; +} + +// CHECK-LABEL: define dso_local void @test_no_sanitize_separate( +__attribute__((no_sanitize("address"))) +__attribute__((no_sanitize("memory"))) +__attribute__((no_sanitize("thread"))) +__attribute__((no_sanitize("bounds"))) +__attribute__((no_sanitize("undefined"))) +__attribute__((no_sanitize("coverage"))) +void test_no_sanitize_separate(int n) { + // CHECK-NOT: call void @__sanitizer_cov_trace_pc + // CHECK-NOT: call void @__sanitizer_cov_trace_const_cmp + // ASAN-NOT: call void @__asan_report_store + // MSAN-NOT: call void @__msan_warning + // BOUNDS-NOT: call void @__ubsan_handle_out_of_bounds + // TSAN-NOT: call void @__tsan_func_entry + // UBSAN-NOT: call void @__ubsan_handle + if (n) + x[n] = 42; +} + // CHECK-LABEL: declare void diff --git a/llvm/include/llvm/Bitcode/LLVMBitCodes.h b/llvm/include/llvm/Bitcode/LLVMBitCodes.h --- a/llvm/include/llvm/Bitcode/LLVMBitCodes.h +++ b/llvm/include/llvm/Bitcode/LLVMBitCodes.h @@ -666,6 +666,7 @@ ATTR_KIND_NO_PROFILE = 73, ATTR_KIND_VSCALE_RANGE = 74, ATTR_KIND_SWIFT_ASYNC = 75, + ATTR_KIND_NO_SANITIZE_COVERAGE = 76, }; enum ComdatSelectionKindCodes { diff --git a/llvm/include/llvm/IR/Attributes.td b/llvm/include/llvm/IR/Attributes.td --- a/llvm/include/llvm/IR/Attributes.td +++ b/llvm/include/llvm/IR/Attributes.td @@ -154,6 +154,9 @@ /// Function doesn't unwind stack. def NoUnwind : EnumAttr<"nounwind">; +/// No SanitizeCoverage instrumentation. +def NoSanitizeCoverage : EnumAttr<"no_sanitize_coverage">; + /// Null pointer in address space zero is valid. def NullPointerIsValid : EnumAttr<"null_pointer_is_valid">; diff --git a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp --- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp +++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp @@ -1474,6 +1474,8 @@ return Attribute::NoCfCheck; case bitc::ATTR_KIND_NO_UNWIND: return Attribute::NoUnwind; + case bitc::ATTR_KIND_NO_SANITIZE_COVERAGE: + return Attribute::NoSanitizeCoverage; case bitc::ATTR_KIND_NULL_POINTER_IS_VALID: return Attribute::NullPointerIsValid; case bitc::ATTR_KIND_OPT_FOR_FUZZING: diff --git a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp --- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp +++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp @@ -686,6 +686,8 @@ return bitc::ATTR_KIND_NO_PROFILE; case Attribute::NoUnwind: return bitc::ATTR_KIND_NO_UNWIND; + case Attribute::NoSanitizeCoverage: + return bitc::ATTR_KIND_NO_SANITIZE_COVERAGE; case Attribute::NullPointerIsValid: return bitc::ATTR_KIND_NULL_POINTER_IS_VALID; case Attribute::OptForFuzzing: diff --git a/llvm/lib/IR/Attributes.cpp b/llvm/lib/IR/Attributes.cpp --- a/llvm/lib/IR/Attributes.cpp +++ b/llvm/lib/IR/Attributes.cpp @@ -442,6 +442,8 @@ return "noprofile"; if (hasAttribute(Attribute::NoUnwind)) return "nounwind"; + if (hasAttribute(Attribute::NoSanitizeCoverage)) + return "no_sanitize_coverage"; if (hasAttribute(Attribute::OptForFuzzing)) return "optforfuzzing"; if (hasAttribute(Attribute::OptimizeNone)) diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp --- a/llvm/lib/IR/Verifier.cpp +++ b/llvm/lib/IR/Verifier.cpp @@ -1661,6 +1661,7 @@ case Attribute::NoCfCheck: case Attribute::NoUnwind: case Attribute::NoInline: + case Attribute::NoSanitizeCoverage: case Attribute::AlwaysInline: case Attribute::OptimizeForSize: case Attribute::StackProtect: diff --git a/llvm/lib/Transforms/Instrumentation/SanitizerCoverage.cpp b/llvm/lib/Transforms/Instrumentation/SanitizerCoverage.cpp --- a/llvm/lib/Transforms/Instrumentation/SanitizerCoverage.cpp +++ b/llvm/lib/Transforms/Instrumentation/SanitizerCoverage.cpp @@ -621,6 +621,8 @@ return; if (Blocklist && Blocklist->inSection("coverage", "fun", F.getName())) return; + if (F.hasFnAttribute(Attribute::NoSanitizeCoverage)) + return; if (Options.CoverageType >= SanitizerCoverageOptions::SCK_Edge) SplitAllCriticalEdges(F, CriticalEdgeSplittingOptions().setIgnoreUnreachableDests()); SmallVector IndirCalls; diff --git a/llvm/lib/Transforms/Utils/CodeExtractor.cpp b/llvm/lib/Transforms/Utils/CodeExtractor.cpp --- a/llvm/lib/Transforms/Utils/CodeExtractor.cpp +++ b/llvm/lib/Transforms/Utils/CodeExtractor.cpp @@ -954,6 +954,7 @@ case Attribute::NonLazyBind: case Attribute::NoRedZone: case Attribute::NoUnwind: + case Attribute::NoSanitizeCoverage: case Attribute::NullPointerIsValid: case Attribute::OptForFuzzing: case Attribute::OptimizeNone: