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 <justinst...@google.com> Date: Tue, 5 Mar 2024 03:14:49 +0000 Subject: [PATCH 1/5] 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@+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@+1 {{'wraps' attribute may be implicitly discarded}} + implicitly_discards_wraps_attribute(A); + + // expected-warning@+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@+1 {{using attribute 'wraps' with non-integer type}} + float __attribute__((wraps)) A = 3.14; + // expected-warning@+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 <justinst...@google.com> Date: Tue, 5 Nov 2024 16:03:45 -0800 Subject: [PATCH 2/5] fix typo in docs Signed-off-by: Justin Stitt <justinst...@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 <justinst...@google.com> Date: Tue, 5 Nov 2024 16:05:03 -0800 Subject: [PATCH 3/5] fix more language in docs Signed-off-by: Justin Stitt <justinst...@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 <justinst...@google.com> Date: Tue, 5 Nov 2024 16:11:11 -0800 Subject: [PATCH 4/5] run formatter Signed-off-by: Justin Stitt <justinst...@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 0932bcb87e3c5b797088b9d92be68de07dac8650 Mon Sep 17 00:00:00 2001 From: Justin Stitt <justinst...@google.com> Date: Wed, 6 Nov 2024 11:18:42 -0800 Subject: [PATCH 5/5] 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 <justinst...@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 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: _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits