[clang] [Clang] Overflow Pattern Exclusions (PR #100272)

Justin Stitt via cfe-commits cfe-commits at lists.llvm.org
Mon Aug 12 18:00:15 PDT 2024


https://github.com/JustinStitt updated https://github.com/llvm/llvm-project/pull/100272

>From 154d3505ab13275086b3dffed67bcdcac52f79a3 Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Tue, 23 Jul 2024 20:21:49 +0000
Subject: [PATCH 01/10] implement idiom exclusions

Add flag `-fno-sanitize-overflow-idioms` which disables integer
overflow/truncation sanitizer instrumentation for common
overflow-dependent code patterns.

Signed-off-by: Justin Stitt <justinstitt at google.com>
---
 clang/include/clang/AST/Expr.h                |   5 +
 clang/include/clang/Basic/LangOptions.def     |   2 +
 clang/include/clang/Driver/Options.td         |  10 ++
 clang/include/clang/Driver/SanitizerArgs.h    |   1 +
 clang/lib/AST/Expr.cpp                        |  53 +++++++
 clang/lib/CodeGen/CGExprScalar.cpp            |  26 +++-
 clang/lib/Driver/SanitizerArgs.cpp            |   7 +
 clang/lib/Driver/ToolChains/Clang.cpp         |   7 +
 .../CodeGen/overflow-idiom-exclusion-fp.c     |  77 ++++++++++
 clang/test/CodeGen/overflow-idiom-exclusion.c | 141 ++++++++++++++++++
 10 files changed, 327 insertions(+), 2 deletions(-)
 create mode 100644 clang/test/CodeGen/overflow-idiom-exclusion-fp.c
 create mode 100644 clang/test/CodeGen/overflow-idiom-exclusion.c

diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index 5b813bfc2faf90..c329ee061cf008 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -3860,6 +3860,7 @@ class CStyleCastExpr final
 class BinaryOperator : public Expr {
   enum { LHS, RHS, END_EXPR };
   Stmt *SubExprs[END_EXPR];
+  bool isOverflowIdiom;
 
 public:
   typedef BinaryOperatorKind Opcode;
@@ -4018,6 +4019,10 @@ class BinaryOperator : public Expr {
     return isShiftAssignOp(getOpcode());
   }
 
+  bool ignoreOverflowSanitizers() const {
+    return isOverflowIdiom;
+  }
+
   /// 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.
diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def
index 0035092ce0d863..4619e2d8c4fb7f 100644
--- a/clang/include/clang/Basic/LangOptions.def
+++ b/clang/include/clang/Basic/LangOptions.def
@@ -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,
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index c8c56dbb51b28a..f253f7535776b8 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -2558,6 +2558,16 @@ defm sanitize_stats : BoolOption<"f", "sanitize-stats",
           "Disable">,
   BothFlags<[], [ClangOption], " sanitizer statistics gathering.">>,
   Group<f_clang_Group>;
+defm sanitize_overflow_idioms : BoolFOption<"sanitize-overflow-idioms",
+  LangOpts<"SanitizeOverflowIdioms">, DefaultTrue,
+  PosFlag<SetTrue, [], [], "Enable">,
+  NegFlag<SetFalse, [], [], "Disable">,
+  BothFlags<[], [ClangOption], " the instrumentation of common overflow idioms">>;
+defm sanitize_negation_overflow : BoolFOption<"sanitize-negation-overflow",
+  LangOpts<"IgnoreNegationOverflow">, DefaultFalse,
+  PosFlag<SetFalse, [], [], "Enable">,
+  NegFlag<SetTrue, [], [], "Disable">,
+  BothFlags<[], [ClangOption], " integer overflow sanitizer instrumentation for negation">>;
 def fsanitize_thread_memory_access : Flag<["-"], "fsanitize-thread-memory-access">,
                                      Group<f_clang_Group>,
                                      HelpText<"Enable memory access instrumentation in ThreadSanitizer (default)">;
diff --git a/clang/include/clang/Driver/SanitizerArgs.h b/clang/include/clang/Driver/SanitizerArgs.h
index 47ef175302679f..291739e1221d96 100644
--- a/clang/include/clang/Driver/SanitizerArgs.h
+++ b/clang/include/clang/Driver/SanitizerArgs.h
@@ -64,6 +64,7 @@ class SanitizerArgs {
   // True if cross-dso CFI support if provided by the system (i.e. Android).
   bool ImplicitCfiRuntime = false;
   bool NeedsMemProfRt = false;
+  bool SanitizeOverflowIdioms = true;
   bool HwasanUseAliases = false;
   llvm::AsanDetectStackUseAfterReturnMode AsanUseAfterReturn =
       llvm::AsanDetectStackUseAfterReturnMode::Invalid;
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index 9d5b8167d0ee62..c07560c92100d7 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -4759,6 +4759,54 @@ ParenListExpr *ParenListExpr::CreateEmpty(const ASTContext &Ctx,
   return new (Mem) ParenListExpr(EmptyShell(), NumExprs);
 }
 
+namespace {
+/// Certain overflow-dependent code idioms 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 *> getOverflowIdiomBinOp(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,
@@ -4770,6 +4818,11 @@ BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
   BinaryOperatorBits.OpLoc = opLoc;
   SubExprs[LHS] = lhs;
   SubExprs[RHS] = rhs;
+  if (!Ctx.getLangOpts().SanitizeOverflowIdioms) {
+    std::optional<BinaryOperator*> Result = getOverflowIdiomBinOp(this);
+    if (Result.has_value())
+      Result.value()->isOverflowIdiom = true;
+  }
   BinaryOperatorBits.HasFPFeatures = FPFeatures.requiresTrailingStorage();
   if (hasStoredFPFeatures())
     setStoredFPFeatures(FPFeatures);
diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp
index 84392745ea6144..089f841bb3acdf 100644
--- a/clang/lib/CodeGen/CGExprScalar.cpp
+++ b/clang/lib/CodeGen/CGExprScalar.cpp
@@ -25,6 +25,7 @@
 #include "clang/AST/DeclObjC.h"
 #include "clang/AST/Expr.h"
 #include "clang/AST/RecordLayout.h"
+#include "clang/AST/ParentMapContext.h"
 #include "clang/AST/StmtVisitor.h"
 #include "clang/Basic/CodeGenOptions.h"
 #include "clang/Basic/TargetInfo.h"
@@ -195,13 +196,22 @@ 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 &&
+      Ctx.getLangOpts().IgnoreNegationOverflow)
+    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;
@@ -2877,6 +2887,17 @@ ScalarExprEmitter::EmitScalarPrePostIncDec(const UnaryOperator *E, LValue LV,
   } else if (type->isIntegerType()) {
     QualType promotedType;
     bool canPerformLossyDemotionCheck = false;
+
+    // Does this match the pattern of while(i--) {...}? If so, and if
+    // SanitizeOverflowIdioms is disabled, we don't want to enable sanitizer.
+    bool disableSanitizer =
+        (!isInc && !isPre &&
+         !CGF.getContext().getLangOpts().SanitizeOverflowIdioms &&
+         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.");
@@ -2936,7 +2957,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 {
diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp
index 1fd870b72286e5..567840bcfbbdb6 100644
--- a/clang/lib/Driver/SanitizerArgs.cpp
+++ b/clang/lib/Driver/SanitizerArgs.cpp
@@ -1069,6 +1069,10 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC,
                                 options::OPT_fmemory_profile_EQ,
                                 options::OPT_fno_memory_profile, false);
 
+  SanitizeOverflowIdioms =
+      Args.hasFlag(options::OPT_fsanitize_overflow_idioms,
+                   options::OPT_fno_sanitize_overflow_idioms, true);
+
   // Finally, initialize the set of available and recoverable sanitizers.
   Sanitizers.Mask |= Kinds;
   RecoverableSanitizers.Mask |= RecoverableKinds;
@@ -1356,6 +1360,9 @@ void SanitizerArgs::addArgs(const ToolChain &TC, const llvm::opt::ArgList &Args,
     CmdArgs.push_back("+tagged-globals");
   }
 
+  if (!SanitizeOverflowIdioms)
+    CmdArgs.push_back("-fno-sanitize-overflow-idioms");
+
   // MSan: Workaround for PR16386.
   // ASan: This is mainly to help LSan with cases such as
   // https://github.com/google/sanitizers/issues/373
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 843d68c85bc592..914c1a770f2e5f 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -6844,6 +6844,10 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
     }
   }
 
+  if (Args.hasArg(options::OPT_fno_sanitize_overflow_idioms) &&
+      !Args.hasArg(options::OPT_fsanitize_negation_overflow))
+    CmdArgs.push_back("-fno-sanitize-negation-overflow");
+
   if (Args.getLastArg(options::OPT_fapple_kext) ||
       (Args.hasArg(options::OPT_mkernel) && types::isCXX(InputType)))
     CmdArgs.push_back("-fapple-kext");
@@ -6897,6 +6901,9 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
   Args.addOptInFlag(CmdArgs, options::OPT_mspeculative_load_hardening,
                     options::OPT_mno_speculative_load_hardening);
 
+  Args.AddLastArg(CmdArgs, options::OPT_fsanitize_negation_overflow,
+                  options::OPT_fno_sanitize_negation_overflow);
+
   RenderSSPOptions(D, TC, Args, CmdArgs, KernelOrKext);
   RenderSCPOptions(TC, Args, CmdArgs);
   RenderTrivialAutoVarInitOptions(D, TC, Args, CmdArgs);
diff --git a/clang/test/CodeGen/overflow-idiom-exclusion-fp.c b/clang/test/CodeGen/overflow-idiom-exclusion-fp.c
new file mode 100644
index 00000000000000..0fa847f733a7fb
--- /dev/null
+++ b/clang/test/CodeGen/overflow-idiom-exclusion-fp.c
@@ -0,0 +1,77 @@
+// Check for potential false positives from patterns that _almost_ match classic overflow idioms
+// RUN: %clang %s -O2 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fno-sanitize-overflow-idioms -S -emit-llvm -o - | FileCheck %s
+// RUN: %clang %s -O2 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fno-sanitize-overflow-idioms -fwrapv -S -emit-llvm -o - | FileCheck %s
+
+extern unsigned a, b, c;
+extern int u, v, w;
+
+extern unsigned some(void);
+
+// Make sure all these still have handler paths, we shouldn't be excluding
+// instrumentation of any "near" idioms.
+void close_but_not_quite(void) {
+  // CHECK: br i1{{.*}}handler.
+  if (a + b > a)
+    c = 9;
+
+  // CHECK: br i1{{.*}}handler.
+  if (a - b < a)
+    c = 9;
+
+  // CHECK: br i1{{.*}}handler.
+  if (a + b < a)
+    c = 9;
+
+  // CHECK: br i1{{.*}}handler.
+  if (a + b + 1 < a)
+    c = 9;
+
+  // CHECK: br i1{{.*}}handler.
+  if (a + b < a + 1)
+    c = 9;
+
+  // CHECK: br i1{{.*}}handler.
+  if (b >= a + b)
+    c = 9;
+
+  // CHECK: br i1{{.*}}handler.
+  if (a + a < a)
+    c = 9;
+
+  // CHECK: br i1{{.*}}handler.
+  if (a + b == a)
+    c = 9;
+
+  // CHECK: br i1{{.*}}handler
+  if (u + v < u) /* matches overflow idiom, but is signed */
+    c = 9;
+
+  // CHECK: br i1{{.*}}handler
+  // Although this can never actually overflow we are still checking that the
+  // sanitizer instruments it.
+  while (--a)
+    some();
+}
+
+// cvise'd kernel code that caused problems during development
+// CHECK: br i1{{.*}}handler
+typedef unsigned size_t;
+typedef enum { FSE_repeat_none } FSE_repeat;
+typedef enum { ZSTD_defaultAllowed } ZSTD_defaultPolicy_e;
+FSE_repeat ZSTD_selectEncodingType_repeatMode;
+ZSTD_defaultPolicy_e ZSTD_selectEncodingType_isDefaultAllowed;
+size_t ZSTD_NCountCost(void);
+
+void ZSTD_selectEncodingType(void) {
+  size_t basicCost =
+             ZSTD_selectEncodingType_isDefaultAllowed ? ZSTD_NCountCost() : 0,
+         compressedCost = 3 + ZSTD_NCountCost();
+  if (basicCost <= compressedCost)
+    ZSTD_selectEncodingType_repeatMode = FSE_repeat_none;
+}
+
+void function_calls(void) {
+  // CHECK: br i1{{.*}}handler
+  if (some() + b < some())
+    c = 9;
+}
diff --git a/clang/test/CodeGen/overflow-idiom-exclusion.c b/clang/test/CodeGen/overflow-idiom-exclusion.c
new file mode 100644
index 00000000000000..df138fb8f5db84
--- /dev/null
+++ b/clang/test/CodeGen/overflow-idiom-exclusion.c
@@ -0,0 +1,141 @@
+// Ensure some common idioms don't trigger the overflow sanitizers when
+// -fno-sanitize-overflow-idioms is enabled. In many cases, overflow warnings
+// caused by these idioms are seen as "noise" and result in users turning off
+// sanitization all together.
+
+// A pattern like "if (a + b < a)" simply checks for overflow and usually means
+// the user is trying to handle it gracefully.
+
+// Similarly, a pattern resembling "while (i--)" is extremely common and
+// warning on its inevitable overflow can be seen as superfluous. Do note that
+// using "i" in future calculations can be tricky because it will still
+// wrap-around. Using -fno-sanitize-overflow-idioms or not doesn't change this
+// fact -- we just won't warn/trap with sanitizers.
+
+// Another common pattern that, in some cases, is found to be too noisy is
+// unsigned negation, for example:
+// unsigned long A = -1UL;
+
+// RUN: %clang %s -O2 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fno-sanitize-overflow-idioms -S -emit-llvm -o - | FileCheck %s
+// RUN: %clang %s -O2 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fno-sanitize-overflow-idioms -fwrapv -S -emit-llvm -o - | FileCheck %s
+// RUN: %clang %s -O2 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fno-sanitize-overflow-idioms -S -emit-llvm -o - | FileCheck %s --check-prefix=NEGATION
+// RUN: %clang %s -O2 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fno-sanitize-overflow-idioms -fsanitize-negation-overflow -S -emit-llvm -o - | FileCheck %s --check-prefix=NEGATIONOV
+// CHECK-NOT: br{{.*}}overflow
+
+extern unsigned a, b, c;
+extern unsigned some(void);
+
+void basic_commutativity(void) {
+  if (a + b < a)
+    c = 9;
+  if (a + b < b)
+    c = 9;
+  if (b + a < b)
+    c = 9;
+  if (b + a < a)
+    c = 9;
+  if (a > a + b)
+    c = 9;
+  if (a > b + a)
+    c = 9;
+  if (b > a + b)
+    c = 9;
+  if (b > b + a)
+    c = 9;
+}
+
+void arguments_and_commutativity(unsigned V1, unsigned V2) {
+  if (V1 + V2 < V1)
+    c = 9;
+  if (V1 + V2 < V2)
+    c = 9;
+  if (V2 + V1 < V2)
+    c = 9;
+  if (V2 + V1 < V1)
+    c = 9;
+  if (V1 > V1 + V2)
+    c = 9;
+  if (V1 > V2 + V1)
+    c = 9;
+  if (V2 > V1 + V2)
+    c = 9;
+  if (V2 > V2 + V1)
+    c = 9;
+}
+
+void pointers(unsigned *P1, unsigned *P2, unsigned V1) {
+  if (*P1 + *P2 < *P1)
+    c = 9;
+  if (*P1 + V1 < V1)
+    c = 9;
+  if (V1 + *P2 < *P2)
+    c = 9;
+}
+
+struct OtherStruct {
+  unsigned foo, bar;
+};
+
+struct MyStruct {
+  unsigned base, offset;
+  struct OtherStruct os;
+};
+
+extern struct MyStruct ms;
+
+void structs(void) {
+  if (ms.base + ms.offset < ms.base)
+    c = 9;
+}
+
+void nestedstructs(void) {
+  if (ms.os.foo + ms.os.bar < ms.os.foo)
+    c = 9;
+}
+
+// Normally, this would be folded into a simple call to the overflow handler
+// and a store. Excluding this idiom results in just a store.
+void constants(void) {
+  unsigned base = 4294967295;
+  unsigned offset = 1;
+  if (base + offset < base)
+    c = 9;
+}
+
+void common_while(unsigned i) {
+  // This post-decrement usually causes overflow sanitizers to trip on the very
+  // last operation.
+  while (i--) {
+    some();
+  }
+}
+
+// NEGATION-LABEL,NEGATIONOV-LABEL: define{{.*}}negation_overflow
+// NEGATION-NOT: negate_overflow
+// NEGATIONOV: negate_overflow
+// Normally, these assignments would trip the unsigned overflow sanitizer.
+void negation_overflow(void) {
+#define SOME -1UL
+  unsigned long A = -1UL;
+  unsigned long B = -2UL;
+  unsigned long C = -3UL;
+  unsigned long D = -1337UL;
+  (void)A;(void)B;(void)C;(void)D;
+}
+
+// cvise'd kernel code that caused problems during development due to sign
+// extension
+typedef unsigned long size_t;
+int qnbytes;
+int *key_alloc_key;
+size_t key_alloc_quotalen;
+int *key_alloc(void) {
+  if (qnbytes + key_alloc_quotalen < qnbytes)
+    return key_alloc_key;
+  return key_alloc_key + 3;;
+}
+
+void function_call(void) {
+  if (b + some() < b)
+    c = 9;
+}

>From e53caaef235ebe8045c0256a18e750b07c9a62d0 Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Tue, 23 Jul 2024 22:44:47 +0000
Subject: [PATCH 02/10] add docs

Signed-off-by: Justin Stitt <justinstitt at google.com>
---
 clang/docs/ReleaseNotes.rst               | 26 ++++++++++++++++++
 clang/docs/UndefinedBehaviorSanitizer.rst | 32 +++++++++++++++++++++++
 2 files changed, 58 insertions(+)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 3c2e0282d1c72d..ecb6d67463bdb3 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -294,6 +294,32 @@ Moved checkers
 Sanitizers
 ----------
 
+- Added the ``-fno-sanitize-overflow-idioms`` flag which disables integer
+  overflow and truncation sanitizer instrumentation for specific
+  overflow-dependent code patterns. The noise created by these idioms is a
+  large reason as to why large projects refuse to turn on arithmetic
+  sanitizers.
+
+  .. code-block:: c++
+
+     void negation_overflow() {
+       unsigned long foo = -1UL; // No longer causes a negation overflow warning
+       unsigned long bar = -2UL; // and so on...
+     }
+
+     void while_post_decrement() {
+       unsigned char count = 16;
+       while (count--) { /* ... */} // No longer causes unsigned-integer-overflow sanitizer to trip
+     }
+
+     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
+     }
+
+  Note that the ``-fsanitize-overflow-idioms`` flag now also exists but has
+  virtually no function other than to disable an already present
+  ``-fno-sanitize-overflow-idioms``.
+
 Python Binding Changes
 ----------------------
 
diff --git a/clang/docs/UndefinedBehaviorSanitizer.rst b/clang/docs/UndefinedBehaviorSanitizer.rst
index 531d56e313826c..b856f601aec81f 100644
--- a/clang/docs/UndefinedBehaviorSanitizer.rst
+++ b/clang/docs/UndefinedBehaviorSanitizer.rst
@@ -312,6 +312,38 @@ This attribute may not be
 supported by other compilers, so consider using it together with
 ``#if defined(__clang__)``.
 
+Disabling instrumentation of common overflow idioms
+=====================================================
+
+There are certain overflow-dependent code patterns which produce a lot of noise
+for integer overflow/truncation sanitizers. To disable instrumentation for
+these common patterns one should use ``-fno-sanitize-overflow-idioms``. Its
+inverse ``-fsanitize-overflow-idioms`` also exists but has no function other
+than to disable an already present ``-fno-sanitize-overflow-idioms``.
+
+Currently, this option handles three pervasive overflow-dependent code idioms:
+
+.. code-block:: c++
+
+    unsigned long foo = -1UL; // No longer causes a negation overflow warning
+    unsigned long bar = -2UL; // and so on...
+
+.. code-block:: c++
+
+    unsigned char count = 16;
+    while (count--) { /* ... */ } // No longer causes unsigned-integer-overflow sanitizer to trip
+
+.. code-block:: c++
+
+    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. Existing
+projects that enable more warnings or sanitizers may find that the compiler is
+too noisy. Now, they can use ``-fno-sanitize-overflow-idioms`` with no source
+modifications.
+
 Suppressing Errors in Recompiled Code (Ignorelist)
 --------------------------------------------------
 

>From 653584a878cc7c8272dde712c50a8c7ab4c9909b Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Tue, 23 Jul 2024 23:08:23 +0000
Subject: [PATCH 03/10] format

Signed-off-by: Justin Stitt <justinstitt at google.com>
---
 clang/include/clang/AST/Expr.h     | 4 +---
 clang/lib/AST/Expr.cpp             | 5 +++--
 clang/lib/CodeGen/CGExprScalar.cpp | 2 +-
 3 files changed, 5 insertions(+), 6 deletions(-)

diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index c329ee061cf008..e8dc22c51a03dd 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -4019,9 +4019,7 @@ class BinaryOperator : public Expr {
     return isShiftAssignOp(getOpcode());
   }
 
-  bool ignoreOverflowSanitizers() const {
-    return isOverflowIdiom;
-  }
+  bool ignoreOverflowSanitizers() const { return isOverflowIdiom; }
 
   /// 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
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index c07560c92100d7..d963d473442d8e 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -4764,7 +4764,8 @@ namespace {
 /// 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 *> getOverflowIdiomBinOp(const BinaryOperator *E) {
+static std::optional<BinaryOperator *>
+getOverflowIdiomBinOp(const BinaryOperator *E) {
   Expr *Addition, *ComparedTo;
   if (E->getOpcode() == BO_LT) {
     Addition = E->getLHS();
@@ -4819,7 +4820,7 @@ BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
   SubExprs[LHS] = lhs;
   SubExprs[RHS] = rhs;
   if (!Ctx.getLangOpts().SanitizeOverflowIdioms) {
-    std::optional<BinaryOperator*> Result = getOverflowIdiomBinOp(this);
+    std::optional<BinaryOperator *> Result = getOverflowIdiomBinOp(this);
     if (Result.has_value())
       Result.value()->isOverflowIdiom = true;
   }
diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp
index 089f841bb3acdf..f069039d3702aa 100644
--- a/clang/lib/CodeGen/CGExprScalar.cpp
+++ b/clang/lib/CodeGen/CGExprScalar.cpp
@@ -24,8 +24,8 @@
 #include "clang/AST/Attr.h"
 #include "clang/AST/DeclObjC.h"
 #include "clang/AST/Expr.h"
-#include "clang/AST/RecordLayout.h"
 #include "clang/AST/ParentMapContext.h"
+#include "clang/AST/RecordLayout.h"
 #include "clang/AST/StmtVisitor.h"
 #include "clang/Basic/CodeGenOptions.h"
 #include "clang/Basic/TargetInfo.h"

>From 821ce7a0c612f600af55ba6b7851991a0d8c4ace Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Fri, 26 Jul 2024 00:06:03 +0000
Subject: [PATCH 04/10] add support for comma-separated pattern specification

Signed-off-by: Justin Stitt <justinstitt at google.com>
---
 clang/include/clang/AST/Expr.h                |  4 +-
 clang/include/clang/Basic/LangOptions.h       | 28 ++++++++++++++
 clang/include/clang/Driver/Options.td         | 10 ++---
 clang/include/clang/Driver/SanitizerArgs.h    |  1 +
 clang/lib/AST/Expr.cpp                        |  5 ++-
 clang/lib/CodeGen/CGExprScalar.cpp            | 11 ++++--
 clang/lib/Driver/SanitizerArgs.cpp            | 37 +++++++++++++++++++
 clang/lib/Driver/ToolChains/Clang.cpp         | 10 ++---
 clang/lib/Frontend/CompilerInvocation.cpp     | 16 ++++++++
 .../CodeGen/overflow-idiom-exclusion-fp.c     |  4 +-
 clang/test/CodeGen/overflow-idiom-exclusion.c | 30 ++++++++++-----
 11 files changed, 124 insertions(+), 32 deletions(-)

diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index e8dc22c51a03dd..b25a4368aa1527 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -3860,7 +3860,7 @@ class CStyleCastExpr final
 class BinaryOperator : public Expr {
   enum { LHS, RHS, END_EXPR };
   Stmt *SubExprs[END_EXPR];
-  bool isOverflowIdiom;
+  bool ExcludedOverflowPattern;
 
 public:
   typedef BinaryOperatorKind Opcode;
@@ -4019,7 +4019,7 @@ class BinaryOperator : public Expr {
     return isShiftAssignOp(getOpcode());
   }
 
-  bool ignoreOverflowSanitizers() const { return isOverflowIdiom; }
+  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
diff --git a/clang/include/clang/Basic/LangOptions.h b/clang/include/clang/Basic/LangOptions.h
index 91f1c2f2e6239e..d57a9d7f31c733 100644
--- a/clang/include/clang/Basic/LangOptions.h
+++ b/clang/include/clang/Basic/LangOptions.h
@@ -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
@@ -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;
 
@@ -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();
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index f253f7535776b8..1b9da8617c8924 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -2563,11 +2563,11 @@ defm sanitize_overflow_idioms : BoolFOption<"sanitize-overflow-idioms",
   PosFlag<SetTrue, [], [], "Enable">,
   NegFlag<SetFalse, [], [], "Disable">,
   BothFlags<[], [ClangOption], " the instrumentation of common overflow idioms">>;
-defm sanitize_negation_overflow : BoolFOption<"sanitize-negation-overflow",
-  LangOpts<"IgnoreNegationOverflow">, DefaultFalse,
-  PosFlag<SetFalse, [], [], "Enable">,
-  NegFlag<SetTrue, [], [], "Disable">,
-  BothFlags<[], [ClangOption], " integer overflow sanitizer instrumentation for negation">>;
+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)">;
diff --git a/clang/include/clang/Driver/SanitizerArgs.h b/clang/include/clang/Driver/SanitizerArgs.h
index 291739e1221d96..1e23a6a6f85f12 100644
--- a/clang/include/clang/Driver/SanitizerArgs.h
+++ b/clang/include/clang/Driver/SanitizerArgs.h
@@ -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;
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index d963d473442d8e..8069cf80ea5058 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -4819,10 +4819,11 @@ BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
   BinaryOperatorBits.OpLoc = opLoc;
   SubExprs[LHS] = lhs;
   SubExprs[RHS] = rhs;
-  if (!Ctx.getLangOpts().SanitizeOverflowIdioms) {
+  if (Ctx.getLangOpts().isOverflowPatternExcluded(
+          LangOptions::OverflowPatternExclusionKind::AddOverflowTest)) {
     std::optional<BinaryOperator *> Result = getOverflowIdiomBinOp(this);
     if (Result.has_value())
-      Result.value()->isOverflowIdiom = true;
+      Result.value()->ExcludedOverflowPattern = true;
   }
   BinaryOperatorBits.HasFPFeatures = FPFeatures.requiresTrailingStorage();
   if (hasStoredFPFeatures())
diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp
index f069039d3702aa..f1e71557fb5add 100644
--- a/clang/lib/CodeGen/CGExprScalar.cpp
+++ b/clang/lib/CodeGen/CGExprScalar.cpp
@@ -198,8 +198,11 @@ static bool CanElideOverflowCheck(const ASTContext &Ctx, const BinOpInfo &Op) {
 
   const UnaryOperator *UO = dyn_cast<UnaryOperator>(Op.E);
 
+  // TODO: rename or rework NegUnsignedConst because it doesn't only work on
+  // constants.
   if (UO && UO->getOpcode() == UO_Minus &&
-      Ctx.getLangOpts().IgnoreNegationOverflow)
+      Ctx.getLangOpts().isOverflowPatternExcluded(
+          LangOptions::OverflowPatternExclusionKind::NegUnsignedConst))
     return true;
 
   // If a unary op has a widened operand, the op cannot overflow.
@@ -2888,11 +2891,11 @@ ScalarExprEmitter::EmitScalarPrePostIncDec(const UnaryOperator *E, LValue LV,
     QualType promotedType;
     bool canPerformLossyDemotionCheck = false;
 
-    // Does this match the pattern of while(i--) {...}? If so, and if
-    // SanitizeOverflowIdioms is disabled, we don't want to enable sanitizer.
+    // Is the pattern "while (i--)" and overflow exclusion?
     bool disableSanitizer =
         (!isInc && !isPre &&
-         !CGF.getContext().getLangOpts().SanitizeOverflowIdioms &&
+         CGF.getContext().getLangOpts().isOverflowPatternExcluded(
+             LangOptions::OverflowPatternExclusionKind::PostDecrInWhile) &&
          llvm::all_of(CGF.getContext().getParentMapContext().getParents(*E),
                       [&](const DynTypedNode &Parent) -> bool {
                         return Parent.get<WhileStmt>();
diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp
index 567840bcfbbdb6..3b1c0b645ffbe0 100644
--- a/clang/lib/Driver/SanitizerArgs.cpp
+++ b/clang/lib/Driver/SanitizerArgs.cpp
@@ -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,
@@ -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) {
@@ -1245,6 +1256,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)));
@@ -1433,6 +1448,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) ||
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 914c1a770f2e5f..6b88c5e833440f 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -6844,10 +6844,6 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
     }
   }
 
-  if (Args.hasArg(options::OPT_fno_sanitize_overflow_idioms) &&
-      !Args.hasArg(options::OPT_fsanitize_negation_overflow))
-    CmdArgs.push_back("-fno-sanitize-negation-overflow");
-
   if (Args.getLastArg(options::OPT_fapple_kext) ||
       (Args.hasArg(options::OPT_mkernel) && types::isCXX(InputType)))
     CmdArgs.push_back("-fapple-kext");
@@ -6901,9 +6897,6 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
   Args.addOptInFlag(CmdArgs, options::OPT_mspeculative_load_hardening,
                     options::OPT_mno_speculative_load_hardening);
 
-  Args.AddLastArg(CmdArgs, options::OPT_fsanitize_negation_overflow,
-                  options::OPT_fno_sanitize_negation_overflow);
-
   RenderSSPOptions(D, TC, Args, CmdArgs, KernelOrKext);
   RenderSCPOptions(TC, Args, CmdArgs);
   RenderTrivialAutoVarInitOptions(D, TC, Args, CmdArgs);
@@ -7753,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);
 
diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp
index f6b6c44a4cab6a..0d257a40859fde 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -4248,6 +4248,22 @@ bool CompilerInvocation::ParseLangArgs(LangOptions &Opts, ArgList &Args,
       Diags.Report(diag::err_drv_invalid_value) << A->getAsString(Args) << Val;
   }
 
+  if (auto *A = Args.getLastArg(OPT_fsanitize_overflow_pattern_exclusion_EQ)) {
+    for (int i = 0, n = A->getNumValues(); i != n; ++i) {
+      StringRef Value = A->getValue(i);
+      if (Value == "none")
+        Opts.OverflowPatternExclusionMask |= LangOptionsBase::None;
+      else if (Value == "all")
+        Opts.OverflowPatternExclusionMask |= LangOptionsBase::All;
+      else if (Value == "add-overflow-test")
+        Opts.OverflowPatternExclusionMask |= LangOptionsBase::AddOverflowTest;
+      else if (Value == "negated-unsigned-const")
+        Opts.OverflowPatternExclusionMask |= LangOptionsBase::NegUnsignedConst;
+      else if (Value == "post-decr-while")
+        Opts.OverflowPatternExclusionMask |= LangOptionsBase::PostDecrInWhile;
+    }
+  }
+
   // Parse -fsanitize= arguments.
   parseSanitizerKinds("-fsanitize=", Args.getAllArgValues(OPT_fsanitize_EQ),
                       Diags, Opts.Sanitize);
diff --git a/clang/test/CodeGen/overflow-idiom-exclusion-fp.c b/clang/test/CodeGen/overflow-idiom-exclusion-fp.c
index 0fa847f733a7fb..7bd7f94a3f039c 100644
--- a/clang/test/CodeGen/overflow-idiom-exclusion-fp.c
+++ b/clang/test/CodeGen/overflow-idiom-exclusion-fp.c
@@ -1,6 +1,6 @@
 // Check for potential false positives from patterns that _almost_ match classic overflow idioms
-// RUN: %clang %s -O2 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fno-sanitize-overflow-idioms -S -emit-llvm -o - | FileCheck %s
-// RUN: %clang %s -O2 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fno-sanitize-overflow-idioms -fwrapv -S -emit-llvm -o - | FileCheck %s
+// RUN: %clang %s -O2 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-overflow-pattern-exclusion=all -S -emit-llvm -o - | FileCheck %s
+// RUN: %clang %s -O2 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-overflow-pattern-exclusion=all -fwrapv -S -emit-llvm -o - | FileCheck %s
 
 extern unsigned a, b, c;
 extern int u, v, w;
diff --git a/clang/test/CodeGen/overflow-idiom-exclusion.c b/clang/test/CodeGen/overflow-idiom-exclusion.c
index df138fb8f5db84..d09e8c364963c5 100644
--- a/clang/test/CodeGen/overflow-idiom-exclusion.c
+++ b/clang/test/CodeGen/overflow-idiom-exclusion.c
@@ -16,12 +16,25 @@
 // unsigned negation, for example:
 // unsigned long A = -1UL;
 
-// RUN: %clang %s -O2 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fno-sanitize-overflow-idioms -S -emit-llvm -o - | FileCheck %s
-// RUN: %clang %s -O2 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fno-sanitize-overflow-idioms -fwrapv -S -emit-llvm -o - | FileCheck %s
-// RUN: %clang %s -O2 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fno-sanitize-overflow-idioms -S -emit-llvm -o - | FileCheck %s --check-prefix=NEGATION
-// RUN: %clang %s -O2 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fno-sanitize-overflow-idioms -fsanitize-negation-overflow -S -emit-llvm -o - | FileCheck %s --check-prefix=NEGATIONOV
-// CHECK-NOT: br{{.*}}overflow
+// RUN: %clang %s -O2 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-overflow-pattern-exclusion=all -S -emit-llvm -o - | FileCheck %s
+// RUN: %clang %s -O2 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-overflow-pattern-exclusion=all -fwrapv -S -emit-llvm -o - | FileCheck %s
+// RUN: %clang %s -O2 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-overflow-pattern-exclusion=add-overflow-test -S -emit-llvm -o - | FileCheck %s --check-prefix=ADD
+// RUN: %clang %s -O2 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-overflow-pattern-exclusion=negated-unsigned-const -S -emit-llvm -o - | FileCheck %s --check-prefix=NEGATE
+// RUN: %clang %s -O2 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-overflow-pattern-exclusion=post-decr-while -S -emit-llvm -o - | FileCheck %s --check-prefix=WHILE
 
+// CHECK-NOT: handle{{.*}}overflow
+
+// ADD: usub.with.overflow
+// ADD: negate_overflow
+// ADD-NOT: handler.add_overflow
+
+// NEGATE: handler.add_overflow
+// NEGATE: usub.with.overflow
+// NEGATE-NOT: negate_overflow
+
+// WHILE: handler.add_overflow
+// WHILE: negate_overflow
+// WHILE-NOT: usub.with.overflow
 extern unsigned a, b, c;
 extern unsigned some(void);
 
@@ -110,16 +123,13 @@ void common_while(unsigned i) {
   }
 }
 
-// NEGATION-LABEL,NEGATIONOV-LABEL: define{{.*}}negation_overflow
-// NEGATION-NOT: negate_overflow
-// NEGATIONOV: negate_overflow
 // Normally, these assignments would trip the unsigned overflow sanitizer.
-void negation_overflow(void) {
+void negation(void) {
 #define SOME -1UL
   unsigned long A = -1UL;
   unsigned long B = -2UL;
   unsigned long C = -3UL;
-  unsigned long D = -1337UL;
+  unsigned long D = -SOME;
   (void)A;(void)B;(void)C;(void)D;
 }
 

>From 1f15671ad9f206f7e842e1741a634e5859860578 Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Mon, 29 Jul 2024 20:19:30 +0000
Subject: [PATCH 05/10] remove more instances of 'idiom'

Signed-off-by: Justin Stitt <justinstitt at google.com>
---
 clang/docs/ReleaseNotes.rst                   | 28 ++++----
 clang/docs/UndefinedBehaviorSanitizer.rst     | 64 ++++++++++---------
 clang/include/clang/Driver/Options.td         |  5 --
 clang/include/clang/Driver/SanitizerArgs.h    |  1 -
 clang/lib/AST/Expr.cpp                        |  6 +-
 clang/lib/CodeGen/CGExprScalar.cpp            |  4 +-
 clang/lib/Driver/SanitizerArgs.cpp            |  7 --
 .../CodeGen/overflow-idiom-exclusion-fp.c     | 20 ++++--
 clang/test/CodeGen/overflow-idiom-exclusion.c | 11 ++--
 9 files changed, 71 insertions(+), 75 deletions(-)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index ecb6d67463bdb3..b03d748307b753 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -294,31 +294,35 @@ Moved checkers
 Sanitizers
 ----------
 
-- Added the ``-fno-sanitize-overflow-idioms`` flag which disables integer
-  overflow and truncation sanitizer instrumentation for specific
-  overflow-dependent code patterns. The noise created by these idioms is a
-  large reason as to why large projects refuse to turn on arithmetic
-  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
      }
 
-     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
-     }
-
-  Note that the ``-fsanitize-overflow-idioms`` flag now also exists but has
-  virtually no function other than to disable an already present
-  ``-fno-sanitize-overflow-idioms``.
+  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
 ----------------------
diff --git a/clang/docs/UndefinedBehaviorSanitizer.rst b/clang/docs/UndefinedBehaviorSanitizer.rst
index b856f601aec81f..2b15d14ff449be 100644
--- a/clang/docs/UndefinedBehaviorSanitizer.rst
+++ b/clang/docs/UndefinedBehaviorSanitizer.rst
@@ -293,56 +293,58 @@ 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.
 
-Issue Suppression
-=================
-
-UndefinedBehaviorSanitizer is not expected to produce false positives.
-If you see one, look again; most likely it is a true positive!
-
-Disabling Instrumentation with ``__attribute__((no_sanitize("undefined")))``
-----------------------------------------------------------------------------
-
-You disable UBSan checks for particular functions with
-``__attribute__((no_sanitize("undefined")))``. You can use all values of
-``-fsanitize=`` flag in this attribute, e.g. if your function deliberately
-contains possible signed integer overflow, you can use
-``__attribute__((no_sanitize("signed-integer-overflow")))``.
-
-This attribute may not be
-supported by other compilers, so consider using it together with
-``#if defined(__clang__)``.
-
-Disabling instrumentation of common overflow idioms
-=====================================================
+Disabling instrumentation for common overflow patterns
+------------------------------------------------------
 
-There are certain overflow-dependent code patterns which produce a lot of noise
-for integer overflow/truncation sanitizers. To disable instrumentation for
-these common patterns one should use ``-fno-sanitize-overflow-idioms``. Its
-inverse ``-fsanitize-overflow-idioms`` also exists but has no function other
-than to disable an already present ``-fno-sanitize-overflow-idioms``.
+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 handles three pervasive overflow-dependent code idioms:
+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. Existing
-projects that enable more warnings or sanitizers may find that the compiler is
-too noisy. Now, they can use ``-fno-sanitize-overflow-idioms`` with no source
-modifications.
+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
+=================
+
+UndefinedBehaviorSanitizer is not expected to produce false positives.
+If you see one, look again; most likely it is a true positive!
+
+Disabling Instrumentation with ``__attribute__((no_sanitize("undefined")))``
+----------------------------------------------------------------------------
+
+You disable UBSan checks for particular functions with
+``__attribute__((no_sanitize("undefined")))``. You can use all values of
+``-fsanitize=`` flag in this attribute, e.g. if your function deliberately
+contains possible signed integer overflow, you can use
+``__attribute__((no_sanitize("signed-integer-overflow")))``.
+
+This attribute may not be
+supported by other compilers, so consider using it together with
+``#if defined(__clang__)``.
 
 Suppressing Errors in Recompiled Code (Ignorelist)
 --------------------------------------------------
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index 1b9da8617c8924..6bb9a0cfe7083a 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -2558,11 +2558,6 @@ defm sanitize_stats : BoolOption<"f", "sanitize-stats",
           "Disable">,
   BothFlags<[], [ClangOption], " sanitizer statistics gathering.">>,
   Group<f_clang_Group>;
-defm sanitize_overflow_idioms : BoolFOption<"sanitize-overflow-idioms",
-  LangOpts<"SanitizeOverflowIdioms">, DefaultTrue,
-  PosFlag<SetTrue, [], [], "Enable">,
-  NegFlag<SetFalse, [], [], "Disable">,
-  BothFlags<[], [ClangOption], " the instrumentation of common overflow idioms">>;
 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]>,
diff --git a/clang/include/clang/Driver/SanitizerArgs.h b/clang/include/clang/Driver/SanitizerArgs.h
index 1e23a6a6f85f12..e64ec463ca8907 100644
--- a/clang/include/clang/Driver/SanitizerArgs.h
+++ b/clang/include/clang/Driver/SanitizerArgs.h
@@ -65,7 +65,6 @@ class SanitizerArgs {
   // True if cross-dso CFI support if provided by the system (i.e. Android).
   bool ImplicitCfiRuntime = false;
   bool NeedsMemProfRt = false;
-  bool SanitizeOverflowIdioms = true;
   bool HwasanUseAliases = false;
   llvm::AsanDetectStackUseAfterReturnMode AsanUseAfterReturn =
       llvm::AsanDetectStackUseAfterReturnMode::Invalid;
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index 8069cf80ea5058..ffe10ecb241734 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -4760,12 +4760,12 @@ ParenListExpr *ParenListExpr::CreateEmpty(const ASTContext &Ctx,
 }
 
 namespace {
-/// Certain overflow-dependent code idioms can have their integer overflow
+/// 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 *>
-getOverflowIdiomBinOp(const BinaryOperator *E) {
+getOverflowPatternBinOp(const BinaryOperator *E) {
   Expr *Addition, *ComparedTo;
   if (E->getOpcode() == BO_LT) {
     Addition = E->getLHS();
@@ -4821,7 +4821,7 @@ BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
   SubExprs[RHS] = rhs;
   if (Ctx.getLangOpts().isOverflowPatternExcluded(
           LangOptions::OverflowPatternExclusionKind::AddOverflowTest)) {
-    std::optional<BinaryOperator *> Result = getOverflowIdiomBinOp(this);
+    std::optional<BinaryOperator *> Result = getOverflowPatternBinOp(this);
     if (Result.has_value())
       Result.value()->ExcludedOverflowPattern = true;
   }
diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp
index f1e71557fb5add..3064e744be0666 100644
--- a/clang/lib/CodeGen/CGExprScalar.cpp
+++ b/clang/lib/CodeGen/CGExprScalar.cpp
@@ -198,9 +198,7 @@ static bool CanElideOverflowCheck(const ASTContext &Ctx, const BinOpInfo &Op) {
 
   const UnaryOperator *UO = dyn_cast<UnaryOperator>(Op.E);
 
-  // TODO: rename or rework NegUnsignedConst because it doesn't only work on
-  // constants.
-  if (UO && UO->getOpcode() == UO_Minus &&
+  if (UO && UO->getOpcode() == UO_Minus && UO->isIntegerConstantExpr(Ctx) &&
       Ctx.getLangOpts().isOverflowPatternExcluded(
           LangOptions::OverflowPatternExclusionKind::NegUnsignedConst))
     return true;
diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp
index 3b1c0b645ffbe0..a63ee944fd1bb4 100644
--- a/clang/lib/Driver/SanitizerArgs.cpp
+++ b/clang/lib/Driver/SanitizerArgs.cpp
@@ -1080,10 +1080,6 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC,
                                 options::OPT_fmemory_profile_EQ,
                                 options::OPT_fno_memory_profile, false);
 
-  SanitizeOverflowIdioms =
-      Args.hasFlag(options::OPT_fsanitize_overflow_idioms,
-                   options::OPT_fno_sanitize_overflow_idioms, true);
-
   // Finally, initialize the set of available and recoverable sanitizers.
   Sanitizers.Mask |= Kinds;
   RecoverableSanitizers.Mask |= RecoverableKinds;
@@ -1375,9 +1371,6 @@ void SanitizerArgs::addArgs(const ToolChain &TC, const llvm::opt::ArgList &Args,
     CmdArgs.push_back("+tagged-globals");
   }
 
-  if (!SanitizeOverflowIdioms)
-    CmdArgs.push_back("-fno-sanitize-overflow-idioms");
-
   // MSan: Workaround for PR16386.
   // ASan: This is mainly to help LSan with cases such as
   // https://github.com/google/sanitizers/issues/373
diff --git a/clang/test/CodeGen/overflow-idiom-exclusion-fp.c b/clang/test/CodeGen/overflow-idiom-exclusion-fp.c
index 7bd7f94a3f039c..621d8f43babd8b 100644
--- a/clang/test/CodeGen/overflow-idiom-exclusion-fp.c
+++ b/clang/test/CodeGen/overflow-idiom-exclusion-fp.c
@@ -1,4 +1,4 @@
-// Check for potential false positives from patterns that _almost_ match classic overflow idioms
+// Check for potential false positives from patterns that _almost_ match classic overflow-dependent or overflow-prone code patterns
 // RUN: %clang %s -O2 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-overflow-pattern-exclusion=all -S -emit-llvm -o - | FileCheck %s
 // RUN: %clang %s -O2 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-overflow-pattern-exclusion=all -fwrapv -S -emit-llvm -o - | FileCheck %s
 
@@ -8,7 +8,8 @@ extern int u, v, w;
 extern unsigned some(void);
 
 // Make sure all these still have handler paths, we shouldn't be excluding
-// instrumentation of any "near" idioms.
+// instrumentation of any "near" patterns.
+// CHECK-LABEL: close_but_not_quite
 void close_but_not_quite(void) {
   // CHECK: br i1{{.*}}handler.
   if (a + b > a)
@@ -26,6 +27,7 @@ void close_but_not_quite(void) {
   if (a + b + 1 < a)
     c = 9;
 
+  // CHECK: br i1{{.*}}handler.
   // CHECK: br i1{{.*}}handler.
   if (a + b < a + 1)
     c = 9;
@@ -42,10 +44,6 @@ void close_but_not_quite(void) {
   if (a + b == a)
     c = 9;
 
-  // CHECK: br i1{{.*}}handler
-  if (u + v < u) /* matches overflow idiom, but is signed */
-    c = 9;
-
   // CHECK: br i1{{.*}}handler
   // Although this can never actually overflow we are still checking that the
   // sanitizer instruments it.
@@ -54,7 +52,6 @@ void close_but_not_quite(void) {
 }
 
 // cvise'd kernel code that caused problems during development
-// CHECK: br i1{{.*}}handler
 typedef unsigned size_t;
 typedef enum { FSE_repeat_none } FSE_repeat;
 typedef enum { ZSTD_defaultAllowed } ZSTD_defaultPolicy_e;
@@ -62,6 +59,8 @@ FSE_repeat ZSTD_selectEncodingType_repeatMode;
 ZSTD_defaultPolicy_e ZSTD_selectEncodingType_isDefaultAllowed;
 size_t ZSTD_NCountCost(void);
 
+// CHECK-LABEL: ZSTD_selectEncodingType
+// CHECK: br i1{{.*}}handler
 void ZSTD_selectEncodingType(void) {
   size_t basicCost =
              ZSTD_selectEncodingType_isDefaultAllowed ? ZSTD_NCountCost() : 0,
@@ -70,8 +69,15 @@ void ZSTD_selectEncodingType(void) {
     ZSTD_selectEncodingType_repeatMode = FSE_repeat_none;
 }
 
+// CHECK-LABEL: function_calls
 void function_calls(void) {
   // CHECK: br i1{{.*}}handler
   if (some() + b < some())
     c = 9;
 }
+
+// CHECK-LABEL: not_quite_a_negated_unsigned_const
+void not_quite_a_negated_unsigned_const(void) {
+  // CHECK: br i1{{.*}}handler
+  a = -b;
+}
diff --git a/clang/test/CodeGen/overflow-idiom-exclusion.c b/clang/test/CodeGen/overflow-idiom-exclusion.c
index d09e8c364963c5..e753343542a061 100644
--- a/clang/test/CodeGen/overflow-idiom-exclusion.c
+++ b/clang/test/CodeGen/overflow-idiom-exclusion.c
@@ -1,6 +1,6 @@
-// Ensure some common idioms don't trigger the overflow sanitizers when
-// -fno-sanitize-overflow-idioms is enabled. In many cases, overflow warnings
-// caused by these idioms are seen as "noise" and result in users turning off
+// Ensure some common overflow-dependent or overflow-prone code patterns don't
+// trigger the overflow sanitizers. In many cases, overflow warnings caused by
+// these patterns are seen as "noise" and result in users turning off
 // sanitization all together.
 
 // A pattern like "if (a + b < a)" simply checks for overflow and usually means
@@ -9,8 +9,7 @@
 // Similarly, a pattern resembling "while (i--)" is extremely common and
 // warning on its inevitable overflow can be seen as superfluous. Do note that
 // using "i" in future calculations can be tricky because it will still
-// wrap-around. Using -fno-sanitize-overflow-idioms or not doesn't change this
-// fact -- we just won't warn/trap with sanitizers.
+// wrap-around.
 
 // Another common pattern that, in some cases, is found to be too noisy is
 // unsigned negation, for example:
@@ -107,7 +106,7 @@ void nestedstructs(void) {
 }
 
 // Normally, this would be folded into a simple call to the overflow handler
-// and a store. Excluding this idiom results in just a store.
+// and a store. Excluding this pattern results in just a store.
 void constants(void) {
   unsigned base = 4294967295;
   unsigned offset = 1;

>From 95bab4cbecdb946143e470892df9c970c7586d77 Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Thu, 1 Aug 2024 21:46:13 +0000
Subject: [PATCH 06/10] default initialize field

Signed-off-by: Justin Stitt <justinstitt at google.com>
---
 clang/include/clang/AST/Expr.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index b25a4368aa1527..31572cd4465ebc 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -3860,7 +3860,7 @@ class CStyleCastExpr final
 class BinaryOperator : public Expr {
   enum { LHS, RHS, END_EXPR };
   Stmt *SubExprs[END_EXPR];
-  bool ExcludedOverflowPattern;
+  bool ExcludedOverflowPattern = false;
 
 public:
   typedef BinaryOperatorKind Opcode;

>From 4a3c53161f82162d14e621b87eb43d30a6412f5c Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Thu, 1 Aug 2024 23:12:42 +0000
Subject: [PATCH 07/10] update tests to not redefine size_t

Signed-off-by: Justin Stitt <justinstitt at google.com>
---
 clang/test/CodeGen/overflow-idiom-exclusion-fp.c | 6 +++---
 clang/test/CodeGen/overflow-idiom-exclusion.c    | 4 ++--
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/clang/test/CodeGen/overflow-idiom-exclusion-fp.c b/clang/test/CodeGen/overflow-idiom-exclusion-fp.c
index 621d8f43babd8b..e6b729e0c788e6 100644
--- a/clang/test/CodeGen/overflow-idiom-exclusion-fp.c
+++ b/clang/test/CodeGen/overflow-idiom-exclusion-fp.c
@@ -52,17 +52,17 @@ void close_but_not_quite(void) {
 }
 
 // cvise'd kernel code that caused problems during development
-typedef unsigned size_t;
+typedef unsigned _size_t;
 typedef enum { FSE_repeat_none } FSE_repeat;
 typedef enum { ZSTD_defaultAllowed } ZSTD_defaultPolicy_e;
 FSE_repeat ZSTD_selectEncodingType_repeatMode;
 ZSTD_defaultPolicy_e ZSTD_selectEncodingType_isDefaultAllowed;
-size_t ZSTD_NCountCost(void);
+_size_t ZSTD_NCountCost(void);
 
 // CHECK-LABEL: ZSTD_selectEncodingType
 // CHECK: br i1{{.*}}handler
 void ZSTD_selectEncodingType(void) {
-  size_t basicCost =
+  _size_t basicCost =
              ZSTD_selectEncodingType_isDefaultAllowed ? ZSTD_NCountCost() : 0,
          compressedCost = 3 + ZSTD_NCountCost();
   if (basicCost <= compressedCost)
diff --git a/clang/test/CodeGen/overflow-idiom-exclusion.c b/clang/test/CodeGen/overflow-idiom-exclusion.c
index e753343542a061..c93da342f91491 100644
--- a/clang/test/CodeGen/overflow-idiom-exclusion.c
+++ b/clang/test/CodeGen/overflow-idiom-exclusion.c
@@ -134,10 +134,10 @@ void negation(void) {
 
 // cvise'd kernel code that caused problems during development due to sign
 // extension
-typedef unsigned long size_t;
+typedef unsigned long _size_t;
 int qnbytes;
 int *key_alloc_key;
-size_t key_alloc_quotalen;
+_size_t key_alloc_quotalen;
 int *key_alloc(void) {
   if (qnbytes + key_alloc_quotalen < qnbytes)
     return key_alloc_key;

>From 4b3efbb41ff86eeff15671b1d876e1ef6a58a536 Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Thu, 8 Aug 2024 17:29:00 -0700
Subject: [PATCH 08/10] separate logic into function and prefer StringSwitch

Addressing Bill's feedback:
* separate while(i--) disableSanitizer logic into a function, rename this
field.

* slightly rewrite some docs by adding titles

* Prefer stringswitch to if-else chain

* move comments within tests to after RUN lines

Signed-off-by: Justin Stitt <justinstitt at google.com>
---
 clang/docs/UndefinedBehaviorSanitizer.rst     | 26 ++++++++++-----
 clang/include/clang/Basic/LangOptions.h       |  2 +-
 clang/lib/CodeGen/CGExprScalar.cpp            | 33 +++++++++++++------
 clang/lib/Frontend/CompilerInvocation.cpp     | 19 +++++------
 .../CodeGen/overflow-idiom-exclusion-fp.c     |  2 +-
 clang/test/CodeGen/overflow-idiom-exclusion.c | 11 ++++---
 6 files changed, 56 insertions(+), 37 deletions(-)

diff --git a/clang/docs/UndefinedBehaviorSanitizer.rst b/clang/docs/UndefinedBehaviorSanitizer.rst
index 2b15d14ff449be..9f3d980eefbea7 100644
--- a/clang/docs/UndefinedBehaviorSanitizer.rst
+++ b/clang/docs/UndefinedBehaviorSanitizer.rst
@@ -297,11 +297,16 @@ 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=``.
+produce a lot of noise for integer overflow/truncation sanitizers. Negated
+unsigned constants, post-decrements in a while loop condition and simple
+overflow checks are accepted and pervasive code patterns. However, the signal
+received from sanitizers instrumenting these code patterns may be too noisy for
+some projects. To disable instrumentation for these common patterns one should
+use ``-fsanitize-overflow-pattern-exclusion=``.
 
-Currently, this option supports three pervasive overflow-dependent code idioms:
+Currently, this option supports three overflow-dependent code idioms:
+
+``negated-unsigned-const``
 
 .. code-block:: c++
 
@@ -309,23 +314,26 @@ Currently, this option supports three pervasive overflow-dependent code idioms:
     unsigned long foo = -1UL; // No longer causes a negation overflow warning
     unsigned long bar = -2UL; // and so on...
 
+``post-decr-while``
+
 .. 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
 
+``add-overflow-test``
+
 .. 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.
+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
 =================
diff --git a/clang/include/clang/Basic/LangOptions.h b/clang/include/clang/Basic/LangOptions.h
index d57a9d7f31c733..eb4cb4b5a7e93f 100644
--- a/clang/include/clang/Basic/LangOptions.h
+++ b/clang/include/clang/Basic/LangOptions.h
@@ -571,7 +571,7 @@ class LangOptions : public LangOptionsBase {
   GPUDefaultStreamKind GPUDefaultStream;
 
   /// Which overflow patterns should be excluded from sanitizer instrumentation
-  int OverflowPatternExclusionMask = 0;
+  unsigned OverflowPatternExclusionMask = 0;
 
   std::vector<std::string> OverflowPatternExclusionValues;
 
diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp
index 3064e744be0666..4695228b1b9e57 100644
--- a/clang/lib/CodeGen/CGExprScalar.cpp
+++ b/clang/lib/CodeGen/CGExprScalar.cpp
@@ -2777,6 +2777,26 @@ llvm::Value *ScalarExprEmitter::EmitIncDecConsiderOverflowBehavior(
   llvm_unreachable("Unknown SignedOverflowBehaviorTy");
 }
 
+/// For the purposes of overflow pattern exclusion, does this match the
+/// "while(i--)" pattern?
+static bool matchesPostDecrInWhile(const UnaryOperator *UO, bool isInc,
+                                   bool isPre, ASTContext &Ctx) {
+  if (isInc || isPre)
+    return false;
+
+  // -fsanitize-overflow-pattern-exclusion=post-decr-while
+  if (!Ctx.getLangOpts().isOverflowPatternExcluded(
+          LangOptions::OverflowPatternExclusionKind::PostDecrInWhile))
+    return false;
+
+  // all Parents (usually just one) must be a WhileStmt
+  for (const auto &Parent : Ctx.getParentMapContext().getParents(*UO))
+    if (!Parent.get<WhileStmt>())
+      return false;
+
+  return true;
+}
+
 namespace {
 /// Handles check and update for lastprivate conditional variables.
 class OMPLastprivateConditionalUpdateRAII {
@@ -2889,15 +2909,8 @@ ScalarExprEmitter::EmitScalarPrePostIncDec(const UnaryOperator *E, LValue LV,
     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>();
-                      }));
+    bool excludeOverflowPattern =
+        matchesPostDecrInWhile(E, isInc, isPre, CGF.getContext());
 
     if (CGF.getContext().isPromotableIntegerType(type)) {
       promotedType = CGF.getContext().getPromotedIntegerType(type);
@@ -2959,7 +2972,7 @@ ScalarExprEmitter::EmitScalarPrePostIncDec(const UnaryOperator *E, LValue LV,
       value = EmitIncDecConsiderOverflowBehavior(E, value, isInc);
     } else if (E->canOverflow() && type->isUnsignedIntegerType() &&
                CGF.SanOpts.has(SanitizerKind::UnsignedIntegerOverflow) &&
-               !disableSanitizer) {
+               !excludeOverflowPattern) {
       value = EmitOverflowCheckedBinOp(createBinOpInfoFromIncDec(
           E, value, isInc, E->getFPFeaturesInEffect(CGF.getLangOpts())));
     } else {
diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp
index 0d257a40859fde..8ab7b69c00312f 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -4250,17 +4250,14 @@ bool CompilerInvocation::ParseLangArgs(LangOptions &Opts, ArgList &Args,
 
   if (auto *A = Args.getLastArg(OPT_fsanitize_overflow_pattern_exclusion_EQ)) {
     for (int i = 0, n = A->getNumValues(); i != n; ++i) {
-      StringRef Value = A->getValue(i);
-      if (Value == "none")
-        Opts.OverflowPatternExclusionMask |= LangOptionsBase::None;
-      else if (Value == "all")
-        Opts.OverflowPatternExclusionMask |= LangOptionsBase::All;
-      else if (Value == "add-overflow-test")
-        Opts.OverflowPatternExclusionMask |= LangOptionsBase::AddOverflowTest;
-      else if (Value == "negated-unsigned-const")
-        Opts.OverflowPatternExclusionMask |= LangOptionsBase::NegUnsignedConst;
-      else if (Value == "post-decr-while")
-        Opts.OverflowPatternExclusionMask |= LangOptionsBase::PostDecrInWhile;
+      Opts.OverflowPatternExclusionMask |=
+          llvm::StringSwitch<unsigned>(A->getValue(i))
+              .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);
     }
   }
 
diff --git a/clang/test/CodeGen/overflow-idiom-exclusion-fp.c b/clang/test/CodeGen/overflow-idiom-exclusion-fp.c
index e6b729e0c788e6..d21405c56beab3 100644
--- a/clang/test/CodeGen/overflow-idiom-exclusion-fp.c
+++ b/clang/test/CodeGen/overflow-idiom-exclusion-fp.c
@@ -1,7 +1,7 @@
-// Check for potential false positives from patterns that _almost_ match classic overflow-dependent or overflow-prone code patterns
 // RUN: %clang %s -O2 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-overflow-pattern-exclusion=all -S -emit-llvm -o - | FileCheck %s
 // RUN: %clang %s -O2 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-overflow-pattern-exclusion=all -fwrapv -S -emit-llvm -o - | FileCheck %s
 
+// Check for potential false positives from patterns that _almost_ match classic overflow-dependent or overflow-prone code patterns
 extern unsigned a, b, c;
 extern int u, v, w;
 
diff --git a/clang/test/CodeGen/overflow-idiom-exclusion.c b/clang/test/CodeGen/overflow-idiom-exclusion.c
index c93da342f91491..7c8c4af61029de 100644
--- a/clang/test/CodeGen/overflow-idiom-exclusion.c
+++ b/clang/test/CodeGen/overflow-idiom-exclusion.c
@@ -1,3 +1,9 @@
+// RUN: %clang %s -O2 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-overflow-pattern-exclusion=all -S -emit-llvm -o - | FileCheck %s
+// RUN: %clang %s -O2 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-overflow-pattern-exclusion=all -fwrapv -S -emit-llvm -o - | FileCheck %s
+// RUN: %clang %s -O2 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-overflow-pattern-exclusion=add-overflow-test -S -emit-llvm -o - | FileCheck %s --check-prefix=ADD
+// RUN: %clang %s -O2 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-overflow-pattern-exclusion=negated-unsigned-const -S -emit-llvm -o - | FileCheck %s --check-prefix=NEGATE
+// RUN: %clang %s -O2 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-overflow-pattern-exclusion=post-decr-while -S -emit-llvm -o - | FileCheck %s --check-prefix=WHILE
+
 // Ensure some common overflow-dependent or overflow-prone code patterns don't
 // trigger the overflow sanitizers. In many cases, overflow warnings caused by
 // these patterns are seen as "noise" and result in users turning off
@@ -15,11 +21,6 @@
 // unsigned negation, for example:
 // unsigned long A = -1UL;
 
-// RUN: %clang %s -O2 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-overflow-pattern-exclusion=all -S -emit-llvm -o - | FileCheck %s
-// RUN: %clang %s -O2 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-overflow-pattern-exclusion=all -fwrapv -S -emit-llvm -o - | FileCheck %s
-// RUN: %clang %s -O2 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-overflow-pattern-exclusion=add-overflow-test -S -emit-llvm -o - | FileCheck %s --check-prefix=ADD
-// RUN: %clang %s -O2 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-overflow-pattern-exclusion=negated-unsigned-const -S -emit-llvm -o - | FileCheck %s --check-prefix=NEGATE
-// RUN: %clang %s -O2 -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-overflow-pattern-exclusion=post-decr-while -S -emit-llvm -o - | FileCheck %s --check-prefix=WHILE
 
 // CHECK-NOT: handle{{.*}}overflow
 

>From 2e3d4795633bb1a134be10b44e83a025a7b21d8e Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Mon, 12 Aug 2024 11:33:36 -0700
Subject: [PATCH 09/10] remove anonymous namespace

remove the anonymous namespace as a static qualifier is enough for this
method.

Signed-off-by: Justin Stitt <justinstitt at google.com>
---

Thanks Bill.
---
 clang/lib/AST/Expr.cpp | 2 --
 1 file changed, 2 deletions(-)

diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index ffe10ecb241734..5c8624d77e8c7e 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -4759,7 +4759,6 @@ 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
@@ -4806,7 +4805,6 @@ getOverflowPatternBinOp(const BinaryOperator *E) {
     return BO;
   return {};
 }
-} // namespace
 
 BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
                                Opcode opc, QualType ResTy, ExprValueKind VK,

>From cb0fcd1c3b1ded7258bb20cff4d187ca56f17149 Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Mon, 12 Aug 2024 17:57:03 -0700
Subject: [PATCH 10/10] move ExcludedOverflowPattern to BinaryOperatorBits

* move ExcludedOverflowPattern to BinaryOperatorBits
* reorder some logic to benefit from short-circuiting

Signed-off-by: Justin Stitt <justinstitt at google.com>
---
 clang/include/clang/AST/Expr.h     | 5 +++--
 clang/include/clang/AST/Stmt.h     | 2 ++
 clang/lib/AST/Expr.cpp             | 3 ++-
 clang/lib/CodeGen/CGExprScalar.cpp | 5 +++--
 4 files changed, 10 insertions(+), 5 deletions(-)

diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index 31572cd4465ebc..db5a07014a9942 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -3860,7 +3860,6 @@ class CStyleCastExpr final
 class BinaryOperator : public Expr {
   enum { LHS, RHS, END_EXPR };
   Stmt *SubExprs[END_EXPR];
-  bool ExcludedOverflowPattern = false;
 
 public:
   typedef BinaryOperatorKind Opcode;
@@ -4019,7 +4018,9 @@ class BinaryOperator : public Expr {
     return isShiftAssignOp(getOpcode());
   }
 
-  bool ignoreOverflowSanitizers() const { return ExcludedOverflowPattern; }
+  bool ignoreOverflowSanitizers() const {
+    return BinaryOperatorBits.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
diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h
index bbd7634bcc3bfb..22e17a03f974ba 100644
--- a/clang/include/clang/AST/Stmt.h
+++ b/clang/include/clang/AST/Stmt.h
@@ -649,6 +649,8 @@ class alignas(void *) Stmt {
     /// It is 0 otherwise.
     LLVM_PREFERRED_TYPE(bool)
     unsigned HasFPFeatures : 1;
+    LLVM_PREFERRED_TYPE(bool)
+    unsigned ExcludedOverflowPattern : 1;
 
     SourceLocation OpLoc;
   };
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index 5c8624d77e8c7e..57475c66a94e35 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -4815,13 +4815,14 @@ BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
   assert(!isCompoundAssignmentOp() &&
          "Use CompoundAssignOperator for compound assignments");
   BinaryOperatorBits.OpLoc = opLoc;
+  BinaryOperatorBits.ExcludedOverflowPattern = 0;
   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;
+      Result.value()->BinaryOperatorBits.ExcludedOverflowPattern = 1;
   }
   BinaryOperatorBits.HasFPFeatures = FPFeatures.requiresTrailingStorage();
   if (hasStoredFPFeatures())
diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp
index 4695228b1b9e57..5285fd4c314b07 100644
--- a/clang/lib/CodeGen/CGExprScalar.cpp
+++ b/clang/lib/CodeGen/CGExprScalar.cpp
@@ -198,9 +198,10 @@ static bool CanElideOverflowCheck(const ASTContext &Ctx, const BinOpInfo &Op) {
 
   const UnaryOperator *UO = dyn_cast<UnaryOperator>(Op.E);
 
-  if (UO && UO->getOpcode() == UO_Minus && UO->isIntegerConstantExpr(Ctx) &&
+  if (UO && UO->getOpcode() == UO_Minus &&
       Ctx.getLangOpts().isOverflowPatternExcluded(
-          LangOptions::OverflowPatternExclusionKind::NegUnsignedConst))
+          LangOptions::OverflowPatternExclusionKind::NegUnsignedConst) &&
+      UO->isIntegerConstantExpr(Ctx))
     return true;
 
   // If a unary op has a widened operand, the op cannot overflow.



More information about the cfe-commits mailing list