Skip to content

Commit 1c38c46

Browse files
authored
[clang-tidy] Make P +- BS / sizeof(*P) opt-outable in bugprone-sizeof-expression (#111178)
In some cases and for projects that deal with a lot of low-level buffers, a pattern often emerges that an array and its full size, not in the number of "elements" but in "bytes", are known with no syntax-level connection between the two values. To access the array elements, the pointer arithmetic involved will have to divide 'SizeInBytes' (a numeric value) with `sizeof(*Buffer)`. Since the previous patch introduced this new warning, potential false-positives were triggered from `bugprone-sizeof-expression`, as `sizeof` appeared in pointer arithmetic where integers are scaled. This patch adds a new check option, `WarnOnOffsetDividedBySizeOf`, which allows users to opt out of warning about the division case. In arbitrary projects, it might still be worthwhile to get these warnings until an opt-out from the detection of scaling issues, especially if a project might not be using low-level buffers intensively.
1 parent d0d0380 commit 1c38c46

File tree

6 files changed

+52
-6
lines changed

6 files changed

+52
-6
lines changed

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ SizeofExpressionCheck::SizeofExpressionCheck(StringRef Name,
7070
Options.get("WarnOnSizeOfCompareToConstant", true)),
7171
WarnOnSizeOfPointerToAggregate(
7272
Options.get("WarnOnSizeOfPointerToAggregate", true)),
73-
WarnOnSizeOfPointer(Options.get("WarnOnSizeOfPointer", false)) {}
73+
WarnOnSizeOfPointer(Options.get("WarnOnSizeOfPointer", false)),
74+
WarnOnOffsetDividedBySizeOf(
75+
Options.get("WarnOnOffsetDividedBySizeOf", true)) {}
7476

7577
void SizeofExpressionCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
7678
Options.store(Opts, "WarnOnSizeOfConstant", WarnOnSizeOfConstant);
@@ -82,6 +84,8 @@ void SizeofExpressionCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
8284
Options.store(Opts, "WarnOnSizeOfPointerToAggregate",
8385
WarnOnSizeOfPointerToAggregate);
8486
Options.store(Opts, "WarnOnSizeOfPointer", WarnOnSizeOfPointer);
87+
Options.store(Opts, "WarnOnOffsetDividedBySizeOf",
88+
WarnOnOffsetDividedBySizeOf);
8589
}
8690

8791
void SizeofExpressionCheck::registerMatchers(MatchFinder *Finder) {
@@ -307,7 +311,8 @@ void SizeofExpressionCheck::registerMatchers(MatchFinder *Finder) {
307311
offsetOfExpr()))
308312
.bind("sizeof-in-ptr-arithmetic-scale-expr");
309313
const auto PtrArithmeticIntegerScaleExpr = binaryOperator(
310-
hasAnyOperatorName("*", "/"),
314+
WarnOnOffsetDividedBySizeOf ? binaryOperator(hasAnyOperatorName("*", "/"))
315+
: binaryOperator(hasOperatorName("*")),
311316
// sizeof(...) * sizeof(...) and sizeof(...) / sizeof(...) is handled
312317
// by this check on another path.
313318
hasOperands(expr(hasType(isInteger()), unless(SizeofLikeScaleExpr)),

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class SizeofExpressionCheck : public ClangTidyCheck {
3131
const bool WarnOnSizeOfCompareToConstant;
3232
const bool WarnOnSizeOfPointerToAggregate;
3333
const bool WarnOnSizeOfPointer;
34+
const bool WarnOnOffsetDividedBySizeOf;
3435
};
3536

3637
} // namespace clang::tidy::bugprone

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ Changes in existing checks
158158
- Improved :doc:`bugprone-sizeof-expression
159159
<clang-tidy/checks/bugprone/sizeof-expression>` check to find suspicious
160160
usages of ``sizeof()``, ``alignof()``, and ``offsetof()`` when adding or
161-
subtracting from a pointer.
161+
subtracting from a pointer directly or when used to scale a numeric value.
162162

163163
- Improved :doc:`bugprone-unchecked-optional-access
164164
<clang-tidy/checks/bugprone/unchecked-optional-access>` to support

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,17 @@ and the result is typically unintended, often out of bounds.
221221
``Ptr + sizeof(T)`` will offset the pointer by ``sizeof(T)`` elements,
222222
effectively exponentiating the scaling factor to the power of 2.
223223

224+
Similarly, multiplying or dividing a numeric value with the ``sizeof`` of an
225+
element or the whole buffer is suspicious, because the dimensional connection
226+
between the numeric value and the actual ``sizeof`` result can not always be
227+
deduced.
228+
While scaling an integer up (multiplying) with ``sizeof`` is likely **always**
229+
an issue, a scaling down (division) is not always inherently dangerous, in case
230+
the developer is aware that the division happens between an appropriate number
231+
of _bytes_ and a ``sizeof`` value.
232+
Turning :option:`WarnOnOffsetDividedBySizeOf` off will restrict the
233+
warnings to the multiplication case.
234+
224235
This case also checks suspicious ``alignof`` and ``offsetof`` usages in
225236
pointer arithmetic, as both return the "size" in bytes and not elements,
226237
potentially resulting in doubly-scaled offsets.
@@ -299,3 +310,9 @@ Options
299310
``sizeof`` is an expression that produces a pointer (except for a few
300311
idiomatic expressions that are probably intentional and correct).
301312
This detects occurrences of CWE 467. Default is `false`.
313+
314+
.. option:: WarnOnOffsetDividedBySizeOf
315+
316+
When `true`, the check will warn on pointer arithmetic where the
317+
element count is obtained from a division with ``sizeof(...)``,
318+
e.g., ``Ptr + Bytes / sizeof(*T)``. Default is `true`.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// RUN: %check_clang_tidy %s bugprone-sizeof-expression %t -- \
2+
// RUN: -config='{CheckOptions: { \
3+
// RUN: bugprone-sizeof-expression.WarnOnOffsetDividedBySizeOf: false \
4+
// RUN: }}'
5+
6+
typedef __SIZE_TYPE__ size_t;
7+
8+
void situational14(int *Buffer, size_t BufferSize) {
9+
int *P = &Buffer[0];
10+
while (P < Buffer + BufferSize / sizeof(*Buffer)) {
11+
// NO-WARNING: This test opted out of "P +- N */ sizeof(...)" warnings.
12+
++P;
13+
}
14+
}

clang-tools-extra/test/clang-tidy/checkers/bugprone/sizeof-expression-pointer-arithmetics.c

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -352,21 +352,30 @@ void good13(void) {
352352
int Buffer[BufferSize];
353353

354354
int *P = &Buffer[0];
355-
while (P < (Buffer + sizeof(Buffer) / sizeof(int))) {
355+
while (P < Buffer + sizeof(Buffer) / sizeof(int)) {
356356
// NO-WARNING: Calculating the element count of the buffer here, which is
357357
// safe with this idiom (as long as the types don't change).
358358
++P;
359359
}
360360

361-
while (P < (Buffer + sizeof(Buffer) / sizeof(Buffer[0]))) {
361+
while (P < Buffer + sizeof(Buffer) / sizeof(Buffer[0])) {
362362
// NO-WARNING: Calculating the element count of the buffer here, which is
363363
// safe with this idiom.
364364
++P;
365365
}
366366

367-
while (P < (Buffer + sizeof(Buffer) / sizeof(*P))) {
367+
while (P < Buffer + sizeof(Buffer) / sizeof(*P)) {
368368
// NO-WARNING: Calculating the element count of the buffer here, which is
369369
// safe with this idiom.
370370
++P;
371371
}
372372
}
373+
374+
void situational14(int *Buffer, size_t BufferSize) {
375+
int *P = &Buffer[0];
376+
while (P < Buffer + BufferSize / sizeof(*Buffer)) {
377+
// CHECK-MESSAGES: :[[@LINE-1]]:21: warning: suspicious usage of 'sizeof(...)' in pointer arithmetic; this scaled value will be scaled again by the '+' operator
378+
// CHECK-MESSAGES: :[[@LINE-2]]:21: note: '+' in pointer arithmetic internally scales with 'sizeof(int)' == {{[0-9]+}}
379+
++P;
380+
}
381+
}

0 commit comments

Comments
 (0)