Skip to content

Commit af16c49

Browse files
committed
implement wraps attribute
Signed-off-by: Justin Stitt <[email protected]>
1 parent 954b692 commit af16c49

20 files changed

+376
-26
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,16 @@ Attribute Changes in Clang
525525
The attributes declare constraints about a function's behavior pertaining to blocking and
526526
heap memory allocation.
527527

528+
- Introduced ``__attribute((wraps))__`` which can be added to type or variable
529+
declarations. Using an attributed type or variable in an arithmetic
530+
expression will define the overflow behavior for that expression as having
531+
two's complement wrap-around. These expressions cannot trigger integer
532+
overflow warnings or sanitizer warnings. They also cannot be optimized away
533+
by some eager UB optimizations.
534+
535+
This attribute is only valid for C, as there are built-in language
536+
alternatives for other languages.
537+
528538
Improvements to Clang's diagnostics
529539
-----------------------------------
530540
- 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
@@ -4061,6 +4061,9 @@ class BinaryOperator : public Expr {
40614061
return getFPFeaturesInEffect(LO).getAllowFEnvAccess();
40624062
}
40634063

4064+
/// Does one of the subexpressions have the wraps attribute?
4065+
bool hasWrappingOperand(const ASTContext &Ctx) const;
4066+
40644067
protected:
40654068
BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs, Opcode opc,
40664069
QualType ResTy, ExprValueKind VK, ExprObjectKind OK,

clang/include/clang/AST/Type.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1457,6 +1457,8 @@ class QualType {
14571457
return getQualifiers().hasStrongOrWeakObjCLifetime();
14581458
}
14591459

1460+
bool hasWrapsAttr() const;
1461+
14601462
// true when Type is objc's weak and weak is enabled but ARC isn't.
14611463
bool isNonWeakInMRRWithObjCWeak(const ASTContext &Context) const;
14621464

clang/include/clang/Basic/Attr.td

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4630,3 +4630,10 @@ def ClspvLibclcBuiltin: InheritableAttr {
46304630
let Documentation = [ClspvLibclcBuiltinDoc];
46314631
let SimpleHandler = 1;
46324632
}
4633+
4634+
def Wraps : DeclOrTypeAttr {
4635+
let Spellings = [Clang<"wraps">];
4636+
let Subjects = SubjectList<[Var, TypedefName, Field]>;
4637+
let Documentation = [WrapsDocs];
4638+
let LangOpts = [COnly];
4639+
}

clang/include/clang/Basic/AttrDocs.td

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8176,3 +8176,71 @@ of ``nonallocating`` by the compiler.
81768176
}];
81778177
}
81788178

8179+
def WrapsDocs : Documentation {
8180+
let Category = DocCatField;
8181+
let Content = [{
8182+
This attribute can be used with type or variable declarations to denote that
8183+
arithmetic containing these marked components have defined overflow behavior.
8184+
Specifically, the behavior is defined as being consistent with two's complement
8185+
wrap-around. For the purposes of sanitizers or warnings that concern themselves
8186+
with the definedness of integer arithmetic, they will cease to instrument or
8187+
warn about arithmetic that directly involves a "wrapping" component.
8188+
8189+
For example, ``-fsanitize=signed-integer-overflow`` or ``-Winteger-overflow``
8190+
will not warn about suspicious overflowing arithmetic -- assuming correct usage
8191+
of the wraps attribute.
8192+
8193+
This example shows some basic usage of ``__attribute__((wraps))`` on a type
8194+
definition when building with ``-fsanitize=signed-integer-overflow``
8195+
8196+
.. code-block:: c
8197+
8198+
typedef int __attribute__((wraps)) wrapping_int;
8199+
8200+
void foo() {
8201+
wrapping_int a = INT_MAX;
8202+
++a; // no sanitizer warning
8203+
}
8204+
8205+
int main() { foo(); }
8206+
8207+
In the following example, we use ``__attribute__((wraps))`` on a variable to
8208+
disable overflow instrumentation for arithmetic expressions it appears in. We
8209+
do so with a popular overflow-checking pattern which we might not want to trip
8210+
sanitizers (like ``-fsanitize=unsigned-integer-overflow``).
8211+
8212+
.. code-block:: c
8213+
8214+
void foo(int offset) {
8215+
unsigned int A __attribute__((wraps)) = UINT_MAX;
8216+
8217+
// check for overflow using a common pattern, however we may accidentally
8218+
// perform a real overflow thus triggering sanitizers to step in. Since "A"
8219+
// is "wrapping", we can avoid sanitizer warnings.
8220+
if (A + offset < A) {
8221+
// handle overflow manually
8222+
// ...
8223+
return;
8224+
}
8225+
8226+
// now, handle non-overflow case ...
8227+
}
8228+
8229+
The above example demonstrates some of the power and elegance this attribute
8230+
provides. We can use code patterns we are already familiar with (like ``if (x +
8231+
y < x)``) while gaining control over the overflow behavior on a case-by-case
8232+
basis.
8233+
8234+
When combined with ``-fwrapv``, this attribute can still be applied as normal
8235+
but has no function apart from annotating types and variables for readers. This
8236+
is because ``-fwrapv`` defines all arithmetic as being "wrapping", rendering
8237+
this attribute's efforts redundant.
8238+
8239+
When using this attribute without ``-fwrapv`` and without any sanitizers, it
8240+
still has an impact on the definedness of arithmetic expressions containing
8241+
wrapping components. Since the behavior of said expressions is now technically
8242+
defined, the compiler will forgo some eager optimizations that are used on
8243+
expressions containing UB.
8244+
}];
8245+
}
8246+

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1539,3 +1539,9 @@ def BitIntExtension : DiagGroup<"bit-int-extension">;
15391539

15401540
// Warnings about misuse of ExtractAPI options.
15411541
def ExtractAPIMisuse : DiagGroup<"extractapi-misuse">;
1542+
1543+
// Warnings regarding the usage of __attribute__((wraps)) on non-integer types.
1544+
def UselessWrapsAttr : DiagGroup<"useless-wraps-attribute">;
1545+
1546+
// Warnings about the wraps attribute getting implicitly discarded
1547+
def ImpDiscardedWrapsAttr : DiagGroup<"implicitly-discarded-wraps-attribute">;

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6601,6 +6601,13 @@ def warn_counted_by_attr_elt_type_unknown_size :
66016601
Warning<err_counted_by_attr_pointee_unknown_size.Summary>,
66026602
InGroup<BoundsSafetyCountedByEltTyUnknownSize>;
66036603

6604+
def warn_wraps_attr_var_decl_type_not_integer : Warning<
6605+
"using attribute 'wraps' with non-integer type '%0' has no function">,
6606+
InGroup<UselessWrapsAttr>;
6607+
def warn_wraps_attr_maybe_lost : Warning<
6608+
"'wraps' attribute may be implicitly discarded when converted to %0">,
6609+
InGroup<ImpDiscardedWrapsAttr>;
6610+
66046611
let CategoryName = "ARC Semantic Issue" in {
66056612

66066613
// ARC-mode diagnostics.

clang/lib/AST/Expr.cpp

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

2240+
bool BinaryOperator::hasWrappingOperand(const ASTContext &Ctx) const {
2241+
return getLHS()->getType().hasWrapsAttr() ||
2242+
getRHS()->getType().hasWrapsAttr();
2243+
}
2244+
22402245
SourceLocExpr::SourceLocExpr(const ASTContext &Ctx, SourceLocIdentKind Kind,
22412246
QualType ResultTy, SourceLocation BLoc,
22422247
SourceLocation RParenLoc,
@@ -4774,6 +4779,8 @@ BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
47744779
if (hasStoredFPFeatures())
47754780
setStoredFPFeatures(FPFeatures);
47764781
setDependence(computeDependence(this));
4782+
if (hasWrappingOperand(Ctx))
4783+
setType(Ctx.getAttributedType(attr::Wraps, getType(), getType()));
47774784
}
47784785

47794786
BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
@@ -4791,6 +4798,8 @@ BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
47914798
if (hasStoredFPFeatures())
47924799
setStoredFPFeatures(FPFeatures);
47934800
setDependence(computeDependence(this));
4801+
if (hasWrappingOperand(Ctx))
4802+
setType(Ctx.getAttributedType(attr::Wraps, getType(), getType()));
47944803
}
47954804

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

clang/lib/AST/ExprConstant.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2781,7 +2781,7 @@ static bool CheckedIntArithmetic(EvalInfo &Info, const Expr *E,
27812781
APSInt Value(Op(LHS.extend(BitWidth), RHS.extend(BitWidth)), false);
27822782
Result = Value.trunc(LHS.getBitWidth());
27832783
if (Result.extend(BitWidth) != Value) {
2784-
if (Info.checkingForUndefinedBehavior())
2784+
if (Info.checkingForUndefinedBehavior() && !E->getType().hasWrapsAttr())
27852785
Info.Ctx.getDiagnostics().Report(E->getExprLoc(),
27862786
diag::warn_integer_constant_overflow)
27872787
<< toString(Result, 10, Result.isSigned(), /*formatAsCLiteral=*/false,
@@ -14176,7 +14176,7 @@ bool IntExprEvaluator::VisitUnaryOperator(const UnaryOperator *E) {
1417614176
if (!Result.isInt()) return Error(E);
1417714177
const APSInt &Value = Result.getInt();
1417814178
if (Value.isSigned() && Value.isMinSignedValue() && E->canOverflow()) {
14179-
if (Info.checkingForUndefinedBehavior())
14179+
if (Info.checkingForUndefinedBehavior() && !E->getType().hasWrapsAttr())
1418014180
Info.Ctx.getDiagnostics().Report(E->getExprLoc(),
1418114181
diag::warn_integer_constant_overflow)
1418214182
<< toString(Value, 10, Value.isSigned(), /*formatAsCLiteral=*/false,

clang/lib/AST/Type.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2875,6 +2875,10 @@ bool QualType::isTriviallyEqualityComparableType(
28752875
CanonicalType, /*CheckIfTriviallyCopyable=*/false);
28762876
}
28772877

2878+
bool QualType::hasWrapsAttr() const {
2879+
return !isNull() && getTypePtr()->hasAttr(attr::Wraps);
2880+
}
2881+
28782882
bool QualType::isNonWeakInMRRWithObjCWeak(const ASTContext &Context) const {
28792883
return !Context.getLangOpts().ObjCAutoRefCount &&
28802884
Context.getLangOpts().ObjCWeak &&

clang/lib/AST/TypePrinter.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1995,6 +1995,9 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
19951995
case attr::AArch64SVEPcs: OS << "aarch64_sve_pcs"; break;
19961996
case attr::AMDGPUKernelCall: OS << "amdgpu_kernel"; break;
19971997
case attr::IntelOclBicc: OS << "inteloclbicc"; break;
1998+
case attr::Wraps:
1999+
OS << "wraps";
2000+
break;
19982001
case attr::PreserveMost:
19992002
OS << "preserve_most";
20002003
break;

clang/lib/CodeGen/CGExprScalar.cpp

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,10 @@ struct BinOpInfo {
156156
}
157157
return false;
158158
}
159+
160+
/// Does the BinaryOperator have the wraps attribute?
161+
/// If so, we can elide overflow sanitizer checks.
162+
bool hasWrappingOperand() const { return E->getType().hasWrapsAttr(); }
159163
};
160164

161165
static bool MustVisitNullValue(const Expr *E) {
@@ -736,7 +740,8 @@ class ScalarExprEmitter
736740

737741
// Binary Operators.
738742
Value *EmitMul(const BinOpInfo &Ops) {
739-
if (Ops.Ty->isSignedIntegerOrEnumerationType()) {
743+
if (Ops.Ty->isSignedIntegerOrEnumerationType() &&
744+
!Ops.hasWrappingOperand()) {
740745
switch (CGF.getLangOpts().getSignedOverflowBehavior()) {
741746
case LangOptions::SOB_Defined:
742747
if (!CGF.SanOpts.has(SanitizerKind::SignedIntegerOverflow))
@@ -772,7 +777,8 @@ class ScalarExprEmitter
772777

773778
if (Ops.Ty->isUnsignedIntegerType() &&
774779
CGF.SanOpts.has(SanitizerKind::UnsignedIntegerOverflow) &&
775-
!CanElideOverflowCheck(CGF.getContext(), Ops))
780+
!CanElideOverflowCheck(CGF.getContext(), Ops) &&
781+
!Ops.hasWrappingOperand())
776782
return EmitOverflowCheckedBinOp(Ops);
777783

778784
if (Ops.LHS->getType()->isFPOrFPVectorTy()) {
@@ -1104,7 +1110,7 @@ void ScalarExprEmitter::EmitIntegerTruncationCheck(Value *Src, QualType SrcType,
11041110
// If the comparison result is 'i1 false', then the truncation was lossy.
11051111

11061112
// Do we care about this type of truncation?
1107-
if (!CGF.SanOpts.has(Check.second.second))
1113+
if (!CGF.SanOpts.has(Check.second.second) || DstType.hasWrapsAttr())
11081114
return;
11091115

11101116
llvm::Constant *StaticArgs[] = {
@@ -1337,6 +1343,14 @@ void CodeGenFunction::EmitBitfieldConversionCheck(Value *Src, QualType SrcType,
13371343
bool SrcSigned = SrcType->isSignedIntegerOrEnumerationType();
13381344
bool DstSigned = DstType->isSignedIntegerOrEnumerationType();
13391345

1346+
bool SrcWraps = SrcType.hasWrapsAttr();
1347+
bool DstWraps = DstType.hasWrapsAttr();
1348+
1349+
// The wraps attribute should silence any sanitizer warnings
1350+
// regarding truncation or overflow
1351+
if (SrcWraps || DstWraps)
1352+
return;
1353+
13401354
CodeGenFunction::SanitizerScope SanScope(this);
13411355

13421356
std::pair<ScalarExprEmitter::ImplicitConversionCheckKind,
@@ -2870,6 +2884,9 @@ ScalarExprEmitter::EmitScalarPrePostIncDec(const UnaryOperator *E, LValue LV,
28702884
} else if (type->isIntegerType()) {
28712885
QualType promotedType;
28722886
bool canPerformLossyDemotionCheck = false;
2887+
BinOpInfo Ops = createBinOpInfoFromIncDec(
2888+
E, value, isInc, E->getFPFeaturesInEffect(CGF.getLangOpts()));
2889+
28732890
if (CGF.getContext().isPromotableIntegerType(type)) {
28742891
promotedType = CGF.getContext().getPromotedIntegerType(type);
28752892
assert(promotedType != type && "Shouldn't promote to the same type.");
@@ -2926,10 +2943,12 @@ ScalarExprEmitter::EmitScalarPrePostIncDec(const UnaryOperator *E, LValue LV,
29262943
// Note that signed integer inc/dec with width less than int can't
29272944
// overflow because of promotion rules; we're just eliding a few steps
29282945
// here.
2929-
} else if (E->canOverflow() && type->isSignedIntegerOrEnumerationType()) {
2946+
} else if (E->canOverflow() && type->isSignedIntegerOrEnumerationType() &&
2947+
!Ops.hasWrappingOperand()) {
29302948
value = EmitIncDecConsiderOverflowBehavior(E, value, isInc);
29312949
} else if (E->canOverflow() && type->isUnsignedIntegerType() &&
2932-
CGF.SanOpts.has(SanitizerKind::UnsignedIntegerOverflow)) {
2950+
CGF.SanOpts.has(SanitizerKind::UnsignedIntegerOverflow) &&
2951+
!Ops.hasWrappingOperand()) {
29332952
value = EmitOverflowCheckedBinOp(createBinOpInfoFromIncDec(
29342953
E, value, isInc, E->getFPFeaturesInEffect(CGF.getLangOpts())));
29352954
} else {
@@ -3718,7 +3737,8 @@ Value *ScalarExprEmitter::EmitDiv(const BinOpInfo &Ops) {
37183737
if ((CGF.SanOpts.has(SanitizerKind::IntegerDivideByZero) ||
37193738
CGF.SanOpts.has(SanitizerKind::SignedIntegerOverflow)) &&
37203739
Ops.Ty->isIntegerType() &&
3721-
(Ops.mayHaveIntegerDivisionByZero() || Ops.mayHaveIntegerOverflow())) {
3740+
(Ops.mayHaveIntegerDivisionByZero() || Ops.mayHaveIntegerOverflow()) &&
3741+
!Ops.hasWrappingOperand()) {
37223742
llvm::Value *Zero = llvm::Constant::getNullValue(ConvertType(Ops.Ty));
37233743
EmitUndefinedBehaviorIntegerDivAndRemCheck(Ops, Zero, true);
37243744
} else if (CGF.SanOpts.has(SanitizerKind::FloatDivideByZero) &&
@@ -3767,7 +3787,8 @@ Value *ScalarExprEmitter::EmitRem(const BinOpInfo &Ops) {
37673787
if ((CGF.SanOpts.has(SanitizerKind::IntegerDivideByZero) ||
37683788
CGF.SanOpts.has(SanitizerKind::SignedIntegerOverflow)) &&
37693789
Ops.Ty->isIntegerType() &&
3770-
(Ops.mayHaveIntegerDivisionByZero() || Ops.mayHaveIntegerOverflow())) {
3790+
(Ops.mayHaveIntegerDivisionByZero() || Ops.mayHaveIntegerOverflow()) &&
3791+
!Ops.hasWrappingOperand()) {
37713792
CodeGenFunction::SanitizerScope SanScope(&CGF);
37723793
llvm::Value *Zero = llvm::Constant::getNullValue(ConvertType(Ops.Ty));
37733794
EmitUndefinedBehaviorIntegerDivAndRemCheck(Ops, Zero, false);
@@ -4132,7 +4153,7 @@ Value *ScalarExprEmitter::EmitAdd(const BinOpInfo &op) {
41324153
op.RHS->getType()->isPointerTy())
41334154
return emitPointerArithmetic(CGF, op, CodeGenFunction::NotSubtraction);
41344155

4135-
if (op.Ty->isSignedIntegerOrEnumerationType()) {
4156+
if (op.Ty->isSignedIntegerOrEnumerationType() && !op.hasWrappingOperand()) {
41364157
switch (CGF.getLangOpts().getSignedOverflowBehavior()) {
41374158
case LangOptions::SOB_Defined:
41384159
if (!CGF.SanOpts.has(SanitizerKind::SignedIntegerOverflow))
@@ -4165,7 +4186,7 @@ Value *ScalarExprEmitter::EmitAdd(const BinOpInfo &op) {
41654186

41664187
if (op.Ty->isUnsignedIntegerType() &&
41674188
CGF.SanOpts.has(SanitizerKind::UnsignedIntegerOverflow) &&
4168-
!CanElideOverflowCheck(CGF.getContext(), op))
4189+
!CanElideOverflowCheck(CGF.getContext(), op) && !op.hasWrappingOperand())
41694190
return EmitOverflowCheckedBinOp(op);
41704191

41714192
if (op.LHS->getType()->isFPOrFPVectorTy()) {
@@ -4288,7 +4309,7 @@ Value *ScalarExprEmitter::EmitFixedPointBinOp(const BinOpInfo &op) {
42884309
Value *ScalarExprEmitter::EmitSub(const BinOpInfo &op) {
42894310
// The LHS is always a pointer if either side is.
42904311
if (!op.LHS->getType()->isPointerTy()) {
4291-
if (op.Ty->isSignedIntegerOrEnumerationType()) {
4312+
if (op.Ty->isSignedIntegerOrEnumerationType() && !op.hasWrappingOperand()) {
42924313
switch (CGF.getLangOpts().getSignedOverflowBehavior()) {
42934314
case LangOptions::SOB_Defined:
42944315
if (!CGF.SanOpts.has(SanitizerKind::SignedIntegerOverflow))
@@ -4321,7 +4342,8 @@ Value *ScalarExprEmitter::EmitSub(const BinOpInfo &op) {
43214342

43224343
if (op.Ty->isUnsignedIntegerType() &&
43234344
CGF.SanOpts.has(SanitizerKind::UnsignedIntegerOverflow) &&
4324-
!CanElideOverflowCheck(CGF.getContext(), op))
4345+
!CanElideOverflowCheck(CGF.getContext(), op) &&
4346+
!op.hasWrappingOperand())
43254347
return EmitOverflowCheckedBinOp(op);
43264348

43274349
if (op.LHS->getType()->isFPOrFPVectorTy()) {
@@ -4441,7 +4463,8 @@ Value *ScalarExprEmitter::EmitShl(const BinOpInfo &Ops) {
44414463
bool SanitizeSignedBase = CGF.SanOpts.has(SanitizerKind::ShiftBase) &&
44424464
Ops.Ty->hasSignedIntegerRepresentation() &&
44434465
!CGF.getLangOpts().isSignedOverflowDefined() &&
4444-
!CGF.getLangOpts().CPlusPlus20;
4466+
!CGF.getLangOpts().CPlusPlus20 &&
4467+
!Ops.hasWrappingOperand();
44454468
bool SanitizeUnsignedBase =
44464469
CGF.SanOpts.has(SanitizerKind::UnsignedShiftBase) &&
44474470
Ops.Ty->hasUnsignedIntegerRepresentation();

clang/lib/Sema/Sema.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,9 @@ ExprResult Sema::ImpCastExprToType(Expr *E, QualType Ty,
735735
QualType ExprTy = Context.getCanonicalType(E->getType());
736736
QualType TypeTy = Context.getCanonicalType(Ty);
737737

738+
if (E->getType().getTypePtr()->isIntegerType() && E->getType().hasWrapsAttr())
739+
Ty = Context.getAttributedType(attr::Wraps, Ty, Ty);
740+
738741
if (ExprTy == TypeTy)
739742
return E;
740743

0 commit comments

Comments
 (0)