Skip to content

Commit 267ad43

Browse files
zporkywhisperity
andauthored
[clang-tidy] Extend bugprone-sizeof-expression with matching P +- sizeof(T) and P +- N */ sizeof(T) cases, add cert-arr39-c alias (#106061)
Improved `bugprone-sizeof-expression` check to find suspicious pointer arithmetic calculations where the pointer is offset by an `alignof()`, `offsetof()`, or `sizeof()` expression. Pointer arithmetic expressions implicitly scale the offset added to or subtracted from the address by the size of the pointee type. Using an offset expression that is already scaled by the size of the underlying type effectively results in a squared offset, which is likely an invalid pointer that points beyond the end of the intended array. ```c void printEveryEvenIndexElement(int *Array, size_t N) { int *P = Array; while (P <= Array + N * sizeof(int)) { // Suspicious pointer arithmetics using sizeof()! printf("%d ", *P); P += 2 * sizeof(int); // Suspicious pointer arithmetics using sizeof()! } } ``` --------- Co-authored-by: Whisperity <[email protected]>
1 parent f4172f6 commit 267ad43

File tree

12 files changed

+649
-20
lines changed

12 files changed

+649
-20
lines changed

clang-tools-extra/clang-tidy/bugprone/SizeofExpressionCheck.cpp

Lines changed: 91 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ AST_MATCHER_P2(Expr, hasSizeOfDescendant, int, Depth,
4848
return false;
4949
}
5050

51+
AST_MATCHER(Expr, offsetOfExpr) { return isa<OffsetOfExpr>(Node); }
52+
5153
CharUnits getSizeOfType(const ASTContext &Ctx, const Type *Ty) {
5254
if (!Ty || Ty->isIncompleteType() || Ty->isDependentType() ||
5355
isa<DependentSizedArrayType>(Ty) || !Ty->isConstantSizeType())
@@ -221,17 +223,15 @@ void SizeofExpressionCheck::registerMatchers(MatchFinder *Finder) {
221223
const auto ElemType =
222224
arrayType(hasElementType(recordType().bind("elem-type")));
223225
const auto ElemPtrType = pointerType(pointee(type().bind("elem-ptr-type")));
226+
const auto SizeofDivideExpr = binaryOperator(
227+
hasOperatorName("/"),
228+
hasLHS(
229+
ignoringParenImpCasts(sizeOfExpr(hasArgumentOfType(hasCanonicalType(
230+
type(anyOf(ElemType, ElemPtrType, type())).bind("num-type")))))),
231+
hasRHS(ignoringParenImpCasts(sizeOfExpr(
232+
hasArgumentOfType(hasCanonicalType(type().bind("denom-type")))))));
224233

225-
Finder->addMatcher(
226-
binaryOperator(
227-
hasOperatorName("/"),
228-
hasLHS(ignoringParenImpCasts(sizeOfExpr(hasArgumentOfType(
229-
hasCanonicalType(type(anyOf(ElemType, ElemPtrType, type()))
230-
.bind("num-type")))))),
231-
hasRHS(ignoringParenImpCasts(sizeOfExpr(
232-
hasArgumentOfType(hasCanonicalType(type().bind("denom-type")))))))
233-
.bind("sizeof-divide-expr"),
234-
this);
234+
Finder->addMatcher(SizeofDivideExpr.bind("sizeof-divide-expr"), this);
235235

236236
// Detect expression like: sizeof(...) * sizeof(...)); most likely an error.
237237
Finder->addMatcher(binaryOperator(hasOperatorName("*"),
@@ -257,8 +257,9 @@ void SizeofExpressionCheck::registerMatchers(MatchFinder *Finder) {
257257
.bind("sizeof-sizeof-expr"),
258258
this);
259259

260-
// Detect sizeof in pointer arithmetic like: N * sizeof(S) == P1 - P2 or
261-
// (P1 - P2) / sizeof(S) where P1 and P2 are pointers to type S.
260+
// Detect sizeof usage in comparisons involving pointer arithmetics, such as
261+
// N * sizeof(T) == P1 - P2 or (P1 - P2) / sizeof(T), where P1 and P2 are
262+
// pointers to a type T.
262263
const auto PtrDiffExpr = binaryOperator(
263264
hasOperatorName("-"),
264265
hasLHS(hasType(hasUnqualifiedDesugaredType(pointerType(pointee(
@@ -285,6 +286,47 @@ void SizeofExpressionCheck::registerMatchers(MatchFinder *Finder) {
285286
hasRHS(ignoringParenImpCasts(SizeOfExpr.bind("sizeof-ptr-div-expr"))))
286287
.bind("sizeof-in-ptr-arithmetic-div"),
287288
this);
289+
290+
// SEI CERT ARR39-C. Do not add or subtract a scaled integer to a pointer.
291+
// Detect sizeof, alignof and offsetof usage in pointer arithmetics where
292+
// they are used to scale the numeric distance, which is scaled again by
293+
// the pointer arithmetic operator. This can result in forming invalid
294+
// offsets.
295+
//
296+
// Examples, where P is a pointer, N is some integer (both compile-time and
297+
// run-time): P + sizeof(T), P + sizeof(*P), P + N * sizeof(*P).
298+
//
299+
// This check does not warn on cases where the pointee type is "1 byte",
300+
// as those cases can often come from generics and also do not constitute a
301+
// problem because the size does not affect the scale used.
302+
const auto InterestingPtrTyForPtrArithmetic =
303+
pointerType(pointee(qualType().bind("pointee-type")));
304+
const auto SizeofLikeScaleExpr =
305+
expr(anyOf(unaryExprOrTypeTraitExpr(ofKind(UETT_SizeOf)),
306+
unaryExprOrTypeTraitExpr(ofKind(UETT_AlignOf)),
307+
offsetOfExpr()))
308+
.bind("sizeof-in-ptr-arithmetic-scale-expr");
309+
const auto PtrArithmeticIntegerScaleExpr = binaryOperator(
310+
hasAnyOperatorName("*", "/"),
311+
// sizeof(...) * sizeof(...) and sizeof(...) / sizeof(...) is handled
312+
// by this check on another path.
313+
hasOperands(expr(hasType(isInteger()), unless(SizeofLikeScaleExpr)),
314+
SizeofLikeScaleExpr));
315+
const auto PtrArithmeticScaledIntegerExpr =
316+
expr(anyOf(SizeofLikeScaleExpr, PtrArithmeticIntegerScaleExpr),
317+
unless(SizeofDivideExpr));
318+
319+
Finder->addMatcher(
320+
expr(anyOf(
321+
binaryOperator(hasAnyOperatorName("+", "-"),
322+
hasOperands(hasType(InterestingPtrTyForPtrArithmetic),
323+
PtrArithmeticScaledIntegerExpr))
324+
.bind("sizeof-in-ptr-arithmetic-plusminus"),
325+
binaryOperator(hasAnyOperatorName("+=", "-="),
326+
hasLHS(hasType(InterestingPtrTyForPtrArithmetic)),
327+
hasRHS(PtrArithmeticScaledIntegerExpr))
328+
.bind("sizeof-in-ptr-arithmetic-plusminus"))),
329+
this);
288330
}
289331

290332
void SizeofExpressionCheck::check(const MatchFinder::MatchResult &Result) {
@@ -409,6 +451,43 @@ void SizeofExpressionCheck::check(const MatchFinder::MatchResult &Result) {
409451
<< SizeOfExpr->getSourceRange() << E->getOperatorLoc()
410452
<< E->getLHS()->getSourceRange() << E->getRHS()->getSourceRange();
411453
}
454+
} else if (const auto *E = Result.Nodes.getNodeAs<BinaryOperator>(
455+
"sizeof-in-ptr-arithmetic-plusminus")) {
456+
const auto *PointeeTy = Result.Nodes.getNodeAs<QualType>("pointee-type");
457+
const auto *ScaleExpr =
458+
Result.Nodes.getNodeAs<Expr>("sizeof-in-ptr-arithmetic-scale-expr");
459+
const CharUnits PointeeSize = getSizeOfType(Ctx, PointeeTy->getTypePtr());
460+
const int ScaleKind = [ScaleExpr]() {
461+
if (const auto *UTTE = dyn_cast<UnaryExprOrTypeTraitExpr>(ScaleExpr))
462+
switch (UTTE->getKind()) {
463+
case UETT_SizeOf:
464+
return 0;
465+
case UETT_AlignOf:
466+
return 1;
467+
default:
468+
return -1;
469+
}
470+
471+
if (isa<OffsetOfExpr>(ScaleExpr))
472+
return 2;
473+
474+
return -1;
475+
}();
476+
477+
if (ScaleKind != -1 && PointeeSize > CharUnits::One()) {
478+
diag(E->getExprLoc(),
479+
"suspicious usage of '%select{sizeof|alignof|offsetof}0(...)' in "
480+
"pointer arithmetic; this scaled value will be scaled again by the "
481+
"'%1' operator")
482+
<< ScaleKind << E->getOpcodeStr() << ScaleExpr->getSourceRange();
483+
diag(E->getExprLoc(),
484+
"'%0' in pointer arithmetic internally scales with 'sizeof(%1)' == "
485+
"%2",
486+
DiagnosticIDs::Note)
487+
<< E->getOpcodeStr()
488+
<< PointeeTy->getAsString(Ctx.getPrintingPolicy())
489+
<< PointeeSize.getQuantity();
490+
}
412491
}
413492
}
414493

clang-tools-extra/clang-tidy/bugprone/SizeofExpressionCheck.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
namespace clang::tidy::bugprone {
1515

16-
/// Find suspicious usages of sizeof expression.
16+
/// Find suspicious usages of sizeof expressions.
1717
///
1818
/// For the user-facing documentation see:
1919
/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/sizeof-expression.html

clang-tools-extra/clang-tidy/cert/CERTTidyModule.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "../bugprone/ReservedIdentifierCheck.h"
1515
#include "../bugprone/SignalHandlerCheck.h"
1616
#include "../bugprone/SignedCharMisuseCheck.h"
17+
#include "../bugprone/SizeofExpressionCheck.h"
1718
#include "../bugprone/SpuriouslyWakeUpFunctionsCheck.h"
1819
#include "../bugprone/SuspiciousMemoryComparisonCheck.h"
1920
#include "../bugprone/UnhandledSelfAssignmentCheck.h"
@@ -281,6 +282,9 @@ class CERTModule : public ClangTidyModule {
281282
"cert-oop58-cpp");
282283

283284
// C checkers
285+
// ARR
286+
CheckFactories.registerCheck<bugprone::SizeofExpressionCheck>(
287+
"cert-arr39-c");
284288
// CON
285289
CheckFactories.registerCheck<bugprone::SpuriouslyWakeUpFunctionsCheck>(
286290
"cert-con36-c");
@@ -332,6 +336,12 @@ class CERTModule : public ClangTidyModule {
332336
ClangTidyOptions getModuleOptions() override {
333337
ClangTidyOptions Options;
334338
ClangTidyOptions::OptionMap &Opts = Options.CheckOptions;
339+
Opts["cert-arr39-c.WarnOnSizeOfConstant"] = "false";
340+
Opts["cert-arr39-c.WarnOnSizeOfIntegerExpression"] = "false";
341+
Opts["cert-arr39-c.WarnOnSizeOfThis"] = "false";
342+
Opts["cert-arr39-c.WarnOnSizeOfCompareToConstant"] = "false";
343+
Opts["cert-arr39-c.WarnOnSizeOfPointer"] = "false";
344+
Opts["cert-arr39-c.WarnOnSizeOfPointerToAggregate"] = "false";
335345
Opts["cert-dcl16-c.NewSuffixes"] = "L;LL;LU;LLU";
336346
Opts["cert-err33-c.CheckedFunctions"] = CertErr33CCheckedFunctions;
337347
Opts["cert-err33-c.AllowCastToVoid"] = "true";

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ New checks
104104
New check aliases
105105
^^^^^^^^^^^^^^^^^
106106

107+
- New alias :doc:`cert-arr39-c <clang-tidy/checks/cert/arr39-c>` to
108+
:doc:`bugprone-sizeof-expression
109+
<clang-tidy/checks/bugprone/sizeof-expression>` was added.
110+
107111
Changes in existing checks
108112
^^^^^^^^^^^^^^^^^^^^^^^^^^
109113

@@ -115,14 +119,19 @@ Changes in existing checks
115119
<clang-tidy/checks/bugprone/forwarding-reference-overload>` check by fixing
116120
a crash when determining if an ``enable_if[_t]`` was found.
117121

118-
- Improved :doc:`cert-flp30-c<clang-tidy/checks/cert/flp30-c>` check to
122+
- Improved :doc:`bugprone-sizeof-expression
123+
<clang-tidy/checks/bugprone/sizeof-expression>` check to find suspicious
124+
usages of ``sizeof()``, ``alignof()``, and ``offsetof()`` when adding or
125+
subtracting from a pointer.
126+
127+
- Improved :doc:`cert-flp30-c <clang-tidy/checks/cert/flp30-c>` check to
119128
fix false positive that floating point variable is only used in increment
120129
expression.
121130

122131
- Improved :doc:`cppcoreguidelines-prefer-member-initializer
123-
<clang-tidy/checks/cppcoreguidelines/prefer-member-initializer>` check to avoid
124-
false positive when member initialization depends on a structured binging
125-
variable.
132+
<clang-tidy/checks/cppcoreguidelines/prefer-member-initializer>` check to
133+
avoid false positive when member initialization depends on a structured
134+
binding variable.
126135

127136
- Improved :doc:`misc-definitions-in-headers
128137
<clang-tidy/checks/misc/definitions-in-headers>` check by rewording the

clang-tools-extra/docs/clang-tidy/checks/bugprone/sizeof-expression.rst

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,103 @@ hidden through macros.
164164
memcpy(dst, buf, sizeof(INT_SZ)); // sizeof(sizeof(int)) is suspicious.
165165
}
166166

167+
Suspicious usages of 'sizeof(...)' in pointer arithmetic
168+
--------------------------------------------------------
169+
170+
Arithmetic operators on pointers automatically scale the result with the size
171+
of the pointed typed.
172+
Further use of ``sizeof`` around pointer arithmetic will typically result in an
173+
unintended result.
174+
175+
Scaling the result of pointer difference
176+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
177+
178+
Subtracting two pointers results in an integer expression (of type
179+
``ptrdiff_t``) which expresses the distance between the two pointed objects in
180+
"number of objects between".
181+
A common mistake is to think that the result is "number of bytes between", and
182+
scale the difference with ``sizeof``, such as ``P1 - P2 == N * sizeof(T)``
183+
(instead of ``P1 - P2 == N``) or ``(P1 - P2) / sizeof(T)`` instead of
184+
``P1 - P2``.
185+
186+
.. code-block:: c++
187+
188+
void splitFour(const Obj* Objs, size_t N, Obj Delimiter) {
189+
const Obj *P = Objs;
190+
while (P < Objs + N) {
191+
if (*P == Delimiter) {
192+
break;
193+
}
194+
}
195+
196+
if (P - Objs != 4 * sizeof(Obj)) { // Expecting a distance multiplied by sizeof is suspicious.
197+
error();
198+
}
199+
}
200+
201+
.. code-block:: c++
202+
203+
void iterateIfEvenLength(int *Begin, int *End) {
204+
auto N = (Begin - End) / sizeof(int); // Dividing by sizeof() is suspicious.
205+
if (N % 2)
206+
return;
207+
208+
// ...
209+
}
210+
211+
Stepping a pointer with a scaled integer
212+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
213+
214+
Conversely, when performing pointer arithmetics to add or subtract from a
215+
pointer, the arithmetic operator implicitly scales the value actually added to
216+
the pointer with the size of the pointee, as ``Ptr + N`` expects ``N`` to be
217+
"number of objects to step", and not "number of bytes to step".
218+
219+
Seeing the calculation of a pointer where ``sizeof`` appears is suspicious,
220+
and the result is typically unintended, often out of bounds.
221+
``Ptr + sizeof(T)`` will offset the pointer by ``sizeof(T)`` elements,
222+
effectively exponentiating the scaling factor to the power of 2.
223+
224+
This case also checks suspicious ``alignof`` and ``offsetof`` usages in
225+
pointer arithmetic, as both return the "size" in bytes and not elements,
226+
potentially resulting in doubly-scaled offsets.
227+
228+
.. code-block:: c++
229+
230+
void printEveryEvenIndexElement(int *Array, size_t N) {
231+
int *P = Array;
232+
while (P <= Array + N * sizeof(int)) { // Suspicious pointer arithmetic using sizeof()!
233+
printf("%d ", *P);
234+
235+
P += 2 * sizeof(int); // Suspicious pointer arithmetic using sizeof()!
236+
}
237+
}
238+
239+
.. code-block:: c++
240+
241+
struct Message { /* ... */; char Flags[8]; };
242+
void clearFlags(Message *Array, size_t N) {
243+
const Message *End = Array + N;
244+
while (Array < End) {
245+
memset(Array + offsetof(Message, Flags), // Suspicious pointer arithmetic using offsetof()!
246+
0, sizeof(Message::Flags));
247+
++Array;
248+
}
249+
}
250+
251+
For this checked bogus pattern, `cert-arr39-c` redirects here as an alias of
252+
this check.
253+
254+
This check corresponds to the CERT C Coding Standard rule
255+
`ARR39-C. Do not add or subtract a scaled integer to a pointer
256+
<http://wiki.sei.cmu.edu/confluence/display/c/ARR39-C.+Do+not+add+or+subtract+a+scaled+integer+to+a+pointer>`_.
257+
258+
Limitations
259+
"""""""""""
260+
261+
Cases where the pointee type has a size of `1` byte (such as, and most
262+
importantly, ``char``) are excluded.
263+
167264
Options
168265
-------
169266

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.. title:: clang-tidy - cert-arr39-c
2+
.. meta::
3+
:http-equiv=refresh: 5;URL=../bugprone/sizeof-expression.html
4+
5+
cert-arr39-c
6+
============
7+
8+
The `cert-arr39-c` check is an alias, please see
9+
:doc:`bugprone-sizeof-expression <../bugprone/sizeof-expression>`
10+
for more information.

clang-tools-extra/docs/clang-tidy/checks/list.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@ Check aliases
407407
:header: "Name", "Redirect", "Offers fixes"
408408

409409
:doc:`bugprone-narrowing-conversions <bugprone/narrowing-conversions>`, :doc:`cppcoreguidelines-narrowing-conversions <cppcoreguidelines/narrowing-conversions>`,
410+
:doc:`cert-arr39-c <cert/arr39-c>`, :doc:`bugprone-sizeof-expression <bugprone/sizeof-expression>`,
410411
:doc:`cert-con36-c <cert/con36-c>`, :doc:`bugprone-spuriously-wake-up-functions <bugprone/spuriously-wake-up-functions>`,
411412
:doc:`cert-con54-cpp <cert/con54-cpp>`, :doc:`bugprone-spuriously-wake-up-functions <bugprone/spuriously-wake-up-functions>`,
412413
:doc:`cert-ctr56-cpp <cert/ctr56-cpp>`, :doc:`bugprone-pointer-arithmetic-on-polymorphic-object <bugprone/pointer-arithmetic-on-polymorphic-object>`,
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// RUN: %check_clang_tidy -std=c11-or-later %s bugprone-sizeof-expression %t
2+
3+
#define alignof(type_name) _Alignof(type_name)
4+
extern void sink(const void *P);
5+
6+
enum { BufferSize = 1024 };
7+
8+
struct S {
9+
long A, B, C;
10+
};
11+
12+
void bad4d(void) {
13+
struct S Buffer[BufferSize];
14+
15+
struct S *P = &Buffer[0];
16+
struct S *Q = P;
17+
while (Q < P + alignof(Buffer)) {
18+
// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: suspicious usage of 'alignof(...)' in pointer arithmetic; this scaled value will be scaled again by the '+' operator [bugprone-sizeof-expression]
19+
// CHECK-MESSAGES: :[[@LINE-2]]:16: note: '+' in pointer arithmetic internally scales with 'sizeof(struct S)' == {{[0-9]+}}
20+
sink(Q++);
21+
}
22+
}

0 commit comments

Comments
 (0)