Skip to content

Commit ef7a23a

Browse files
committed
Fix #15027 (man/checkers: document remaining checkers) [skip ci]
1 parent 27eed5e commit ef7a23a

312 files changed

Lines changed: 13258 additions & 155 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# AssignmentAddressToInteger and AssignmentIntegerToAddress
2+
3+
**Message**: Assigning a pointer to an integer is not portable.<br/>
4+
**Category**: Portability<br/>
5+
**Severity**: Portability<br/>
6+
**Language**: C/C++
7+
8+
## Description
9+
10+
Code silently narrows a pointer (address) down to a plain integer type, or the other way around, via a
11+
plain assignment. On platforms where `sizeof(void*) != sizeof(int)` (most notably 64-bit platforms,
12+
where a pointer is 8 bytes and `int` is usually still 4 bytes) this loses information: part of the
13+
address is silently discarded.
14+
15+
- `AssignmentAddressToInteger`: a pointer value is assigned to a plain integer variable, for example
16+
`int i = p;`.
17+
- `AssignmentIntegerToAddress`: a plain integer value is assigned to a pointer variable, for example
18+
`int *p = i;`.
19+
20+
This checks `char`/`short`/`int` variables (not `long`/`long long`, and not `bool`, which is a common,
21+
intentional null-check idiom rather than a truncation bug). This checker only runs when the
22+
`portability` severity is enabled.
23+
24+
## Motivation
25+
26+
Storing an address in a type that is narrower than a pointer is not portable: it works by accident on
27+
platforms where the two types happen to be the same width, and silently truncates the address (or
28+
sign-extends a small integer into a bogus address) on platforms where they are not, most notably when
29+
porting 32-bit code to 64-bit.
30+
31+
## How to fix
32+
33+
Use a pointer type, or an integer type explicitly meant to hold a pointer (`intptr_t`/`uintptr_t` from
34+
`<cstdint>`), instead of a plain `int`/`char`/etc.
35+
36+
Before:
37+
```cpp
38+
int foo(int *p) {
39+
int a = p; // <- AssignmentAddressToInteger
40+
return a;
41+
}
42+
```
43+
44+
After:
45+
```cpp
46+
#include <cstdint>
47+
intptr_t foo(int *p) {
48+
intptr_t a = reinterpret_cast<intptr_t>(p);
49+
return a;
50+
}
51+
```
52+
53+
## Related checkers
54+
55+
- [CastAddressToIntegerAtReturn.md](CastAddressToIntegerAtReturn.md) - the same idea, but for a
56+
function `return` rather than a plain assignment.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# CastAddressToIntegerAtReturn and CastIntegerToAddressAtReturn
2+
3+
**Message**: Returning an address value in a function with integer return type is not portable.<br/>
4+
**Category**: Portability<br/>
5+
**Severity**: Portability<br/>
6+
**Language**: C/C++
7+
8+
## Description
9+
10+
A function silently narrows a pointer (address) down to a plain integer return type, or the other way
11+
around. On platforms where `sizeof(void*) != sizeof(int)` (most notably 64-bit platforms, where a
12+
pointer is 8 bytes and `int` is usually still 4 bytes) this loses information: part of the address is
13+
silently discarded.
14+
15+
- `CastAddressToIntegerAtReturn`: a function with an integer return type returns a pointer value, for
16+
example `int foo(char *p) { return p; }`.
17+
- `CastIntegerToAddressAtReturn`: a function with a pointer return type returns a plain integer value,
18+
for example `void* foo(int i) { return i; }`.
19+
20+
This checks `char`/`short`/`int` (not `long`/`long long`, and not `bool`, which is a common,
21+
intentional idiom rather than a truncation bug), and only when analyzing for a 64-bit target - on a
22+
32-bit target a pointer and an `int` are the same width, so returning one as the other isn't a
23+
portability problem there. This checker only runs when the `portability` severity is enabled.
24+
25+
## Motivation
26+
27+
Storing an address in a type that is narrower than a pointer is not portable: it works by accident on
28+
platforms where the two types happen to be the same width, and silently truncates the address (or
29+
sign-extends a small integer into a bogus address) on platforms where they are not, most notably when
30+
porting 32-bit code to 64-bit.
31+
32+
## How to fix
33+
34+
Use a pointer type, or an integer type explicitly meant to hold a pointer (`intptr_t`/`uintptr_t` from
35+
`<cstdint>`), instead of a plain `int`/`char`/etc.
36+
37+
Before:
38+
```cpp
39+
void* foo(int i) {
40+
return i; // <- CastIntegerToAddressAtReturn
41+
}
42+
```
43+
44+
After:
45+
```cpp
46+
#include <cstdint>
47+
void* foo(intptr_t i) {
48+
return reinterpret_cast<void*>(i);
49+
}
50+
```
51+
52+
## Related checkers
53+
54+
- [AssignmentAddressToInteger.md](AssignmentAddressToInteger.md) - the same idea, but for a plain
55+
assignment rather than a function `return`.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# IOWithoutPositioning
2+
3+
**Message**: Read and write operations without a call to a positioning function (fseek, fsetpos or rewind) or fflush in between result in undefined behaviour.<br/>
4+
**Category**: Undefined Behaviour<br/>
5+
**Severity**: Error<br/>
6+
**Language**: C/C++
7+
8+
## Description
9+
10+
A read is immediately followed by a write (or vice versa) on a file opened for both, with no
11+
`fseek()`/`fsetpos()`/`rewind()`/`fflush()` in between - the C standard says this is undefined
12+
behaviour.
13+
14+
## Motivation
15+
16+
The C standard requires a positioning call (or an `fflush()`) between a read and a following write (or
17+
vice versa) on the same read/write stream. Skipping it is undefined behaviour, even though many
18+
implementations happen to do something predictable with it. cppcheck only follows a local `FILE*`
19+
variable through straight-line code in the function that opened it; a global or member file handle, or
20+
passing the handle to another function, is enough uncertainty that it stops checking rather than guess,
21+
so this only catches the mismatches it can actually prove.
22+
23+
## How to fix
24+
25+
Before:
26+
```cpp
27+
#include <cstdio>
28+
void f() {
29+
FILE *fp = fopen("a.txt", "r+");
30+
if (!fp) return;
31+
char buf[10];
32+
fread(buf, 1, 10, fp);
33+
fwrite(buf, 1, 10, fp); // <- no seek/rewind/fflush since the read above
34+
fclose(fp);
35+
}
36+
```
37+
38+
After:
39+
```cpp
40+
#include <cstdio>
41+
void f() {
42+
FILE *fp = fopen("a.txt", "r+");
43+
if (!fp) return;
44+
char buf[10];
45+
fread(buf, 1, 10, fp);
46+
fseek(fp, 0, SEEK_CUR);
47+
fwrite(buf, 1, 10, fp);
48+
fclose(fp);
49+
}
50+
```
51+
52+
## Related checkers
53+
54+
- [useClosedFile.md](useClosedFile.md), [readWriteOnlyFile.md](readWriteOnlyFile.md),
55+
[writeReadOnlyFile.md](writeReadOnlyFile.md), [seekOnAppendedFile.md](seekOnAppendedFile.md),
56+
[incompatibleFileOpen.md](incompatibleFileOpen.md) - other checks that follow the same `FILE*` through
57+
a function.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# StlMissingComparison
2+
3+
**Message**: Missing bounds check for extra iterator increment in loop.<br/>
4+
**Category**: Undefined Behaviour<br/>
5+
**Severity**: Warning<br/>
6+
**Language**: C++
7+
8+
## Description
9+
10+
Inside a loop, the iterator is incremented a second time (in addition to the loop's own increment)
11+
without any bounds check in between, risking incrementing it past `end()`.
12+
13+
## Motivation
14+
15+
An iterator that's advanced twice per iteration without checking for `end()` in between can walk right
16+
past the end of the container - dereferencing it afterwards, or even just comparing it again, is then
17+
undefined behaviour.
18+
19+
## How to fix
20+
21+
Before:
22+
```cpp
23+
#include <set>
24+
void f(std::set<int> &ints, bool a) {
25+
for (std::set<int>::iterator it = ints.begin(); it != ints.end(); ++it) {
26+
if (a) {
27+
it++; // <- StlMissingComparison: might increment 'it' past end()
28+
}
29+
}
30+
}
31+
```
32+
33+
After: don't increment the iterator a second time inside the loop body.
34+
```cpp
35+
#include <set>
36+
void f(std::set<int> &ints, bool a) {
37+
for (std::set<int>::iterator it = ints.begin(); it != ints.end(); ++it) {
38+
if (a) {
39+
}
40+
}
41+
}
42+
```

man/checkers/UnionZeroInit.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# UnionZeroInit
2+
3+
**Message**: You are using memset() to initialize a union that also contains a member with a bigger size.<br/>
4+
**Category**: Portability<br/>
5+
**Severity**: Portability<br/>
6+
**Language**: C/C++
7+
8+
## Description
9+
10+
A union is zero-initialized (`= {0}` or `= {}`), but its largest member isn't declared first - only the
11+
first member is guaranteed to be fully written by that initializer, so the rest of the union's storage
12+
(beyond the first member's size) may not actually end up zeroed.
13+
14+
## Motivation
15+
16+
An aggregate initializer for a union only initializes the first named member. If a smaller member is
17+
listed first, the initializer only guarantees that member's bytes are zeroed - the remaining bytes,
18+
which are only reachable through a later, larger member, are left with whatever was already in memory.
19+
Code that expects the whole union to be zero can then read uninitialized bytes through the larger
20+
member.
21+
22+
## How to fix
23+
24+
Declare the union's largest member first, so a `{0}`/`{}` initializer zeroes its entire storage.
25+
26+
Before:
27+
```cpp
28+
void foo() {
29+
union { char c; int i; } bad0 = {0}; // <- 'i' (the larger member) isn't first
30+
}
31+
```
32+
33+
After:
34+
```cpp
35+
void foo() {
36+
union { int i; char c; } good0 = {0};
37+
}
38+
```
39+
40+
## Related checkers
41+
42+
- [overlappingWriteUnion.md](overlappingWriteUnion.md) - a different union-related pitfall, about
43+
reading and writing two overlapping members in the same expression.

man/checkers/accessMoved.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# accessMoved and accessForwarded
2+
3+
**Message**: Access of moved variable 'v'.<br/>
4+
**Category**: Correctness<br/>
5+
**Severity**: Warning<br/>
6+
**Language**: C++ only
7+
8+
## Description
9+
10+
A variable is read after it has been passed to `std::move()` (`accessMoved`) or `std::forward()`
11+
(`accessForwarded`) - by convention, once a value has been moved/forwarded from, its contents are
12+
unspecified and shouldn't be relied on (except to reset or destroy it).
13+
14+
## Motivation
15+
16+
`std::move()`/`std::forward()` don't themselves do anything except change how the compiler treats the
17+
expression - the actual "move" happens in whatever constructor or assignment operator the moved-from
18+
value is subsequently passed into, and its effect on the source object is entirely up to that type. By
19+
convention a moved-from object is left in a valid but unspecified state, so any code that reads its
20+
value afterwards (rather than just reassigning or destroying it) is relying on something the language
21+
doesn't guarantee.
22+
23+
## How to fix
24+
25+
Before:
26+
```cpp
27+
#include <utility>
28+
struct A {};
29+
void g(A a);
30+
void f() {
31+
A a;
32+
g(std::move(a));
33+
g(std::move(a)); // <- accessMoved: 'a' was already moved from above
34+
}
35+
```
36+
37+
After:
38+
```cpp
39+
#include <utility>
40+
struct A {};
41+
void g(A a);
42+
void f() {
43+
A a;
44+
g(std::move(a));
45+
}
46+
```
47+
48+
Before:
49+
```cpp
50+
#include <utility>
51+
template<typename T>
52+
void g(T&&);
53+
template<typename T>
54+
void f(T && t) {
55+
g(std::forward<T>(t));
56+
T s = t; // <- accessForwarded: 't' was already forwarded above
57+
}
58+
```
59+
60+
After:
61+
```cpp
62+
#include <utility>
63+
template<typename T>
64+
void g(T&&);
65+
template<typename T>
66+
void f(T && t) {
67+
T s = t;
68+
g(std::forward<T>(t));
69+
}
70+
```

man/checkers/algorithmOutOfBounds.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# algorithmOutOfBounds
22

33
**Message**: The algorithm 'std::copy' accesses 5 elements through the iterator 'v1.begin()' but only 3 elements are available.<br/>
4-
**Category**: Correctness<br/>
4+
**Category**: Undefined Behaviour<br/>
55
**Severity**: Error<br/>
66
**Language**: C++
77

@@ -11,8 +11,8 @@ Many STL algorithms take an iterator that denotes the beginning of a second rang
1111
assume that this range is large enough. If it is not, the algorithm writes or reads past the end of the container,
1212
which is undefined behavior.
1313

14-
This checker uses the ValueFlow analysis to compare the number of elements an algorithm accesses with the number of
15-
elements that are actually available through the iterator, and warns when the access is out of bounds. Three groups
14+
This checker compares the number of elements an algorithm accesses with the number of elements that
15+
are actually available through the iterator, and warns when the access is out of bounds. Three groups
1616
of algorithms are checked:
1717

1818
- Algorithms that access exactly `last1 - first1` elements through the other iterator: `std::copy`, `std::move`,

man/checkers/allocaCalled.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# allocaCalled
2+
3+
**Message**: Obsolete function 'alloca' called. In C99 and later it is recommended to use a variable length array instead.<br/>
4+
**Category**: Correctness<br/>
5+
**Severity**: Warning<br/>
6+
**Language**: C/C++
7+
8+
## Description
9+
10+
`alloca()` is called. `alloca()` allocates memory on the stack that is freed automatically when the
11+
calling function returns, but it has no standard error-handling convention (a too-large request can
12+
overflow the stack silently) and its lifetime rules are easy to get wrong (the memory becomes invalid
13+
the moment the calling function returns, even if a pointer to it is kept around).
14+
15+
## Motivation
16+
17+
Unlike `malloc()`, `alloca()` has no way to report failure - if the requested size is too large, the
18+
program's behaviour is undefined (typically a stack overflow) rather than a clean, checkable error. It's
19+
also easy to accidentally keep using the returned pointer after the function that called `alloca()` has
20+
returned, which is undefined behaviour.
21+
22+
## How to fix
23+
24+
Before:
25+
```cpp
26+
#include <alloca.h>
27+
void f(int n) {
28+
char *buf = alloca(n); // <- obsolete, no error handling if 'n' is too large
29+
}
30+
```
31+
32+
After (C99 and later):
33+
```cpp
34+
void f(int n) {
35+
char buf[n]; // variable length array
36+
}
37+
```
38+
39+
After (C++11 and later):
40+
```cpp
41+
#include <array>
42+
void f() {
43+
std::array<char, 128> buf; // fixed-size, or use a dynamically allocated container
44+
}
45+
```

0 commit comments

Comments
 (0)