Skip to content

[Clang] Overflow Pattern Exclusions #100272

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 13 commits into from
Aug 15, 2024
Merged
Show file tree
Hide file tree
Changes from 7 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
30 changes: 30 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,36 @@ Moved checkers
Sanitizers
----------

- Added the ``-fsanitize-overflow-pattern-exclusion=`` flag which can be used
to disable specific overflow-dependent code patterns. The supported patterns
are: ``add-overflow-test``, ``negated-unsigned-const``, and
``post-decr-while``. The sanitizer instrumentation can be toggled off for all
available patterns by specifying ``all``. Conversely, you can disable all
exclusions with ``none``.

.. code-block:: c++

/// specified with ``-fsanitize-overflow-pattern-exclusion=add-overflow-test``
int common_overflow_check_pattern(unsigned base, unsigned offset) {
if (base + offset < base) { /* ... */ } // The pattern of `a + b < a`, and other re-orderings, won't be instrumented
}

/// specified with ``-fsanitize-overflow-pattern-exclusion=negated-unsigned-const``
void negation_overflow() {
unsigned long foo = -1UL; // No longer causes a negation overflow warning
unsigned long bar = -2UL; // and so on...
}

/// specified with ``-fsanitize-overflow-pattern-exclusion=post-decr-while``
void while_post_decrement() {
unsigned char count = 16;
while (count--) { /* ... */} // No longer causes unsigned-integer-overflow sanitizer to trip
}

Many existing projects have a large amount of these code patterns present.
This new flag should allow those projects to enable integer sanitizers with
less noise.

Python Binding Changes
----------------------

Expand Down
34 changes: 34 additions & 0 deletions clang/docs/UndefinedBehaviorSanitizer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,40 @@ To silence reports from unsigned integer overflow, you can set
``-fsanitize-recover=unsigned-integer-overflow``, is particularly useful for
providing fuzzing signal without blowing up logs.

Disabling instrumentation for common overflow patterns
------------------------------------------------------

There are certain overflow-dependent or overflow-prone code patterns which
produce a lot of noise for integer overflow/truncation sanitizers. To disable
instrumentation for these common patterns one should use
``-fsanitize-overflow-pattern-exclusion=``.

Currently, this option supports three pervasive overflow-dependent code idioms:

.. code-block:: c++

/// -fsanitize-overflow-pattern-exclusion=negated-unsigned-const
unsigned long foo = -1UL; // No longer causes a negation overflow warning
unsigned long bar = -2UL; // and so on...

.. code-block:: c++

/// -fsanitize-overflow-pattern-exclusion=post-decr-while
unsigned char count = 16;
while (count--) { /* ... */ } // No longer causes unsigned-integer-overflow sanitizer to trip

.. code-block:: c++

/// -fsanitize-overflow-pattern-exclusion=add-overflow-test
if (base + offset < base) { /* ... */ } // The pattern of `a + b < a`, and other re-orderings,
// won't be instrumented (same for signed types)

Negated unsigned constants, post-decrements in a while loop condition and
simple overflow checks are accepted and pervasive code patterns. You can enable
all exclusions with ``-fsanitize-overflow-pattern-exclusion=all`` or disable
all exclusions with ``-fsanitize-overflow-pattern-exclusion=none``. Specifying
``none`` has precedence over other values.

Issue Suppression
=================

Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/AST/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -3860,6 +3860,7 @@ class CStyleCastExpr final
class BinaryOperator : public Expr {
enum { LHS, RHS, END_EXPR };
Stmt *SubExprs[END_EXPR];
bool ExcludedOverflowPattern = false;

public:
typedef BinaryOperatorKind Opcode;
Expand Down Expand Up @@ -4018,6 +4019,8 @@ class BinaryOperator : public Expr {
return isShiftAssignOp(getOpcode());
}

bool ignoreOverflowSanitizers() const { return ExcludedOverflowPattern; }

/// Return true if a binary operator using the specified opcode and operands
/// would match the 'p = (i8*)nullptr + n' idiom for casting a pointer-sized
/// integer to a pointer.
Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Basic/LangOptions.def
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,8 @@ VALUE_LANGOPT(TrivialAutoVarInitMaxSize, 32, 0,
"stop trivial automatic variable initialization if var size exceeds the specified size (in bytes). Must be greater than 0.")
ENUM_LANGOPT(SignedOverflowBehavior, SignedOverflowBehaviorTy, 2, SOB_Undefined,
"signed integer overflow handling")
LANGOPT(IgnoreNegationOverflow, 1, 0, "ignore overflow caused by negation")
LANGOPT(SanitizeOverflowIdioms, 1, 1, "enable instrumentation for common overflow idioms")
ENUM_LANGOPT(ThreadModel , ThreadModelKind, 2, ThreadModelKind::POSIX, "Thread Model")

BENIGN_LANGOPT(ArrowDepth, 32, 256,
Expand Down
28 changes: 28 additions & 0 deletions clang/include/clang/Basic/LangOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,21 @@ class LangOptionsBase {
PerThread,
};

/// Exclude certain code patterns from being instrumented by arithmetic
/// overflow sanitizers
enum OverflowPatternExclusionKind {
/// Don't exclude any overflow patterns from sanitizers
None = 1 << 0,
/// Exclude all overflow patterns (below)
All = 1 << 1,
/// if (a + b < a)
AddOverflowTest = 1 << 2,
/// -1UL
NegUnsignedConst = 1 << 3,
/// while (count--)
PostDecrInWhile = 1 << 4,
};

enum class DefaultVisiblityExportMapping {
None,
/// map only explicit default visibilities to exported
Expand Down Expand Up @@ -555,6 +570,11 @@ class LangOptions : public LangOptionsBase {
/// The default stream kind used for HIP kernel launching.
GPUDefaultStreamKind GPUDefaultStream;

/// Which overflow patterns should be excluded from sanitizer instrumentation
int OverflowPatternExclusionMask = 0;

std::vector<std::string> OverflowPatternExclusionValues;

/// The seed used by the randomize structure layout feature.
std::string RandstructSeed;

Expand Down Expand Up @@ -630,6 +650,14 @@ class LangOptions : public LangOptionsBase {
return MSCompatibilityVersion >= MajorVersion * 100000U;
}

bool isOverflowPatternExcluded(OverflowPatternExclusionKind Kind) const {
if (OverflowPatternExclusionMask & OverflowPatternExclusionKind::None)
return false;
if (OverflowPatternExclusionMask & OverflowPatternExclusionKind::All)
return true;
return OverflowPatternExclusionMask & Kind;
}

/// Reset all of the options that are not considered when building a
/// module.
void resetNonModularOptions();
Expand Down
5 changes: 5 additions & 0 deletions clang/include/clang/Driver/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -2558,6 +2558,11 @@ defm sanitize_stats : BoolOption<"f", "sanitize-stats",
"Disable">,
BothFlags<[], [ClangOption], " sanitizer statistics gathering.">>,
Group<f_clang_Group>;
def fsanitize_overflow_pattern_exclusion_EQ : CommaJoined<["-"], "fsanitize-overflow-pattern-exclusion=">,
HelpText<"Specify the overflow patterns to exclude from artihmetic sanitizer instrumentation">,
Visibility<[ClangOption, CC1Option]>,
Values<"none,all,add-overflow-test,negated-unsigned-const,post-decr-while">,
MarshallingInfoStringVector<LangOpts<"OverflowPatternExclusionValues">>;
def fsanitize_thread_memory_access : Flag<["-"], "fsanitize-thread-memory-access">,
Group<f_clang_Group>,
HelpText<"Enable memory access instrumentation in ThreadSanitizer (default)">;
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Driver/SanitizerArgs.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class SanitizerArgs {
std::vector<std::string> BinaryMetadataIgnorelistFiles;
int CoverageFeatures = 0;
int BinaryMetadataFeatures = 0;
int OverflowPatternExclusions = 0;
int MsanTrackOrigins = 0;
bool MsanUseAfterDtor = true;
bool MsanParamRetval = true;
Expand Down
55 changes: 55 additions & 0 deletions clang/lib/AST/Expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4759,6 +4759,55 @@ ParenListExpr *ParenListExpr::CreateEmpty(const ASTContext &Ctx,
return new (Mem) ParenListExpr(EmptyShell(), NumExprs);
}

namespace {
/// Certain overflow-dependent code patterns can have their integer overflow
/// sanitization disabled. Check for the common pattern `if (a + b < a)` and
/// return the resulting BinaryOperator responsible for the addition so we can
/// elide overflow checks during codegen.
static std::optional<BinaryOperator *>
getOverflowPatternBinOp(const BinaryOperator *E) {
Expr *Addition, *ComparedTo;
if (E->getOpcode() == BO_LT) {
Addition = E->getLHS();
ComparedTo = E->getRHS();
} else if (E->getOpcode() == BO_GT) {
Addition = E->getRHS();
ComparedTo = E->getLHS();
} else {
return {};
}

const Expr *AddLHS = nullptr, *AddRHS = nullptr;
BinaryOperator *BO = dyn_cast<BinaryOperator>(Addition);

if (BO && BO->getOpcode() == clang::BO_Add) {
// now store addends for lookup on other side of '>'
AddLHS = BO->getLHS();
AddRHS = BO->getRHS();
}

if (!AddLHS || !AddRHS)
return {};

const Decl *LHSDecl, *RHSDecl, *OtherDecl;

LHSDecl = AddLHS->IgnoreParenImpCasts()->getReferencedDeclOfCallee();
RHSDecl = AddRHS->IgnoreParenImpCasts()->getReferencedDeclOfCallee();
OtherDecl = ComparedTo->IgnoreParenImpCasts()->getReferencedDeclOfCallee();

if (!OtherDecl)
return {};

if (!LHSDecl && !RHSDecl)
return {};

if ((LHSDecl && LHSDecl == OtherDecl && LHSDecl != RHSDecl) ||
(RHSDecl && RHSDecl == OtherDecl && RHSDecl != LHSDecl))
return BO;
return {};
}
} // namespace

BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
Opcode opc, QualType ResTy, ExprValueKind VK,
ExprObjectKind OK, SourceLocation opLoc,
Expand All @@ -4770,6 +4819,12 @@ BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
BinaryOperatorBits.OpLoc = opLoc;
SubExprs[LHS] = lhs;
SubExprs[RHS] = rhs;
if (Ctx.getLangOpts().isOverflowPatternExcluded(
LangOptions::OverflowPatternExclusionKind::AddOverflowTest)) {
std::optional<BinaryOperator *> Result = getOverflowPatternBinOp(this);
if (Result.has_value())
Result.value()->ExcludedOverflowPattern = true;
}
BinaryOperatorBits.HasFPFeatures = FPFeatures.requiresTrailingStorage();
if (hasStoredFPFeatures())
setStoredFPFeatures(FPFeatures);
Expand Down
27 changes: 25 additions & 2 deletions clang/lib/CodeGen/CGExprScalar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "clang/AST/Attr.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ParentMapContext.h"
#include "clang/AST/RecordLayout.h"
#include "clang/AST/StmtVisitor.h"
#include "clang/Basic/CodeGenOptions.h"
Expand Down Expand Up @@ -195,13 +196,23 @@ static bool CanElideOverflowCheck(const ASTContext &Ctx, const BinOpInfo &Op) {
if (!Op.mayHaveIntegerOverflow())
return true;

const UnaryOperator *UO = dyn_cast<UnaryOperator>(Op.E);

if (UO && UO->getOpcode() == UO_Minus && UO->isIntegerConstantExpr(Ctx) &&
Ctx.getLangOpts().isOverflowPatternExcluded(
LangOptions::OverflowPatternExclusionKind::NegUnsignedConst))
return true;

// If a unary op has a widened operand, the op cannot overflow.
if (const auto *UO = dyn_cast<UnaryOperator>(Op.E))
if (UO)
return !UO->canOverflow();

// We usually don't need overflow checks for binops with widened operands.
// Multiplication with promoted unsigned operands is a special case.
const auto *BO = cast<BinaryOperator>(Op.E);
if (BO->ignoreOverflowSanitizers())
return true;

auto OptionalLHSTy = getUnwidenedIntegerType(Ctx, BO->getLHS());
if (!OptionalLHSTy)
return false;
Expand Down Expand Up @@ -2877,6 +2888,17 @@ ScalarExprEmitter::EmitScalarPrePostIncDec(const UnaryOperator *E, LValue LV,
} else if (type->isIntegerType()) {
QualType promotedType;
bool canPerformLossyDemotionCheck = false;

// Is the pattern "while (i--)" and overflow exclusion?
bool disableSanitizer =
(!isInc && !isPre &&
CGF.getContext().getLangOpts().isOverflowPatternExcluded(
LangOptions::OverflowPatternExclusionKind::PostDecrInWhile) &&
llvm::all_of(CGF.getContext().getParentMapContext().getParents(*E),
[&](const DynTypedNode &Parent) -> bool {
return Parent.get<WhileStmt>();
}));

if (CGF.getContext().isPromotableIntegerType(type)) {
promotedType = CGF.getContext().getPromotedIntegerType(type);
assert(promotedType != type && "Shouldn't promote to the same type.");
Expand Down Expand Up @@ -2936,7 +2958,8 @@ ScalarExprEmitter::EmitScalarPrePostIncDec(const UnaryOperator *E, LValue LV,
} else if (E->canOverflow() && type->isSignedIntegerOrEnumerationType()) {
value = EmitIncDecConsiderOverflowBehavior(E, value, isInc);
} else if (E->canOverflow() && type->isUnsignedIntegerType() &&
CGF.SanOpts.has(SanitizerKind::UnsignedIntegerOverflow)) {
CGF.SanOpts.has(SanitizerKind::UnsignedIntegerOverflow) &&
!disableSanitizer) {
value = EmitOverflowCheckedBinOp(createBinOpInfoFromIncDec(
E, value, isInc, E->getFPFeaturesInEffect(CGF.getLangOpts())));
} else {
Expand Down
37 changes: 37 additions & 0 deletions clang/lib/Driver/SanitizerArgs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ static SanitizerMask parseArgValues(const Driver &D, const llvm::opt::Arg *A,
static int parseCoverageFeatures(const Driver &D, const llvm::opt::Arg *A,
bool DiagnoseErrors);

static int parseOverflowPatternExclusionValues(const Driver &D,
const llvm::opt::Arg *A,
bool DiagnoseErrors);

/// Parse -f(no-)?sanitize-metadata= flag values, diagnosing any invalid
/// components. Returns OR of members of \c BinaryMetadataFeature enumeration.
static int parseBinaryMetadataFeatures(const Driver &D, const llvm::opt::Arg *A,
Expand Down Expand Up @@ -788,6 +792,13 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC,
<< "fsanitize-trap=cfi";
}

for (const auto *Arg :
Args.filtered(options::OPT_fsanitize_overflow_pattern_exclusion_EQ)) {
Arg->claim();
OverflowPatternExclusions |=
parseOverflowPatternExclusionValues(D, Arg, DiagnoseErrors);
}

// Parse -f(no-)?sanitize-coverage flags if coverage is supported by the
// enabled sanitizers.
for (const auto *Arg : Args) {
Expand Down Expand Up @@ -1241,6 +1252,10 @@ void SanitizerArgs::addArgs(const ToolChain &TC, const llvm::opt::ArgList &Args,
addSpecialCaseListOpt(Args, CmdArgs,
"-fsanitize-system-ignorelist=", SystemIgnorelistFiles);

if (OverflowPatternExclusions)
Args.AddAllArgs(CmdArgs,
options::OPT_fsanitize_overflow_pattern_exclusion_EQ);

if (MsanTrackOrigins)
CmdArgs.push_back(Args.MakeArgString("-fsanitize-memory-track-origins=" +
Twine(MsanTrackOrigins)));
Expand Down Expand Up @@ -1426,6 +1441,28 @@ SanitizerMask parseArgValues(const Driver &D, const llvm::opt::Arg *A,
return Kinds;
}

static int parseOverflowPatternExclusionValues(const Driver &D,
const llvm::opt::Arg *A,
bool DiagnoseErrors) {
int Exclusions = 0;
for (int i = 0, n = A->getNumValues(); i != n; ++i) {
const char *Value = A->getValue(i);
int E =
llvm::StringSwitch<int>(Value)
.Case("none", LangOptionsBase::None)
.Case("all", LangOptionsBase::All)
.Case("add-overflow-test", LangOptionsBase::AddOverflowTest)
.Case("negated-unsigned-const", LangOptionsBase::NegUnsignedConst)
.Case("post-decr-while", LangOptionsBase::PostDecrInWhile)
.Default(0);
if (E == 0)
D.Diag(clang::diag::err_drv_unsupported_option_argument)
<< A->getSpelling() << Value;
Exclusions |= E;
}
return Exclusions;
}

int parseCoverageFeatures(const Driver &D, const llvm::opt::Arg *A,
bool DiagnoseErrors) {
assert(A->getOption().matches(options::OPT_fsanitize_coverage) ||
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/Driver/ToolChains/Clang.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7746,6 +7746,9 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
Args.AddLastArg(CmdArgs, options::OPT_fgpu_default_stream_EQ);
}

Args.AddAllArgs(CmdArgs,
options::OPT_fsanitize_overflow_pattern_exclusion_EQ);

Args.AddLastArg(CmdArgs, options::OPT_foffload_uniform_block,
options::OPT_fno_offload_uniform_block);

Expand Down
Loading
Loading