Skip to content

Commit 10ee328

Browse files
committed
implement wraps attribute
Signed-off-by: Justin Stitt <[email protected]>
1 parent eec41d2 commit 10ee328

File tree

15 files changed

+298
-16
lines changed

15 files changed

+298
-16
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,15 @@ Attribute Changes in Clang
282282
This allows the ``_Nullable`` and ``_Nonnull`` family of type attributes to
283283
apply to this class.
284284

285+
- Introduced ``__attribute((wraps))__`` which can be added to type or variable
286+
declarations. Using an attributed type or variable in an arithmetic
287+
expression will define the overflow behavior for that expression as having
288+
two's complement wrap-around. These expressions cannot trigger integer
289+
overflow warnings or sanitizer warnings. They also cannot be optimized away
290+
by some eager UB optimizations.
291+
292+
This attribute is ignored in C++.
293+
285294
Improvements to Clang's diagnostics
286295
-----------------------------------
287296
- Clang now applies syntax highlighting to the code snippets it

clang/include/clang/AST/Expr.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4077,6 +4077,9 @@ class BinaryOperator : public Expr {
40774077
static unsigned sizeOfTrailingObjects(bool HasFPFeatures) {
40784078
return HasFPFeatures * sizeof(FPOptionsOverride);
40794079
}
4080+
4081+
/// Do one of the subexpressions have the wraps attribute?
4082+
bool oneOfWraps(const ASTContext &Ctx) const;
40804083
};
40814084

40824085
/// CompoundAssignOperator - For compound assignments (e.g. +=), we keep

clang/include/clang/Basic/Attr.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4506,3 +4506,9 @@ def CodeAlign: StmtAttr {
45064506
static constexpr int MaximumAlignment = 4096;
45074507
}];
45084508
}
4509+
4510+
def Wraps : DeclOrTypeAttr {
4511+
let Spellings = [Clang<"wraps">, CXX11<"", "wraps", 202403>];
4512+
let Subjects = SubjectList<[Var, TypedefName, Field]>;
4513+
let Documentation = [WrapsDocs];
4514+
}

clang/include/clang/Basic/AttrDocs.td

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8044,3 +8044,69 @@ requirement:
80448044
}
80458045
}];
80468046
}
8047+
8048+
def WrapsDocs : Documentation {
8049+
let Category = DocCatField;
8050+
let Content = [{
8051+
This attribute can be used with type or variable declarations to denote that
8052+
arithmetic containing these marked components have defined overflow behavior.
8053+
Specifically, the behavior is defined as being consistent with two's complement
8054+
wrap-around. For the purposes of sanitizers or warnings that concern themselves
8055+
with the definedness of integer arithmetic, they will cease to instrument or
8056+
warn about arithmetic that directly involves a "wrapping" component.
8057+
8058+
For example, ``-fsanitize=signed-integer-overflow`` or ``-Winteger-overflow``
8059+
will not warn about suspicious overflowing arithmetic -- assuming correct usage
8060+
of the wraps attribute.
8061+
8062+
This example shows some basic usage of ``__attribute__((wraps))`` on a type
8063+
definition when building with ``-fsanitize=signed-integer-overflow``
8064+
8065+
.. code-block:: c
8066+
typedef int __attribute__((wraps)) wrapping_int;
8067+
8068+
void foo() {
8069+
wrapping_int a = INT_MAX;
8070+
++a; // no sanitizer warning
8071+
}
8072+
8073+
int main() { foo(); }
8074+
8075+
In the following example, we use ``__attribute__((wraps))`` on a variable to
8076+
disable overflow instrumentation for arithmetic expressions it appears in. We
8077+
do so with a popular overflow-checking pattern which we might not want to trip
8078+
sanitizers (like ``-fsanitize=unsigned-integer-overflow``).
8079+
8080+
.. code-block:: c
8081+
void foo(int offset) {
8082+
unsigned int A __attribute__((wraps)) = UINT_MAX;
8083+
8084+
// to check for overflow using this pattern, we may perform a real overflow
8085+
// thus triggering sanitizers to step in. Since A is "wrapping", we can be
8086+
// sure there are no sanitizer warnings.
8087+
if (A + offset < A) {
8088+
// handle overflow manually
8089+
// ...
8090+
return;
8091+
}
8092+
8093+
// now, handle non-overflow case
8094+
// ...
8095+
}
8096+
8097+
The above example demonstrates some of the power and elegance this attribute
8098+
provides. We can use code patterns we are already familiar with (like ``if (x +
8099+
y < x)``) while gaining control over the overflow behavior on a case-by-case
8100+
basis.
8101+
8102+
When combined with ``-fwrapv``, this attribute can still be applied as normal
8103+
but has no function apart from annotating types and variables for readers. This
8104+
is because ``-fwrapv`` defines all arithmetic as being "wrapping", rending this
8105+
attribute's efforts redundant.
8106+
8107+
When using this attribute without ``-fwrapv`` and without any sanitizers, it
8108+
still has an impact on the definedness of arithmetic expressions containing
8109+
wrapping components. Since the behavior of said expressions is now technically
8110+
defined, the compiler will forgo some eager optimizations that are used on
8111+
expressions containing UB.}];
8112+
}

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6529,6 +6529,9 @@ def err_counted_by_attr_refer_to_union : Error<
65296529
def note_flexible_array_counted_by_attr_field : Note<
65306530
"field %0 declared here">;
65316531

6532+
def warn_wraps_attr_var_decl_type_not_integer : Warning<
6533+
"using attribute 'wraps' with non-integer type '%0' has no function">;
6534+
65326535
let CategoryName = "ARC Semantic Issue" in {
65336536

65346537
// ARC-mode diagnostics.

clang/include/clang/Sema/Sema.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3630,6 +3630,10 @@ class Sema final : public SemaBase {
36303630
void AddAnnotationAttr(Decl *D, const AttributeCommonInfo &CI,
36313631
StringRef Annot, MutableArrayRef<Expr *> Args);
36323632

3633+
/// AddWrapsAttr - Adds the "wraps" attribute to a particular
3634+
/// declaration.
3635+
void AddWrapsAttr(Decl *D, const AttributeCommonInfo &CI);
3636+
36333637
bool checkMSInheritanceAttrOnDefinition(CXXRecordDecl *RD, SourceRange Range,
36343638
bool BestCase,
36353639
MSInheritanceModel SemanticSpelling);

clang/lib/AST/Expr.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2237,6 +2237,21 @@ bool BinaryOperator::isNullPointerArithmeticExtension(ASTContext &Ctx,
22372237
return true;
22382238
}
22392239

2240+
bool BinaryOperator::oneOfWraps(const ASTContext &Ctx) const {
2241+
llvm::SmallVector<Expr *, 2> Both = {getLHS(), getRHS()};
2242+
2243+
for (const Expr *oneOf : Both) {
2244+
if (!oneOf)
2245+
continue;
2246+
if (auto *TypePtr =
2247+
oneOf->IgnoreParenImpCasts()->getType().getTypePtrOrNull())
2248+
if (TypePtr->hasAttr(attr::Wraps)) {
2249+
return true;
2250+
}
2251+
}
2252+
return false;
2253+
}
2254+
22402255
SourceLocExpr::SourceLocExpr(const ASTContext &Ctx, SourceLocIdentKind Kind,
22412256
QualType ResultTy, SourceLocation BLoc,
22422257
SourceLocation RParenLoc,
@@ -4751,6 +4766,8 @@ BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
47514766
if (hasStoredFPFeatures())
47524767
setStoredFPFeatures(FPFeatures);
47534768
setDependence(computeDependence(this));
4769+
if (oneOfWraps(Ctx))
4770+
setType(Ctx.getAttributedType(attr::Wraps, getType(), getType()));
47544771
}
47554772

47564773
BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
@@ -4768,6 +4785,8 @@ BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
47684785
if (hasStoredFPFeatures())
47694786
setStoredFPFeatures(FPFeatures);
47704787
setDependence(computeDependence(this));
4788+
if (oneOfWraps(Ctx))
4789+
setType(Ctx.getAttributedType(attr::Wraps, getType(), getType()));
47714790
}
47724791

47734792
BinaryOperator *BinaryOperator::CreateEmpty(const ASTContext &C,

clang/lib/AST/ExprConstant.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2775,7 +2775,8 @@ static bool CheckedIntArithmetic(EvalInfo &Info, const Expr *E,
27752775
APSInt Value(Op(LHS.extend(BitWidth), RHS.extend(BitWidth)), false);
27762776
Result = Value.trunc(LHS.getBitWidth());
27772777
if (Result.extend(BitWidth) != Value) {
2778-
if (Info.checkingForUndefinedBehavior())
2778+
if (Info.checkingForUndefinedBehavior() &&
2779+
!E->getType().getTypePtr()->hasAttr(attr::Wraps))
27792780
Info.Ctx.getDiagnostics().Report(E->getExprLoc(),
27802781
diag::warn_integer_constant_overflow)
27812782
<< toString(Result, 10, Result.isSigned(), /*formatAsCLiteral=*/false,
@@ -13993,7 +13994,8 @@ bool IntExprEvaluator::VisitUnaryOperator(const UnaryOperator *E) {
1399313994
if (!Result.isInt()) return Error(E);
1399413995
const APSInt &Value = Result.getInt();
1399513996
if (Value.isSigned() && Value.isMinSignedValue() && E->canOverflow()) {
13996-
if (Info.checkingForUndefinedBehavior())
13997+
if (Info.checkingForUndefinedBehavior() &&
13998+
!E->getType().getTypePtr()->hasAttr(attr::Wraps))
1399713999
Info.Ctx.getDiagnostics().Report(E->getExprLoc(),
1399814000
diag::warn_integer_constant_overflow)
1399914001
<< toString(Value, 10, Value.isSigned(), /*formatAsCLiteral=*/false,

clang/lib/AST/TypePrinter.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1962,6 +1962,9 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
19621962
case attr::AArch64SVEPcs: OS << "aarch64_sve_pcs"; break;
19631963
case attr::AMDGPUKernelCall: OS << "amdgpu_kernel"; break;
19641964
case attr::IntelOclBicc: OS << "inteloclbicc"; break;
1965+
case attr::Wraps:
1966+
OS << "wraps";
1967+
break;
19651968
case attr::PreserveMost:
19661969
OS << "preserve_most";
19671970
break;

clang/lib/CodeGen/CGExprScalar.cpp

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,15 @@ struct BinOpInfo {
147147
return UnOp->getSubExpr()->getType()->isFixedPointType();
148148
return false;
149149
}
150+
151+
/// Does the BinaryOperator have the wraps attribute?
152+
/// If so, we can ellide overflow sanitizer checks.
153+
bool oneOfWraps() const {
154+
const Type *TyPtr = E->getType().getTypePtrOrNull();
155+
if (TyPtr)
156+
return TyPtr->hasAttr(attr::Wraps);
157+
return false;
158+
}
150159
};
151160

152161
static bool MustVisitNullValue(const Expr *E) {
@@ -726,6 +735,11 @@ class ScalarExprEmitter
726735

727736
// Binary Operators.
728737
Value *EmitMul(const BinOpInfo &Ops) {
738+
if ((Ops.Ty->isSignedIntegerOrEnumerationType() ||
739+
Ops.Ty->isUnsignedIntegerType()) &&
740+
Ops.oneOfWraps())
741+
return Builder.CreateMul(Ops.LHS, Ops.RHS, "mul");
742+
729743
if (Ops.Ty->isSignedIntegerOrEnumerationType()) {
730744
switch (CGF.getLangOpts().getSignedOverflowBehavior()) {
731745
case LangOptions::SOB_Defined:
@@ -2822,6 +2836,9 @@ ScalarExprEmitter::EmitScalarPrePostIncDec(const UnaryOperator *E, LValue LV,
28222836
} else if (type->isIntegerType()) {
28232837
QualType promotedType;
28242838
bool canPerformLossyDemotionCheck = false;
2839+
BinOpInfo Ops = (createBinOpInfoFromIncDec(
2840+
E, value, isInc, E->getFPFeaturesInEffect(CGF.getLangOpts())));
2841+
28252842
if (CGF.getContext().isPromotableIntegerType(type)) {
28262843
promotedType = CGF.getContext().getPromotedIntegerType(type);
28272844
assert(promotedType != type && "Shouldn't promote to the same type.");
@@ -2878,10 +2895,12 @@ ScalarExprEmitter::EmitScalarPrePostIncDec(const UnaryOperator *E, LValue LV,
28782895
// Note that signed integer inc/dec with width less than int can't
28792896
// overflow because of promotion rules; we're just eliding a few steps
28802897
// here.
2881-
} else if (E->canOverflow() && type->isSignedIntegerOrEnumerationType()) {
2898+
} else if (E->canOverflow() && type->isSignedIntegerOrEnumerationType() &&
2899+
!Ops.oneOfWraps()) {
28822900
value = EmitIncDecConsiderOverflowBehavior(E, value, isInc);
28832901
} else if (E->canOverflow() && type->isUnsignedIntegerType() &&
2884-
CGF.SanOpts.has(SanitizerKind::UnsignedIntegerOverflow)) {
2902+
CGF.SanOpts.has(SanitizerKind::UnsignedIntegerOverflow) &&
2903+
!Ops.oneOfWraps()) {
28852904
value = EmitOverflowCheckedBinOp(createBinOpInfoFromIncDec(
28862905
E, value, isInc, E->getFPFeaturesInEffect(CGF.getLangOpts())));
28872906
} else {
@@ -3670,7 +3689,8 @@ Value *ScalarExprEmitter::EmitDiv(const BinOpInfo &Ops) {
36703689
if ((CGF.SanOpts.has(SanitizerKind::IntegerDivideByZero) ||
36713690
CGF.SanOpts.has(SanitizerKind::SignedIntegerOverflow)) &&
36723691
Ops.Ty->isIntegerType() &&
3673-
(Ops.mayHaveIntegerDivisionByZero() || Ops.mayHaveIntegerOverflow())) {
3692+
(Ops.mayHaveIntegerDivisionByZero() || Ops.mayHaveIntegerOverflow()) &&
3693+
!Ops.oneOfWraps()) {
36743694
llvm::Value *Zero = llvm::Constant::getNullValue(ConvertType(Ops.Ty));
36753695
EmitUndefinedBehaviorIntegerDivAndRemCheck(Ops, Zero, true);
36763696
} else if (CGF.SanOpts.has(SanitizerKind::FloatDivideByZero) &&
@@ -3719,7 +3739,8 @@ Value *ScalarExprEmitter::EmitRem(const BinOpInfo &Ops) {
37193739
if ((CGF.SanOpts.has(SanitizerKind::IntegerDivideByZero) ||
37203740
CGF.SanOpts.has(SanitizerKind::SignedIntegerOverflow)) &&
37213741
Ops.Ty->isIntegerType() &&
3722-
(Ops.mayHaveIntegerDivisionByZero() || Ops.mayHaveIntegerOverflow())) {
3742+
(Ops.mayHaveIntegerDivisionByZero() || Ops.mayHaveIntegerOverflow()) &&
3743+
!Ops.oneOfWraps()) {
37233744
CodeGenFunction::SanitizerScope SanScope(&CGF);
37243745
llvm::Value *Zero = llvm::Constant::getNullValue(ConvertType(Ops.Ty));
37253746
EmitUndefinedBehaviorIntegerDivAndRemCheck(Ops, Zero, false);
@@ -4084,6 +4105,11 @@ Value *ScalarExprEmitter::EmitAdd(const BinOpInfo &op) {
40844105
op.RHS->getType()->isPointerTy())
40854106
return emitPointerArithmetic(CGF, op, CodeGenFunction::NotSubtraction);
40864107

4108+
if ((op.Ty->isSignedIntegerOrEnumerationType() ||
4109+
op.Ty->isUnsignedIntegerType()) &&
4110+
op.oneOfWraps())
4111+
return Builder.CreateAdd(op.LHS, op.RHS, "add");
4112+
40874113
if (op.Ty->isSignedIntegerOrEnumerationType()) {
40884114
switch (CGF.getLangOpts().getSignedOverflowBehavior()) {
40894115
case LangOptions::SOB_Defined:
@@ -4240,6 +4266,10 @@ Value *ScalarExprEmitter::EmitFixedPointBinOp(const BinOpInfo &op) {
42404266
Value *ScalarExprEmitter::EmitSub(const BinOpInfo &op) {
42414267
// The LHS is always a pointer if either side is.
42424268
if (!op.LHS->getType()->isPointerTy()) {
4269+
if ((op.Ty->isSignedIntegerOrEnumerationType() ||
4270+
op.Ty->isUnsignedIntegerType()) &&
4271+
op.oneOfWraps())
4272+
return Builder.CreateSub(op.LHS, op.RHS, "sub");
42434273
if (op.Ty->isSignedIntegerOrEnumerationType()) {
42444274
switch (CGF.getLangOpts().getSignedOverflowBehavior()) {
42454275
case LangOptions::SOB_Defined:
@@ -4390,7 +4420,7 @@ Value *ScalarExprEmitter::EmitShl(const BinOpInfo &Ops) {
43904420
bool SanitizeSignedBase = CGF.SanOpts.has(SanitizerKind::ShiftBase) &&
43914421
Ops.Ty->hasSignedIntegerRepresentation() &&
43924422
!CGF.getLangOpts().isSignedOverflowDefined() &&
4393-
!CGF.getLangOpts().CPlusPlus20;
4423+
!CGF.getLangOpts().CPlusPlus20 && !Ops.oneOfWraps();
43944424
bool SanitizeUnsignedBase =
43954425
CGF.SanOpts.has(SanitizerKind::UnsignedShiftBase) &&
43964426
Ops.Ty->hasUnsignedIntegerRepresentation();

0 commit comments

Comments
 (0)