Skip to content

Commit 97de899

Browse files
committed
implement wraps attribute
Signed-off-by: Justin Stitt <[email protected]>
1 parent 389f339 commit 97de899

20 files changed

+375
-26
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,16 @@ Attribute Changes in Clang
209209
- ``[[clang::lifetimebound]]`` is now explicitly disallowed on explicit object member functions
210210
where they were previously silently ignored.
211211

212+
- Introduced ``__attribute((wraps))__`` which can be added to type or variable
213+
declarations. Using an attributed type or variable in an arithmetic
214+
expression will define the overflow behavior for that expression as having
215+
two's complement wrap-around. These expressions cannot trigger integer
216+
overflow warnings or sanitizer warnings. They also cannot be optimized away
217+
by some eager UB optimizations.
218+
219+
This attribute is only valid for C, as there are built-in language
220+
alternatives for other languages.
221+
212222
Improvements to Clang's diagnostics
213223
-----------------------------------
214224

clang/include/clang/AST/Expr.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4095,6 +4095,9 @@ class BinaryOperator : public Expr {
40954095
return getFPFeaturesInEffect(LO).getAllowFEnvAccess();
40964096
}
40974097

4098+
/// Does one of the subexpressions have the wraps attribute?
4099+
bool hasWrappingOperand(const ASTContext &Ctx) const;
4100+
40984101
protected:
40994102
BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs, Opcode opc,
41004103
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
@@ -1453,6 +1453,8 @@ class QualType {
14531453
return getQualifiers().hasStrongOrWeakObjCLifetime();
14541454
}
14551455

1456+
bool hasWrapsAttr() const;
1457+
14561458
// true when Type is objc's weak and weak is enabled but ARC isn't.
14571459
bool isNonWeakInMRRWithObjCWeak(const ASTContext &Context) const;
14581460

clang/include/clang/Basic/Attr.td

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4736,3 +4736,10 @@ def ClspvLibclcBuiltin: InheritableAttr {
47364736
let Documentation = [ClspvLibclcBuiltinDoc];
47374737
let SimpleHandler = 1;
47384738
}
4739+
4740+
def Wraps : DeclOrTypeAttr {
4741+
let Spellings = [Clang<"wraps">];
4742+
let Subjects = SubjectList<[Var, TypedefName, Field]>;
4743+
let Documentation = [WrapsDocs];
4744+
let LangOpts = [COnly];
4745+
}

clang/include/clang/Basic/AttrDocs.td

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8333,3 +8333,71 @@ of ``nonallocating`` by the compiler.
83338333
}];
83348334
}
83358335

8336+
def WrapsDocs : Documentation {
8337+
let Category = DocCatField;
8338+
let Content = [{
8339+
This attribute can be used with type or variable declarations to denote that
8340+
arithmetic containing these marked components have defined overflow behavior.
8341+
Specifically, the behavior is defined as being consistent with two's complement
8342+
wrap-around. For the purposes of sanitizers or warnings that concern themselves
8343+
with the definedness of integer arithmetic, they will cease to instrument or
8344+
warn about arithmetic that directly involves a "wrapping" component.
8345+
8346+
For example, ``-fsanitize=signed-integer-overflow`` or ``-Winteger-overflow``
8347+
will not warn about suspicious overflowing arithmetic -- assuming correct usage
8348+
of the wraps attribute.
8349+
8350+
This example shows some basic usage of ``__attribute__((wraps))`` on a type
8351+
definition when building with ``-fsanitize=signed-integer-overflow``
8352+
8353+
.. code-block:: c
8354+
8355+
typedef int __attribute__((wraps)) wrapping_int;
8356+
8357+
void foo() {
8358+
wrapping_int a = INT_MAX;
8359+
++a; // no sanitizer warning
8360+
}
8361+
8362+
int main() { foo(); }
8363+
8364+
In the following example, we use ``__attribute__((wraps))`` on a variable to
8365+
disable overflow instrumentation for arithmetic expressions it appears in. We
8366+
do so with a popular overflow-checking pattern which we might not want to trip
8367+
sanitizers (like ``-fsanitize=unsigned-integer-overflow``).
8368+
8369+
.. code-block:: c
8370+
8371+
void foo(int offset) {
8372+
unsigned int A __attribute__((wraps)) = UINT_MAX;
8373+
8374+
// check for overflow using a common pattern, however we may accidentally
8375+
// perform a real overflow thus triggering sanitizers to step in. Since "A"
8376+
// is "wrapping", we can avoid sanitizer warnings.
8377+
if (A + offset < A) {
8378+
// handle overflow manually
8379+
// ...
8380+
return;
8381+
}
8382+
8383+
// now, handle non-overflow case ...
8384+
}
8385+
8386+
The above example demonstrates some of the power and elegance this attribute
8387+
provides. We can use code patterns we are already familiar with (like ``if (x +
8388+
y < x)``) while gaining control over the overflow behavior on a case-by-case
8389+
basis.
8390+
8391+
When combined with ``-fwrapv``, this attribute can still be applied as normal
8392+
but has no function apart from annotating types and variables for readers. This
8393+
is because ``-fwrapv`` defines all arithmetic as being "wrapping", rendering
8394+
this attribute's efforts redundant.
8395+
8396+
When using this attribute without ``-fwrapv`` and without any sanitizers, it
8397+
still has an impact on the definedness of arithmetic expressions containing
8398+
wrapping components. Since the behavior of said expressions is now technically
8399+
defined, the compiler will forgo some eager optimizations that are used on
8400+
expressions containing UB.
8401+
}];
8402+
}
8403+

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1570,3 +1570,9 @@ def ExtractAPIMisuse : DiagGroup<"extractapi-misuse">;
15701570
// Warnings about using the non-standard extension having an explicit specialization
15711571
// with a storage class specifier.
15721572
def ExplicitSpecializationStorageClass : DiagGroup<"explicit-specialization-storage-class">;
1573+
1574+
// Warnings regarding the usage of __attribute__((wraps)) on non-integer types.
1575+
def UselessWrapsAttr : DiagGroup<"useless-wraps-attribute">;
1576+
1577+
// Warnings about the wraps attribute getting implicitly discarded
1578+
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
@@ -6633,6 +6633,13 @@ def warn_counted_by_attr_elt_type_unknown_size :
66336633
Warning<err_counted_by_attr_pointee_unknown_size.Summary>,
66346634
InGroup<BoundsSafetyCountedByEltTyUnknownSize>;
66356635

6636+
def warn_wraps_attr_var_decl_type_not_integer : Warning<
6637+
"using attribute 'wraps' with non-integer type '%0' has no function">,
6638+
InGroup<UselessWrapsAttr>;
6639+
def warn_wraps_attr_maybe_lost : Warning<
6640+
"'wraps' attribute may be implicitly discarded when converted to %0">,
6641+
InGroup<ImpDiscardedWrapsAttr>;
6642+
66366643
let CategoryName = "ARC Semantic Issue" in {
66376644

66386645
// 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,
@@ -4828,6 +4833,8 @@ BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
48284833
if (hasStoredFPFeatures())
48294834
setStoredFPFeatures(FPFeatures);
48304835
setDependence(computeDependence(this));
4836+
if (hasWrappingOperand(Ctx))
4837+
setType(Ctx.getAttributedType(attr::Wraps, getType(), getType()));
48314838
}
48324839

48334840
BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
@@ -4846,6 +4853,8 @@ BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
48464853
if (hasStoredFPFeatures())
48474854
setStoredFPFeatures(FPFeatures);
48484855
setDependence(computeDependence(this));
4856+
if (hasWrappingOperand(Ctx))
4857+
setType(Ctx.getAttributedType(attr::Wraps, getType(), getType()));
48494858
}
48504859

48514860
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
@@ -2805,7 +2805,7 @@ static bool CheckedIntArithmetic(EvalInfo &Info, const Expr *E,
28052805
APSInt Value(Op(LHS.extend(BitWidth), RHS.extend(BitWidth)), false);
28062806
Result = Value.trunc(LHS.getBitWidth());
28072807
if (Result.extend(BitWidth) != Value) {
2808-
if (Info.checkingForUndefinedBehavior())
2808+
if (Info.checkingForUndefinedBehavior() && !E->getType().hasWrapsAttr())
28092809
Info.Ctx.getDiagnostics().Report(E->getExprLoc(),
28102810
diag::warn_integer_constant_overflow)
28112811
<< toString(Result, 10, Result.isSigned(), /*formatAsCLiteral=*/false,
@@ -14370,7 +14370,7 @@ bool IntExprEvaluator::VisitUnaryOperator(const UnaryOperator *E) {
1437014370
if (!Result.isInt()) return Error(E);
1437114371
const APSInt &Value = Result.getInt();
1437214372
if (Value.isSigned() && Value.isMinSignedValue() && E->canOverflow()) {
14373-
if (Info.checkingForUndefinedBehavior())
14373+
if (Info.checkingForUndefinedBehavior() && !E->getType().hasWrapsAttr())
1437414374
Info.Ctx.getDiagnostics().Report(E->getExprLoc(),
1437514375
diag::warn_integer_constant_overflow)
1437614376
<< 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
@@ -2850,6 +2850,10 @@ bool QualType::isWebAssemblyFuncrefType() const {
28502850
getAddressSpace() == LangAS::wasm_funcref;
28512851
}
28522852

2853+
bool QualType::hasWrapsAttr() const {
2854+
return !isNull() && getTypePtr()->hasAttr(attr::Wraps);
2855+
}
2856+
28532857
QualType::PrimitiveDefaultInitializeKind
28542858
QualType::isNonTrivialToPrimitiveDefaultInitialize() const {
28552859
if (const auto *RT =

clang/lib/AST/TypePrinter.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2004,6 +2004,9 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
20042004
case attr::AArch64SVEPcs: OS << "aarch64_sve_pcs"; break;
20052005
case attr::AMDGPUKernelCall: OS << "amdgpu_kernel"; break;
20062006
case attr::IntelOclBicc: OS << "inteloclbicc"; break;
2007+
case attr::Wraps:
2008+
OS << "wraps";
2009+
break;
20072010
case attr::PreserveMost:
20082011
OS << "preserve_most";
20092012
break;

clang/lib/CodeGen/CGExprScalar.cpp

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

162166
static bool MustVisitNullValue(const Expr *E) {
@@ -749,7 +753,8 @@ class ScalarExprEmitter
749753

750754
// Binary Operators.
751755
Value *EmitMul(const BinOpInfo &Ops) {
752-
if (Ops.Ty->isSignedIntegerOrEnumerationType()) {
756+
if (Ops.Ty->isSignedIntegerOrEnumerationType() &&
757+
!Ops.hasWrappingOperand()) {
753758
switch (CGF.getLangOpts().getSignedOverflowBehavior()) {
754759
case LangOptions::SOB_Defined:
755760
if (!CGF.SanOpts.has(SanitizerKind::SignedIntegerOverflow))
@@ -785,7 +790,8 @@ class ScalarExprEmitter
785790

786791
if (Ops.Ty->isUnsignedIntegerType() &&
787792
CGF.SanOpts.has(SanitizerKind::UnsignedIntegerOverflow) &&
788-
!CanElideOverflowCheck(CGF.getContext(), Ops))
793+
!CanElideOverflowCheck(CGF.getContext(), Ops) &&
794+
!Ops.hasWrappingOperand())
789795
return EmitOverflowCheckedBinOp(Ops);
790796

791797
if (Ops.LHS->getType()->isFPOrFPVectorTy()) {
@@ -1117,7 +1123,7 @@ void ScalarExprEmitter::EmitIntegerTruncationCheck(Value *Src, QualType SrcType,
11171123
// If the comparison result is 'i1 false', then the truncation was lossy.
11181124

11191125
// Do we care about this type of truncation?
1120-
if (!CGF.SanOpts.has(Check.second.second))
1126+
if (!CGF.SanOpts.has(Check.second.second) || DstType.hasWrapsAttr())
11211127
return;
11221128

11231129
llvm::Constant *StaticArgs[] = {
@@ -1350,6 +1356,14 @@ void CodeGenFunction::EmitBitfieldConversionCheck(Value *Src, QualType SrcType,
13501356
bool SrcSigned = SrcType->isSignedIntegerOrEnumerationType();
13511357
bool DstSigned = DstType->isSignedIntegerOrEnumerationType();
13521358

1359+
bool SrcWraps = SrcType.hasWrapsAttr();
1360+
bool DstWraps = DstType.hasWrapsAttr();
1361+
1362+
// The wraps attribute should silence any sanitizer warnings
1363+
// regarding truncation or overflow
1364+
if (SrcWraps || DstWraps)
1365+
return;
1366+
13531367
CodeGenFunction::SanitizerScope SanScope(this);
13541368

13551369
std::pair<ScalarExprEmitter::ImplicitConversionCheckKind,
@@ -2913,6 +2927,9 @@ ScalarExprEmitter::EmitScalarPrePostIncDec(const UnaryOperator *E, LValue LV,
29132927
bool excludeOverflowPattern =
29142928
matchesPostDecrInWhile(E, isInc, isPre, CGF.getContext());
29152929

2930+
BinOpInfo Ops = createBinOpInfoFromIncDec(
2931+
E, value, isInc, E->getFPFeaturesInEffect(CGF.getLangOpts()));
2932+
29162933
if (CGF.getContext().isPromotableIntegerType(type)) {
29172934
promotedType = CGF.getContext().getPromotedIntegerType(type);
29182935
assert(promotedType != type && "Shouldn't promote to the same type.");
@@ -2969,11 +2986,12 @@ ScalarExprEmitter::EmitScalarPrePostIncDec(const UnaryOperator *E, LValue LV,
29692986
// Note that signed integer inc/dec with width less than int can't
29702987
// overflow because of promotion rules; we're just eliding a few steps
29712988
// here.
2972-
} else if (E->canOverflow() && type->isSignedIntegerOrEnumerationType()) {
2989+
} else if (E->canOverflow() && type->isSignedIntegerOrEnumerationType() &&
2990+
!Ops.hasWrappingOperand()) {
29732991
value = EmitIncDecConsiderOverflowBehavior(E, value, isInc);
29742992
} else if (E->canOverflow() && type->isUnsignedIntegerType() &&
29752993
CGF.SanOpts.has(SanitizerKind::UnsignedIntegerOverflow) &&
2976-
!excludeOverflowPattern) {
2994+
!Ops.hasWrappingOperand() && !excludeOverflowPattern) {
29772995
value = EmitOverflowCheckedBinOp(createBinOpInfoFromIncDec(
29782996
E, value, isInc, E->getFPFeaturesInEffect(CGF.getLangOpts())));
29792997
} else {
@@ -3762,7 +3780,8 @@ Value *ScalarExprEmitter::EmitDiv(const BinOpInfo &Ops) {
37623780
if ((CGF.SanOpts.has(SanitizerKind::IntegerDivideByZero) ||
37633781
CGF.SanOpts.has(SanitizerKind::SignedIntegerOverflow)) &&
37643782
Ops.Ty->isIntegerType() &&
3765-
(Ops.mayHaveIntegerDivisionByZero() || Ops.mayHaveIntegerOverflow())) {
3783+
(Ops.mayHaveIntegerDivisionByZero() || Ops.mayHaveIntegerOverflow()) &&
3784+
!Ops.hasWrappingOperand()) {
37663785
llvm::Value *Zero = llvm::Constant::getNullValue(ConvertType(Ops.Ty));
37673786
EmitUndefinedBehaviorIntegerDivAndRemCheck(Ops, Zero, true);
37683787
} else if (CGF.SanOpts.has(SanitizerKind::FloatDivideByZero) &&
@@ -3811,7 +3830,8 @@ Value *ScalarExprEmitter::EmitRem(const BinOpInfo &Ops) {
38113830
if ((CGF.SanOpts.has(SanitizerKind::IntegerDivideByZero) ||
38123831
CGF.SanOpts.has(SanitizerKind::SignedIntegerOverflow)) &&
38133832
Ops.Ty->isIntegerType() &&
3814-
(Ops.mayHaveIntegerDivisionByZero() || Ops.mayHaveIntegerOverflow())) {
3833+
(Ops.mayHaveIntegerDivisionByZero() || Ops.mayHaveIntegerOverflow()) &&
3834+
!Ops.hasWrappingOperand()) {
38153835
CodeGenFunction::SanitizerScope SanScope(&CGF);
38163836
llvm::Value *Zero = llvm::Constant::getNullValue(ConvertType(Ops.Ty));
38173837
EmitUndefinedBehaviorIntegerDivAndRemCheck(Ops, Zero, false);
@@ -4176,7 +4196,7 @@ Value *ScalarExprEmitter::EmitAdd(const BinOpInfo &op) {
41764196
op.RHS->getType()->isPointerTy())
41774197
return emitPointerArithmetic(CGF, op, CodeGenFunction::NotSubtraction);
41784198

4179-
if (op.Ty->isSignedIntegerOrEnumerationType()) {
4199+
if (op.Ty->isSignedIntegerOrEnumerationType() && !op.hasWrappingOperand()) {
41804200
switch (CGF.getLangOpts().getSignedOverflowBehavior()) {
41814201
case LangOptions::SOB_Defined:
41824202
if (!CGF.SanOpts.has(SanitizerKind::SignedIntegerOverflow))
@@ -4209,7 +4229,7 @@ Value *ScalarExprEmitter::EmitAdd(const BinOpInfo &op) {
42094229

42104230
if (op.Ty->isUnsignedIntegerType() &&
42114231
CGF.SanOpts.has(SanitizerKind::UnsignedIntegerOverflow) &&
4212-
!CanElideOverflowCheck(CGF.getContext(), op))
4232+
!CanElideOverflowCheck(CGF.getContext(), op) && !op.hasWrappingOperand())
42134233
return EmitOverflowCheckedBinOp(op);
42144234

42154235
if (op.LHS->getType()->isFPOrFPVectorTy()) {
@@ -4332,7 +4352,7 @@ Value *ScalarExprEmitter::EmitFixedPointBinOp(const BinOpInfo &op) {
43324352
Value *ScalarExprEmitter::EmitSub(const BinOpInfo &op) {
43334353
// The LHS is always a pointer if either side is.
43344354
if (!op.LHS->getType()->isPointerTy()) {
4335-
if (op.Ty->isSignedIntegerOrEnumerationType()) {
4355+
if (op.Ty->isSignedIntegerOrEnumerationType() && !op.hasWrappingOperand()) {
43364356
switch (CGF.getLangOpts().getSignedOverflowBehavior()) {
43374357
case LangOptions::SOB_Defined:
43384358
if (!CGF.SanOpts.has(SanitizerKind::SignedIntegerOverflow))
@@ -4365,7 +4385,8 @@ Value *ScalarExprEmitter::EmitSub(const BinOpInfo &op) {
43654385

43664386
if (op.Ty->isUnsignedIntegerType() &&
43674387
CGF.SanOpts.has(SanitizerKind::UnsignedIntegerOverflow) &&
4368-
!CanElideOverflowCheck(CGF.getContext(), op))
4388+
!CanElideOverflowCheck(CGF.getContext(), op) &&
4389+
!op.hasWrappingOperand())
43694390
return EmitOverflowCheckedBinOp(op);
43704391

43714392
if (op.LHS->getType()->isFPOrFPVectorTy()) {
@@ -4485,7 +4506,8 @@ Value *ScalarExprEmitter::EmitShl(const BinOpInfo &Ops) {
44854506
bool SanitizeSignedBase = CGF.SanOpts.has(SanitizerKind::ShiftBase) &&
44864507
Ops.Ty->hasSignedIntegerRepresentation() &&
44874508
!CGF.getLangOpts().isSignedOverflowDefined() &&
4488-
!CGF.getLangOpts().CPlusPlus20;
4509+
!CGF.getLangOpts().CPlusPlus20 &&
4510+
!Ops.hasWrappingOperand();
44894511
bool SanitizeUnsignedBase =
44904512
CGF.SanOpts.has(SanitizerKind::UnsignedShiftBase) &&
44914513
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)