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

Justin Stitt via cfe-commits cfe-commits at lists.llvm.org
Wed Nov 6 11:22:52 PST 2024


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

>From f58e6481650f8cda6089b2a0637c94596a45370e 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 1/6] 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 dc45202f6b2e86..5457d802d820f3 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -386,6 +386,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 96a7b2fba4ae43..10462643b69c7f 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 1bcc7ee0b70dee..c7f368c0193664 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 156fbd1c4442eb..134395abf6e8f8 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -4838,3 +4838,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 b497cce37625c9..5d9f77b05ee96d 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -8526,3 +8526,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 d697e6d61afa9a..84d62d4fcb561b 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -6652,6 +6652,13 @@ def warn_counted_by_attr_elt_type_unknown_size :
   Warning<err_counted_by_attr_pointee_unknown_size.Summary>,
   InGroup<BoundsSafetyCountedByEltTyUnknownSize>;
 
+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 6bf2908e667c07..a6348a0f406263 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 d78968179b1fdc..32c6f2570a84a2 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -10547,7 +10547,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(),
@@ -10765,11 +10766,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;
 
@@ -11272,7 +11301,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;
@@ -11321,7 +11350,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 1aa3e8edfe1b13..a09519fd62c3a7 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"
@@ -6717,7 +6718,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 601c6f2eef1d9c..1f1c8a8dee9cb6 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();
@@ -6940,13 +6948,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 e28b0775410c0a..8914025ff962e1 100644
--- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test
+++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
@@ -131,6 +131,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)
@@ -216,6 +217,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 b656e94b521c5e708246de736bf7d5cb82f7331b 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 2/6] 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 5d9f77b05ee96d..83cf1092263d7a 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -8559,7 +8559,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 681b57f6d3f0ccac2c6e0088fa24e3bf2bdc75ce 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 3/6] 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 83cf1092263d7a..582b094187e6a1 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -8559,8 +8559,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 fff14248b822006389cefa1c4ca77d65824e78a8 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 4/6] 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 a09519fd62c3a7..c29a5fb6a681b0 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"
@@ -6715,9 +6715,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 1f1c8a8dee9cb6..c48b947591a15e 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -6961,7 +6961,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 3e3ad8aa2dacdd0ea209e6540582b25caf3b9b00 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 5/6] use standalone doc string

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 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:

>From cfa9cb9120a9f57f01d1193c90aa2d0bb92b5e84 Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Wed, 6 Nov 2024 11:22:36 -0800
Subject: [PATCH 6/6] reinstate unrelated newline

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

diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index c48b947591a15e..8a7c1d9c691772 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -6948,6 +6948,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:



More information about the cfe-commits mailing list