-
Notifications
You must be signed in to change notification settings - Fork 11.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Clang] add wraps and no_wraps attributes #115094
base: main
Are you sure you want to change the base?
Conversation
@llvm/pr-subscribers-clang-codegen Author: Justin Stitt (JustinStitt) Changes> [!NOTE] Implement These attributes serve as convenient in-source annotations for the expected overflow behavior of code. In addition, these attributes also have functional changes when used alongside certain sanitizers or warnings. The This is most useful for users that want to enable overflow or truncation sanitizers but also have some code they want to not be instrumented. Without this PR, they can only achieve translation unit level or at best function level granularity without changing arithmetic expressions directly. In an effort to not make basic arithmetic rely on wrappers like Examplevoid foo(int __attribute__((wraps)) A) {
++A; // No overflow/truncation sanitizers will instrument this code
} no_wraps
What makes
typedef int __attribute__((no_wraps)) non_wrapping_int;
void bar(int A, non_wrapping_int B) {
++A; // will not have instrumentation (ignored within the ignorelist)
++B; // will have instrumentation (ignored within ignorelist, but overridden by no_wraps)
} CC@vitalybuka @kees @melver @bwendling Patch is 44.62 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/115094.diff 23 Files Affected:
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index dc45202f6b2e86..c18410e6544081 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -386,6 +386,27 @@ 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::Inte...
[truncated]
|
@llvm/pr-subscribers-clang Author: Justin Stitt (JustinStitt) Changes> [!NOTE] Implement These attributes serve as convenient in-source annotations for the expected overflow behavior of code. In addition, these attributes also have functional changes when used alongside certain sanitizers or warnings. The This is most useful for users that want to enable overflow or truncation sanitizers but also have some code they want to not be instrumented. Without this PR, they can only achieve translation unit level or at best function level granularity without changing arithmetic expressions directly. In an effort to not make basic arithmetic rely on wrappers like Examplevoid foo(int __attribute__((wraps)) A) {
++A; // No overflow/truncation sanitizers will instrument this code
} no_wraps
What makes
typedef int __attribute__((no_wraps)) non_wrapping_int;
void bar(int A, non_wrapping_int B) {
++A; // will not have instrumentation (ignored within the ignorelist)
++B; // will have instrumentation (ignored within ignorelist, but overridden by no_wraps)
} CC@vitalybuka @kees @melver @bwendling Patch is 44.62 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/115094.diff 23 Files Affected:
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index dc45202f6b2e86..c18410e6544081 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -386,6 +386,27 @@ 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::Inte...
[truncated]
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
Signed-off-by: Justin Stitt <[email protected]>
bd63565
to
b656e94
Compare
Signed-off-by: Justin Stitt <[email protected]>
Signed-off-by: Justin Stitt <[email protected]>
@@ -67,9 +67,11 @@ types specified within an ignorelist. | |||
int a = 2147483647; // INT_MAX | |||
++a; // Normally, an overflow with -fsanitize=signed-integer-overflow | |||
} | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please do unrelated fixes in separate PRs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The wraps docs reference the SSCL docs and I fixed some whitespace in an SSCL example to make it more readable. I think it's related and necessary 👍
Are there other changes specifically that you think aren't related/necessary?
edit: found and fixed one in cfa9cb9
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 <[email protected]>
cfa9cb9
to
0932bcb
Compare
Note
This PR is an expansion of #86618, with a larger scope including
no_wraps
and SSCL integration. There's more background and review that is worth reading in #86618 as well.Implement
__attribute__((wraps))
and__attribute__((no_wraps))
. These attributes are used to annotate the expected wrap-around behavior for types, variables, struct members, or function parameters.These attributes serve as convenient in-source annotations for the expected overflow behavior of code. In addition, these attributes also have functional changes when used alongside certain sanitizers or warnings.
The
signed-integer-overflow
,unsigned-integer-overflow
,implicit-signed-integer-truncation
and theimplicit-unsigned-integer-truncation
sanitizers have different behavior when interacting with thewraps
attribute.This is most useful for users that want to enable overflow or truncation sanitizers but also have some code they want to not be instrumented. Without this PR, they can only achieve translation unit level or at best function level granularity without changing arithmetic expressions directly. In an effort to not make basic arithmetic rely on wrappers like
__builtin_add_overflow()
, this PR introduces type and variable level granularity.Example
no_wraps
no_wraps
is interesting because it reflects the default case for integer operations and the associated sanitizers. In the general case,no_wraps
just serves as a friendly annotation to readers of code that something is really not supposed to wrap-around. The sanitizers will instrument them all the same as they would if this attribute was not present.What makes
no_wraps
useful is when SSCLs are used alongside them. #107332 introduced type-filtering for the overflow and truncation sanitizers which can be overriden byno_wraps
andwraps
. To illustrate, let's look at another example:CC
@vitalybuka @kees @melver @bwendling