[clang] [Clang] add wraps and no_wraps attributes (PR #115094)

Justin Stitt via cfe-commits cfe-commits at lists.llvm.org
Tue Nov 12 17:20:54 PST 2024


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

>From b5515eba87ffd96d010952bf18fe4044861df298 Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Tue, 5 Mar 2024 03:14:49 +0000
Subject: [PATCH 01/14] add wraps, no_wraps attributes

---
 clang/docs/ReleaseNotes.rst                   |  20 ++++
 clang/docs/SanitizerSpecialCaseList.rst       |   2 +
 clang/include/clang/AST/Expr.h                |   6 ++
 clang/include/clang/AST/Type.h                |   3 +
 clang/include/clang/Basic/Attr.td             |  15 +++
 clang/include/clang/Basic/AttrDocs.td         | 100 ++++++++++++++++++
 clang/include/clang/Basic/DiagnosticGroups.td |   6 ++
 .../clang/Basic/DiagnosticSemaKinds.td        |   7 ++
 clang/lib/AST/Expr.cpp                        |  20 ++++
 clang/lib/AST/ExprConstant.cpp                |   4 +-
 clang/lib/AST/Type.cpp                        |   9 ++
 clang/lib/AST/TypePrinter.cpp                 |   6 ++
 clang/lib/CodeGen/CGExprScalar.cpp            |  48 ++++++---
 clang/lib/Sema/Sema.cpp                       |   3 +
 clang/lib/Sema/SemaChecking.cpp               |  35 +++++-
 clang/lib/Sema/SemaDecl.cpp                   |   2 +-
 clang/lib/Sema/SemaDeclAttr.cpp               |  16 ++-
 clang/lib/Sema/SemaType.cpp                   |  25 +++++
 clang/test/CodeGen/integer-overflow.c         |  66 ++++++++++++
 clang/test/CodeGen/unsigned-overflow.c        |  63 +++++++++--
 clang/test/CodeGen/wraps-attribute-scl.test   |  78 ++++++++++++++
 ...a-attribute-supported-attributes-list.test |   2 +
 clang/test/Sema/attr-wraps.c                  |  48 +++++++++
 23 files changed, 556 insertions(+), 28 deletions(-)
 create mode 100644 clang/test/CodeGen/wraps-attribute-scl.test
 create mode 100644 clang/test/Sema/attr-wraps.c

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 0b0f2053f634ee..fd12e0e8d201d5 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -433,6 +433,26 @@ Attribute Changes in Clang
 - Fix a bug where clang doesn't automatically apply the ``[[gsl::Owner]]`` or
   ``[[gsl::Pointer]]`` to STL explicit template specialization decls. (#GH109442)
 
+- Introduced ``__attribute__((wraps))`` which can be added to type or variable
+  declarations. Using an attributed type or variable in an arithmetic
+  expression will define the overflow behavior for that expression as having
+  two's complement wrap-around. These expressions will not be instrumented by
+  overflow sanitizers nor will they cause integer overflow warnings. They also
+  cannot be optimized away by some eager UB optimizations as the behavior of
+  the arithmetic is no longer "undefined".
+
+  There is also ``__attribute__((no_wraps))`` which can be added to types or
+  variable declarations. Types or variables with this attribute may be
+  instrumented by overflow sanitizers, if enabled. Note that this matches the
+  default behavior of integer types. So, in most cases, ``no_wraps`` serves
+  purely as an annotation to readers of code that a type or variable really
+  shouldn't wrap-around. ``__attribute__((no_wraps))`` has the most function
+  when paired with `Sanitizer Special Case Lists (SSCL)
+  <https://clang.llvm.org/docs/SanitizerSpecialCaseList.html>`_.
+
+  These attributes are only valid for C, as there are built-in language
+  alternatives for other languages.
+
 Improvements to Clang's diagnostics
 -----------------------------------
 
diff --git a/clang/docs/SanitizerSpecialCaseList.rst b/clang/docs/SanitizerSpecialCaseList.rst
index 5c88c2976e8613..274409f47437c4 100644
--- a/clang/docs/SanitizerSpecialCaseList.rst
+++ b/clang/docs/SanitizerSpecialCaseList.rst
@@ -67,9 +67,11 @@ types specified within an ignorelist.
     int a = 2147483647; // INT_MAX
     ++a;                // Normally, an overflow with -fsanitize=signed-integer-overflow
   }
+
   $ cat ignorelist.txt
   [signed-integer-overflow]
   type:int
+
   $ clang -fsanitize=signed-integer-overflow -fsanitize-ignorelist=ignorelist.txt foo.c ; ./a.out
   # no signed-integer-overflow error
 
diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index 466c65a9685ad3..4472c941ed5c79 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -4142,6 +4142,12 @@ class BinaryOperator : public Expr {
     return getFPFeaturesInEffect(LO).getAllowFEnvAccess();
   }
 
+  /// Does one of the subexpressions have the wraps attribute?
+  bool hasWrappingOperand(const ASTContext &Ctx) const;
+
+  /// How about the no_wraps attribute?
+  bool hasNonWrappingOperand(const ASTContext &Ctx) const;
+
 protected:
   BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs, Opcode opc,
                  QualType ResTy, ExprValueKind VK, ExprObjectKind OK,
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 8979129017163b..30217408856783 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -1458,6 +1458,9 @@ class QualType {
     return getQualifiers().hasStrongOrWeakObjCLifetime();
   }
 
+  bool hasWrapsAttr() const;
+  bool hasNoWrapsAttr() const;
+
   // true when Type is objc's weak and weak is enabled but ARC isn't.
   bool isNonWeakInMRRWithObjCWeak(const ASTContext &Context) const;
 
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 24cfb5ddb6d4ca..d5a6a5ef8c02de 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -4855,3 +4855,18 @@ def ClspvLibclcBuiltin: InheritableAttr {
   let Documentation = [ClspvLibclcBuiltinDoc];
   let SimpleHandler = 1;
 }
+
+def Wraps : DeclOrTypeAttr {
+  let Spellings = [Clang<"wraps">];
+  let Subjects = SubjectList<[Var, TypedefName, Field]>;
+  let Documentation = [WrapsDocs];
+  let LangOpts = [COnly];
+}
+
+def NoWraps : DeclOrTypeAttr {
+  let Spellings = [Clang<"no_wraps">];
+  let Subjects = SubjectList<[Var, TypedefName, Field]>;
+  let Documentation = [NoWrapsDocs];
+  let LangOpts = [COnly];
+}
+
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 23c8eb2d163c86..2323febe7840e1 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -8710,3 +8710,103 @@ Declares that a function potentially allocates heap memory, and prevents any pot
 of ``nonallocating`` by the compiler.
   }];
 }
+
+def WrapsDocs : Documentation {
+  let Category = DocCatField;
+  let Content = [{
+The ``wraps`` attribute can be used with type or variable declarations to
+denote that arithmetic containing attributed types or variables have defined
+overflow behavior. Specifically, the behavior is defined as being consistent
+with two's complement wrap-around. For the purposes of sanitizers or warnings
+that concern themselves with the definedness of integer arithmetic, they will
+cease to instrument or warn about arithmetic that directly involves operands
+attributed with the ``wraps`` attribute.
+
+The ``signed-integer-overflow``, ``unsigned-integer-overflow``,
+``implicit-signed-integer-truncation`` and the
+``implicit-unsigned-integer-truncation`` sanitizers will not instrument
+arithmetic containing any operands attributed by ``wraps``. Similarly, the
+``-Winteger-overflow`` warning is disabled for these instances.
+
+The following example shows how one may disable ``signed-integer-overflow``
+sanitizer instrumentation using ``__attribute__((wraps))`` on a type definition
+when building with ``-fsanitize=signed-integer-overflow``:
+
+.. code-block:: c
+
+  typedef int __attribute__((wraps)) wrapping_int;
+
+  void foo(void) {
+    wrapping_int A = INT_MAX;
+    ++A; // no sanitizer instrumentation
+  }
+
+``wraps`` may also be used with function parameters or declarations of
+variables as well as members of structures. Using ``wraps`` on non-integer
+types will result in a `-Wuseless-wraps-attribute`. One may disable this
+warning with ``-Wno-useless-wraps-attribute``.
+
+``wraps`` persists through implicit type promotions and will be applied to the
+result type of arithmetic expressions containing a wrapping operand.
+``-Wimplicitly-discarded-wraps-attribute`` warnings can be caused in situations
+where the ``wraps`` attribute cannot persist through implicit type conversions.
+Disable this with ``-Wno-implicitly-discarded-wraps-attribute``.
+}];
+}
+
+def NoWrapsDocs : Documentation {
+  let Category = DocCatField;
+  let Content = [{
+The ``no_wraps`` attribute can be used to annotate types or variables as
+non-wrapping. This may serve as a helpful annotation to readers of code that
+particular arithmetic expressions involving these types or variables are not
+meant to wrap-around.
+
+When overflow or truncation sanitizer instrumentation is modified at the
+type-level through `SSCLs
+<https://clang.llvm.org/docs/SanitizerSpecialCaseList.html>`_, ``no_wraps`` or
+``wraps`` may be used to override sanitizer behavior.
+
+For example, one may specify an ignorelist (with ``-fsanitize-ignorelist=``) to
+disable the ``signed-integer-overflow`` sanitizer for all types:
+
+.. code-block:: text
+
+  [signed-integer-overflow]
+  type:*
+
+``no_wraps`` can override the behavior provided by the ignorelist to
+effectively re-enable instrumentation for specific types or variables.
+
+.. code-block:: c
+
+  typedef int __attribute__((no_wraps)) non_wrapping_int;
+
+  void foo(non_wrapping_int A, int B) {
+      ++A; // will be instrumented if built with -fsanitize=signed-integer-overflow
+      ++B; // won't be instrumented as it is ignored by the ignorelist
+  }
+
+Like ``wraps``, ``no_wraps`` persists through implicit type promotions and will
+be automatically applied to the result type of arithmetic expressions
+containing a wrapping operand.
+
+If a type or variable is attributed by both ``wraps`` and ``no_wraps``, then
+``no_wraps`` takes precedence -- regardless of the order of attribution.
+
+Note that ``no_wraps`` makes no guarantees about the definedness of arithmetic
+overflow. Instead, use ``-fwrapv`` or ``-fno-strict-overflow``.
+
+Like ``wraps``, ``no_wraps`` may also be used with function parameters or
+declarations of variables as well as members of structures. Using ``wraps`` on
+non-integer types will result in a `-Wuseless-wraps-attribute`. One may disable
+this warning with ``-Wno-useless-wraps-attribute``.
+
+``no_wraps`` also persists through implicit type promotions and will be applied
+to the result type of arithmetic expressions containing a wrapping operand.
+``-Wimplicitly-discarded-wraps-attribute`` warnings can be caused in situations
+where the ``wraps`` attribute cannot persist through implicit type conversions.
+Disable this with ``-Wno-implicitly-discarded-wraps-attribute``.
+}];
+}
+
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 72eada50a56cc9..0c4d0fa5528047 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1588,3 +1588,9 @@ def ExplicitSpecializationStorageClass : DiagGroup<"explicit-specialization-stor
 // A warning for options that enable a feature that is not yet complete
 def ExperimentalOption : DiagGroup<"experimental-option">;
 
+
+// Warnings regarding the usage of __attribute__((wraps)) on non-integer types.
+def UselessWrapsAttr : DiagGroup<"useless-wraps-attribute">;
+
+// Warnings about the wraps attribute getting implicitly discarded
+def ImpDiscardedWrapsAttr : DiagGroup<"implicitly-discarded-wraps-attribute">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 6a244c276facd6..6e5160a7be9b13 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -6664,6 +6664,13 @@ def err_builtin_counted_by_ref_invalid_lhs_use : Error<
 def err_builtin_counted_by_ref_has_side_effects : Error<
   "'__builtin_counted_by_ref' argument cannot have side-effects">;
 
+def warn_wraps_attr_var_decl_type_not_integer : Warning<
+  "using attribute '%select{wraps|no_wraps}0' with non-integer type '%1' has no function and is potentially misleading">,
+  InGroup<UselessWrapsAttr>;
+def warn_wraps_attr_maybe_lost : Warning<
+  "'%select{wraps|no_wraps}0' attribute may be implicitly discarded when converted to %1">,
+  InGroup<ImpDiscardedWrapsAttr>;
+
 let CategoryName = "ARC Semantic Issue" in {
 
 // ARC-mode diagnostics.
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index bf2c1b92fa6b49..7de87039cc95c2 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -2236,6 +2236,16 @@ bool BinaryOperator::isNullPointerArithmeticExtension(ASTContext &Ctx,
   return true;
 }
 
+bool BinaryOperator::hasWrappingOperand(const ASTContext &Ctx) const {
+  return getLHS()->getType().hasWrapsAttr() ||
+         getRHS()->getType().hasWrapsAttr();
+}
+
+bool BinaryOperator::hasNonWrappingOperand(const ASTContext &Ctx) const {
+  return getLHS()->getType().hasNoWrapsAttr() ||
+         getRHS()->getType().hasNoWrapsAttr();
+}
+
 SourceLocExpr::SourceLocExpr(const ASTContext &Ctx, SourceLocIdentKind Kind,
                              QualType ResultTy, SourceLocation BLoc,
                              SourceLocation RParenLoc,
@@ -4852,6 +4862,11 @@ BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
   if (hasStoredFPFeatures())
     setStoredFPFeatures(FPFeatures);
   setDependence(computeDependence(this));
+  if (hasWrappingOperand(Ctx))
+    setType(Ctx.getAttributedType(attr::Wraps, getType(), getType()));
+  if (hasNonWrappingOperand(Ctx))
+    setType(Ctx.getAttributedType(attr::NoWraps, getType(), getType()));
+
 }
 
 BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
@@ -4870,6 +4885,11 @@ BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
   if (hasStoredFPFeatures())
     setStoredFPFeatures(FPFeatures);
   setDependence(computeDependence(this));
+  if (hasWrappingOperand(Ctx))
+    setType(Ctx.getAttributedType(attr::Wraps, getType(), getType()));
+  if (hasNonWrappingOperand(Ctx))
+    setType(Ctx.getAttributedType(attr::NoWraps, getType(), getType()));
+
 }
 
 BinaryOperator *BinaryOperator::CreateEmpty(const ASTContext &C,
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index d664c503655ba6..f2758f5d6c7f35 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -2898,7 +2898,7 @@ static bool CheckedIntArithmetic(EvalInfo &Info, const Expr *E,
   APSInt Value(Op(LHS.extend(BitWidth), RHS.extend(BitWidth)), false);
   Result = Value.trunc(LHS.getBitWidth());
   if (Result.extend(BitWidth) != Value) {
-    if (Info.checkingForUndefinedBehavior())
+    if (Info.checkingForUndefinedBehavior() && !E->getType().hasWrapsAttr())
       Info.Ctx.getDiagnostics().Report(E->getExprLoc(),
                                        diag::warn_integer_constant_overflow)
           << toString(Result, 10, Result.isSigned(), /*formatAsCLiteral=*/false,
@@ -14694,7 +14694,7 @@ bool IntExprEvaluator::VisitUnaryOperator(const UnaryOperator *E) {
     if (!Result.isInt()) return Error(E);
     const APSInt &Value = Result.getInt();
     if (Value.isSigned() && Value.isMinSignedValue() && E->canOverflow()) {
-      if (Info.checkingForUndefinedBehavior())
+      if (Info.checkingForUndefinedBehavior() && !E->getType().hasWrapsAttr())
         Info.Ctx.getDiagnostics().Report(E->getExprLoc(),
                                          diag::warn_integer_constant_overflow)
             << toString(Value, 10, Value.isSigned(), /*formatAsCLiteral=*/false,
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 7de13977176f2d..a7cf5fecde8865 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -2863,6 +2863,15 @@ bool QualType::isWebAssemblyFuncrefType() const {
          getAddressSpace() == LangAS::wasm_funcref;
 }
 
+bool QualType::hasWrapsAttr() const {
+  return !isNull() && getTypePtr()->hasAttr(attr::Wraps) &&
+         !getTypePtr()->hasAttr(attr::NoWraps);
+}
+
+bool QualType::hasNoWrapsAttr() const {
+  return !isNull() && getTypePtr()->hasAttr(attr::NoWraps);
+}
+
 QualType::PrimitiveDefaultInitializeKind
 QualType::isNonTrivialToPrimitiveDefaultInitialize() const {
   if (const auto *RT =
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index 6d8db5cf4ffd22..9825abcade9afc 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -2037,6 +2037,12 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
   case attr::AArch64SVEPcs: OS << "aarch64_sve_pcs"; break;
   case attr::AMDGPUKernelCall: OS << "amdgpu_kernel"; break;
   case attr::IntelOclBicc: OS << "inteloclbicc"; break;
+  case attr::Wraps:
+    OS << "wraps";
+    break;
+  case attr::NoWraps:
+    OS << "no_wraps";
+    break;
   case attr::PreserveMost:
     OS << "preserve_most";
     break;
diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp
index 287d911e10ba58..a4efb0cd5da010 100644
--- a/clang/lib/CodeGen/CGExprScalar.cpp
+++ b/clang/lib/CodeGen/CGExprScalar.cpp
@@ -158,6 +158,12 @@ struct BinOpInfo {
     }
     return false;
   }
+
+  /// Does the BinaryOperator have the wraps attribute?
+  /// If so, we can elide overflow sanitizer checks.
+  bool hasWrappingOperand() const {
+    return E->getType().hasWrapsAttr() && !E->getType().hasNoWrapsAttr();
+  }
 };
 
 static bool MustVisitNullValue(const Expr *E) {
@@ -197,13 +203,13 @@ static bool CanElideOverflowCheck(const ASTContext &Ctx, const BinOpInfo &Op) {
   if (!Op.mayHaveIntegerOverflow())
     return true;
 
-  if (Op.Ty->isSignedIntegerType() &&
+  if (Op.Ty->isSignedIntegerType() && !Op.Ty.hasNoWrapsAttr() &&
       Ctx.isTypeIgnoredBySanitizer(SanitizerKind::SignedIntegerOverflow,
                                    Op.Ty)) {
     return true;
   }
 
-  if (Op.Ty->isUnsignedIntegerType() &&
+  if (Op.Ty->isUnsignedIntegerType() && !Op.Ty.hasNoWrapsAttr() &&
       Ctx.isTypeIgnoredBySanitizer(SanitizerKind::UnsignedIntegerOverflow,
                                    Op.Ty)) {
     return true;
@@ -766,7 +772,8 @@ class ScalarExprEmitter
 
   // Binary Operators.
   Value *EmitMul(const BinOpInfo &Ops) {
-    if (Ops.Ty->isSignedIntegerOrEnumerationType()) {
+    if (Ops.Ty->isSignedIntegerOrEnumerationType() &&
+        !Ops.hasWrappingOperand()) {
       switch (CGF.getLangOpts().getSignedOverflowBehavior()) {
       case LangOptions::SOB_Defined:
         if (!CGF.SanOpts.has(SanitizerKind::SignedIntegerOverflow))
@@ -802,7 +809,8 @@ class ScalarExprEmitter
 
     if (Ops.Ty->isUnsignedIntegerType() &&
         CGF.SanOpts.has(SanitizerKind::UnsignedIntegerOverflow) &&
-        !CanElideOverflowCheck(CGF.getContext(), Ops))
+        !CanElideOverflowCheck(CGF.getContext(), Ops) &&
+        !Ops.hasWrappingOperand())
       return EmitOverflowCheckedBinOp(Ops);
 
     if (Ops.LHS->getType()->isFPOrFPVectorTy()) {
@@ -1134,7 +1142,7 @@ void ScalarExprEmitter::EmitIntegerTruncationCheck(Value *Src, QualType SrcType,
   // If the comparison result is 'i1 false', then the truncation was lossy.
 
   // Do we care about this type of truncation?
-  if (!CGF.SanOpts.has(Check.second.second))
+  if (!CGF.SanOpts.has(Check.second.second) || DstType.hasWrapsAttr())
     return;
 
   // Does some SSCL ignore this type?
@@ -1380,6 +1388,11 @@ void CodeGenFunction::EmitBitfieldConversionCheck(Value *Src, QualType SrcType,
   bool SrcSigned = SrcType->isSignedIntegerOrEnumerationType();
   bool DstSigned = DstType->isSignedIntegerOrEnumerationType();
 
+  // The wraps attribute will silence any sanitizer warnings
+  // regarding truncation or overflow
+  if (SrcType.hasWrapsAttr() || DstType.hasWrapsAttr())
+    return;
+
   CodeGenFunction::SanitizerScope SanScope(this);
 
   std::pair<ScalarExprEmitter::ImplicitConversionCheckKind,
@@ -2956,6 +2969,9 @@ ScalarExprEmitter::EmitScalarPrePostIncDec(const UnaryOperator *E, LValue LV,
     bool excludeOverflowPattern =
         matchesPostDecrInWhile(E, isInc, isPre, CGF.getContext());
 
+    BinOpInfo Ops = createBinOpInfoFromIncDec(
+        E, value, isInc, E->getFPFeaturesInEffect(CGF.getLangOpts()));
+
     if (CGF.getContext().isPromotableIntegerType(type)) {
       promotedType = CGF.getContext().getPromotedIntegerType(type);
       assert(promotedType != type && "Shouldn't promote to the same type.");
@@ -3012,10 +3028,12 @@ ScalarExprEmitter::EmitScalarPrePostIncDec(const UnaryOperator *E, LValue LV,
       // Note that signed integer inc/dec with width less than int can't
       // overflow because of promotion rules; we're just eliding a few steps
       // here.
-    } else if (E->canOverflow() && type->isSignedIntegerOrEnumerationType()) {
+    } else if (E->canOverflow() && type->isSignedIntegerOrEnumerationType() &&
+               !Ops.hasWrappingOperand()) {
       value = EmitIncDecConsiderOverflowBehavior(E, value, isInc);
     } else if (E->canOverflow() && type->isUnsignedIntegerType() &&
                CGF.SanOpts.has(SanitizerKind::UnsignedIntegerOverflow) &&
+               !Ops.hasWrappingOperand() &&
                !excludeOverflowPattern &&
                !CGF.getContext().isTypeIgnoredBySanitizer(
                    SanitizerKind::UnsignedIntegerOverflow, E->getType())) {
@@ -3807,7 +3825,8 @@ Value *ScalarExprEmitter::EmitDiv(const BinOpInfo &Ops) {
     if ((CGF.SanOpts.has(SanitizerKind::IntegerDivideByZero) ||
          CGF.SanOpts.has(SanitizerKind::SignedIntegerOverflow)) &&
         Ops.Ty->isIntegerType() &&
-        (Ops.mayHaveIntegerDivisionByZero() || Ops.mayHaveIntegerOverflow())) {
+        (Ops.mayHaveIntegerDivisionByZero() || Ops.mayHaveIntegerOverflow()) &&
+        !Ops.hasWrappingOperand()) {
       llvm::Value *Zero = llvm::Constant::getNullValue(ConvertType(Ops.Ty));
       EmitUndefinedBehaviorIntegerDivAndRemCheck(Ops, Zero, true);
     } else if (CGF.SanOpts.has(SanitizerKind::FloatDivideByZero) &&
@@ -3856,7 +3875,8 @@ Value *ScalarExprEmitter::EmitRem(const BinOpInfo &Ops) {
   if ((CGF.SanOpts.has(SanitizerKind::IntegerDivideByZero) ||
        CGF.SanOpts.has(SanitizerKind::SignedIntegerOverflow)) &&
       Ops.Ty->isIntegerType() &&
-      (Ops.mayHaveIntegerDivisionByZero() || Ops.mayHaveIntegerOverflow())) {
+      (Ops.mayHaveIntegerDivisionByZero() || Ops.mayHaveIntegerOverflow()) &&
+      !Ops.hasWrappingOperand()) {
     CodeGenFunction::SanitizerScope SanScope(&CGF);
     llvm::Value *Zero = llvm::Constant::getNullValue(ConvertType(Ops.Ty));
     EmitUndefinedBehaviorIntegerDivAndRemCheck(Ops, Zero, false);
@@ -4221,7 +4241,7 @@ Value *ScalarExprEmitter::EmitAdd(const BinOpInfo &op) {
       op.RHS->getType()->isPointerTy())
     return emitPointerArithmetic(CGF, op, CodeGenFunction::NotSubtraction);
 
-  if (op.Ty->isSignedIntegerOrEnumerationType()) {
+  if (op.Ty->isSignedIntegerOrEnumerationType() && !op.hasWrappingOperand()) {
     switch (CGF.getLangOpts().getSignedOverflowBehavior()) {
     case LangOptions::SOB_Defined:
       if (!CGF.SanOpts.has(SanitizerKind::SignedIntegerOverflow))
@@ -4254,7 +4274,7 @@ Value *ScalarExprEmitter::EmitAdd(const BinOpInfo &op) {
 
   if (op.Ty->isUnsignedIntegerType() &&
       CGF.SanOpts.has(SanitizerKind::UnsignedIntegerOverflow) &&
-      !CanElideOverflowCheck(CGF.getContext(), op))
+      !CanElideOverflowCheck(CGF.getContext(), op) && !op.hasWrappingOperand())
     return EmitOverflowCheckedBinOp(op);
 
   if (op.LHS->getType()->isFPOrFPVectorTy()) {
@@ -4377,7 +4397,7 @@ Value *ScalarExprEmitter::EmitFixedPointBinOp(const BinOpInfo &op) {
 Value *ScalarExprEmitter::EmitSub(const BinOpInfo &op) {
   // The LHS is always a pointer if either side is.
   if (!op.LHS->getType()->isPointerTy()) {
-    if (op.Ty->isSignedIntegerOrEnumerationType()) {
+    if (op.Ty->isSignedIntegerOrEnumerationType() && !op.hasWrappingOperand()) {
       switch (CGF.getLangOpts().getSignedOverflowBehavior()) {
       case LangOptions::SOB_Defined:
         if (!CGF.SanOpts.has(SanitizerKind::SignedIntegerOverflow))
@@ -4410,7 +4430,8 @@ Value *ScalarExprEmitter::EmitSub(const BinOpInfo &op) {
 
     if (op.Ty->isUnsignedIntegerType() &&
         CGF.SanOpts.has(SanitizerKind::UnsignedIntegerOverflow) &&
-        !CanElideOverflowCheck(CGF.getContext(), op))
+        !CanElideOverflowCheck(CGF.getContext(), op) &&
+        !op.hasWrappingOperand())
       return EmitOverflowCheckedBinOp(op);
 
     if (op.LHS->getType()->isFPOrFPVectorTy()) {
@@ -4530,7 +4551,8 @@ Value *ScalarExprEmitter::EmitShl(const BinOpInfo &Ops) {
   bool SanitizeSignedBase = CGF.SanOpts.has(SanitizerKind::ShiftBase) &&
                             Ops.Ty->hasSignedIntegerRepresentation() &&
                             !CGF.getLangOpts().isSignedOverflowDefined() &&
-                            !CGF.getLangOpts().CPlusPlus20;
+                            !CGF.getLangOpts().CPlusPlus20 &&
+                            !Ops.hasWrappingOperand();
   bool SanitizeUnsignedBase =
       CGF.SanOpts.has(SanitizerKind::UnsignedShiftBase) &&
       Ops.Ty->hasUnsignedIntegerRepresentation();
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 2b51765e80864a..ab934c3feb346e 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -726,6 +726,9 @@ ExprResult Sema::ImpCastExprToType(Expr *E, QualType Ty,
   QualType ExprTy = Context.getCanonicalType(E->getType());
   QualType TypeTy = Context.getCanonicalType(Ty);
 
+  if (E->getType().getTypePtr()->isIntegerType() && E->getType().hasWrapsAttr())
+    Ty = Context.getAttributedType(attr::Wraps, Ty, Ty);
+
   if (ExprTy == TypeTy)
     return E;
 
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 96008b14225a4c..1a2b8ec8855f5c 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -10600,7 +10600,8 @@ static void AnalyzeAssignment(Sema &S, BinaryOperator *E) {
   // We want to recurse on the RHS as normal unless we're assigning to
   // a bitfield.
   if (FieldDecl *Bitfield = E->getLHS()->getSourceBitField()) {
-    if (AnalyzeBitFieldAssignment(S, Bitfield, E->getRHS(),
+    if (!E->hasWrappingOperand(S.Context) &&
+        AnalyzeBitFieldAssignment(S, Bitfield, E->getRHS(),
                                   E->getOperatorLoc())) {
       // Recurse, ignoring any implicit conversions on the RHS.
       return AnalyzeImplicitConversions(S, E->getRHS()->IgnoreParenImpCasts(),
@@ -10818,11 +10819,39 @@ static bool IsImplicitBoolFloatConversion(Sema &S, Expr *Ex, bool ToBool) {
           FloatCandidateBT && (FloatCandidateBT->isFloatingPoint()));
 }
 
+/// Check to see if the wraps or no_wraps attribute may have been lost through
+/// a function call. For cases where we are unsure, assume not.
+static bool IsWrapsAttrLost(Sema &S, const CallExpr *TheCall,
+                            const FunctionDecl *FD, unsigned i) {
+  // We may not have a FunctionDecl if this CallExpr is associated virtual,
+  // templated or overloaded functions.
+  if (!FD)
+    return false;
+
+  if (i >= TheCall->getNumArgs() || i >= FD->getNumParams())
+    return false;
+
+  const QualType ProvidedArgTy = TheCall->getArg(i)->getType();
+  const QualType PrototypedArgTy = FD->getParamDecl(i)->getType();
+
+  return (ProvidedArgTy.hasWrapsAttr() && !PrototypedArgTy.hasWrapsAttr()) ||
+         (ProvidedArgTy.hasNoWrapsAttr() && !PrototypedArgTy.hasNoWrapsAttr());
+}
+
 static void CheckImplicitArgumentConversions(Sema &S, CallExpr *TheCall,
                                              SourceLocation CC) {
   unsigned NumArgs = TheCall->getNumArgs();
+  const FunctionDecl *FD = TheCall->getDirectCallee();
+
   for (unsigned i = 0; i < NumArgs; ++i) {
     Expr *CurrA = TheCall->getArg(i);
+
+    if (IsWrapsAttrLost(S, TheCall, FD, i))
+      S.Diag(CurrA->getSourceRange().getBegin(),
+             diag::warn_wraps_attr_maybe_lost)
+          << (int)CurrA->getType().hasNoWrapsAttr()
+          << FD->getParamDecl(i)->getType();
+
     if (!IsImplicitBoolFloatConversion(S, CurrA, true))
       continue;
 
@@ -11325,7 +11354,7 @@ void Sema::CheckImplicitConversion(Expr *E, QualType T, SourceLocation CC,
       IntRange::forTargetOfCanonicalType(Context, Source);
   IntRange TargetRange = IntRange::forTargetOfCanonicalType(Context, Target);
 
-  if (LikelySourceRange->Width > TargetRange.Width) {
+  if (LikelySourceRange->Width > TargetRange.Width && !T.hasWrapsAttr()) {
     // If the source is a constant, use a default-on diagnostic.
     // TODO: this should happen for bitfield stores, too.
     Expr::EvalResult Result;
@@ -11374,7 +11403,7 @@ void Sema::CheckImplicitConversion(Expr *E, QualType T, SourceLocation CC,
 
   if (TargetRange.Width == LikelySourceRange->Width &&
       !TargetRange.NonNegative && LikelySourceRange->NonNegative &&
-      Source->isSignedIntegerType()) {
+      Source->isSignedIntegerType() && !T.hasWrapsAttr()) {
     // Warn when doing a signed to signed conversion, warn if the positive
     // source value is exactly the width of the target type, which will
     // cause a negative value to be stored.
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index c9cd81a48fbe51..fe8b6512565d0f 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -56,6 +56,7 @@
 #include "clang/Sema/SemaSwift.h"
 #include "clang/Sema/SemaWasm.h"
 #include "clang/Sema/Template.h"
+#include "clang/Basic/NoSanitizeList.h"
 #include "llvm/ADT/STLForwardCompat.h"
 #include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/StringExtras.h"
@@ -6715,7 +6716,6 @@ Sema::CheckTypedefForVariablyModifiedType(Scope *S, TypedefNameDecl *NewTD) {
 NamedDecl*
 Sema::ActOnTypedefNameDecl(Scope *S, DeclContext *DC, TypedefNameDecl *NewTD,
                            LookupResult &Previous, bool &Redeclaration) {
-
   // Find the shadowed declaration before filtering for scope.
   NamedDecl *ShadowedDecl = getShadowedDeclaration(NewTD, Previous);
 
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index d05d326178e1b8..9843cd11cdb9c8 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -4009,6 +4009,14 @@ void Sema::AddAlignValueAttr(Decl *D, const AttributeCommonInfo &CI, Expr *E) {
   D->addAttr(::new (Context) AlignValueAttr(Context, CI, E));
 }
 
+static void handleWrapsAttr(Sema &S, Decl *D, const ParsedAttr &AL,
+                            bool NoWraps = false) {
+  if (NoWraps)
+    D->addAttr(::new (S.Context) NoWrapsAttr(S.Context, AL));
+  else
+    D->addAttr(::new (S.Context) WrapsAttr(S.Context, AL));
+}
+
 static void handleAlignedAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
   if (AL.hasParsedType()) {
     const ParsedType &TypeArg = AL.getTypeArg();
@@ -6955,13 +6963,19 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
   case ParsedAttr::AT_AvailableOnlyInDefaultEvalMethod:
     handleAvailableOnlyInDefaultEvalMethod(S, D, AL);
     break;
-
   case ParsedAttr::AT_CountedBy:
   case ParsedAttr::AT_CountedByOrNull:
   case ParsedAttr::AT_SizedBy:
   case ParsedAttr::AT_SizedByOrNull:
     handleCountedByAttrField(S, D, AL);
     break;
+  case ParsedAttr::AT_Wraps:
+    handleWrapsAttr(S, D, AL);
+    break;
+  case ParsedAttr::AT_NoWraps:
+    handleWrapsAttr(S, D, AL, true);
+    break;
+
 
   // Microsoft attributes:
   case ParsedAttr::AT_LayoutVersion:
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index e526a11973975d..08e00879d617b0 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -6536,6 +6536,25 @@ static void HandleBTFTypeTagAttribute(QualType &Type, const ParsedAttr &Attr,
       ::new (Ctx) BTFTypeTagAttr(Ctx, Attr, BTFTypeTag), Type);
 }
 
+static void handleWrapsAttr(QualType &Type, const ParsedAttr &Attr,
+                            TypeProcessingState &State, bool NoWraps = false) {
+  Sema &S = State.getSema();
+  ASTContext &Ctx = S.Context;
+
+  if (!Type->isIntegerType()) {
+    S.Diag(Attr.getLoc(), diag::warn_wraps_attr_var_decl_type_not_integer)
+      << (int)NoWraps << Type.getAsString();
+    Attr.setInvalid();
+  }
+
+  if (NoWraps)
+    Type =
+        State.getAttributedType(::new (Ctx) NoWrapsAttr(Ctx, Attr), Type, Type);
+  else
+    Type =
+        State.getAttributedType(::new (Ctx) WrapsAttr(Ctx, Attr), Type, Type);
+}
+
 /// HandleAddressSpaceTypeAttribute - Process an address_space attribute on the
 /// specified type.  The attribute contains 1 argument, the id of the address
 /// space for the type.
@@ -8706,6 +8725,12 @@ static void processTypeAttrs(TypeProcessingState &state, QualType &type,
       HandleBTFTypeTagAttribute(type, attr, state);
       attr.setUsedAsTypeAttr();
       break;
+    case ParsedAttr::AT_Wraps:
+      handleWrapsAttr(type, attr, state);
+      break;
+    case ParsedAttr::AT_NoWraps:
+      handleWrapsAttr(type, attr, state, true);
+      break;
 
     case ParsedAttr::AT_MayAlias:
       // FIXME: This attribute needs to actually be handled, but if we ignore
diff --git a/clang/test/CodeGen/integer-overflow.c b/clang/test/CodeGen/integer-overflow.c
index 9e8cde8b33b16e..733527df6d4873 100644
--- a/clang/test/CodeGen/integer-overflow.c
+++ b/clang/test/CodeGen/integer-overflow.c
@@ -105,3 +105,69 @@ void test1(void) {
   // TRAPV:    call ptr @llvm.frameaddress.p0(i32 0)
   // CATCH_UB: call ptr @llvm.frameaddress.p0(i32 0)
 }
+
+// Tests for integer overflow using __attribute__((wraps))
+typedef int __attribute__((wraps)) wrapping_int;
+
+void test2(void) {
+  // DEFAULT-LABEL: define{{.*}} void @test2
+  // WRAPV-LABEL: define{{.*}} void @test2
+  // TRAPV-LABEL: define{{.*}} void @test2
+  extern volatile wrapping_int a, b, c;
+
+  // Basically, all cases should match the WRAPV case since this attribute
+  // effectively enables wrapv for expressions containing wrapping types.
+
+  // DEFAULT,WRAPV,TRAPV,CATCH_UB,TRAPV_HANDLER: add i32
+  a = b + c;
+
+  // DEFAULT,WRAPV,TRAPV,CATCH_UB,TRAPV_HANDLER: sub i32
+  a = b - c;
+
+  // DEFAULT,WRAPV,TRAPV,CATCH_UB,TRAPV_HANDLER: mul i32
+  a = b * c;
+
+  // DEFAULT,WRAPV,TRAPV,CATCH_UB,TRAPV_HANDLER: sub i32 0,
+  a = -b;
+
+  // DEFAULT,WRAPV,TRAPV,CATCH_UB,TRAPV_HANDLER: add i32 {{.*}}, 1
+  ++b;
+
+  // DEFAULT,WRAPV,TRAPV,CATCH_UB,TRAPV_HANDLER: add i32 {{.*}}, -1
+  --b;
+
+  // Less trivial cases
+  extern volatile wrapping_int u, v;
+  extern volatile int w;
+
+  // DEFAULT,WRAPV,TRAPV,CATCH_UB,TRAPV_HANDLER: add i32
+  if (u + v < u) {}
+
+  // DEFAULT,WRAPV,TRAPV,CATCH_UB,TRAPV_HANDLER: add i32
+  for (;u + v < u;) {}
+
+  // this (w+1) should have instrumentation
+  // DEFAULT,WRAPV,TRAPV,CATCH_UB,TRAPV_HANDLER: call {{.*}} @llvm.sadd.with.overflow.i32
+  u = (w+1) + v;
+
+  // no parts of this expression should have instrumentation
+  // DEFAULT,WRAPV,TRAPV,CATCH_UB,TRAPV_HANDLER: add i32 {{.*}}, 1
+  u = (v+1) + w;
+
+  // downcast off the wraps attribute
+  // DEFAULT,WRAPV,TRAPV,CATCH_UB,TRAPV_HANDLER: call { i32, i1 } @llvm.sadd.with.overflow.i32
+  u = (int) u + (int) v;
+
+  // DEFAULT,WRAPV,TRAPV,CATCH_UB,TRAPV_HANDLER: call { i32, i1 } @llvm.sadd.with.overflow.i32
+  u = (int) u + w;
+
+  // persist wraps attribute through implicit integer promotion
+  // DEFAULT,WRAPV,TRAPV,CATCH_UB,TRAPV_HANDLER: store i8 127, ptr [[D1:%.*]]
+  // DEFAULT,WRAPV,TRAPV,CATCH_UB,TRAPV_HANDLER: mul{{.*}} = mul i32
+  // would look like mul{{.*}} = mul nsw i32
+  // if overflow behavior was not defined.
+  char __attribute__((wraps)) d;
+  d = 127;
+  w = d*d*d*d*d; // d is promoted to int, then calculation is made.
+                 // wraps prevents instrumentation even through promotion
+}
diff --git a/clang/test/CodeGen/unsigned-overflow.c b/clang/test/CodeGen/unsigned-overflow.c
index 6c2f0c1efc145e..471a06e5fa63ff 100644
--- a/clang/test/CodeGen/unsigned-overflow.c
+++ b/clang/test/CodeGen/unsigned-overflow.c
@@ -5,6 +5,11 @@
 unsigned long li, lj, lk;
 unsigned int ii, ij, ik;
 
+// The wraps attribute disables sanitizer instrumentation for arithmetic
+// expressions containing these types.
+unsigned long __attribute__((wraps)) li_w, lj_w, lk_w;
+unsigned int __attribute__((wraps)) ii_w, ij_w, ik_w;
+
 extern void opaquelong(unsigned long);
 extern void opaqueint(unsigned int);
 
@@ -18,6 +23,11 @@ void testlongadd(void) {
   // CHECK-NEXT: [[T5:%.*]] = extractvalue { i64, i1 } [[T3]], 1
   // CHECK: call void @__ubsan_handle_add_overflow
   li = lj + lk;
+
+  // CHECK: [[T6:%.*]] = load i64, ptr @lj_w
+  // CHECK-NEXT: [[T7:%.*]] = load i64, ptr @lk_w
+  // CHECK-NEXT: add i64 [[T6]], [[T7]]
+  li_w = lj_w + lk_w;
 }
 
 // CHECK-LABEL: define{{.*}} void @testlongsub()
@@ -30,6 +40,11 @@ void testlongsub(void) {
   // CHECK-NEXT: [[T5:%.*]] = extractvalue { i64, i1 } [[T3]], 1
   // CHECK: call void @__ubsan_handle_sub_overflow
   li = lj - lk;
+
+  // CHECK: [[T6:%.*]] = load i64, ptr @lj_w
+  // CHECK-NEXT: [[T7:%.*]] = load i64, ptr @lk_w
+  // CHECK-NEXT: sub i64 [[T6]], [[T7]]
+  li_w = lj_w - lk_w;
 }
 
 // CHECK-LABEL: define{{.*}} void @testlongmul()
@@ -42,28 +57,39 @@ void testlongmul(void) {
   // CHECK-NEXT: [[T5:%.*]] = extractvalue { i64, i1 } [[T3]], 1
   // CHECK: call void @__ubsan_handle_mul_overflow
   li = lj * lk;
+
+  // CHECK: [[T6:%.*]] = load i64, ptr @lj_w
+  // CHECK-NEXT: [[T7:%.*]] = load i64, ptr @lk_w
+  // CHECK-NEXT: mul i64 [[T6]], [[T7]]
+  li_w = lj_w * lk_w;
 }
 
 // CHECK-LABEL: define{{.*}} void @testlongpostinc()
 void testlongpostinc(void) {
-  opaquelong(li++);
-
   // CHECK:      [[T1:%.*]] = load i64, ptr @li
   // CHECK-NEXT: [[T2:%.*]] = call { i64, i1 } @llvm.uadd.with.overflow.i64(i64 [[T1]], i64 1)
   // CHECK-NEXT: [[T3:%.*]] = extractvalue { i64, i1 } [[T2]], 0
   // CHECK-NEXT: [[T4:%.*]] = extractvalue { i64, i1 } [[T2]], 1
   // CHECK:      call void @__ubsan_handle_add_overflow
+  opaquelong(li++);
+
+  // CHECK: [[T5:%.*]] = load i64, ptr @li_w
+  // CHECK-NEXT: add i64 [[T5]], 1
+  opaquelong(li_w++);
 }
 
 // CHECK-LABEL: define{{.*}} void @testlongpreinc()
 void testlongpreinc(void) {
-  opaquelong(++li);
-
   // CHECK:      [[T1:%.*]] = load i64, ptr @li
   // CHECK-NEXT: [[T2:%.*]] = call { i64, i1 } @llvm.uadd.with.overflow.i64(i64 [[T1]], i64 1)
   // CHECK-NEXT: [[T3:%.*]] = extractvalue { i64, i1 } [[T2]], 0
   // CHECK-NEXT: [[T4:%.*]] = extractvalue { i64, i1 } [[T2]], 1
   // CHECK:      call void @__ubsan_handle_add_overflow
+  opaquelong(++li);
+
+  // CHECK: [[T5:%.*]] = load i64, ptr @li_w
+  // CHECK-NEXT: add i64 [[T5]], 1
+  opaquelong(++li_w);
 }
 
 // CHECK-LABEL: define{{.*}} void @testintadd()
@@ -76,6 +102,11 @@ void testintadd(void) {
   // CHECK-NEXT: [[T5:%.*]] = extractvalue { i32, i1 } [[T3]], 1
   // CHECK:      call void @__ubsan_handle_add_overflow
   ii = ij + ik;
+
+  // CHECK: [[T6:%.*]] = load i32, ptr @ij_w
+  // CHECK-NEXT: [[T7:%.*]] = load i32, ptr @ik_w
+  // CHECK-NEXT: add i32 [[T6]], [[T7]]
+  ii_w = ij_w + ik_w;
 }
 
 // CHECK-LABEL: define{{.*}} void @testintsub()
@@ -88,6 +119,11 @@ void testintsub(void) {
   // CHECK-NEXT: [[T5:%.*]] = extractvalue { i32, i1 } [[T3]], 1
   // CHECK:      call void @__ubsan_handle_sub_overflow
   ii = ij - ik;
+
+  // CHECK: [[T6:%.*]] = load i32, ptr @ij_w
+  // CHECK-NEXT: [[T7:%.*]] = load i32, ptr @ik_w
+  // CHECK-NEXT: sub i32 [[T6]], [[T7]]
+  ii_w = ij_w - ik_w;
 }
 
 // CHECK-LABEL: define{{.*}} void @testintmul()
@@ -100,26 +136,37 @@ void testintmul(void) {
   // CHECK-NEXT: [[T5:%.*]] = extractvalue { i32, i1 } [[T3]], 1
   // CHECK:      call void @__ubsan_handle_mul_overflow
   ii = ij * ik;
+
+  // CHECK: [[T6:%.*]] = load i32, ptr @ij_w
+  // CHECK-NEXT: [[T7:%.*]] = load i32, ptr @ik_w
+  // CHECK-NEXT: mul i32 [[T6]], [[T7]]
+  ii_w = ij_w * ik_w;
 }
 
 // CHECK-LABEL: define{{.*}} void @testintpostinc()
 void testintpostinc(void) {
-  opaqueint(ii++);
-
   // CHECK:      [[T1:%.*]] = load i32, ptr @ii
   // CHECK-NEXT: [[T2:%.*]] = call { i32, i1 } @llvm.uadd.with.overflow.i32(i32 [[T1]], i32 1)
   // CHECK-NEXT: [[T3:%.*]] = extractvalue { i32, i1 } [[T2]], 0
   // CHECK-NEXT: [[T4:%.*]] = extractvalue { i32, i1 } [[T2]], 1
   // CHECK:      call void @__ubsan_handle_add_overflow
+  opaqueint(ii++);
+
+  // CHECK: [[T5:%.*]] = load i32, ptr @ii_w
+  // CHECK-NEXT: add i32 [[T5]], 1
+  opaqueint(ii_w++);
 }
 
 // CHECK-LABEL: define{{.*}} void @testintpreinc()
 void testintpreinc(void) {
-  opaqueint(++ii);
-
   // CHECK:      [[T1:%.*]] = load i32, ptr @ii
   // CHECK-NEXT: [[T2:%.*]] = call { i32, i1 } @llvm.uadd.with.overflow.i32(i32 [[T1]], i32 1)
   // CHECK-NEXT: [[T3:%.*]] = extractvalue { i32, i1 } [[T2]], 0
   // CHECK-NEXT: [[T4:%.*]] = extractvalue { i32, i1 } [[T2]], 1
   // CHECK:      call void @__ubsan_handle_add_overflow
+  opaqueint(++ii);
+
+  // CHECK: [[T5:%.*]] = load i32, ptr @ii_w
+  // CHECK-NEXT: add i32 [[T5]], 1
+  opaqueint(++ii_w);
 }
diff --git a/clang/test/CodeGen/wraps-attribute-scl.test b/clang/test/CodeGen/wraps-attribute-scl.test
new file mode 100644
index 00000000000000..adcb4e8c8e8df5
--- /dev/null
+++ b/clang/test/CodeGen/wraps-attribute-scl.test
@@ -0,0 +1,78 @@
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+
+// Ensure the wraps and no_wraps attributes properly adjust SSCL coverage
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=signed-integer-overflow -fsanitize-ignorelist=%t/ignoreall.ignorelist -emit-llvm %t/test.c -o - | FileCheck %s
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=signed-integer-overflow -fsanitize-ignorelist=%t/precedence.ignorelist -emit-llvm %t/test.c -o - | FileCheck %s --check-prefix=PREC
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=signed-integer-overflow -fsanitize-ignorelist=%t/promotion.ignorelist -emit-llvm %t/test.c -o - | FileCheck %s --check-prefix=PROMO
+
+//--- ignoreall.ignorelist
+[signed-integer-overflow]
+type:*
+
+//--- precedence.ignorelist
+[signed-integer-overflow]
+type:myty
+type:qaz=sanitize
+
+//--- promotion.ignorelist
+[{signed-integer-overflow,unsigned-integer-overflow}]
+type:*
+type:int=sanitize
+
+//--- test.c
+typedef int __attribute__((no_wraps)) non_wrapping_int;
+
+// CHECK-LABEL: define dso_local void @foo
+void foo(non_wrapping_int A, int B) {
+// CHECK: %[[A:.*]] = load i32, ptr %A.addr
+// CHECK-NEXT: @llvm.sadd.with.overflow.i32(i32 %[[A]], i32 1)
+  ++A;
+
+// CHECK: %[[B:.*]] = load i32, ptr %B.addr
+// CHECK-NEXT: %[[INC1:.*]] = add nsw i32 %[[B]], 1
+// CHECK-NEXT: store i32 %[[INC1]], ptr %B.addr
+  ++B;
+}
+
+// If our ignorelist (precedence.ignorelist) has an entry for `myty`, the
+// `no_wraps` attribute should take precedence -- enable overflow sanitizer
+// instrumentation.
+typedef int __attribute__((no_wraps)) myty;
+
+// If our ignorelist (precedence.ignorelist) has an etry for `qaz=sanitize`,
+// the `wraps` attribute should take precedence -- disabling overflow sanitizer
+// instrumentation.
+typedef int __attribute__((wraps)) qaz;
+
+// PREC-LABEL: define dso_local void @bar
+void bar(myty C, qaz D) {
+// PREC: %[[C:.*]] = load i32, ptr %C.addr
+// PREC-NEXT: @llvm.sadd.with.overflow.i32(i32 %[[C]], i32 1)
+  ++C;
+
+// CHECK: %[[D:.*]] = load i32, ptr %D.addr
+// CHECK-NEXT: %[[INC2:.*]] = add i32 %[[D]], 1
+// CHECK-NEXT: store i32 %[[INC2]], ptr %D.addr
+  ++D;
+}
+
+// If a type is marked as both `wraps` and `no_wraps`, `no_wraps` should take
+// precedence, but really users shouldn't be marking a type as both of these.
+// Let's introduce a warning at some point.
+typedef int __attribute__((wraps)) __attribute__((no_wraps)) both;
+
+// CHECK-LABEL: define dso_local void @jam
+void jam(both E) {
+// CHECK: %[[E:.*]] = load i32, ptr %E.addr
+// CHECK-NEXT: @llvm.sadd.with.overflow.i32(i32 %[[E]], i32 1)
+  ++E;
+}
+
+// PROMO-LABEL: define dso_local void @bux
+void bux(char __attribute__((wraps)) F) {
+// PROMO: %[[F:.*]] = load i8, ptr %F.addr
+// PROMO-NEXT: %[[CONV1:.*]] = sext i8 %[[F]] to i32
+// PROMO-NEXT: add i32 %[[CONV1]], 1
+  (F + 1); // F is promoted to `int` but should carry the `wraps` attribute with it
+}
diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
index 4a6ac39da18ad2..c41a7f37596ae7 100644
--- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test
+++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
@@ -132,6 +132,7 @@
 // CHECK-NEXT: NoThreadSafetyAnalysis (SubjectMatchRule_function)
 // CHECK-NEXT: NoThrow (SubjectMatchRule_hasType_functionType)
 // CHECK-NEXT: NoUwtable (SubjectMatchRule_hasType_functionType)
+// CHECK-NEXT: NoWraps (SubjectMatchRule_variable, SubjectMatchRule_type_alias, SubjectMatchRule_field)
 // CHECK-NEXT: NotTailCalled (SubjectMatchRule_function)
 // CHECK-NEXT: OMPAssume (SubjectMatchRule_function, SubjectMatchRule_objc_method)
 // CHECK-NEXT: OSConsumed (SubjectMatchRule_variable_is_parameter)
@@ -218,6 +219,7 @@
 // CHECK-NEXT: WebAssemblyImportModule (SubjectMatchRule_function)
 // CHECK-NEXT: WebAssemblyImportName (SubjectMatchRule_function)
 // CHECK-NEXT: WorkGroupSizeHint (SubjectMatchRule_function)
+// CHECK-NEXT: Wraps (SubjectMatchRule_variable, SubjectMatchRule_type_alias, SubjectMatchRule_field)
 // CHECK-NEXT: XRayInstrument (SubjectMatchRule_function, SubjectMatchRule_objc_method)
 // CHECK-NEXT: XRayLogArgs (SubjectMatchRule_function, SubjectMatchRule_objc_method)
 // CHECK-NEXT: ZeroCallUsedRegs (SubjectMatchRule_function)
diff --git a/clang/test/Sema/attr-wraps.c b/clang/test/Sema/attr-wraps.c
new file mode 100644
index 00000000000000..59045609cb678d
--- /dev/null
+++ b/clang/test/Sema/attr-wraps.c
@@ -0,0 +1,48 @@
+// RUN: %clang_cc1 %s -verify -fsyntax-only -triple x86_64-pc-linux-gnu
+typedef int __attribute__((wraps)) wrapping_int;
+typedef unsigned __attribute__((wraps)) wrapping_u32;
+
+int implicit_truncation(void) {
+  const wrapping_int A = 1;
+  return 2147483647 + A; // no warning
+}
+
+struct R {
+  wrapping_int a: 2; // test bitfield sign change -- no warning
+  wrapping_u32 b: 1; // test bitfield overflow/truncation -- no warning
+  int baseline: 2; // baseline, should warn
+};
+
+void bitfields_truncation(void) {
+  struct R r;
+  r.a = 2; // this value changes from 2 to -2
+  ++r.a;
+
+  r.b = 2; // changes from 2 to 0
+  ++r.b;
+
+  // expected-warning at +1 {{to bit-field changes value from}}
+  r.baseline = 2;
+}
+
+extern void implicitly_discards_wraps_attribute(int discards);
+
+int discard_test(void) {
+  wrapping_int A = 1;
+  int __attribute__((no_wraps)) B = 1;
+  // expected-warning at +1 {{'wraps' attribute may be implicitly discarded}}
+  implicitly_discards_wraps_attribute(A);
+
+  // expected-warning at +1 {{'no_wraps' attribute may be implicitly discarded}}
+  implicitly_discards_wraps_attribute(B);
+
+  int C = A; // assignments don't warn right now -- probably too noisy
+  return A; // neither do non-wrapping return types
+}
+
+void useless_wraps_attribute(void) {
+  // expected-warning at +1 {{using attribute 'wraps' with non-integer type}}
+  float __attribute__((wraps)) A = 3.14;
+  // expected-warning at +1 {{using attribute 'no_wraps' with non-integer type}}
+  float __attribute__((no_wraps)) B = 3.14;
+}

>From 3a8593dc1003ec7f0f0678cb56e412b90347d381 Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Tue, 5 Nov 2024 16:03:45 -0800
Subject: [PATCH 02/14] fix typo in docs

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

diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 2323febe7840e1..fd728bc4bb3f5d 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -8743,7 +8743,7 @@ when building with ``-fsanitize=signed-integer-overflow``:
 
 ``wraps`` may also be used with function parameters or declarations of
 variables as well as members of structures. Using ``wraps`` on non-integer
-types will result in a `-Wuseless-wraps-attribute`. One may disable this
+types will result in a ``-Wuseless-wraps-attribute``. One may disable this
 warning with ``-Wno-useless-wraps-attribute``.
 
 ``wraps`` persists through implicit type promotions and will be applied to the

>From 68e5ec1732ebfc0bf4085f8a6fdaa529b9393ecd Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Tue, 5 Nov 2024 16:05:03 -0800
Subject: [PATCH 03/14] fix more language in docs

Signed-off-by: Justin Stitt <justinstitt at google.com>
---
 clang/include/clang/Basic/AttrDocs.td | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index fd728bc4bb3f5d..1da6ebbb15ff8d 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -8743,8 +8743,8 @@ when building with ``-fsanitize=signed-integer-overflow``:
 
 ``wraps`` may also be used with function parameters or declarations of
 variables as well as members of structures. Using ``wraps`` on non-integer
-types will result in a ``-Wuseless-wraps-attribute``. One may disable this
-warning with ``-Wno-useless-wraps-attribute``.
+types will result in a ``-Wuseless-wraps-attribute`` warning. One may disable
+this warning with ``-Wno-useless-wraps-attribute``.
 
 ``wraps`` persists through implicit type promotions and will be applied to the
 result type of arithmetic expressions containing a wrapping operand.

>From 55f52cb2456510f22ec159d37226916b81dc9840 Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Tue, 5 Nov 2024 16:11:11 -0800
Subject: [PATCH 04/14] run formatter

Signed-off-by: Justin Stitt <justinstitt at google.com>
---
 clang/lib/AST/Expr.cpp             |  2 --
 clang/lib/CodeGen/CGExprScalar.cpp |  3 +--
 clang/lib/Sema/SemaDecl.cpp        | 11 ++++++-----
 clang/lib/Sema/SemaDeclAttr.cpp    |  1 -
 clang/lib/Sema/SemaType.cpp        |  2 +-
 5 files changed, 8 insertions(+), 11 deletions(-)

diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index 7de87039cc95c2..5a3655ec8f2c59 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -4866,7 +4866,6 @@ BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
     setType(Ctx.getAttributedType(attr::Wraps, getType(), getType()));
   if (hasNonWrappingOperand(Ctx))
     setType(Ctx.getAttributedType(attr::NoWraps, getType(), getType()));
-
 }
 
 BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
@@ -4889,7 +4888,6 @@ BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
     setType(Ctx.getAttributedType(attr::Wraps, getType(), getType()));
   if (hasNonWrappingOperand(Ctx))
     setType(Ctx.getAttributedType(attr::NoWraps, getType(), getType()));
-
 }
 
 BinaryOperator *BinaryOperator::CreateEmpty(const ASTContext &C,
diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp
index a4efb0cd5da010..4f0365ba800e2a 100644
--- a/clang/lib/CodeGen/CGExprScalar.cpp
+++ b/clang/lib/CodeGen/CGExprScalar.cpp
@@ -3033,8 +3033,7 @@ ScalarExprEmitter::EmitScalarPrePostIncDec(const UnaryOperator *E, LValue LV,
       value = EmitIncDecConsiderOverflowBehavior(E, value, isInc);
     } else if (E->canOverflow() && type->isUnsignedIntegerType() &&
                CGF.SanOpts.has(SanitizerKind::UnsignedIntegerOverflow) &&
-               !Ops.hasWrappingOperand() &&
-               !excludeOverflowPattern &&
+               !Ops.hasWrappingOperand() && !excludeOverflowPattern &&
                !CGF.getContext().isTypeIgnoredBySanitizer(
                    SanitizerKind::UnsignedIntegerOverflow, E->getType())) {
       value = EmitOverflowCheckedBinOp(createBinOpInfoFromIncDec(
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index fe8b6512565d0f..52ce09a4b39687 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -24,13 +24,14 @@
 #include "clang/AST/EvaluatedExprVisitor.h"
 #include "clang/AST/Expr.h"
 #include "clang/AST/ExprCXX.h"
-#include "clang/AST/NonTrivialTypeVisitor.h"
 #include "clang/AST/MangleNumberingContext.h"
+#include "clang/AST/NonTrivialTypeVisitor.h"
 #include "clang/AST/Randstruct.h"
 #include "clang/AST/StmtCXX.h"
 #include "clang/AST/Type.h"
 #include "clang/Basic/Builtins.h"
 #include "clang/Basic/HLSLRuntime.h"
+#include "clang/Basic/NoSanitizeList.h"
 #include "clang/Basic/PartialDiagnostic.h"
 #include "clang/Basic/SourceManager.h"
 #include "clang/Basic/TargetInfo.h"
@@ -56,7 +57,6 @@
 #include "clang/Sema/SemaSwift.h"
 #include "clang/Sema/SemaWasm.h"
 #include "clang/Sema/Template.h"
-#include "clang/Basic/NoSanitizeList.h"
 #include "llvm/ADT/STLForwardCompat.h"
 #include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/StringExtras.h"
@@ -6713,9 +6713,10 @@ Sema::CheckTypedefForVariablyModifiedType(Scope *S, TypedefNameDecl *NewTD) {
   }
 }
 
-NamedDecl*
-Sema::ActOnTypedefNameDecl(Scope *S, DeclContext *DC, TypedefNameDecl *NewTD,
-                           LookupResult &Previous, bool &Redeclaration) {
+NamedDecl *Sema::ActOnTypedefNameDecl(Scope *S, DeclContext *DC,
+                                      TypedefNameDecl *NewTD,
+                                      LookupResult &Previous,
+                                      bool &Redeclaration) {
   // Find the shadowed declaration before filtering for scope.
   NamedDecl *ShadowedDecl = getShadowedDeclaration(NewTD, Previous);
 
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 9843cd11cdb9c8..6add2a82ac1ed6 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -6976,7 +6976,6 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
     handleWrapsAttr(S, D, AL, true);
     break;
 
-
   // Microsoft attributes:
   case ParsedAttr::AT_LayoutVersion:
     handleLayoutVersion(S, D, AL);
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 08e00879d617b0..1933ce56ba3a18 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -6543,7 +6543,7 @@ static void handleWrapsAttr(QualType &Type, const ParsedAttr &Attr,
 
   if (!Type->isIntegerType()) {
     S.Diag(Attr.getLoc(), diag::warn_wraps_attr_var_decl_type_not_integer)
-      << (int)NoWraps << Type.getAsString();
+        << (int)NoWraps << Type.getAsString();
     Attr.setInvalid();
   }
 

>From 0cb962f13687fd3060136dc3e9c74bcf1ebde950 Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Wed, 6 Nov 2024 11:18:42 -0800
Subject: [PATCH 05/14] use standalone doc string and reinsate removed newline

This commit is a combination of two commits. I'm force pushing because
the build bot had some problems with an (empty?) or whitespace-only
commit during checkout. Hopefully it takes this one.

Signed-off-by: Justin Stitt <justinstitt at google.com>
---
 clang/include/clang/AST/Expr.h  | 2 +-
 clang/lib/Sema/SemaDeclAttr.cpp | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index 4472c941ed5c79..877842597ea3f3 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -4145,7 +4145,7 @@ class BinaryOperator : public Expr {
   /// Does one of the subexpressions have the wraps attribute?
   bool hasWrappingOperand(const ASTContext &Ctx) const;
 
-  /// How about the no_wraps attribute?
+  /// Does one of the subexpressions have the no_wraps attribute?
   bool hasNonWrappingOperand(const ASTContext &Ctx) const;
 
 protected:
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 6add2a82ac1ed6..6e453267111061 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -6963,6 +6963,7 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
   case ParsedAttr::AT_AvailableOnlyInDefaultEvalMethod:
     handleAvailableOnlyInDefaultEvalMethod(S, D, AL);
     break;
+
   case ParsedAttr::AT_CountedBy:
   case ParsedAttr::AT_CountedByOrNull:
   case ParsedAttr::AT_SizedBy:

>From b9f3a5a738bc009cd2e981a1857849dd6838759f Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Thu, 7 Nov 2024 12:54:41 -0800
Subject: [PATCH 06/14] simplify parsing by limiting to TypeAttr

Changing the attr base class from DeclOrTypeAttr to TypeAttr allows us
to better isolate the attribute handling logic just in SemaType.cpp in
the handleWrapsAttr() method. Furthermore, check for language
compatibility at this step as well.

Rely on `COnly` to provide the warnings to the user, then silently
discard the attribute.

|	let LangOpts = [COnly];

Signed-off-by: Justin Stitt <justinstitt at google.com>
---
 clang/include/clang/Basic/Attr.td |  4 ++--
 clang/lib/Sema/SemaDeclAttr.cpp   | 14 --------------
 clang/lib/Sema/SemaType.cpp       |  5 +++++
 3 files changed, 7 insertions(+), 16 deletions(-)

diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index d5a6a5ef8c02de..4d9c036f3f7157 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -4856,14 +4856,14 @@ def ClspvLibclcBuiltin: InheritableAttr {
   let SimpleHandler = 1;
 }
 
-def Wraps : DeclOrTypeAttr {
+def Wraps : TypeAttr {
   let Spellings = [Clang<"wraps">];
   let Subjects = SubjectList<[Var, TypedefName, Field]>;
   let Documentation = [WrapsDocs];
   let LangOpts = [COnly];
 }
 
-def NoWraps : DeclOrTypeAttr {
+def NoWraps : TypeAttr {
   let Spellings = [Clang<"no_wraps">];
   let Subjects = SubjectList<[Var, TypedefName, Field]>;
   let Documentation = [NoWrapsDocs];
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 6e453267111061..d05d326178e1b8 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -4009,14 +4009,6 @@ void Sema::AddAlignValueAttr(Decl *D, const AttributeCommonInfo &CI, Expr *E) {
   D->addAttr(::new (Context) AlignValueAttr(Context, CI, E));
 }
 
-static void handleWrapsAttr(Sema &S, Decl *D, const ParsedAttr &AL,
-                            bool NoWraps = false) {
-  if (NoWraps)
-    D->addAttr(::new (S.Context) NoWrapsAttr(S.Context, AL));
-  else
-    D->addAttr(::new (S.Context) WrapsAttr(S.Context, AL));
-}
-
 static void handleAlignedAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
   if (AL.hasParsedType()) {
     const ParsedType &TypeArg = AL.getTypeArg();
@@ -6970,12 +6962,6 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
   case ParsedAttr::AT_SizedByOrNull:
     handleCountedByAttrField(S, D, AL);
     break;
-  case ParsedAttr::AT_Wraps:
-    handleWrapsAttr(S, D, AL);
-    break;
-  case ParsedAttr::AT_NoWraps:
-    handleWrapsAttr(S, D, AL, true);
-    break;
 
   // Microsoft attributes:
   case ParsedAttr::AT_LayoutVersion:
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 1933ce56ba3a18..845c328e006ddc 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -6541,6 +6541,11 @@ static void handleWrapsAttr(QualType &Type, const ParsedAttr &Attr,
   Sema &S = State.getSema();
   ASTContext &Ctx = S.Context;
 
+  // wraps and no_wraps are most useful and consistent when used with C. Other
+  // languages have better alternatives within their type systems.
+  if (S.LangOpts.CPlusPlus || S.LangOpts.ObjC)
+    return;
+
   if (!Type->isIntegerType()) {
     S.Diag(Attr.getLoc(), diag::warn_wraps_attr_var_decl_type_not_integer)
         << (int)NoWraps << Type.getAsString();

>From 45796e3f3901ef515970edaaa284ea1be44b9069 Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Fri, 8 Nov 2024 14:19:30 -0800
Subject: [PATCH 07/14] undo change to whitespace

Signed-off-by: Justin Stitt <justinstitt at google.com>
---
 clang/docs/SanitizerSpecialCaseList.rst | 2 --
 1 file changed, 2 deletions(-)

diff --git a/clang/docs/SanitizerSpecialCaseList.rst b/clang/docs/SanitizerSpecialCaseList.rst
index 274409f47437c4..5c88c2976e8613 100644
--- a/clang/docs/SanitizerSpecialCaseList.rst
+++ b/clang/docs/SanitizerSpecialCaseList.rst
@@ -67,11 +67,9 @@ types specified within an ignorelist.
     int a = 2147483647; // INT_MAX
     ++a;                // Normally, an overflow with -fsanitize=signed-integer-overflow
   }
-
   $ cat ignorelist.txt
   [signed-integer-overflow]
   type:int
-
   $ clang -fsanitize=signed-integer-overflow -fsanitize-ignorelist=ignorelist.txt foo.c ; ./a.out
   # no signed-integer-overflow error
 

>From acc654b05dfb038f8b0c800eeb63db246d78d667 Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Fri, 8 Nov 2024 14:31:39 -0800
Subject: [PATCH 08/14] add similarity to -fwrapv to docs

Signed-off-by: Justin Stitt <justinstitt at google.com>
---
 clang/include/clang/Basic/AttrDocs.td | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 1da6ebbb15ff8d..631ef5a2dcc20e 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -8717,9 +8717,10 @@ def WrapsDocs : Documentation {
 The ``wraps`` attribute can be used with type or variable declarations to
 denote that arithmetic containing attributed types or variables have defined
 overflow behavior. Specifically, the behavior is defined as being consistent
-with two's complement wrap-around. For the purposes of sanitizers or warnings
-that concern themselves with the definedness of integer arithmetic, they will
-cease to instrument or warn about arithmetic that directly involves operands
+with two's complement wrap-around. This is similar to ``-fwrapv`` but at the
+type and declaration level. For the purposes of sanitizers or warnings that
+concern themselves with the definedness of integer arithmetic, they will cease
+to instrument or warn about arithmetic that directly involves operands
 attributed with the ``wraps`` attribute.
 
 The ``signed-integer-overflow``, ``unsigned-integer-overflow``,

>From f9a79ab52bcd966ac745fa21466ae94d96f3530c Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Fri, 8 Nov 2024 14:52:54 -0800
Subject: [PATCH 09/14] better discard of useless wraps attr

Signed-off-by: Justin Stitt <justinstitt at google.com>
---
 clang/lib/Sema/SemaType.cpp | 1 +
 1 file changed, 1 insertion(+)

diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 845c328e006ddc..2ec78184e8eca4 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -6550,6 +6550,7 @@ static void handleWrapsAttr(QualType &Type, const ParsedAttr &Attr,
     S.Diag(Attr.getLoc(), diag::warn_wraps_attr_var_decl_type_not_integer)
         << (int)NoWraps << Type.getAsString();
     Attr.setInvalid();
+    return;
   }
 
   if (NoWraps)

>From 3de3561f6c6e4e73ae1f5cda3a27a6aff5bf9904 Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Mon, 11 Nov 2024 17:25:53 -0800
Subject: [PATCH 10/14] -Wuseless-wraps-attribute is now an error

Signed-off-by: Justin Stitt <justinstitt at google.com>
---
 clang/include/clang/Basic/AttrDocs.td         | 27 +++++++++----------
 clang/include/clang/Basic/DiagnosticGroups.td |  4 ---
 .../clang/Basic/DiagnosticSemaKinds.td        |  5 ++--
 clang/lib/Sema/SemaType.cpp                   |  2 +-
 clang/test/Sema/attr-wraps.c                  |  4 +--
 5 files changed, 17 insertions(+), 25 deletions(-)

diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 631ef5a2dcc20e..13e53340fbb095 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -8743,9 +8743,9 @@ when building with ``-fsanitize=signed-integer-overflow``:
   }
 
 ``wraps`` may also be used with function parameters or declarations of
-variables as well as members of structures. Using ``wraps`` on non-integer
-types will result in a ``-Wuseless-wraps-attribute`` warning. One may disable
-this warning with ``-Wno-useless-wraps-attribute``.
+variables as well as members of structures.
+
+Using ``wraps`` on non-integer types will result in an error.
 
 ``wraps`` persists through implicit type promotions and will be applied to the
 result type of arithmetic expressions containing a wrapping operand.
@@ -8790,24 +8790,21 @@ effectively re-enable instrumentation for specific types or variables.
 
 Like ``wraps``, ``no_wraps`` persists through implicit type promotions and will
 be automatically applied to the result type of arithmetic expressions
-containing a wrapping operand.
+containing a wrapping operand. ``-Wimplicitly-discarded-wraps-attribute``
+warnings can be caused in situations where the ``wraps`` attribute cannot
+persist through implicit type conversions. Disable this with
+``-Wno-implicitly-discarded-wraps-attribute``.
+
 
-If a type or variable is attributed by both ``wraps`` and ``no_wraps``, then
-``no_wraps`` takes precedence -- regardless of the order of attribution.
+Applying both ``__attribute__((wraps))`` and ``__attribute__((no_wraps))``
+results in an error, as you should only use one or the other.
 
 Note that ``no_wraps`` makes no guarantees about the definedness of arithmetic
 overflow. Instead, use ``-fwrapv`` or ``-fno-strict-overflow``.
 
 Like ``wraps``, ``no_wraps`` may also be used with function parameters or
-declarations of variables as well as members of structures. Using ``wraps`` on
-non-integer types will result in a `-Wuseless-wraps-attribute`. One may disable
-this warning with ``-Wno-useless-wraps-attribute``.
-
-``no_wraps`` also persists through implicit type promotions and will be applied
-to the result type of arithmetic expressions containing a wrapping operand.
-``-Wimplicitly-discarded-wraps-attribute`` warnings can be caused in situations
-where the ``wraps`` attribute cannot persist through implicit type conversions.
-Disable this with ``-Wno-implicitly-discarded-wraps-attribute``.
+declarations of variables as well as members of structures but can not be used
+with non-integer types.
 }];
 }
 
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 0c4d0fa5528047..5bcf0dad4a207a 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1588,9 +1588,5 @@ def ExplicitSpecializationStorageClass : DiagGroup<"explicit-specialization-stor
 // A warning for options that enable a feature that is not yet complete
 def ExperimentalOption : DiagGroup<"experimental-option">;
 
-
-// Warnings regarding the usage of __attribute__((wraps)) on non-integer types.
-def UselessWrapsAttr : DiagGroup<"useless-wraps-attribute">;
-
 // Warnings about the wraps attribute getting implicitly discarded
 def ImpDiscardedWrapsAttr : DiagGroup<"implicitly-discarded-wraps-attribute">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 6e5160a7be9b13..5b9b19320eb54c 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -6664,12 +6664,11 @@ def err_builtin_counted_by_ref_invalid_lhs_use : Error<
 def err_builtin_counted_by_ref_has_side_effects : Error<
   "'__builtin_counted_by_ref' argument cannot have side-effects">;
 
-def warn_wraps_attr_var_decl_type_not_integer : Warning<
-  "using attribute '%select{wraps|no_wraps}0' with non-integer type '%1' has no function and is potentially misleading">,
-  InGroup<UselessWrapsAttr>;
 def warn_wraps_attr_maybe_lost : Warning<
   "'%select{wraps|no_wraps}0' attribute may be implicitly discarded when converted to %1">,
   InGroup<ImpDiscardedWrapsAttr>;
+def err_wraps_attr_var_decl_type_not_integer : Error<
+  "cannot use attribute '%select{wraps|no_wraps}0' with non-integer type '%1'">;
 
 let CategoryName = "ARC Semantic Issue" in {
 
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 2ec78184e8eca4..d6bf7db92a8d75 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -6547,7 +6547,7 @@ static void handleWrapsAttr(QualType &Type, const ParsedAttr &Attr,
     return;
 
   if (!Type->isIntegerType()) {
-    S.Diag(Attr.getLoc(), diag::warn_wraps_attr_var_decl_type_not_integer)
+    S.Diag(Attr.getLoc(), diag::err_wraps_attr_var_decl_type_not_integer)
         << (int)NoWraps << Type.getAsString();
     Attr.setInvalid();
     return;
diff --git a/clang/test/Sema/attr-wraps.c b/clang/test/Sema/attr-wraps.c
index 59045609cb678d..4321159bbf6183 100644
--- a/clang/test/Sema/attr-wraps.c
+++ b/clang/test/Sema/attr-wraps.c
@@ -41,8 +41,8 @@ int discard_test(void) {
 }
 
 void useless_wraps_attribute(void) {
-  // expected-warning at +1 {{using attribute 'wraps' with non-integer type}}
+  // expected-error at +1 {{cannot use attribute 'wraps' with non-integer type}}
   float __attribute__((wraps)) A = 3.14;
-  // expected-warning at +1 {{using attribute 'no_wraps' with non-integer type}}
+  // expected-error at +1 {{cannot use attribute 'no_wraps' with non-integer type}}
   float __attribute__((no_wraps)) B = 3.14;
 }

>From 093bb7fe7a4bb86bb66c79d1173bc06e7c67c5e6 Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Mon, 11 Nov 2024 17:26:16 -0800
Subject: [PATCH 11/14] add error for using both wraps and no_wraps together

Signed-off-by: Justin Stitt <justinstitt at google.com>
---
 .../clang/Basic/DiagnosticSemaKinds.td        |  4 ++++
 clang/lib/Sema/SemaType.cpp                   | 19 +++++++++++++++++--
 clang/test/CodeGen/wraps-attribute-scl.test   | 12 ------------
 clang/test/Sema/attr-wraps.c                  |  7 +++++++
 4 files changed, 28 insertions(+), 14 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 5b9b19320eb54c..0892a24bb4bd8a 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -6664,11 +6664,15 @@ def err_builtin_counted_by_ref_invalid_lhs_use : Error<
 def err_builtin_counted_by_ref_has_side_effects : Error<
   "'__builtin_counted_by_ref' argument cannot have side-effects">;
 
+// __attribute__((wraps)) and __attribute__((no_wraps)) diagnostics:
 def warn_wraps_attr_maybe_lost : Warning<
   "'%select{wraps|no_wraps}0' attribute may be implicitly discarded when converted to %1">,
   InGroup<ImpDiscardedWrapsAttr>;
 def err_wraps_attr_var_decl_type_not_integer : Error<
   "cannot use attribute '%select{wraps|no_wraps}0' with non-integer type '%1'">;
+def err_wraps_attr_with_no_wraps_attr : Error<
+  "attribute 'wraps' cannot be used alongside 'no_wraps'">;
+
 
 let CategoryName = "ARC Semantic Issue" in {
 
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index d6bf7db92a8d75..fc1223fa0f5d5b 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -6553,12 +6553,27 @@ static void handleWrapsAttr(QualType &Type, const ParsedAttr &Attr,
     return;
   }
 
-  if (NoWraps)
+  if (NoWraps) {
+    const AttributedType *AT = Type.getTypePtr()->getAs<AttributedType>();
+    while (AT) {
+      if (AT->getAttr()->getParsedKind() == ParsedAttr::AT_Wraps) {
+        S.Diag(Attr.getLoc(), diag::err_wraps_attr_with_no_wraps_attr);
+        Attr.setInvalid();
+        return;
+      }
+      AT = AT->getModifiedType().getTypePtr()->getAs<AttributedType>();
+    }
     Type =
         State.getAttributedType(::new (Ctx) NoWrapsAttr(Ctx, Attr), Type, Type);
-  else
+  } else {
+    if (Type.hasNoWrapsAttr()) {
+      S.Diag(Attr.getLoc(), diag::err_wraps_attr_with_no_wraps_attr);
+      Attr.setInvalid();
+      return;
+    }
     Type =
         State.getAttributedType(::new (Ctx) WrapsAttr(Ctx, Attr), Type, Type);
+  }
 }
 
 /// HandleAddressSpaceTypeAttribute - Process an address_space attribute on the
diff --git a/clang/test/CodeGen/wraps-attribute-scl.test b/clang/test/CodeGen/wraps-attribute-scl.test
index adcb4e8c8e8df5..c9a9dfb30b7e52 100644
--- a/clang/test/CodeGen/wraps-attribute-scl.test
+++ b/clang/test/CodeGen/wraps-attribute-scl.test
@@ -57,18 +57,6 @@ void bar(myty C, qaz D) {
   ++D;
 }
 
-// If a type is marked as both `wraps` and `no_wraps`, `no_wraps` should take
-// precedence, but really users shouldn't be marking a type as both of these.
-// Let's introduce a warning at some point.
-typedef int __attribute__((wraps)) __attribute__((no_wraps)) both;
-
-// CHECK-LABEL: define dso_local void @jam
-void jam(both E) {
-// CHECK: %[[E:.*]] = load i32, ptr %E.addr
-// CHECK-NEXT: @llvm.sadd.with.overflow.i32(i32 %[[E]], i32 1)
-  ++E;
-}
-
 // PROMO-LABEL: define dso_local void @bux
 void bux(char __attribute__((wraps)) F) {
 // PROMO: %[[F:.*]] = load i8, ptr %F.addr
diff --git a/clang/test/Sema/attr-wraps.c b/clang/test/Sema/attr-wraps.c
index 4321159bbf6183..9290727371a578 100644
--- a/clang/test/Sema/attr-wraps.c
+++ b/clang/test/Sema/attr-wraps.c
@@ -46,3 +46,10 @@ void useless_wraps_attribute(void) {
   // expected-error at +1 {{cannot use attribute 'no_wraps' with non-integer type}}
   float __attribute__((no_wraps)) B = 3.14;
 }
+
+void wraps_used_with_no_wraps(void) {
+  // expected-error at +1 {{attribute 'wraps' cannot be used alongside 'no_wraps'}}
+  unsigned char __attribute__((wraps)) __attribute__((no_wraps)) A;
+  // expected-error at +1 {{attribute 'wraps' cannot be used alongside 'no_wraps'}}
+  long long __attribute__((no_wraps)) __attribute__((wraps)) B;
+}

>From 445f8c7c93484a5476d85e05a289e609e3f38a94 Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Mon, 11 Nov 2024 17:55:27 -0800
Subject: [PATCH 12/14] only persist one of wraps or no_wraps

... and prefer no_wraps if both want to exist on the same binary expression

Signed-off-by: Justin Stitt <justinstitt at google.com>
---
 clang/lib/AST/Expr.cpp | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index 5a3655ec8f2c59..2ba51de5e70f3f 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -4862,10 +4862,10 @@ BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
   if (hasStoredFPFeatures())
     setStoredFPFeatures(FPFeatures);
   setDependence(computeDependence(this));
-  if (hasWrappingOperand(Ctx))
-    setType(Ctx.getAttributedType(attr::Wraps, getType(), getType()));
   if (hasNonWrappingOperand(Ctx))
     setType(Ctx.getAttributedType(attr::NoWraps, getType(), getType()));
+  else if (hasWrappingOperand(Ctx))
+    setType(Ctx.getAttributedType(attr::Wraps, getType(), getType()));
 }
 
 BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
@@ -4884,10 +4884,10 @@ BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
   if (hasStoredFPFeatures())
     setStoredFPFeatures(FPFeatures);
   setDependence(computeDependence(this));
-  if (hasWrappingOperand(Ctx))
-    setType(Ctx.getAttributedType(attr::Wraps, getType(), getType()));
   if (hasNonWrappingOperand(Ctx))
     setType(Ctx.getAttributedType(attr::NoWraps, getType(), getType()));
+  else if (hasWrappingOperand(Ctx))
+    setType(Ctx.getAttributedType(attr::Wraps, getType(), getType()));
 }
 
 BinaryOperator *BinaryOperator::CreateEmpty(const ASTContext &C,

>From a6bba49b94adea1ccba3519a711df0f1175635a4 Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Mon, 11 Nov 2024 19:29:56 -0800
Subject: [PATCH 13/14] add more truncation tests

Signed-off-by: Justin Stitt <justinstitt at google.com>
---
 clang/test/CodeGen/integer-overflow.c       |  6 +++
 clang/test/CodeGen/wraps-attribute-scl.test | 47 ++++++++++++++++-----
 clang/test/Sema/attr-wraps.c                | 11 +++++
 3 files changed, 53 insertions(+), 11 deletions(-)

diff --git a/clang/test/CodeGen/integer-overflow.c b/clang/test/CodeGen/integer-overflow.c
index 733527df6d4873..45179032236f06 100644
--- a/clang/test/CodeGen/integer-overflow.c
+++ b/clang/test/CodeGen/integer-overflow.c
@@ -170,4 +170,10 @@ void test2(void) {
   d = 127;
   w = d*d*d*d*d; // d is promoted to int, then calculation is made.
                  // wraps prevents instrumentation even through promotion
+
+  char Pointee __attribute__((wraps)) = 127;
+  char *Ptr = &Pointee;
+  // DEFAULT,WRAPV,TRAPV,CATCH_UB: call {{.*}} @llvm.sadd.with.overflow.i32
+  ++(*Ptr);
 }
+
diff --git a/clang/test/CodeGen/wraps-attribute-scl.test b/clang/test/CodeGen/wraps-attribute-scl.test
index c9a9dfb30b7e52..3cf1a79595deab 100644
--- a/clang/test/CodeGen/wraps-attribute-scl.test
+++ b/clang/test/CodeGen/wraps-attribute-scl.test
@@ -2,16 +2,17 @@
 // RUN: split-file %s %t
 
 // Ensure the wraps and no_wraps attributes properly adjust SSCL coverage
-// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=signed-integer-overflow -fsanitize-ignorelist=%t/ignoreall.ignorelist -emit-llvm %t/test.c -o - | FileCheck %s
-// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=signed-integer-overflow -fsanitize-ignorelist=%t/precedence.ignorelist -emit-llvm %t/test.c -o - | FileCheck %s --check-prefix=PREC
-// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=signed-integer-overflow -fsanitize-ignorelist=%t/promotion.ignorelist -emit-llvm %t/test.c -o - | FileCheck %s --check-prefix=PROMO
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-ignorelist=%t/ignoreall.ignorelist -emit-llvm %t/test.c -o - | FileCheck %s
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-ignorelist=%t/precedence.ignorelist -emit-llvm %t/test.c -o - | FileCheck %s --check-prefix=PREC
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-ignorelist=%t/promotion.ignorelist -emit-llvm %t/test.c -o - | FileCheck %s --check-prefix=PROMO
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=implicit-signed-integer-truncation,implicit-unsigned-integer-truncation -fsanitize-ignorelist=%t/truncation.ignorelist -emit-llvm %t/test.c -o - | FileCheck %s --check-prefix=TRUNC
 
 //--- ignoreall.ignorelist
-[signed-integer-overflow]
+[{signed-integer-overflow,unsigned-integer-overflow}]
 type:*
 
 //--- precedence.ignorelist
-[signed-integer-overflow]
+[{signed-integer-overflow,unsigned-integer-overflow}]
 type:myty
 type:qaz=sanitize
 
@@ -20,11 +21,19 @@ type:qaz=sanitize
 type:*
 type:int=sanitize
 
+//--- truncation.ignorelist
+[{implicit-signed-integer-truncation,implicit-unsigned-integer-truncation}]
+type:*
+type:wrapping_int=sanitize
+type:char=sanitize
+type:unsigned char=sanitize
+
 //--- test.c
+typedef int __attribute__((wraps)) wrapping_int;
 typedef int __attribute__((no_wraps)) non_wrapping_int;
 
-// CHECK-LABEL: define dso_local void @foo
-void foo(non_wrapping_int A, int B) {
+// CHECK-LABEL: define dso_local void @increment
+void increment(non_wrapping_int A, int B, unsigned int __attribute__((wraps)) C) {
 // CHECK: %[[A:.*]] = load i32, ptr %A.addr
 // CHECK-NEXT: @llvm.sadd.with.overflow.i32(i32 %[[A]], i32 1)
   ++A;
@@ -33,6 +42,11 @@ void foo(non_wrapping_int A, int B) {
 // CHECK-NEXT: %[[INC1:.*]] = add nsw i32 %[[B]], 1
 // CHECK-NEXT: store i32 %[[INC1]], ptr %B.addr
   ++B;
+
+// CHECK: %[[C:.*]] = load i32, ptr %C.addr
+// CHECK-NEXT: %[[INC2:.*]] = add i32 %[[C]], 1
+// CHECK-NEXT: store i32 %[[INC2]], ptr %C.addr
+  ++C;
 }
 
 // If our ignorelist (precedence.ignorelist) has an entry for `myty`, the
@@ -45,8 +59,8 @@ typedef int __attribute__((no_wraps)) myty;
 // instrumentation.
 typedef int __attribute__((wraps)) qaz;
 
-// PREC-LABEL: define dso_local void @bar
-void bar(myty C, qaz D) {
+// PREC-LABEL: define dso_local void @precedence
+void precedence(myty C, qaz D) {
 // PREC: %[[C:.*]] = load i32, ptr %C.addr
 // PREC-NEXT: @llvm.sadd.with.overflow.i32(i32 %[[C]], i32 1)
   ++C;
@@ -57,10 +71,21 @@ void bar(myty C, qaz D) {
   ++D;
 }
 
-// PROMO-LABEL: define dso_local void @bux
-void bux(char __attribute__((wraps)) F) {
+// PROMO-LABEL: define dso_local void @promotions
+void promotions(char __attribute__((wraps)) F) {
 // PROMO: %[[F:.*]] = load i8, ptr %F.addr
 // PROMO-NEXT: %[[CONV1:.*]] = sext i8 %[[F]] to i32
 // PROMO-NEXT: add i32 %[[CONV1]], 1
   (F + 1); // F is promoted to `int` but should carry the `wraps` attribute with it
 }
+
+// TRUNC-LABEL: define dso_local void @truncation
+void truncation(wrapping_int A, int B) {
+// TRUNC-NOT: call void @__ubsan_handle_implicit_conversion
+  char C = A;
+  char D __attribute__((wraps)) = B;
+  unsigned char E = A;
+  unsigned char F __attribute__((wraps)) = B;
+  short G = B;
+  char *H = &D;
+}
diff --git a/clang/test/Sema/attr-wraps.c b/clang/test/Sema/attr-wraps.c
index 9290727371a578..27e972c1cd3d8b 100644
--- a/clang/test/Sema/attr-wraps.c
+++ b/clang/test/Sema/attr-wraps.c
@@ -53,3 +53,14 @@ void wraps_used_with_no_wraps(void) {
   // expected-error at +1 {{attribute 'wraps' cannot be used alongside 'no_wraps'}}
   long long __attribute__((no_wraps)) __attribute__((wraps)) B;
 }
+
+void pointers_and_pointees(void) {
+  // expected-error at +1 {{cannot use attribute 'wraps' with non-integer type}}
+  char *A __attribute__((wraps));
+  // expected-error at +1 {{cannot use attribute 'no_wraps' with non-integer type}}
+  char *B __attribute__((no_wraps));
+
+  // allowed, see other tests in CodeGen
+  char C __attribute__((wraps)) = 127;
+  char *D = &C;
+}

>From 86663ae44fe4348777b6cddfc9776e8e7d5d4644 Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Tue, 12 Nov 2024 16:02:23 -0800
Subject: [PATCH 14/14] small updates to docs

Signed-off-by: Justin Stitt <justinstitt at google.com>
---
 clang/include/clang/Basic/AttrDocs.td | 12 +++++-------
 1 file changed, 5 insertions(+), 7 deletions(-)

diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 13e53340fbb095..a8b5d95d1efe2f 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -8791,16 +8791,14 @@ effectively re-enable instrumentation for specific types or variables.
 Like ``wraps``, ``no_wraps`` persists through implicit type promotions and will
 be automatically applied to the result type of arithmetic expressions
 containing a wrapping operand. ``-Wimplicitly-discarded-wraps-attribute``
-warnings can be caused in situations where the ``wraps`` attribute cannot
-persist through implicit type conversions. Disable this with
+warnings can be caused in situations where the ``wraps`` or ``no_wraps``
+attribute cannot persist through implicit type conversions. Disable this with
 ``-Wno-implicitly-discarded-wraps-attribute``.
 
-
 Applying both ``__attribute__((wraps))`` and ``__attribute__((no_wraps))``
-results in an error, as you should only use one or the other.
-
-Note that ``no_wraps`` makes no guarantees about the definedness of arithmetic
-overflow. Instead, use ``-fwrapv`` or ``-fno-strict-overflow``.
+results in an error, as you should only use one or the other. Arithmetic binary
+operators containing both a `wraps` and `no_wraps`-attributed type will only
+persist the `no_wraps` attribute towards the final resulting type.
 
 Like ``wraps``, ``no_wraps`` may also be used with function parameters or
 declarations of variables as well as members of structures but can not be used



More information about the cfe-commits mailing list