[clang] [Clang] Add wraps attribute (for granular integer overflow handling) (PR #86618)

Justin Stitt via cfe-commits cfe-commits at lists.llvm.org
Tue Apr 9 14:45:48 PDT 2024


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

>From 0fa8f07c722f9d7f80a90824f961ae6e9c5bdef7 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/4] implement wraps attribute

Signed-off-by: Justin Stitt <justinstitt at google.com>
---
 clang/docs/ReleaseNotes.rst                   |  7 ++
 clang/include/clang/AST/Expr.h                |  3 +
 clang/include/clang/Basic/Attr.td             |  6 ++
 clang/include/clang/Basic/AttrDocs.td         | 66 +++++++++++++++++++
 .../clang/Basic/DiagnosticSemaKinds.td        |  3 +
 clang/include/clang/Sema/Sema.h               |  4 ++
 clang/lib/AST/Expr.cpp                        | 19 ++++++
 clang/lib/AST/ExprConstant.cpp                |  6 +-
 clang/lib/AST/TypePrinter.cpp                 |  3 +
 clang/lib/CodeGen/CGExprScalar.cpp            | 40 +++++++++--
 clang/lib/Sema/SemaDeclAttr.cpp               | 12 +++-
 clang/lib/Sema/SemaType.cpp                   | 15 +++++
 clang/test/CodeGen/integer-overflow.c         | 56 ++++++++++++++++
 clang/test/CodeGen/unsigned-overflow.c        | 63 +++++++++++++++---
 clang/test/Sema/attr-wraps.c                  |  9 +++
 15 files changed, 296 insertions(+), 16 deletions(-)
 create mode 100644 clang/test/Sema/attr-wraps.c

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 7fbe2fec6ca065..20bb9815830592 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -248,6 +248,13 @@ Attribute Changes in Clang
   added a new extension query ``__has_extension(swiftcc)`` corresponding to the
   ``__attribute__((swiftcc))`` attribute.
 
+- Introduced ``__attribute((wraps))`` or ``[[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 cannot trigger
+  integer overflow warnings or sanitizer warnings. They also cannot be
+  optimized away by some eager UB optimizations.
+
 Improvements to Clang's diagnostics
 -----------------------------------
 - Clang now applies syntax highlighting to the code snippets it
diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index 6e153ebe024b42..934146e8a182bc 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -4084,6 +4084,9 @@ class BinaryOperator : public Expr {
   static unsigned sizeOfTrailingObjects(bool HasFPFeatures) {
     return HasFPFeatures * sizeof(FPOptionsOverride);
   }
+
+  /// Do one of the subexpressions have the wraps attribute?
+  bool oneOfWraps(const ASTContext &Ctx) const;
 };
 
 /// CompoundAssignOperator - For compound assignments (e.g. +=), we keep
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 3e03e55612645b..0ea7755791d82e 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -4496,3 +4496,9 @@ def CodeAlign: StmtAttr {
     static constexpr int MaximumAlignment = 4096;
   }];
 }
+
+def Wraps : DeclOrTypeAttr {
+  let Spellings = [Clang<"wraps">, CXX11<"", "wraps", 202403>];
+  let Subjects = SubjectList<[Var, TypedefName, Field]>;
+  let Documentation = [WrapsDocs];
+}
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 9de14f608fd114..af662702edcffa 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -8005,3 +8005,69 @@ requirement:
   }
   }];
 }
+
+def WrapsDocs : Documentation {
+  let Category = DocCatField;
+  let Content = [{
+This attribute can be used with type or variable declarations to denote that
+arithmetic containing these marked components 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 a "wrapping" component.
+
+For example, ``-fsanitize=signed-integer-overflow`` or ``-Winteger-overflow``
+will not warn about suspicious overflowing arithmetic -- assuming correct usage
+of the wraps attribute.
+
+This example shows some basic usage of ``__attribute__((wraps))`` on a type
+definition when building with ``-fsanitize=signed-integer-overflow``
+
+.. code-block:: c
+  typedef int __attribute__((wraps)) wrapping_int;
+
+  void foo() {
+    wrapping_int a = INT_MAX;
+    ++a; // no sanitizer warning
+  }
+
+  int main() { foo(); }
+
+In the following example, we use ``__attribute__((wraps))`` on a variable to
+disable overflow instrumentation for arithmetic expressions it appears in. We
+do so with a popular overflow-checking pattern which we might not want to trip
+sanitizers (like ``-fsanitize=unsigned-integer-overflow``).
+
+.. code-block:: c
+  void foo(int offset) {
+    unsigned int A __attribute__((wraps)) = UINT_MAX;
+ 
+    // to check for overflow using this pattern, we may perform a real overflow
+    // thus triggering sanitizers to step in. Since A is "wrapping", we can be
+    // sure there are no sanitizer warnings.
+    if (A + offset < A) {
+      // handle overflow manually
+      // ...
+      return;
+    }
+
+    // now, handle non-overflow case
+    // ...
+  }
+
+The above example demonstrates some of the power and elegance this attribute
+provides. We can use code patterns we are already familiar with (like ``if (x +
+y < x)``) while gaining control over the overflow behavior on a case-by-case
+basis.
+
+When combined with ``-fwrapv``, this attribute can still be applied as normal
+but has no function apart from annotating types and variables for readers. This
+is because ``-fwrapv`` defines all arithmetic as being "wrapping", rending this
+attribute's efforts redundant.
+
+When using this attribute without ``-fwrapv`` and without any sanitizers, it
+still has an impact on the definedness of arithmetic expressions containing
+wrapping components. Since the behavior of said expressions is now technically
+defined, the compiler will forgo some eager optimizations that are used on
+expressions containing UB.}];
+}
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index fc727cef9cd835..c6e0fec9856cd4 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -6534,6 +6534,9 @@ def err_counted_by_attr_refer_to_union : Error<
 def note_flexible_array_counted_by_attr_field : Note<
   "field %0 declared here">;
 
+def warn_wraps_attr_var_decl_type_not_integer : Warning<
+  "using attribute 'wraps' with non-integer type '%0' has no function">;
+
 let CategoryName = "ARC Semantic Issue" in {
 
 // ARC-mode diagnostics.
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 5ecd2f9eb2881f..1989bbfe7045f0 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -3809,6 +3809,10 @@ class Sema final {
   void AddAnnotationAttr(Decl *D, const AttributeCommonInfo &CI,
                          StringRef Annot, MutableArrayRef<Expr *> Args);
 
+  /// AddWrapsAttr - Adds the "wraps" attribute to a particular
+  /// declaration.
+  void AddWrapsAttr(Decl *D, const AttributeCommonInfo &CI);
+
   bool checkMSInheritanceAttrOnDefinition(CXXRecordDecl *RD, SourceRange Range,
                                           bool BestCase,
                                           MSInheritanceModel SemanticSpelling);
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index 6221ebd5c9b4e9..a9c5f02ddd4093 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -2243,6 +2243,21 @@ bool BinaryOperator::isNullPointerArithmeticExtension(ASTContext &Ctx,
   return true;
 }
 
+bool BinaryOperator::oneOfWraps(const ASTContext &Ctx) const {
+  llvm::SmallVector<Expr *, 2> Both = {getLHS(), getRHS()};
+
+  for (const Expr *oneOf : Both) {
+    if (!oneOf)
+      continue;
+    if (auto *TypePtr =
+            oneOf->IgnoreParenImpCasts()->getType().getTypePtrOrNull())
+      if (TypePtr->hasAttr(attr::Wraps)) {
+        return true;
+      }
+  }
+  return false;
+}
+
 SourceLocExpr::SourceLocExpr(const ASTContext &Ctx, SourceLocIdentKind Kind,
                              QualType ResultTy, SourceLocation BLoc,
                              SourceLocation RParenLoc,
@@ -4757,6 +4772,8 @@ BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
   if (hasStoredFPFeatures())
     setStoredFPFeatures(FPFeatures);
   setDependence(computeDependence(this));
+  if (oneOfWraps(Ctx))
+    setType(Ctx.getAttributedType(attr::Wraps, getType(), getType()));
 }
 
 BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
@@ -4774,6 +4791,8 @@ BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
   if (hasStoredFPFeatures())
     setStoredFPFeatures(FPFeatures);
   setDependence(computeDependence(this));
+  if (oneOfWraps(Ctx))
+    setType(Ctx.getAttributedType(attr::Wraps, getType(), getType()));
 }
 
 BinaryOperator *BinaryOperator::CreateEmpty(const ASTContext &C,
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 592d43597dc1b4..29b7836c940a00 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -2775,7 +2775,8 @@ 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().getTypePtr()->hasAttr(attr::Wraps))
       Info.Ctx.getDiagnostics().Report(E->getExprLoc(),
                                        diag::warn_integer_constant_overflow)
           << toString(Result, 10, Result.isSigned(), /*formatAsCLiteral=*/false,
@@ -13964,7 +13965,8 @@ 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().getTypePtr()->hasAttr(attr::Wraps))
         Info.Ctx.getDiagnostics().Report(E->getExprLoc(),
                                          diag::warn_integer_constant_overflow)
             << toString(Value, 10, Value.isSigned(), /*formatAsCLiteral=*/false,
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index 7032ff2f18468c..52042666cb82cc 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -1947,6 +1947,9 @@ 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::PreserveMost:
     OS << "preserve_most";
     break;
diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp
index 8536570087ad0f..7c6fe78454709f 100644
--- a/clang/lib/CodeGen/CGExprScalar.cpp
+++ b/clang/lib/CodeGen/CGExprScalar.cpp
@@ -146,6 +146,15 @@ struct BinOpInfo {
       return UnOp->getSubExpr()->getType()->isFixedPointType();
     return false;
   }
+
+  /// Does the BinaryOperator have the wraps attribute?
+  /// If so, we can ellide overflow sanitizer checks.
+  bool oneOfWraps() const {
+    const Type *TyPtr = E->getType().getTypePtrOrNull();
+    if (TyPtr)
+      return TyPtr->hasAttr(attr::Wraps);
+    return false;
+  }
 };
 
 static bool MustVisitNullValue(const Expr *E) {
@@ -724,6 +733,11 @@ class ScalarExprEmitter
 
   // Binary Operators.
   Value *EmitMul(const BinOpInfo &Ops) {
+    if ((Ops.Ty->isSignedIntegerOrEnumerationType() ||
+         Ops.Ty->isUnsignedIntegerType()) &&
+        Ops.oneOfWraps())
+      return Builder.CreateMul(Ops.LHS, Ops.RHS, "mul");
+
     if (Ops.Ty->isSignedIntegerOrEnumerationType()) {
       switch (CGF.getLangOpts().getSignedOverflowBehavior()) {
       case LangOptions::SOB_Defined:
@@ -2685,6 +2699,9 @@ ScalarExprEmitter::EmitScalarPrePostIncDec(const UnaryOperator *E, LValue LV,
   } else if (type->isIntegerType()) {
     QualType promotedType;
     bool canPerformLossyDemotionCheck = false;
+    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.");
@@ -2727,10 +2744,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.oneOfWraps()) {
       value = EmitIncDecConsiderOverflowBehavior(E, value, isInc);
     } else if (E->canOverflow() && type->isUnsignedIntegerType() &&
-               CGF.SanOpts.has(SanitizerKind::UnsignedIntegerOverflow)) {
+               CGF.SanOpts.has(SanitizerKind::UnsignedIntegerOverflow) &&
+               !Ops.oneOfWraps()) {
       value = EmitOverflowCheckedBinOp(createBinOpInfoFromIncDec(
           E, value, isInc, E->getFPFeaturesInEffect(CGF.getLangOpts())));
     } else {
@@ -3504,7 +3523,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.oneOfWraps()) {
       llvm::Value *Zero = llvm::Constant::getNullValue(ConvertType(Ops.Ty));
       EmitUndefinedBehaviorIntegerDivAndRemCheck(Ops, Zero, true);
     } else if (CGF.SanOpts.has(SanitizerKind::FloatDivideByZero) &&
@@ -3553,7 +3573,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.oneOfWraps()) {
     CodeGenFunction::SanitizerScope SanScope(&CGF);
     llvm::Value *Zero = llvm::Constant::getNullValue(ConvertType(Ops.Ty));
     EmitUndefinedBehaviorIntegerDivAndRemCheck(Ops, Zero, false);
@@ -3918,6 +3939,11 @@ Value *ScalarExprEmitter::EmitAdd(const BinOpInfo &op) {
       op.RHS->getType()->isPointerTy())
     return emitPointerArithmetic(CGF, op, CodeGenFunction::NotSubtraction);
 
+  if ((op.Ty->isSignedIntegerOrEnumerationType() ||
+       op.Ty->isUnsignedIntegerType()) &&
+      op.oneOfWraps())
+    return Builder.CreateAdd(op.LHS, op.RHS, "add");
+
   if (op.Ty->isSignedIntegerOrEnumerationType()) {
     switch (CGF.getLangOpts().getSignedOverflowBehavior()) {
     case LangOptions::SOB_Defined:
@@ -4074,6 +4100,10 @@ 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() ||
+         op.Ty->isUnsignedIntegerType()) &&
+        op.oneOfWraps())
+      return Builder.CreateSub(op.LHS, op.RHS, "sub");
     if (op.Ty->isSignedIntegerOrEnumerationType()) {
       switch (CGF.getLangOpts().getSignedOverflowBehavior()) {
       case LangOptions::SOB_Defined:
@@ -4224,7 +4254,7 @@ 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.oneOfWraps();
   bool SanitizeUnsignedBase =
       CGF.SanOpts.has(SanitizerKind::UnsignedShiftBase) &&
       Ops.Ty->hasUnsignedIntegerRepresentation();
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 0a62c656d824ff..6bc8d017d2edaf 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -4413,6 +4413,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) {
+  S.AddWrapsAttr(D, AL);
+}
+
+void Sema::AddWrapsAttr(Decl *D, const AttributeCommonInfo &CI) {
+  D->addAttr(::new (Context) WrapsAttr(Context, CI));
+}
+
 static void handleAlignedAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
   if (AL.hasParsedType()) {
     const ParsedType &TypeArg = AL.getTypeArg();
@@ -9683,10 +9691,12 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
   case ParsedAttr::AT_AvailableOnlyInDefaultEvalMethod:
     handleAvailableOnlyInDefaultEvalMethod(S, D, AL);
     break;
-
   case ParsedAttr::AT_CountedBy:
     handleCountedByAttrField(S, D, AL);
     break;
+  case ParsedAttr::AT_Wraps:
+    handleWrapsAttr(S, D, AL);
+    break;
 
   // Microsoft attributes:
   case ParsedAttr::AT_LayoutVersion:
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index d7521a5363a3d2..e2dc290cb7fb82 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -6940,6 +6940,18 @@ 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) {
+  Sema &S = State.getSema();
+  ASTContext &Ctx = S.Context;
+
+  if (!Type->isIntegerType())
+    S.Diag(Attr.getLoc(), diag::warn_wraps_attr_var_decl_type_not_integer)
+        << Type.getAsString();
+
+  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.
@@ -8929,6 +8941,9 @@ 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_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 461b026d39615b..44c42ed9efe577 100644
--- a/clang/test/CodeGen/integer-overflow.c
+++ b/clang/test/CodeGen/integer-overflow.c
@@ -105,3 +105,59 @@ 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;
+}
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/Sema/attr-wraps.c b/clang/test/Sema/attr-wraps.c
new file mode 100644
index 00000000000000..97aff317120633
--- /dev/null
+++ b/clang/test/Sema/attr-wraps.c
@@ -0,0 +1,9 @@
+// RUN: %clang_cc1 %s -verify -fsyntax-only -triple x86_64-pc-linux-gnu
+// expected-no-diagnostics
+typedef int __attribute__((wraps)) wrapping_int;
+
+void foo(void) {
+  const wrapping_int A = 1;
+  int D = 2147483647 + A;
+  (void)D;
+}

>From 80d2520c5a3311f7ac0f75e7311e5f894642a2b5 Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Tue, 26 Mar 2024 02:20:15 +0000
Subject: [PATCH 2/4] fix typo in clang release notes

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

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 20bb9815830592..074936b6358cda 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -248,7 +248,7 @@ Attribute Changes in Clang
   added a new extension query ``__has_extension(swiftcc)`` corresponding to the
   ``__attribute__((swiftcc))`` attribute.
 
-- Introduced ``__attribute((wraps))`` or ``[[wraps]]`` which can be added to
+- Introduced ``__attribute((wraps))__`` or ``[[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 cannot trigger

>From 2b92e934f8fe085ebc3377f7f60e7cd57131e73a Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Tue, 26 Mar 2024 23:29:29 +0000
Subject: [PATCH 3/4] add wraps bypass for
 implicit-(un)signed-integer-truncation sanitizers

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

diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp
index 7c6fe78454709f..98fd70017665a0 100644
--- a/clang/lib/CodeGen/CGExprScalar.cpp
+++ b/clang/lib/CodeGen/CGExprScalar.cpp
@@ -1106,7 +1106,8 @@ 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.getTypePtr()->hasAttr(attr::Wraps))
     return;
 
   llvm::Constant *StaticArgs[] = {
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 246e3577809a79..9468b30d7eaaef 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -16428,7 +16428,8 @@ static void CheckImplicitConversion(Sema &S, Expr *E, QualType T,
       S.Context, E, S.isConstantEvaluatedContext(), /*Approximate=*/true);
   IntRange TargetRange = IntRange::forTargetOfCanonicalType(S.Context, Target);
 
-  if (LikelySourceRange.Width > TargetRange.Width) {
+  if (LikelySourceRange.Width > TargetRange.Width &&
+      !T.getTypePtr()->hasAttr(attr::Wraps)) {
     // If the source is a constant, use a default-on diagnostic.
     // TODO: this should happen for bitfield stores, too.
     Expr::EvalResult Result;
@@ -16476,7 +16477,7 @@ static void CheckImplicitConversion(Sema &S, Expr *E, QualType T,
 
   if (TargetRange.Width == LikelySourceRange.Width &&
       !TargetRange.NonNegative && LikelySourceRange.NonNegative &&
-      Source->isSignedIntegerType()) {
+      Source->isSignedIntegerType() && !T.getTypePtr()->hasAttr(attr::Wraps)) {
     // 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.

>From 6cac20e864e2e63c279121377dd6254e835fe622 Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Tue, 9 Apr 2024 21:29:35 +0000
Subject: [PATCH 4/4] don't support c++

Signed-off-by: Justin Stitt <justinstitt at google.com>
---
 clang/include/clang/Basic/Attr.td | 2 +-
 clang/lib/Sema/SemaDeclAttr.cpp   | 5 +++++
 clang/lib/Sema/SemaType.cpp       | 5 +++++
 3 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 0ea7755791d82e..901275f2d75855 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -4498,7 +4498,7 @@ def CodeAlign: StmtAttr {
 }
 
 def Wraps : DeclOrTypeAttr {
-  let Spellings = [Clang<"wraps">, CXX11<"", "wraps", 202403>];
+  let Spellings = [GNU<"wraps">];
   let Subjects = SubjectList<[Var, TypedefName, Field]>;
   let Documentation = [WrapsDocs];
 }
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 6bc8d017d2edaf..64209d7dab9d10 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -4414,6 +4414,11 @@ void Sema::AddAlignValueAttr(Decl *D, const AttributeCommonInfo &CI, Expr *E) {
 }
 
 static void handleWrapsAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
+  if (S.getLangOpts().CPlusPlus) {
+    S.Diag(AL.getLoc(), diag::warn_attribute_ignored) << AL;
+    return;
+  }
+
   S.AddWrapsAttr(D, AL);
 }
 
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index e2dc290cb7fb82..6fa19753f06ae5 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -6945,6 +6945,11 @@ static void handleWrapsAttr(QualType &Type, const ParsedAttr &Attr,
   Sema &S = State.getSema();
   ASTContext &Ctx = S.Context;
 
+  // No need to warn here, that is handled by SemaDeclAttr.
+  // Simply disable applying this attribute.
+  if (S.getLangOpts().CPlusPlus)
+    return;
+
   if (!Type->isIntegerType())
     S.Diag(Attr.getLoc(), diag::warn_wraps_attr_var_decl_type_not_integer)
         << Type.getAsString();



More information about the cfe-commits mailing list