Skip to content

Commit 58df542

Browse files
committed
implement wraps attribute
Signed-off-by: Justin Stitt <[email protected]>
1 parent 8044158 commit 58df542

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
@@ -526,6 +526,16 @@ Attribute Changes in Clang
526526
The attributes declare constraints about a function's behavior pertaining to blocking and
527527
heap memory allocation.
528528

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

4069+
/// Does one of the subexpressions have the wraps attribute?
4070+
bool hasWrappingOperand(const ASTContext &Ctx) const;
4071+
40694072
protected:
40704073
BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs, Opcode opc,
40714074
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
@@ -4659,3 +4659,10 @@ def ClspvLibclcBuiltin: InheritableAttr {
46594659
let Documentation = [ClspvLibclcBuiltinDoc];
46604660
let SimpleHandler = 1;
46614661
}
4662+
4663+
def Wraps : DeclOrTypeAttr {
4664+
let Spellings = [Clang<"wraps">];
4665+
let Subjects = SubjectList<[Var, TypedefName, Field]>;
4666+
let Documentation = [WrapsDocs];
4667+
let LangOpts = [COnly];
4668+
}

clang/include/clang/Basic/AttrDocs.td

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8173,3 +8173,71 @@ of ``nonallocating`` by the compiler.
81738173
}];
81748174
}
81758175

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

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1543,3 +1543,9 @@ def ExtractAPIMisuse : DiagGroup<"extractapi-misuse">;
15431543
// Warnings about using the non-standard extension having an explicit specialization
15441544
// with a storage class specifier.
15451545
def ExplicitSpecializationStorageClass : DiagGroup<"explicit-specialization-storage-class">;
1546+
1547+
// Warnings regarding the usage of __attribute__((wraps)) on non-integer types.
1548+
def UselessWrapsAttr : DiagGroup<"useless-wraps-attribute">;
1549+
1550+
// Warnings about the wraps attribute getting implicitly discarded
1551+
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
@@ -6611,6 +6611,13 @@ def warn_counted_by_attr_elt_type_unknown_size :
66116611
Warning<err_counted_by_attr_pointee_unknown_size.Summary>,
66126612
InGroup<BoundsSafetyCountedByEltTyUnknownSize>;
66136613

6614+
def warn_wraps_attr_var_decl_type_not_integer : Warning<
6615+
"using attribute 'wraps' with non-integer type '%0' has no function">,
6616+
InGroup<UselessWrapsAttr>;
6617+
def warn_wraps_attr_maybe_lost : Warning<
6618+
"'wraps' attribute may be implicitly discarded when converted to %0">,
6619+
InGroup<ImpDiscardedWrapsAttr>;
6620+
66146621
let CategoryName = "ARC Semantic Issue" in {
66156622

66166623
// 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
@@ -2782,7 +2782,7 @@ static bool CheckedIntArithmetic(EvalInfo &Info, const Expr *E,
27822782
APSInt Value(Op(LHS.extend(BitWidth), RHS.extend(BitWidth)), false);
27832783
Result = Value.trunc(LHS.getBitWidth());
27842784
if (Result.extend(BitWidth) != Value) {
2785-
if (Info.checkingForUndefinedBehavior())
2785+
if (Info.checkingForUndefinedBehavior() && !E->getType().hasWrapsAttr())
27862786
Info.Ctx.getDiagnostics().Report(E->getExprLoc(),
27872787
diag::warn_integer_constant_overflow)
27882788
<< toString(Result, 10, Result.isSigned(), /*formatAsCLiteral=*/false,
@@ -14177,7 +14177,7 @@ bool IntExprEvaluator::VisitUnaryOperator(const UnaryOperator *E) {
1417714177
if (!Result.isInt()) return Error(E);
1417814178
const APSInt &Value = Result.getInt();
1417914179
if (Value.isSigned() && Value.isMinSignedValue() && E->canOverflow()) {
14180-
if (Info.checkingForUndefinedBehavior())
14180+
if (Info.checkingForUndefinedBehavior() && !E->getType().hasWrapsAttr())
1418114181
Info.Ctx.getDiagnostics().Report(E->getExprLoc(),
1418214182
diag::warn_integer_constant_overflow)
1418314183
<< 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
@@ -2846,6 +2846,10 @@ bool QualType::isWebAssemblyFuncrefType() const {
28462846
getAddressSpace() == LangAS::wasm_funcref;
28472847
}
28482848

2849+
bool QualType::hasWrapsAttr() const {
2850+
return !isNull() && getTypePtr()->hasAttr(attr::Wraps);
2851+
}
2852+
28492853
QualType::PrimitiveDefaultInitializeKind
28502854
QualType::isNonTrivialToPrimitiveDefaultInitialize() const {
28512855
if (const auto *RT =

0 commit comments

Comments
 (0)