Skip to content

Commit 6152bd2

Browse files
committed
implement wraps attribute
Signed-off-by: Justin Stitt <[email protected]>
1 parent b6688a0 commit 6152bd2

20 files changed

+377
-26
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,16 @@ Attribute Changes in Clang
472472
};
473473
474474
475+
- Introduced ``__attribute((wraps))__`` which can be added to type or variable
476+
declarations. Using an attributed type or variable in an arithmetic
477+
expression will define the overflow behavior for that expression as having
478+
two's complement wrap-around. These expressions cannot trigger integer
479+
overflow warnings or sanitizer warnings. They also cannot be optimized away
480+
by some eager UB optimizations.
481+
482+
This attribute is only valid for C, as there are built-in language
483+
alternatives for other languages.
484+
475485
Improvements to Clang's diagnostics
476486
-----------------------------------
477487
- 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
@@ -1455,6 +1455,8 @@ class QualType {
14551455
return getQualifiers().hasStrongOrWeakObjCLifetime();
14561456
}
14571457

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

clang/include/clang/Basic/Attr.td

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4606,3 +4606,10 @@ def ClspvLibclcBuiltin: InheritableAttr {
46064606
let Documentation = [ClspvLibclcBuiltinDoc];
46074607
let SimpleHandler = 1;
46084608
}
4609+
4610+
def Wraps : DeclOrTypeAttr {
4611+
let Spellings = [Clang<"wraps">];
4612+
let Subjects = SubjectList<[Var, TypedefName, Field]>;
4613+
let Documentation = [WrapsDocs];
4614+
let LangOpts = [COnly];
4615+
}

clang/include/clang/Basic/AttrDocs.td

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8106,3 +8106,72 @@ Attribute used by `clspv`_ (OpenCL-C to Vulkan SPIR-V compiler) to identify func
81068106
.. _`libclc`: https://libclc.llvm.org
81078107
}];
81088108
}
8109+
8110+
def WrapsDocs : Documentation {
8111+
let Category = DocCatField;
8112+
let Content = [{
8113+
This attribute can be used with type or variable declarations to denote that
8114+
arithmetic containing these marked components have defined overflow behavior.
8115+
Specifically, the behavior is defined as being consistent with two's complement
8116+
wrap-around. For the purposes of sanitizers or warnings that concern themselves
8117+
with the definedness of integer arithmetic, they will cease to instrument or
8118+
warn about arithmetic that directly involves a "wrapping" component.
8119+
8120+
For example, ``-fsanitize=signed-integer-overflow`` or ``-Winteger-overflow``
8121+
will not warn about suspicious overflowing arithmetic -- assuming correct usage
8122+
of the wraps attribute.
8123+
8124+
This example shows some basic usage of ``__attribute__((wraps))`` on a type
8125+
definition when building with ``-fsanitize=signed-integer-overflow``
8126+
8127+
.. code-block:: c
8128+
8129+
typedef int __attribute__((wraps)) wrapping_int;
8130+
8131+
void foo() {
8132+
wrapping_int a = INT_MAX;
8133+
++a; // no sanitizer warning
8134+
}
8135+
8136+
int main() { foo(); }
8137+
8138+
In the following example, we use ``__attribute__((wraps))`` on a variable to
8139+
disable overflow instrumentation for arithmetic expressions it appears in. We
8140+
do so with a popular overflow-checking pattern which we might not want to trip
8141+
sanitizers (like ``-fsanitize=unsigned-integer-overflow``).
8142+
8143+
.. code-block:: c
8144+
8145+
void foo(int offset) {
8146+
unsigned int A __attribute__((wraps)) = UINT_MAX;
8147+
8148+
// check for overflow using a common pattern, however we may accidentally
8149+
// perform a real overflow thus triggering sanitizers to step in. Since "A"
8150+
// is "wrapping", we can avoid sanitizer warnings.
8151+
if (A + offset < A) {
8152+
// handle overflow manually
8153+
// ...
8154+
return;
8155+
}
8156+
8157+
// now, handle non-overflow case ...
8158+
}
8159+
8160+
The above example demonstrates some of the power and elegance this attribute
8161+
provides. We can use code patterns we are already familiar with (like ``if (x +
8162+
y < x)``) while gaining control over the overflow behavior on a case-by-case
8163+
basis.
8164+
8165+
When combined with ``-fwrapv``, this attribute can still be applied as normal
8166+
but has no function apart from annotating types and variables for readers. This
8167+
is because ``-fwrapv`` defines all arithmetic as being "wrapping", rendering
8168+
this attribute's efforts redundant.
8169+
8170+
When using this attribute without ``-fwrapv`` and without any sanitizers, it
8171+
still has an impact on the definedness of arithmetic expressions containing
8172+
wrapping components. Since the behavior of said expressions is now technically
8173+
defined, the compiler will forgo some eager optimizations that are used on
8174+
expressions containing UB.
8175+
}];
8176+
}
8177+

clang/include/clang/Basic/DiagnosticGroups.td

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

15361536
// Warnings about misuse of ExtractAPI options.
15371537
def ExtractAPIMisuse : DiagGroup<"extractapi-misuse">;
1538+
1539+
// Warnings regarding the usage of __attribute__((wraps)) on non-integer types.
1540+
def UselessWrapsAttr : DiagGroup<"useless-wraps-attribute">;
1541+
1542+
// Warnings about the wraps attribute getting implicitly discarded
1543+
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
@@ -6582,6 +6582,13 @@ def warn_counted_by_attr_elt_type_unknown_size :
65826582
Warning<err_counted_by_attr_pointee_unknown_size.Summary>,
65836583
InGroup<BoundsSafetyCountedByEltTyUnknownSize>;
65846584

6585+
def warn_wraps_attr_var_decl_type_not_integer : Warning<
6586+
"using attribute 'wraps' with non-integer type '%0' has no function">,
6587+
InGroup<UselessWrapsAttr>;
6588+
def warn_wraps_attr_maybe_lost : Warning<
6589+
"'wraps' attribute may be implicitly discarded when converted to %0">,
6590+
InGroup<ImpDiscardedWrapsAttr>;
6591+
65856592
let CategoryName = "ARC Semantic Issue" in {
65866593

65876594
// 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,
@@ -4754,6 +4759,8 @@ BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
47544759
if (hasStoredFPFeatures())
47554760
setStoredFPFeatures(FPFeatures);
47564761
setDependence(computeDependence(this));
4762+
if (hasWrappingOperand(Ctx))
4763+
setType(Ctx.getAttributedType(attr::Wraps, getType(), getType()));
47574764
}
47584765

47594766
BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
@@ -4771,6 +4778,8 @@ BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
47714778
if (hasStoredFPFeatures())
47724779
setStoredFPFeatures(FPFeatures);
47734780
setDependence(computeDependence(this));
4781+
if (hasWrappingOperand(Ctx))
4782+
setType(Ctx.getAttributedType(attr::Wraps, getType(), getType()));
47744783
}
47754784

47764785
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
@@ -2779,7 +2779,7 @@ static bool CheckedIntArithmetic(EvalInfo &Info, const Expr *E,
27792779
APSInt Value(Op(LHS.extend(BitWidth), RHS.extend(BitWidth)), false);
27802780
Result = Value.trunc(LHS.getBitWidth());
27812781
if (Result.extend(BitWidth) != Value) {
2782-
if (Info.checkingForUndefinedBehavior())
2782+
if (Info.checkingForUndefinedBehavior() && !E->getType().hasWrapsAttr())
27832783
Info.Ctx.getDiagnostics().Report(E->getExprLoc(),
27842784
diag::warn_integer_constant_overflow)
27852785
<< toString(Result, 10, Result.isSigned(), /*formatAsCLiteral=*/false,
@@ -14117,7 +14117,7 @@ bool IntExprEvaluator::VisitUnaryOperator(const UnaryOperator *E) {
1411714117
if (!Result.isInt()) return Error(E);
1411814118
const APSInt &Value = Result.getInt();
1411914119
if (Value.isSigned() && Value.isMinSignedValue() && E->canOverflow()) {
14120-
if (Info.checkingForUndefinedBehavior())
14120+
if (Info.checkingForUndefinedBehavior() && !E->getType().hasWrapsAttr())
1412114121
Info.Ctx.getDiagnostics().Report(E->getExprLoc(),
1412214122
diag::warn_integer_constant_overflow)
1412314123
<< 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 &&

0 commit comments

Comments
 (0)