Skip to content

Commit f58e648

Browse files
committed
add wraps, no_wraps attributes
1 parent c949500 commit f58e648

23 files changed

+556
-28
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,26 @@ Attribute Changes in Clang
386386
- Fix a bug where clang doesn't automatically apply the ``[[gsl::Owner]]`` or
387387
``[[gsl::Pointer]]`` to STL explicit template specialization decls. (#GH109442)
388388

389+
- Introduced ``__attribute__((wraps))`` which can be added to type or variable
390+
declarations. Using an attributed type or variable in an arithmetic
391+
expression will define the overflow behavior for that expression as having
392+
two's complement wrap-around. These expressions will not be instrumented by
393+
overflow sanitizers nor will they cause integer overflow warnings. They also
394+
cannot be optimized away by some eager UB optimizations as the behavior of
395+
the arithmetic is no longer "undefined".
396+
397+
There is also ``__attribute__((no_wraps))`` which can be added to types or
398+
variable declarations. Types or variables with this attribute may be
399+
instrumented by overflow sanitizers, if enabled. Note that this matches the
400+
default behavior of integer types. So, in most cases, ``no_wraps`` serves
401+
purely as an annotation to readers of code that a type or variable really
402+
shouldn't wrap-around. ``__attribute__((no_wraps))`` has the most function
403+
when paired with `Sanitizer Special Case Lists (SSCL)
404+
<https://clang.llvm.org/docs/SanitizerSpecialCaseList.html>`_.
405+
406+
These attributes are only valid for C, as there are built-in language
407+
alternatives for other languages.
408+
389409
Improvements to Clang's diagnostics
390410
-----------------------------------
391411

clang/docs/SanitizerSpecialCaseList.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,11 @@ types specified within an ignorelist.
6767
int a = 2147483647; // INT_MAX
6868
++a; // Normally, an overflow with -fsanitize=signed-integer-overflow
6969
}
70+
7071
$ cat ignorelist.txt
7172
[signed-integer-overflow]
7273
type:int
74+
7375
$ clang -fsanitize=signed-integer-overflow -fsanitize-ignorelist=ignorelist.txt foo.c ; ./a.out
7476
# no signed-integer-overflow error
7577

clang/include/clang/AST/Expr.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4142,6 +4142,12 @@ class BinaryOperator : public Expr {
41424142
return getFPFeaturesInEffect(LO).getAllowFEnvAccess();
41434143
}
41444144

4145+
/// Does one of the subexpressions have the wraps attribute?
4146+
bool hasWrappingOperand(const ASTContext &Ctx) const;
4147+
4148+
/// How about the no_wraps attribute?
4149+
bool hasNonWrappingOperand(const ASTContext &Ctx) const;
4150+
41454151
protected:
41464152
BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs, Opcode opc,
41474153
QualType ResTy, ExprValueKind VK, ExprObjectKind OK,

clang/include/clang/AST/Type.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1458,6 +1458,9 @@ class QualType {
14581458
return getQualifiers().hasStrongOrWeakObjCLifetime();
14591459
}
14601460

1461+
bool hasWrapsAttr() const;
1462+
bool hasNoWrapsAttr() const;
1463+
14611464
// true when Type is objc's weak and weak is enabled but ARC isn't.
14621465
bool isNonWeakInMRRWithObjCWeak(const ASTContext &Context) const;
14631466

clang/include/clang/Basic/Attr.td

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4838,3 +4838,18 @@ def ClspvLibclcBuiltin: InheritableAttr {
48384838
let Documentation = [ClspvLibclcBuiltinDoc];
48394839
let SimpleHandler = 1;
48404840
}
4841+
4842+
def Wraps : DeclOrTypeAttr {
4843+
let Spellings = [Clang<"wraps">];
4844+
let Subjects = SubjectList<[Var, TypedefName, Field]>;
4845+
let Documentation = [WrapsDocs];
4846+
let LangOpts = [COnly];
4847+
}
4848+
4849+
def NoWraps : DeclOrTypeAttr {
4850+
let Spellings = [Clang<"no_wraps">];
4851+
let Subjects = SubjectList<[Var, TypedefName, Field]>;
4852+
let Documentation = [NoWrapsDocs];
4853+
let LangOpts = [COnly];
4854+
}
4855+

clang/include/clang/Basic/AttrDocs.td

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8526,3 +8526,103 @@ Declares that a function potentially allocates heap memory, and prevents any pot
85268526
of ``nonallocating`` by the compiler.
85278527
}];
85288528
}
8529+
8530+
def WrapsDocs : Documentation {
8531+
let Category = DocCatField;
8532+
let Content = [{
8533+
The ``wraps`` attribute can be used with type or variable declarations to
8534+
denote that arithmetic containing attributed types or variables have defined
8535+
overflow behavior. Specifically, the behavior is defined as being consistent
8536+
with two's complement wrap-around. For the purposes of sanitizers or warnings
8537+
that concern themselves with the definedness of integer arithmetic, they will
8538+
cease to instrument or warn about arithmetic that directly involves operands
8539+
attributed with the ``wraps`` attribute.
8540+
8541+
The ``signed-integer-overflow``, ``unsigned-integer-overflow``,
8542+
``implicit-signed-integer-truncation`` and the
8543+
``implicit-unsigned-integer-truncation`` sanitizers will not instrument
8544+
arithmetic containing any operands attributed by ``wraps``. Similarly, the
8545+
``-Winteger-overflow`` warning is disabled for these instances.
8546+
8547+
The following example shows how one may disable ``signed-integer-overflow``
8548+
sanitizer instrumentation using ``__attribute__((wraps))`` on a type definition
8549+
when building with ``-fsanitize=signed-integer-overflow``:
8550+
8551+
.. code-block:: c
8552+
8553+
typedef int __attribute__((wraps)) wrapping_int;
8554+
8555+
void foo(void) {
8556+
wrapping_int A = INT_MAX;
8557+
++A; // no sanitizer instrumentation
8558+
}
8559+
8560+
``wraps`` may also be used with function parameters or declarations of
8561+
variables as well as members of structures. Using ``wraps`` on non-integer
8562+
types will result in a `-Wuseless-wraps-attribute`. One may disable this
8563+
warning with ``-Wno-useless-wraps-attribute``.
8564+
8565+
``wraps`` persists through implicit type promotions and will be applied to the
8566+
result type of arithmetic expressions containing a wrapping operand.
8567+
``-Wimplicitly-discarded-wraps-attribute`` warnings can be caused in situations
8568+
where the ``wraps`` attribute cannot persist through implicit type conversions.
8569+
Disable this with ``-Wno-implicitly-discarded-wraps-attribute``.
8570+
}];
8571+
}
8572+
8573+
def NoWrapsDocs : Documentation {
8574+
let Category = DocCatField;
8575+
let Content = [{
8576+
The ``no_wraps`` attribute can be used to annotate types or variables as
8577+
non-wrapping. This may serve as a helpful annotation to readers of code that
8578+
particular arithmetic expressions involving these types or variables are not
8579+
meant to wrap-around.
8580+
8581+
When overflow or truncation sanitizer instrumentation is modified at the
8582+
type-level through `SSCLs
8583+
<https://clang.llvm.org/docs/SanitizerSpecialCaseList.html>`_, ``no_wraps`` or
8584+
``wraps`` may be used to override sanitizer behavior.
8585+
8586+
For example, one may specify an ignorelist (with ``-fsanitize-ignorelist=``) to
8587+
disable the ``signed-integer-overflow`` sanitizer for all types:
8588+
8589+
.. code-block:: text
8590+
8591+
[signed-integer-overflow]
8592+
type:*
8593+
8594+
``no_wraps`` can override the behavior provided by the ignorelist to
8595+
effectively re-enable instrumentation for specific types or variables.
8596+
8597+
.. code-block:: c
8598+
8599+
typedef int __attribute__((no_wraps)) non_wrapping_int;
8600+
8601+
void foo(non_wrapping_int A, int B) {
8602+
++A; // will be instrumented if built with -fsanitize=signed-integer-overflow
8603+
++B; // won't be instrumented as it is ignored by the ignorelist
8604+
}
8605+
8606+
Like ``wraps``, ``no_wraps`` persists through implicit type promotions and will
8607+
be automatically applied to the result type of arithmetic expressions
8608+
containing a wrapping operand.
8609+
8610+
If a type or variable is attributed by both ``wraps`` and ``no_wraps``, then
8611+
``no_wraps`` takes precedence -- regardless of the order of attribution.
8612+
8613+
Note that ``no_wraps`` makes no guarantees about the definedness of arithmetic
8614+
overflow. Instead, use ``-fwrapv`` or ``-fno-strict-overflow``.
8615+
8616+
Like ``wraps``, ``no_wraps`` may also be used with function parameters or
8617+
declarations of variables as well as members of structures. Using ``wraps`` on
8618+
non-integer types will result in a `-Wuseless-wraps-attribute`. One may disable
8619+
this warning with ``-Wno-useless-wraps-attribute``.
8620+
8621+
``no_wraps`` also persists through implicit type promotions and will be applied
8622+
to the result type of arithmetic expressions containing a wrapping operand.
8623+
``-Wimplicitly-discarded-wraps-attribute`` warnings can be caused in situations
8624+
where the ``wraps`` attribute cannot persist through implicit type conversions.
8625+
Disable this with ``-Wno-implicitly-discarded-wraps-attribute``.
8626+
}];
8627+
}
8628+

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1588,3 +1588,9 @@ def ExplicitSpecializationStorageClass : DiagGroup<"explicit-specialization-stor
15881588
// A warning for options that enable a feature that is not yet complete
15891589
def ExperimentalOption : DiagGroup<"experimental-option">;
15901590

1591+
1592+
// Warnings regarding the usage of __attribute__((wraps)) on non-integer types.
1593+
def UselessWrapsAttr : DiagGroup<"useless-wraps-attribute">;
1594+
1595+
// Warnings about the wraps attribute getting implicitly discarded
1596+
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
@@ -6652,6 +6652,13 @@ def warn_counted_by_attr_elt_type_unknown_size :
66526652
Warning<err_counted_by_attr_pointee_unknown_size.Summary>,
66536653
InGroup<BoundsSafetyCountedByEltTyUnknownSize>;
66546654

6655+
def warn_wraps_attr_var_decl_type_not_integer : Warning<
6656+
"using attribute '%select{wraps|no_wraps}0' with non-integer type '%1' has no function and is potentially misleading">,
6657+
InGroup<UselessWrapsAttr>;
6658+
def warn_wraps_attr_maybe_lost : Warning<
6659+
"'%select{wraps|no_wraps}0' attribute may be implicitly discarded when converted to %1">,
6660+
InGroup<ImpDiscardedWrapsAttr>;
6661+
66556662
let CategoryName = "ARC Semantic Issue" in {
66566663

66576664
// ARC-mode diagnostics.

clang/lib/AST/Expr.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2236,6 +2236,16 @@ bool BinaryOperator::isNullPointerArithmeticExtension(ASTContext &Ctx,
22362236
return true;
22372237
}
22382238

2239+
bool BinaryOperator::hasWrappingOperand(const ASTContext &Ctx) const {
2240+
return getLHS()->getType().hasWrapsAttr() ||
2241+
getRHS()->getType().hasWrapsAttr();
2242+
}
2243+
2244+
bool BinaryOperator::hasNonWrappingOperand(const ASTContext &Ctx) const {
2245+
return getLHS()->getType().hasNoWrapsAttr() ||
2246+
getRHS()->getType().hasNoWrapsAttr();
2247+
}
2248+
22392249
SourceLocExpr::SourceLocExpr(const ASTContext &Ctx, SourceLocIdentKind Kind,
22402250
QualType ResultTy, SourceLocation BLoc,
22412251
SourceLocation RParenLoc,
@@ -4852,6 +4862,11 @@ BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
48524862
if (hasStoredFPFeatures())
48534863
setStoredFPFeatures(FPFeatures);
48544864
setDependence(computeDependence(this));
4865+
if (hasWrappingOperand(Ctx))
4866+
setType(Ctx.getAttributedType(attr::Wraps, getType(), getType()));
4867+
if (hasNonWrappingOperand(Ctx))
4868+
setType(Ctx.getAttributedType(attr::NoWraps, getType(), getType()));
4869+
48554870
}
48564871

48574872
BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
@@ -4870,6 +4885,11 @@ BinaryOperator::BinaryOperator(const ASTContext &Ctx, Expr *lhs, Expr *rhs,
48704885
if (hasStoredFPFeatures())
48714886
setStoredFPFeatures(FPFeatures);
48724887
setDependence(computeDependence(this));
4888+
if (hasWrappingOperand(Ctx))
4889+
setType(Ctx.getAttributedType(attr::Wraps, getType(), getType()));
4890+
if (hasNonWrappingOperand(Ctx))
4891+
setType(Ctx.getAttributedType(attr::NoWraps, getType(), getType()));
4892+
48734893
}
48744894

48754895
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
@@ -2898,7 +2898,7 @@ static bool CheckedIntArithmetic(EvalInfo &Info, const Expr *E,
28982898
APSInt Value(Op(LHS.extend(BitWidth), RHS.extend(BitWidth)), false);
28992899
Result = Value.trunc(LHS.getBitWidth());
29002900
if (Result.extend(BitWidth) != Value) {
2901-
if (Info.checkingForUndefinedBehavior())
2901+
if (Info.checkingForUndefinedBehavior() && !E->getType().hasWrapsAttr())
29022902
Info.Ctx.getDiagnostics().Report(E->getExprLoc(),
29032903
diag::warn_integer_constant_overflow)
29042904
<< toString(Result, 10, Result.isSigned(), /*formatAsCLiteral=*/false,
@@ -14694,7 +14694,7 @@ bool IntExprEvaluator::VisitUnaryOperator(const UnaryOperator *E) {
1469414694
if (!Result.isInt()) return Error(E);
1469514695
const APSInt &Value = Result.getInt();
1469614696
if (Value.isSigned() && Value.isMinSignedValue() && E->canOverflow()) {
14697-
if (Info.checkingForUndefinedBehavior())
14697+
if (Info.checkingForUndefinedBehavior() && !E->getType().hasWrapsAttr())
1469814698
Info.Ctx.getDiagnostics().Report(E->getExprLoc(),
1469914699
diag::warn_integer_constant_overflow)
1470014700
<< toString(Value, 10, Value.isSigned(), /*formatAsCLiteral=*/false,

0 commit comments

Comments
 (0)