Skip to content

Commit 6fb8f68

Browse files
committed
implement wraps attribute
Signed-off-by: Justin Stitt <[email protected]>
1 parent 8814b6d commit 6fb8f68

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
@@ -249,6 +249,16 @@ Attribute Changes in Clang
249249
- Introduced a new attribute ``[[clang::coro_await_elidable]]`` on coroutine return types
250250
to express elideability at call sites where the coroutine is co_awaited as a prvalue.
251251

252+
- Introduced ``__attribute((wraps))__`` which can be added to type or variable
253+
declarations. Using an attributed type or variable in an arithmetic
254+
expression will define the overflow behavior for that expression as having
255+
two's complement wrap-around. These expressions cannot trigger integer
256+
overflow warnings or sanitizer warnings. They also cannot be optimized away
257+
by some eager UB optimizations.
258+
259+
This attribute is only valid for C, as there are built-in language
260+
alternatives for other languages.
261+
252262
Improvements to Clang's diagnostics
253263
-----------------------------------
254264

clang/include/clang/AST/Expr.h

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

4101+
/// Does one of the subexpressions have the wraps attribute?
4102+
bool hasWrappingOperand(const ASTContext &Ctx) const;
4103+
41014104
protected:
41024105
BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs, Opcode opc,
41034106
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
@@ -1454,6 +1454,8 @@ class QualType {
14541454
return getQualifiers().hasStrongOrWeakObjCLifetime();
14551455
}
14561456

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

clang/include/clang/Basic/Attr.td

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4796,3 +4796,10 @@ def ClspvLibclcBuiltin: InheritableAttr {
47964796
let Documentation = [ClspvLibclcBuiltinDoc];
47974797
let SimpleHandler = 1;
47984798
}
4799+
4800+
def Wraps : DeclOrTypeAttr {
4801+
let Spellings = [Clang<"wraps">];
4802+
let Subjects = SubjectList<[Var, TypedefName, Field]>;
4803+
let Documentation = [WrapsDocs];
4804+
let LangOpts = [COnly];
4805+
}

clang/include/clang/Basic/AttrDocs.td

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8446,3 +8446,72 @@ Declares that a function potentially allocates heap memory, and prevents any pot
84468446
of ``nonallocating`` by the compiler.
84478447
}];
84488448
}
8449+
8450+
def WrapsDocs : Documentation {
8451+
let Category = DocCatField;
8452+
let Content = [{
8453+
This attribute can be used with type or variable declarations to denote that
8454+
arithmetic containing these marked components have defined overflow behavior.
8455+
Specifically, the behavior is defined as being consistent with two's complement
8456+
wrap-around. For the purposes of sanitizers or warnings that concern themselves
8457+
with the definedness of integer arithmetic, they will cease to instrument or
8458+
warn about arithmetic that directly involves a "wrapping" component.
8459+
8460+
For example, ``-fsanitize=signed-integer-overflow`` or ``-Winteger-overflow``
8461+
will not warn about suspicious overflowing arithmetic -- assuming correct usage
8462+
of the wraps attribute.
8463+
8464+
This example shows some basic usage of ``__attribute__((wraps))`` on a type
8465+
definition when building with ``-fsanitize=signed-integer-overflow``
8466+
8467+
.. code-block:: c
8468+
8469+
typedef int __attribute__((wraps)) wrapping_int;
8470+
8471+
void foo() {
8472+
wrapping_int a = INT_MAX;
8473+
++a; // no sanitizer warning
8474+
}
8475+
8476+
int main() { foo(); }
8477+
8478+
In the following example, we use ``__attribute__((wraps))`` on a variable to
8479+
disable overflow instrumentation for arithmetic expressions it appears in. We
8480+
do so with a popular overflow-checking pattern which we might not want to trip
8481+
sanitizers (like ``-fsanitize=unsigned-integer-overflow``).
8482+
8483+
.. code-block:: c
8484+
8485+
void foo(int offset) {
8486+
unsigned int A __attribute__((wraps)) = UINT_MAX;
8487+
8488+
// check for overflow using a common pattern, however we may accidentally
8489+
// perform a real overflow thus triggering sanitizers to step in. Since "A"
8490+
// is "wrapping", we can avoid sanitizer warnings.
8491+
if (A + offset < A) {
8492+
// handle overflow manually
8493+
// ...
8494+
return;
8495+
}
8496+
8497+
// now, handle non-overflow case ...
8498+
}
8499+
8500+
The above example demonstrates some of the power and elegance this attribute
8501+
provides. We can use code patterns we are already familiar with (like ``if (x +
8502+
y < x)``) while gaining control over the overflow behavior on a case-by-case
8503+
basis.
8504+
8505+
When combined with ``-fwrapv``, this attribute can still be applied as normal
8506+
but has no function apart from annotating types and variables for readers. This
8507+
is because ``-fwrapv`` defines all arithmetic as being "wrapping", rendering
8508+
this attribute's efforts redundant.
8509+
8510+
When using this attribute without ``-fwrapv`` and without any sanitizers, it
8511+
still has an impact on the definedness of arithmetic expressions containing
8512+
wrapping components. Since the behavior of said expressions is now technically
8513+
defined, the compiler will forgo some eager optimizations that are used on
8514+
expressions containing UB.
8515+
}];
8516+
}
8517+

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1577,3 +1577,9 @@ def ExtractAPIMisuse : DiagGroup<"extractapi-misuse">;
15771577
// Warnings about using the non-standard extension having an explicit specialization
15781578
// with a storage class specifier.
15791579
def ExplicitSpecializationStorageClass : DiagGroup<"explicit-specialization-storage-class">;
1580+
1581+
// Warnings regarding the usage of __attribute__((wraps)) on non-integer types.
1582+
def UselessWrapsAttr : DiagGroup<"useless-wraps-attribute">;
1583+
1584+
// Warnings about the wraps attribute getting implicitly discarded
1585+
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
@@ -2240,6 +2240,11 @@ bool BinaryOperator::isNullPointerArithmeticExtension(ASTContext &Ctx,
22402240
return true;
22412241
}
22422242

2243+
bool BinaryOperator::hasWrappingOperand(const ASTContext &Ctx) const {
2244+
return getLHS()->getType().hasWrapsAttr() ||
2245+
getRHS()->getType().hasWrapsAttr();
2246+
}
2247+
22432248
SourceLocExpr::SourceLocExpr(const ASTContext &Ctx, SourceLocIdentKind Kind,
22442249
QualType ResultTy, SourceLocation BLoc,
22452250
SourceLocation RParenLoc,
@@ -4856,6 +4861,8 @@ BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
48564861
if (hasStoredFPFeatures())
48574862
setStoredFPFeatures(FPFeatures);
48584863
setDependence(computeDependence(this));
4864+
if (hasWrappingOperand(Ctx))
4865+
setType(Ctx.getAttributedType(attr::Wraps, getType(), getType()));
48594866
}
48604867

48614868
BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
@@ -4874,6 +4881,8 @@ BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
48744881
if (hasStoredFPFeatures())
48754882
setStoredFPFeatures(FPFeatures);
48764883
setDependence(computeDependence(this));
4884+
if (hasWrappingOperand(Ctx))
4885+
setType(Ctx.getAttributedType(attr::Wraps, getType(), getType()));
48774886
}
48784887

48794888
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
@@ -2809,7 +2809,7 @@ static bool CheckedIntArithmetic(EvalInfo &Info, const Expr *E,
28092809
APSInt Value(Op(LHS.extend(BitWidth), RHS.extend(BitWidth)), false);
28102810
Result = Value.trunc(LHS.getBitWidth());
28112811
if (Result.extend(BitWidth) != Value) {
2812-
if (Info.checkingForUndefinedBehavior())
2812+
if (Info.checkingForUndefinedBehavior() && !E->getType().hasWrapsAttr())
28132813
Info.Ctx.getDiagnostics().Report(E->getExprLoc(),
28142814
diag::warn_integer_constant_overflow)
28152815
<< toString(Result, 10, Result.isSigned(), /*formatAsCLiteral=*/false,
@@ -14412,7 +14412,7 @@ bool IntExprEvaluator::VisitUnaryOperator(const UnaryOperator *E) {
1441214412
if (!Result.isInt()) return Error(E);
1441314413
const APSInt &Value = Result.getInt();
1441414414
if (Value.isSigned() && Value.isMinSignedValue() && E->canOverflow()) {
14415-
if (Info.checkingForUndefinedBehavior())
14415+
if (Info.checkingForUndefinedBehavior() && !E->getType().hasWrapsAttr())
1441614416
Info.Ctx.getDiagnostics().Report(E->getExprLoc(),
1441714417
diag::warn_integer_constant_overflow)
1441814418
<< 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
@@ -2024,6 +2024,9 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
20242024
case attr::AArch64SVEPcs: OS << "aarch64_sve_pcs"; break;
20252025
case attr::AMDGPUKernelCall: OS << "amdgpu_kernel"; break;
20262026
case attr::IntelOclBicc: OS << "inteloclbicc"; break;
2027+
case attr::Wraps:
2028+
OS << "wraps";
2029+
break;
20272030
case attr::PreserveMost:
20282031
OS << "preserve_most";
20292032
break;

clang/lib/CodeGen/CGExprScalar.cpp

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

163167
static bool MustVisitNullValue(const Expr *E) {
@@ -750,7 +754,8 @@ class ScalarExprEmitter
750754

751755
// Binary Operators.
752756
Value *EmitMul(const BinOpInfo &Ops) {
753-
if (Ops.Ty->isSignedIntegerOrEnumerationType()) {
757+
if (Ops.Ty->isSignedIntegerOrEnumerationType() &&
758+
!Ops.hasWrappingOperand()) {
754759
switch (CGF.getLangOpts().getSignedOverflowBehavior()) {
755760
case LangOptions::SOB_Defined:
756761
if (!CGF.SanOpts.has(SanitizerKind::SignedIntegerOverflow))
@@ -786,7 +791,8 @@ class ScalarExprEmitter
786791

787792
if (Ops.Ty->isUnsignedIntegerType() &&
788793
CGF.SanOpts.has(SanitizerKind::UnsignedIntegerOverflow) &&
789-
!CanElideOverflowCheck(CGF.getContext(), Ops))
794+
!CanElideOverflowCheck(CGF.getContext(), Ops) &&
795+
!Ops.hasWrappingOperand())
790796
return EmitOverflowCheckedBinOp(Ops);
791797

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

11201126
// Do we care about this type of truncation?
1121-
if (!CGF.SanOpts.has(Check.second.second))
1127+
if (!CGF.SanOpts.has(Check.second.second) || DstType.hasWrapsAttr())
11221128
return;
11231129

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

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

13561370
std::pair<ScalarExprEmitter::ImplicitConversionCheckKind,
@@ -2926,6 +2940,9 @@ ScalarExprEmitter::EmitScalarPrePostIncDec(const UnaryOperator *E, LValue LV,
29262940
bool excludeOverflowPattern =
29272941
matchesPostDecrInWhile(E, isInc, isPre, CGF.getContext());
29282942

2943+
BinOpInfo Ops = createBinOpInfoFromIncDec(
2944+
E, value, isInc, E->getFPFeaturesInEffect(CGF.getLangOpts()));
2945+
29292946
if (CGF.getContext().isPromotableIntegerType(type)) {
29302947
promotedType = CGF.getContext().getPromotedIntegerType(type);
29312948
assert(promotedType != type && "Shouldn't promote to the same type.");
@@ -2982,11 +2999,12 @@ ScalarExprEmitter::EmitScalarPrePostIncDec(const UnaryOperator *E, LValue LV,
29822999
// Note that signed integer inc/dec with width less than int can't
29833000
// overflow because of promotion rules; we're just eliding a few steps
29843001
// here.
2985-
} else if (E->canOverflow() && type->isSignedIntegerOrEnumerationType()) {
3002+
} else if (E->canOverflow() && type->isSignedIntegerOrEnumerationType() &&
3003+
!Ops.hasWrappingOperand()) {
29863004
value = EmitIncDecConsiderOverflowBehavior(E, value, isInc);
29873005
} else if (E->canOverflow() && type->isUnsignedIntegerType() &&
29883006
CGF.SanOpts.has(SanitizerKind::UnsignedIntegerOverflow) &&
2989-
!excludeOverflowPattern) {
3007+
!Ops.hasWrappingOperand() && !excludeOverflowPattern) {
29903008
value = EmitOverflowCheckedBinOp(createBinOpInfoFromIncDec(
29913009
E, value, isInc, E->getFPFeaturesInEffect(CGF.getLangOpts())));
29923010
} else {
@@ -3775,7 +3793,8 @@ Value *ScalarExprEmitter::EmitDiv(const BinOpInfo &Ops) {
37753793
if ((CGF.SanOpts.has(SanitizerKind::IntegerDivideByZero) ||
37763794
CGF.SanOpts.has(SanitizerKind::SignedIntegerOverflow)) &&
37773795
Ops.Ty->isIntegerType() &&
3778-
(Ops.mayHaveIntegerDivisionByZero() || Ops.mayHaveIntegerOverflow())) {
3796+
(Ops.mayHaveIntegerDivisionByZero() || Ops.mayHaveIntegerOverflow()) &&
3797+
!Ops.hasWrappingOperand()) {
37793798
llvm::Value *Zero = llvm::Constant::getNullValue(ConvertType(Ops.Ty));
37803799
EmitUndefinedBehaviorIntegerDivAndRemCheck(Ops, Zero, true);
37813800
} else if (CGF.SanOpts.has(SanitizerKind::FloatDivideByZero) &&
@@ -3824,7 +3843,8 @@ Value *ScalarExprEmitter::EmitRem(const BinOpInfo &Ops) {
38243843
if ((CGF.SanOpts.has(SanitizerKind::IntegerDivideByZero) ||
38253844
CGF.SanOpts.has(SanitizerKind::SignedIntegerOverflow)) &&
38263845
Ops.Ty->isIntegerType() &&
3827-
(Ops.mayHaveIntegerDivisionByZero() || Ops.mayHaveIntegerOverflow())) {
3846+
(Ops.mayHaveIntegerDivisionByZero() || Ops.mayHaveIntegerOverflow()) &&
3847+
!Ops.hasWrappingOperand()) {
38283848
CodeGenFunction::SanitizerScope SanScope(&CGF);
38293849
llvm::Value *Zero = llvm::Constant::getNullValue(ConvertType(Ops.Ty));
38303850
EmitUndefinedBehaviorIntegerDivAndRemCheck(Ops, Zero, false);
@@ -4189,7 +4209,7 @@ Value *ScalarExprEmitter::EmitAdd(const BinOpInfo &op) {
41894209
op.RHS->getType()->isPointerTy())
41904210
return emitPointerArithmetic(CGF, op, CodeGenFunction::NotSubtraction);
41914211

4192-
if (op.Ty->isSignedIntegerOrEnumerationType()) {
4212+
if (op.Ty->isSignedIntegerOrEnumerationType() && !op.hasWrappingOperand()) {
41934213
switch (CGF.getLangOpts().getSignedOverflowBehavior()) {
41944214
case LangOptions::SOB_Defined:
41954215
if (!CGF.SanOpts.has(SanitizerKind::SignedIntegerOverflow))
@@ -4222,7 +4242,7 @@ Value *ScalarExprEmitter::EmitAdd(const BinOpInfo &op) {
42224242

42234243
if (op.Ty->isUnsignedIntegerType() &&
42244244
CGF.SanOpts.has(SanitizerKind::UnsignedIntegerOverflow) &&
4225-
!CanElideOverflowCheck(CGF.getContext(), op))
4245+
!CanElideOverflowCheck(CGF.getContext(), op) && !op.hasWrappingOperand())
42264246
return EmitOverflowCheckedBinOp(op);
42274247

42284248
if (op.LHS->getType()->isFPOrFPVectorTy()) {
@@ -4345,7 +4365,7 @@ Value *ScalarExprEmitter::EmitFixedPointBinOp(const BinOpInfo &op) {
43454365
Value *ScalarExprEmitter::EmitSub(const BinOpInfo &op) {
43464366
// The LHS is always a pointer if either side is.
43474367
if (!op.LHS->getType()->isPointerTy()) {
4348-
if (op.Ty->isSignedIntegerOrEnumerationType()) {
4368+
if (op.Ty->isSignedIntegerOrEnumerationType() && !op.hasWrappingOperand()) {
43494369
switch (CGF.getLangOpts().getSignedOverflowBehavior()) {
43504370
case LangOptions::SOB_Defined:
43514371
if (!CGF.SanOpts.has(SanitizerKind::SignedIntegerOverflow))
@@ -4378,7 +4398,8 @@ Value *ScalarExprEmitter::EmitSub(const BinOpInfo &op) {
43784398

43794399
if (op.Ty->isUnsignedIntegerType() &&
43804400
CGF.SanOpts.has(SanitizerKind::UnsignedIntegerOverflow) &&
4381-
!CanElideOverflowCheck(CGF.getContext(), op))
4401+
!CanElideOverflowCheck(CGF.getContext(), op) &&
4402+
!op.hasWrappingOperand())
43824403
return EmitOverflowCheckedBinOp(op);
43834404

43844405
if (op.LHS->getType()->isFPOrFPVectorTy()) {
@@ -4498,7 +4519,8 @@ Value *ScalarExprEmitter::EmitShl(const BinOpInfo &Ops) {
44984519
bool SanitizeSignedBase = CGF.SanOpts.has(SanitizerKind::ShiftBase) &&
44994520
Ops.Ty->hasSignedIntegerRepresentation() &&
45004521
!CGF.getLangOpts().isSignedOverflowDefined() &&
4501-
!CGF.getLangOpts().CPlusPlus20;
4522+
!CGF.getLangOpts().CPlusPlus20 &&
4523+
!Ops.hasWrappingOperand();
45024524
bool SanitizeUnsignedBase =
45034525
CGF.SanOpts.has(SanitizerKind::UnsignedShiftBase) &&
45044526
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)