diff --git a/man/checkers/AssignmentAddressToInteger.md b/man/checkers/AssignmentAddressToInteger.md new file mode 100644 index 00000000000..a482e0a516b --- /dev/null +++ b/man/checkers/AssignmentAddressToInteger.md @@ -0,0 +1,56 @@ +# AssignmentAddressToInteger and AssignmentIntegerToAddress + +**Message**: Assigning a pointer to an integer is not portable.
+**Category**: Portability
+**Severity**: Portability
+**Language**: C/C++ + +## Description + +Code silently narrows a pointer (address) down to a plain integer type, or the other way around, via a +plain assignment. On platforms where `sizeof(void*) != sizeof(int)` (most notably 64-bit platforms, +where a pointer is 8 bytes and `int` is usually still 4 bytes) this loses information: part of the +address is silently discarded. + +- `AssignmentAddressToInteger`: a pointer value is assigned to a plain integer variable, for example + `int i = p;`. +- `AssignmentIntegerToAddress`: a plain integer value is assigned to a pointer variable, for example + `int *p = i;`. + +This checks `char`/`short`/`int` variables (not `long`/`long long`, and not `bool`, which is a common, +intentional null-check idiom rather than a truncation bug). This checker only runs when the +`portability` severity is enabled. + +## Motivation + +Storing an address in a type that is narrower than a pointer is not portable: it works by accident on +platforms where the two types happen to be the same width, and silently truncates the address (or +sign-extends a small integer into a bogus address) on platforms where they are not, most notably when +porting 32-bit code to 64-bit. + +## How to fix + +Use a pointer type, or an integer type explicitly meant to hold a pointer (`intptr_t`/`uintptr_t` from +``), instead of a plain `int`/`char`/etc. + +Before: +```cpp +int foo(int *p) { + int a = p; // <- AssignmentAddressToInteger + return a; +} +``` + +After: +```cpp +#include +intptr_t foo(int *p) { + intptr_t a = reinterpret_cast(p); + return a; +} +``` + +## Related checkers + +- [CastAddressToIntegerAtReturn.md](CastAddressToIntegerAtReturn.md) - the same idea, but for a + function `return` rather than a plain assignment. diff --git a/man/checkers/CastAddressToIntegerAtReturn.md b/man/checkers/CastAddressToIntegerAtReturn.md new file mode 100644 index 00000000000..bc40eae6a81 --- /dev/null +++ b/man/checkers/CastAddressToIntegerAtReturn.md @@ -0,0 +1,55 @@ +# CastAddressToIntegerAtReturn and CastIntegerToAddressAtReturn + +**Message**: Returning an address value in a function with integer return type is not portable.
+**Category**: Portability
+**Severity**: Portability
+**Language**: C/C++ + +## Description + +A function silently narrows a pointer (address) down to a plain integer return type, or the other way +around. On platforms where `sizeof(void*) != sizeof(int)` (most notably 64-bit platforms, where a +pointer is 8 bytes and `int` is usually still 4 bytes) this loses information: part of the address is +silently discarded. + +- `CastAddressToIntegerAtReturn`: a function with an integer return type returns a pointer value, for + example `int foo(char *p) { return p; }`. +- `CastIntegerToAddressAtReturn`: a function with a pointer return type returns a plain integer value, + for example `void* foo(int i) { return i; }`. + +This checks `char`/`short`/`int` (not `long`/`long long`, and not `bool`, which is a common, +intentional idiom rather than a truncation bug), and only when analyzing for a 64-bit target - on a +32-bit target a pointer and an `int` are the same width, so returning one as the other isn't a +portability problem there. This checker only runs when the `portability` severity is enabled. + +## Motivation + +Storing an address in a type that is narrower than a pointer is not portable: it works by accident on +platforms where the two types happen to be the same width, and silently truncates the address (or +sign-extends a small integer into a bogus address) on platforms where they are not, most notably when +porting 32-bit code to 64-bit. + +## How to fix + +Use a pointer type, or an integer type explicitly meant to hold a pointer (`intptr_t`/`uintptr_t` from +``), instead of a plain `int`/`char`/etc. + +Before: +```cpp +void* foo(int i) { + return i; // <- CastIntegerToAddressAtReturn +} +``` + +After: +```cpp +#include +void* foo(intptr_t i) { + return reinterpret_cast(i); +} +``` + +## Related checkers + +- [AssignmentAddressToInteger.md](AssignmentAddressToInteger.md) - the same idea, but for a plain + assignment rather than a function `return`. diff --git a/man/checkers/IOWithoutPositioning.md b/man/checkers/IOWithoutPositioning.md new file mode 100644 index 00000000000..428eeb39c2f --- /dev/null +++ b/man/checkers/IOWithoutPositioning.md @@ -0,0 +1,57 @@ +# IOWithoutPositioning + +**Message**: Read and write operations without a call to a positioning function (fseek, fsetpos or rewind) or fflush in between result in undefined behaviour.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A read is immediately followed by a write (or vice versa) on a file opened for both, with no +`fseek()`/`fsetpos()`/`rewind()`/`fflush()` in between - the C standard says this is undefined +behaviour. + +## Motivation + +The C standard requires a positioning call (or an `fflush()`) between a read and a following write (or +vice versa) on the same read/write stream. Skipping it is undefined behaviour, even though many +implementations happen to do something predictable with it. cppcheck only follows a local `FILE*` +variable through straight-line code in the function that opened it; a global or member file handle, or +passing the handle to another function, is enough uncertainty that it stops checking rather than guess, +so this only catches the mismatches it can actually prove. + +## How to fix + +Before: +```cpp +#include +void f() { + FILE *fp = fopen("a.txt", "r+"); + if (!fp) return; + char buf[10]; + fread(buf, 1, 10, fp); + fwrite(buf, 1, 10, fp); // <- no seek/rewind/fflush since the read above + fclose(fp); +} +``` + +After: +```cpp +#include +void f() { + FILE *fp = fopen("a.txt", "r+"); + if (!fp) return; + char buf[10]; + fread(buf, 1, 10, fp); + fseek(fp, 0, SEEK_CUR); + fwrite(buf, 1, 10, fp); + fclose(fp); +} +``` + +## Related checkers + +- [useClosedFile.md](useClosedFile.md), [readWriteOnlyFile.md](readWriteOnlyFile.md), + [writeReadOnlyFile.md](writeReadOnlyFile.md), [seekOnAppendedFile.md](seekOnAppendedFile.md), + [incompatibleFileOpen.md](incompatibleFileOpen.md) - other checks that follow the same `FILE*` through + a function. diff --git a/man/checkers/StlMissingComparison.md b/man/checkers/StlMissingComparison.md new file mode 100644 index 00000000000..4e0f57bf089 --- /dev/null +++ b/man/checkers/StlMissingComparison.md @@ -0,0 +1,45 @@ +# StlMissingComparison + +**Message**: Missing bounds check for extra iterator increment in loop.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ + +## Description + +Inside a loop, the iterator is incremented a second time (in addition to the loop's own increment) +without any bounds check in between, risking incrementing it past `end()`. + +## Motivation + +An iterator that's advanced twice per iteration without checking for `end()` in between can walk right +past the end of the container - dereferencing it afterwards, or even just comparing it again, is then +undefined behaviour. This check flags the missing safety check itself, not a proven out-of-bounds +increment - if the container always happens to have enough elements left when the extra increment runs, +the loop never actually goes past `end()` in practice, even though the check that would guarantee this is +absent. + +## How to fix + +Before: +```cpp +#include +void f(std::set &ints, bool a) { + for (std::set::iterator it = ints.begin(); it != ints.end(); ++it) { + if (a) { + it++; // <- StlMissingComparison: might increment 'it' past end() + } + } +} +``` + +After: don't increment the iterator a second time inside the loop body. +```cpp +#include +void f(std::set &ints, bool a) { + for (std::set::iterator it = ints.begin(); it != ints.end(); ++it) { + if (a) { + } + } +} +``` diff --git a/man/checkers/UnionZeroInit.md b/man/checkers/UnionZeroInit.md new file mode 100644 index 00000000000..a7941aa5407 --- /dev/null +++ b/man/checkers/UnionZeroInit.md @@ -0,0 +1,43 @@ +# UnionZeroInit + +**Message**: You are using memset() to initialize a union that also contains a member with a bigger size.
+**Category**: Portability
+**Severity**: Portability
+**Language**: C/C++ + +## Description + +A union is zero-initialized (`= {0}` or `= {}`), but its largest member isn't declared first - only the +first member is guaranteed to be fully written by that initializer, so the rest of the union's storage +(beyond the first member's size) may not actually end up zeroed. + +## Motivation + +An aggregate initializer for a union only initializes the first named member. If a smaller member is +listed first, the initializer only guarantees that member's bytes are zeroed - the remaining bytes, +which are only reachable through a later, larger member, are left with whatever was already in memory. +Code that expects the whole union to be zero can then read uninitialized bytes through the larger +member. + +## How to fix + +Declare the union's largest member first, so a `{0}`/`{}` initializer zeroes its entire storage. + +Before: +```cpp +void foo() { + union { char c; int i; } bad0 = {0}; // <- 'i' (the larger member) isn't first +} +``` + +After: +```cpp +void foo() { + union { int i; char c; } good0 = {0}; +} +``` + +## Related checkers + +- [overlappingWriteUnion.md](overlappingWriteUnion.md) - a different union-related pitfall, about + reading and writing two overlapping members in the same expression. diff --git a/man/checkers/accessMoved.md b/man/checkers/accessMoved.md new file mode 100644 index 00000000000..cba7ab04298 --- /dev/null +++ b/man/checkers/accessMoved.md @@ -0,0 +1,70 @@ +# accessMoved and accessForwarded + +**Message**: Access of moved variable 'v'.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ only + +## Description + +A variable is read after it has been passed to `std::move()` (`accessMoved`) or `std::forward()` +(`accessForwarded`) - by convention, once a value has been moved/forwarded from, its contents are +unspecified and shouldn't be relied on (except to reset or destroy it). + +## Motivation + +`std::move()`/`std::forward()` don't themselves do anything except change how the compiler treats the +expression - the actual "move" happens in whatever constructor or assignment operator the moved-from +value is subsequently passed into, and its effect on the source object is entirely up to that type. By +convention a moved-from object is left in a valid but unspecified state, so any code that reads its +value afterwards (rather than just reassigning or destroying it) is relying on something the language +doesn't guarantee. + +## How to fix + +Before: +```cpp +#include +struct A {}; +void g(A a); +void f() { + A a; + g(std::move(a)); + g(std::move(a)); // <- accessMoved: 'a' was already moved from above +} +``` + +After: +```cpp +#include +struct A {}; +void g(A a); +void f() { + A a; + g(std::move(a)); +} +``` + +Before: +```cpp +#include +template +void g(T&&); +template +void f(T && t) { + g(std::forward(t)); + T s = t; // <- accessForwarded: 't' was already forwarded above +} +``` + +After: +```cpp +#include +template +void g(T&&); +template +void f(T && t) { + T s = t; + g(std::forward(t)); +} +``` diff --git a/man/checkers/algorithmOutOfBounds.md b/man/checkers/algorithmOutOfBounds.md index 8e446f82895..2a1b9d646fd 100644 --- a/man/checkers/algorithmOutOfBounds.md +++ b/man/checkers/algorithmOutOfBounds.md @@ -1,7 +1,7 @@ # algorithmOutOfBounds **Message**: The algorithm 'std::copy' accesses 5 elements through the iterator 'v1.begin()' but only 3 elements are available.
-**Category**: Correctness
+**Category**: Undefined Behaviour
**Severity**: Error
**Language**: C++ @@ -11,8 +11,8 @@ Many STL algorithms take an iterator that denotes the beginning of a second rang assume that this range is large enough. If it is not, the algorithm writes or reads past the end of the container, which is undefined behavior. -This checker uses the ValueFlow analysis to compare the number of elements an algorithm accesses with the number of -elements that are actually available through the iterator, and warns when the access is out of bounds. Three groups +This checker compares the number of elements an algorithm accesses with the number of elements that +are actually available through the iterator, and warns when the access is out of bounds. Three groups of algorithms are checked: - Algorithms that access exactly `last1 - first1` elements through the other iterator: `std::copy`, `std::move`, diff --git a/man/checkers/allocaCalled.md b/man/checkers/allocaCalled.md new file mode 100644 index 00000000000..c7d55e296ad --- /dev/null +++ b/man/checkers/allocaCalled.md @@ -0,0 +1,70 @@ +# allocaCalled + +**Message**: Obsolete function 'alloca' called. In C99 and later it is recommended to use a variable length array instead.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +`alloca()` is called. cppcheck flags every call unconditionally, regardless of the context it's used +in - it does not check, for example, whether the call is inside a loop. + +## Motivation + +`alloca()` allocates memory on the stack, but that memory is only freed when the *entire calling +function* returns - not at the end of the block or loop iteration the call happens to be in, the way a +normal local variable would be. This makes `alloca()` inside a loop a common way to exhaust the stack: +each iteration adds another allocation on top of the previous ones, and none of them are released until +the function finally returns, even though the loop itself may look perfectly ordinary. + +Unlike `malloc()`, `alloca()` also has no way to report failure - if a request is too large (whether from +one oversized call, or many small ones accumulating in a loop), the result is undefined behaviour +(typically a stack overflow) rather than a clean, checkable error. + +## How to fix + +Before: +```cpp +#include +void f(int n, int count) { + for (int i = 0; i < count; i++) { + char *buf = alloca(n); // <- each iteration's allocation piles up; none are freed until f() returns + buf[0] = 0; + } +} +``` + +After (C99 and later): a variable length array declared inside the loop body *is* freed at the end of +each iteration, unlike `alloca()`. +```cpp +void f(int n, int count) { + for (int i = 0; i < count; i++) { + char buf[n]; + buf[0] = 0; + } +} +``` + +Before: +```cpp +#include +void f(int n) { + char *buf = alloca(n); // <- obsolete, no error handling if 'n' is too large +} +``` + +After (C99 and later): +```cpp +void f(int n) { + char buf[n]; // variable length array +} +``` + +After (C++11 and later): +```cpp +#include +void f() { + std::array buf; // fixed-size, or use a dynamically allocated container +} +``` diff --git a/man/checkers/argumentSize.md b/man/checkers/argumentSize.md new file mode 100644 index 00000000000..beff9bbf326 --- /dev/null +++ b/man/checkers/argumentSize.md @@ -0,0 +1,47 @@ +# argumentSize + +**Message**: Buffer 'a' is too small, the function 'f' expects a bigger buffer in 1st argument
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A function is declared with a fixed-size array parameter (`void f(char a[10])`), and is called with an +array that's known to be smaller than that. + +## Motivation + +A parameter declared as `char a[10]` documents (and, inside the function, is used as if) an array of at +least 10 elements. Calling it with a genuinely smaller array is passed silently - the function has no +way to know the actual array it received was too small. cppcheck only compares the declared parameter +size against the size of the array actually passed - it doesn't check whether the function's body goes +on to access an element near the end of the declared size. If it does, as it's entitled to assume it +can, that access reads or writes past the real (smaller) array, which is undefined behaviour; if the +function only ever touches the first few elements in practice, this particular call happens to be +harmless despite the size mismatch. + +## How to fix + +Before: +```cpp +void f(char a[10]); +void g() { + char a[2]; + f(a); // <- 'a' is smaller than what f() expects +} +``` + +After: +```cpp +void f(char a[10]); +void g() { + char a[10]; + f(a); +} +``` + +## Related checkers + +- [ctuArrayIndex.md](ctuArrayIndex.md) - a related, whole-program check for an out-of-bounds access + reached through a function argument, including via a plain pointer parameter. diff --git a/man/checkers/arithOperationsOnVoidPointer.md b/man/checkers/arithOperationsOnVoidPointer.md new file mode 100644 index 00000000000..26cf1a7d08a --- /dev/null +++ b/man/checkers/arithOperationsOnVoidPointer.md @@ -0,0 +1,40 @@ +# arithOperationsOnVoidPointer + +**Message**: 'x' is of type 'void *'. When using void pointers in calculations, the behaviour is undefined.
+**Category**: Portability
+**Severity**: Portability
+**Language**: C/C++ + +## Description + +Pointer arithmetic (`+`, `-`, `++`, `--`, `+=`, `-=`) is performed directly on a `void*`, which isn't +standard C/C++ (also only a GNU extension). + +## Motivation + +Pointer arithmetic advances a pointer by a number of elements, each `sizeof(*p)` bytes - but `void` has +no defined size in standard C/C++, so what "one element" means for a `void*` isn't standard either. Code +relying on this compiles under gcc/clang's extension (which defines it as if `sizeof(void)` were 1) but +isn't portable to a strictly-standard-conforming compiler, and is technically undefined behaviour under +the plain standard rather than the GNU extension cppcheck's own message refers to. + +## How to fix + +Before: +```cpp +void f(void* p) { + p = p + 1; // <- arithmetic directly on a void* +} +``` + +After: +```cpp +void f(char* p) { + p = p + 1; +} +``` + +## Related checkers + +- [sizeofVoid.md](sizeofVoid.md) - another `void`-specific, non-standard construct (`sizeof(void)` / + `sizeof(*voidPointer)`). diff --git a/man/checkers/arrayIndexOutOfBounds.md b/man/checkers/arrayIndexOutOfBounds.md new file mode 100644 index 00000000000..589acbcfa41 --- /dev/null +++ b/man/checkers/arrayIndexOutOfBounds.md @@ -0,0 +1,48 @@ +# arrayIndexOutOfBounds and arrayIndexOutOfBoundsCond + +**Message**: Array 'a[10]' accessed at index 20, which is out of bounds.
+**Category**: Undefined Behaviour
+**Severity**: Error/Warning
+**Language**: C/C++ + +## Description + +An array is indexed with a value outside its bounds: + +- `arrayIndexOutOfBounds`: cppcheck knows for certain the index is outside the array's bounds. +- `arrayIndexOutOfBoundsCond`: the out-of-bounds index only holds on one branch of a condition checked + on the index variable elsewhere in the code - so either that condition is redundant, or this access + is a bug. + +## Motivation + +Reading or writing outside the bounds of an array is undefined behaviour: at best the program crashes +immediately, at worst it silently corrupts nearby memory and fails much later, or in a different run, +in a way that's very hard to trace back to the actual cause. + +## How to fix + +Before: +```cpp +void f() { + int a[10]; + a[20] = 0; // <- arrayIndexOutOfBounds +} +``` + +After: +```cpp +void f() { + int a[20]; + a[19] = 0; +} +``` + +## Related checkers + +- [pointerOutOfBounds.md](pointerOutOfBounds.md) - the pointer-arithmetic equivalent of this check. +- [ctuArrayIndex.md](ctuArrayIndex.md) - the same idea, found by cppcheck's whole-program analysis + across function calls. +- [negativeIndex.md](negativeIndex.md) - the same idea, specifically for a negative index. +- [objectIndex.md](objectIndex.md) - a related out-of-bounds access, through the address of a specific + struct member instead of an array. diff --git a/man/checkers/arrayIndexThenCheck.md b/man/checkers/arrayIndexThenCheck.md new file mode 100644 index 00000000000..a697a3af402 --- /dev/null +++ b/man/checkers/arrayIndexThenCheck.md @@ -0,0 +1,38 @@ +# arrayIndexThenCheck + +**Message**: Array index 'i' is used before limits check.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +An array is indexed with a variable, and only *afterwards* is that same variable checked against its +limits (`a[i] && i < 10`) in a way that looks like it was meant to guard the access. Even where it's +not currently a bug, this ordering means a bounds check that no longer matches the access (after later +edits) won't protect anything. + +## Motivation + +Writing the bounds check after the access it's meant to guard defeats the purpose of a short-circuiting +`&&`/`||`: by the time the check runs, the access has already happened. Even in cases where the access +happens to be safe today, this ordering is fragile - it looks protective without actually being so, and +a later edit to the bounds is easy to get wrong without anything catching it. + +## How to fix + +Before: +```cpp +void f(const char s[], int i) { + if (s[i] == 'x' && i < 20) { // <- index used before the limit check + } +} +``` + +After: +```cpp +void f(const char s[], int i) { + if (i < 20 && s[i] == 'x') { + } +} +``` diff --git a/man/checkers/assertWithSideEffect.md b/man/checkers/assertWithSideEffect.md new file mode 100644 index 00000000000..647cc3c71eb --- /dev/null +++ b/man/checkers/assertWithSideEffect.md @@ -0,0 +1,69 @@ +# assertWithSideEffect + +**Message**: Assert statement calls a function which may have desired side effects: 'foo'.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +The expression inside an `assert(...)` call calls a function that may have a side effect - for example +a non-`const` member function, or a function whose body is seen to modify a reference/pointer argument +it was given. When the function's body isn't visible, cppcheck falls back to a narrower guess based on +whether it's declared `const`/`static`, rather than warning about every unresolvable call - so this +check only flags a call when it has at least some concrete reason to suspect a side effect. + +This checker only runs when the `warning` severity is enabled. + +## Motivation + +`assert()` is compiled out entirely in release builds (when `NDEBUG` is defined). Relying on a function +call inside it to actually do something only happens in debug builds and silently vanishes in release +builds. This is a classic source of code that "works when debugging" and breaks (or does nothing) in +the shipped build. + +## How to fix + +Perform the call outside the `assert()`, and assert on the already-computed result. + +Before: +```cpp +struct Stack { + int top = 0; + bool pop(int& out) { + if (top == 0) + return false; + out = --top; + return true; + } +}; + +void foo(Stack& s) { + int value; + assert(s.pop(value)); // <- pop() only runs in debug builds +} +``` + +After: +```cpp +struct Stack { + int top = 0; + bool pop(int& out) { + if (top == 0) + return false; + out = --top; + return true; + } +}; + +void foo(Stack& s) { + int value; + bool popped = s.pop(value); + assert(popped); +} +``` + +## Related checkers + +- [assignmentInAssert.md](assignmentInAssert.md) - the same idea, but for a direct assignment inside + `assert()` rather than a function call. diff --git a/man/checkers/assignBoolToFloat.md b/man/checkers/assignBoolToFloat.md new file mode 100644 index 00000000000..d4691c002c4 --- /dev/null +++ b/man/checkers/assignBoolToFloat.md @@ -0,0 +1,32 @@ +# assignBoolToFloat + +**Message**: Boolean value assigned to floating point variable.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +A `bool` value is assigned to a `float`/`double`/`long double` variable. + +## Motivation + +Assigning `true`/`false` to a floating-point variable relies on the implicit `bool`-to-float conversion +(`1.0`/`0.0`) and usually signals a typo or a variable of the wrong type, rather than an intentional +numeric `1.0`/`0.0`. + +## How to fix + +Before: +```cpp +void f() { + double d = true; // <- likely meant a numeric value +} +``` + +After: +```cpp +void f() { + double d = 1.0; +} +``` diff --git a/man/checkers/assignBoolToPointer.md b/man/checkers/assignBoolToPointer.md new file mode 100644 index 00000000000..cde6609a1bb --- /dev/null +++ b/man/checkers/assignBoolToPointer.md @@ -0,0 +1,46 @@ +# assignBoolToPointer + +**Message**: Boolean value assigned to pointer.
+**Category**: Correctness/Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +A `bool` value is assigned directly to a pointer variable. + +## Motivation + +Assigning a `bool` to a pointer almost never expresses what a reader would expect a pointer assignment +to mean, and is usually a typo for something like assigning through the pointer (`*p = flag;`) instead +of to it. + +The two possible values are not equally risky. Assigning `false` is harmless: it converts to `0`, which +is always a well-defined way to give the pointer a null value - purely a readability/intent problem, not +a safety one. Assigning `true` is different: in C++ this pattern is normally rejected outright by the +compiler; in C it is typically accepted (at most with a warning) as an implicit conversion of the value +`1` to a pointer, which the C standard leaves implementation-defined - the resulting pointer is not +guaranteed to be aligned or to point to anything real. If that pointer is later dereferenced, the +dereference itself is undefined behaviour, because the pointer doesn't actually point to a valid object +of its type. Since the assigned value is often a variable rather than a literal, either outcome is +possible depending on what the variable holds at runtime. + +## How to fix + +Before: +```cpp +bool flag; +bool *p; +void f() { + p = flag; // <- likely meant '*p = flag;' +} +``` + +After: +```cpp +bool flag; +bool *p; +void f() { + *p = flag; +} +``` diff --git a/man/checkers/assignIfError.md b/man/checkers/assignIfError.md new file mode 100644 index 00000000000..617724ffccd --- /dev/null +++ b/man/checkers/assignIfError.md @@ -0,0 +1,40 @@ +# assignIfError + +**Message**: Mismatching assignment and comparison, comparison 'y==1' is always false.
+**Category**: Correctness
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A variable that was just assigned via a bitmask operation (`x = y & 0xf0;`) is compared against a +constant that mask could never produce. + +## Motivation + +Once a variable has been masked with `& 0xf0`, its value is constrained to the multiples of 16 that mask +allows - comparing it against a value the mask rules out (like `1`) can never match, which usually means +either the mask or the comparison value is wrong. + +## How to fix + +Before: +```cpp +void f(int x) { + int y = x & 0xf0; + if (y == 1) {} // <- 'y' can never be 1, its low bits were just masked off +} +``` + +After: +```cpp +void f(int x) { + int y = x & 0xf0; + if (y == 0x10) {} +} +``` + +## Related checkers + +- [knownConditionTrueFalse.md](knownConditionTrueFalse.md) - the more general check for a condition + whose truth value cppcheck already knows in advance. diff --git a/man/checkers/assignmentInAssert.md b/man/checkers/assignmentInAssert.md new file mode 100644 index 00000000000..bf550ae7f38 --- /dev/null +++ b/man/checkers/assignmentInAssert.md @@ -0,0 +1,46 @@ +# assignmentInAssert + +**Message**: Assert statement modifies 'x'.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +The assert expression assigns to, or increments/decrements, a variable that is also used outside the +assert - for example `assert(x = compute());`. + +This checker only runs when the `warning` severity is enabled. + +## Motivation + +`assert()` is compiled out entirely in release builds (when `NDEBUG` is defined). Any effect placed +inside it - such as assigning to a variable that's relied on afterwards - only happens in debug builds +and silently vanishes in release builds. This is a classic source of code that "works when debugging" +and breaks (or does nothing) in the shipped build. + +## How to fix + +Perform the assignment outside the `assert()`, and assert on the already-computed result. + +Before: +```cpp +void f(int y) { + int x; + assert(x = y + 1); // <- 'x' only gets its value in debug builds +} +``` + +After: +```cpp +void f(int y) { + int x; + x = y + 1; + assert(x == 1); +} +``` + +## Related checkers + +- [assertWithSideEffect.md](assertWithSideEffect.md) - the same idea, but for a function call inside + `assert()` that may have a side effect, rather than a direct assignment. diff --git a/man/checkers/assignmentInCondition.md b/man/checkers/assignmentInCondition.md new file mode 100644 index 00000000000..75920adc6b1 --- /dev/null +++ b/man/checkers/assignmentInCondition.md @@ -0,0 +1,42 @@ +# assignmentInCondition + +**Message**: Suspicious assignment in condition. Condition 't=s' is always true.
+**Category**: Correctness
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A container or iterator is assigned (`=`) directly inside a condition, where `==` was probably meant - +as written, the condition is always true. + +## Motivation + +`if (t = s)` assigns `s` to `t` and then tests the (always-true, for a container/iterator) result of +that assignment - almost certainly a typo for `if (t == s)`. Because it's valid, compiling code, this is +easy to miss in review. + +## How to fix + +Before: +```cpp +#include +void f(const std::string& s) { + std::string t; + if (t = s) {} // <- always true, did you mean '=='? +} +``` + +After: +```cpp +#include +void f(const std::string& s) { + std::string t = s; + if (!t.empty()) {} +} +``` + +## Related checkers + +- [clarifyCondition.md](clarifyCondition.md) - a related but distinct mistake: an assignment or bitwise + operator next to a comparison with ambiguous precedence. diff --git a/man/checkers/autoVariables.md b/man/checkers/autoVariables.md new file mode 100644 index 00000000000..d51b69a3d7f --- /dev/null +++ b/man/checkers/autoVariables.md @@ -0,0 +1,46 @@ +# autoVariables + +**Message**: Address of local auto-variable assigned to a function parameter.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +The address of a local variable is assigned to a pointer function *parameter* (directly, through a +struct member, or through an array element reached via that parameter), so the address leaks back to +the caller. + +## Motivation + +The assignment itself just stores an address - the trouble starts once the function returns and `num` +goes out of scope: the caller is left holding a pointer to memory it doesn't own anymore, and using that +pointer (which is the entire reason it was written out through the parameter) is undefined behaviour. +Because the memory involved is usually still intact for a little while afterwards, this kind of bug +often "works" in testing and then fails unpredictably once something else reuses that memory - which +makes it worth catching at analysis time instead of at runtime. + +## How to fix + +Before: +```cpp +void foo(int **res) { + int num = 2; + *res = # // <- autoVariables: 'num' won't exist once foo() returns +} +``` + +After: return the value itself, or allocate storage that outlives the function. +```cpp +void foo(int *res) { + int num = 2; + *res = num; +} +``` + +## Related checkers + +- [danglingLifetime.md](danglingLifetime.md) - the same idea, but for a local variable's address + escaping into a global/static/member pointer instead of a function parameter. +- [returnDanglingLifetime.md](returnDanglingLifetime.md) - the same idea, but escaping through `return` + instead of a parameter. diff --git a/man/checkers/autovarInvalidDeallocation.md b/man/checkers/autovarInvalidDeallocation.md new file mode 100644 index 00000000000..f84a7b13164 --- /dev/null +++ b/man/checkers/autovarInvalidDeallocation.md @@ -0,0 +1,35 @@ +# autovarInvalidDeallocation + +**Message**: Deallocating a pointer that was not dynamically allocated: tmp
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +`free()`/`delete` is called on something that wasn't allocated dynamically - a local array, a string +literal, or the address of a local/global/static variable. + +## Motivation + +`free()`/`delete` are only valid on memory that was actually obtained from the matching allocation +function. Calling either on something else - stack memory, a string literal, an unrelated variable's +address - is undefined behaviour, typically corrupting the memory allocator's own bookkeeping. + +## How to fix + +Before: +```cpp +void foo() { + char tmp[256]; + free(tmp); // <- autovarInvalidDeallocation: 'tmp' isn't heap-allocated +} +``` + +After: +```cpp +void foo() { + char *tmp = malloc(256); + free(tmp); +} +``` diff --git a/man/checkers/badBitmaskCheck.md b/man/checkers/badBitmaskCheck.md new file mode 100644 index 00000000000..7e6c53c16b7 --- /dev/null +++ b/man/checkers/badBitmaskCheck.md @@ -0,0 +1,46 @@ +# badBitmaskCheck + +**Message**: Result of operator '|' is always true if one operand is non-zero. Did you intend to use '&'?
+**Category**: Correctness
+**Severity**: Warning/Style
+**Language**: C/C++ + +## Description + +A boolean/condition value is computed with `|` where `&` was probably meant - the result of `x | mask` +is true as soon as `x` is nonzero, regardless of `mask`. This message is also used for the opposite, +harmless mistake: an operand `| 0` that has no effect and can be removed (reported at `style` severity +instead of `warning`). + +## Motivation + +`|` and `&` look similar but behave very differently in a boolean context: `x | mask` is true whenever +`x` alone is nonzero, so the mask contributes nothing and the check silently always passes. This is easy +to miss because the code compiles and often "looks right" at a glance. + +This check may need `--check-level=exhaustive` to see every case. + +## How to fix + +Before: +```cpp +bool f(int x) { + bool b = x | 0x02; // <- always true if x is nonzero + return b; +} +``` + +After: +```cpp +bool f(int x) { + bool b = x & 0x02; + return b; +} +``` + +## Related checkers + +- [mismatchingBitAnd.md](mismatchingBitAnd.md) - a different bitmask mistake: chained `&` masks that + share no bits. +- [comparisonError.md](comparisonError.md) - a bitwise expression compared against a constant it can + never produce. diff --git a/man/checkers/bitwiseOnBoolean.md b/man/checkers/bitwiseOnBoolean.md new file mode 100644 index 00000000000..c463b6e4d51 --- /dev/null +++ b/man/checkers/bitwiseOnBoolean.md @@ -0,0 +1,43 @@ +# bitwiseOnBoolean + +**Message**: Boolean expression 'x' is used in bitwise operation. Did you mean '&&'?
+**Category**: Readability
+**Severity**: Style (Inconclusive)
+**Language**: C/C++ (also applies to C's `_Bool`) + +## Description + +`&`/`|`/`&=`/`|=` is used where at least one operand is boolean, when `&&`/`||` was likely intended. + +## Motivation + +This checker is about readability. Readability is subjective - opinions differ about what is more +readable. Please follow your own opinion. + +When both operands are `bool`, `&`/`|` on their `0`/`1` representation happens to produce the same +truth value as `&&`/`||`, so this code usually still works correctly today. The main reason to flag it +anyway is common practice: `&&`/`||` is the conventional, unambiguous way to write boolean logic in +C/C++, while `&`/`|` is understood to mean bitwise work - so a stray single `&`/`|` reads as a likely +typo even when it happens to be harmless. There is also one real behavioural difference: `&`/`|` always +evaluates both operands, so if the other side has a side effect, using `&`/`|` instead of `&&`/`||` +changes whether that side effect happens. + +When the *other* operand isn't itself boolean (e.g. an integer flag or count), `&`/`|` combines the +boolean's `0`/`1` value with it bit-by-bit, which generally is **not** the same truth value `&&`/`||` +would produce - in that case this points at an actual logic bug, not just a style preference. + +## How to fix + +Before: +```cpp +void f(bool a, bool b) { + if (a & b) {} // <- likely meant '&&' +} +``` + +After: +```cpp +void f(bool a, bool b) { + if (a && b) {} +} +``` diff --git a/man/checkers/bufferAccessOutOfBounds.md b/man/checkers/bufferAccessOutOfBounds.md new file mode 100644 index 00000000000..9280c54d554 --- /dev/null +++ b/man/checkers/bufferAccessOutOfBounds.md @@ -0,0 +1,37 @@ +# bufferAccessOutOfBounds + +**Message**: Buffer is accessed out of bounds: d
+**Category**: Undefined Behaviour
+**Severity**: Error/Warning
+**Language**: C/C++ + +## Description + +A call to a function cppcheck has size information for (`strcpy`, `strcat`, `memcpy`, `sprintf`, and +similar, via the library configuration) is passed a buffer that's too small for what the call can read +or write. + +## Motivation + +Many standard library functions read or write a caller-supplied buffer without any bounds checking of +their own - it's entirely up to the caller to make sure the buffer is large enough. Getting this wrong +is a very common, very old source of buffer overruns in C/C++ code. + +## How to fix + +Before: +```cpp +void f() { + char d[3] = {}; + strcat(d, "12345678"); // <- 'd' can't hold this much +} +``` + +After: +```cpp +void f() { + char d[10] = {}; + strcat(d, "12345678"); +} +``` + diff --git a/man/checkers/catchExceptionByValue.md b/man/checkers/catchExceptionByValue.md new file mode 100644 index 00000000000..80f058766ab --- /dev/null +++ b/man/checkers/catchExceptionByValue.md @@ -0,0 +1,45 @@ +# catchExceptionByValue + +**Message**: Exception should be caught by reference.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ only + +## Description + +A `catch` clause catches a class type by value instead of by (const) reference. + +## Motivation + +Catching by value makes a copy of the exception object (and, if the actual thrown type is more derived +than the `catch` clause's type, slices it down, losing information) every time an exception is caught - +catching by `const&` avoids the copy entirely and preserves the exception's real, most-derived type. + +## How to fix + +Before: +```cpp +void doWork(); +void f() { + try { + doWork(); + } catch (std::exception err) { // <- caught by value + } +} +``` + +After: +```cpp +void doWork(); +void f() { + try { + doWork(); + } catch (const std::exception& err) { + } +} +``` + +## Related checkers + +- [exceptRethrowCopy.md](exceptRethrowCopy.md) - a related `catch`-clause mistake: rethrowing with + `throw x;` instead of a bare `throw;`. diff --git a/man/checkers/charBitOp.md b/man/checkers/charBitOp.md new file mode 100644 index 00000000000..3d595982829 --- /dev/null +++ b/man/checkers/charBitOp.md @@ -0,0 +1,48 @@ +# charBitOp + +**Message**: When using 'char' variables in bit operations, sign extension can generate unexpected results.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A `char` variable is used as an operand of `&`/`|`/`^` and the result is stored in a wider type +(`short`/`int`/`long`) - if the `char` is negative, sign extension fills the extra bits with `1`s +instead of `0`s, so the resulting bit pattern isn't the one that was probably intended. + +## Motivation + +On many platforms `char` can hold negative values (typically -128..127 instead of 0..255). When a +negative `char` is used in a bitwise operation with a wider type, it's first sign-extended - the extra, +high-order bits of the wider type are filled with copies of the sign bit (`1` for a negative value) +rather than `0`. Code that expects a `char`'s bit pattern to occupy only its low 8 bits and leave the +rest zero gets a surprising result whenever the `char` happens to be negative. + +## How to fix + +Use `unsigned char` (or mask the operand) if the intent is to treat the byte as a small non-negative +number. + +Before: +```cpp +void foo(int a, int *result) { + signed char ch = -1; + *result = a | ch; // <- sign extension fills the high bits of 'ch' with 1s +} +``` + +After: +```cpp +void foo(int a, int *result) { + unsigned char ch = 0xff; + *result = a | ch; +} +``` + +## Related checkers + +- [signedCharArrayIndex.md](signedCharArrayIndex.md) - the same signed-`char` sign-extension pitfall, + but for using a `char` as an array index instead of a bitwise operand. +- [checkCastIntToCharAndBack.md](checkCastIntToCharAndBack.md) - a different `char`-narrowing pitfall, + about storing `getchar()`'s return value in a `char` before comparing it with `EOF`. diff --git a/man/checkers/checkCastIntToCharAndBack.md b/man/checkers/checkCastIntToCharAndBack.md new file mode 100644 index 00000000000..23f41bba4f9 --- /dev/null +++ b/man/checkers/checkCastIntToCharAndBack.md @@ -0,0 +1,58 @@ +# checkCastIntToCharAndBack + +**Message**: Storing getchar() return value in char variable and then comparing with EOF.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +The return value of `getchar()`/`getc()`/`fgetc()` (an `int`, so it can represent every possible byte +value plus the special value `EOF`) is stored in a `char` and *then* compared with `EOF` - once narrowed +to `char`, a real byte that happens to equal the platform's `EOF` value as a `char` can be mistaken for +end-of-file, or vice versa. + +## Motivation + +`getchar()` and friends return `int` specifically so that every possible `unsigned char` byte value +(0-255) and the distinct sentinel value `EOF` can all be told apart. Storing the result in a `char` +first throws away exactly the information needed to make that distinction - depending on the platform's +`char` signedness and `EOF`'s value, a legitimate byte can end up equal to `EOF` after narrowing, causing +input to be truncated early, or (less commonly) `EOF` itself to go unrecognized. + +## How to fix + +Keep the return value in an `int` until after it has been compared with `EOF`. + +Before: +```cpp +#include +void bar(char); +void f() { + unsigned char c; + c = getchar(); + while (c != EOF) { // <- 'c' can never actually equal EOF once narrowed + bar(c); + c = getchar(); + } +} +``` + +After: +```cpp +#include +void bar(int); +void f() { + int c; + c = getchar(); + while (c != EOF) { + bar(c); + c = getchar(); + } +} +``` + +## Related checkers + +- [charBitOp.md](charBitOp.md) - a different `char`-signedness pitfall, about sign extension in bitwise + operations rather than narrowing a stream-read result. diff --git a/man/checkers/checkLibraryUseIgnore.md b/man/checkers/checkLibraryUseIgnore.md new file mode 100644 index 00000000000..ace4e9ec3e6 --- /dev/null +++ b/man/checkers/checkLibraryUseIgnore.md @@ -0,0 +1,29 @@ +# checkLibraryUseIgnore + +**Message**: --check-library: Function f() should have / configuration
+**Category**: Configuration
+**Severity**: Information
+**Language**: C/C++ + +## Description + +Only produced with `--check-library`: a function call couldn't be classified as either using or +ignoring a tracked (allocated) variable passed into it. This is aimed at people writing library +configurations, not at application code. + +## Motivation + +cppcheck's leak-tracking checks ([memleak.md](memleak.md) and its siblings) rely on knowing, for every +function a tracked variable is passed to, whether that function takes ownership of it, merely reads it, +or does something else. When a library configuration doesn't say, `--check-library` flags the gap so +the configuration can be completed rather than silently guessing. + +## How to fix + +Add a `` or `` entry for the function in the relevant library configuration file, so +cppcheck knows how it treats the argument. + +## Related checkers + +- [memleak.md](memleak.md) and its siblings - the actual leak/double-free/use-after-free checks that + this configuration gap affects the accuracy of. diff --git a/man/checkers/clarifyCalculation.md b/man/checkers/clarifyCalculation.md new file mode 100644 index 00000000000..11d6da450e8 --- /dev/null +++ b/man/checkers/clarifyCalculation.md @@ -0,0 +1,41 @@ +# clarifyCalculation + +**Message**: Clarify calculation precedence for '*' and '?'.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A calculation (`a * b`, `a + b`, ...) sits directly to the left of `?` in a ternary expression +(`a * b ? c : d`) - it's easy to misread this as `a * (b ? c : d)`, but it's actually `(a * b) ? c : d`. + +## Motivation + +The code compiles exactly as written and does exactly what the operator precedence rules say, but that's +not always what a quick read suggests - a reader can easily assume the ternary binds to the calculation's +last operand rather than to the whole calculation. Adding parentheses costs nothing and removes the +ambiguity for the next reader. + +## How to fix + +Add parentheses that make the actual grouping explicit. + +Before: +```cpp +int f(char c) { + return 10 * (c == 0) ? 1 : 2; // <- looks like '10 * ((c == 0) ? 1 : 2)' +} +``` + +After: +```cpp +int f(char c) { + return (10 * (c == 0)) ? 1 : 2; +} +``` + +## Related checkers + +- [clarifyStatement.md](clarifyStatement.md) - a different easy-to-misread-precedence pitfall, about + `*p++;` rather than a calculation next to `?`. diff --git a/man/checkers/clarifyCondition.md b/man/checkers/clarifyCondition.md new file mode 100644 index 00000000000..cf5095956a3 --- /dev/null +++ b/man/checkers/clarifyCondition.md @@ -0,0 +1,42 @@ +# clarifyCondition + +**Message**: Suspicious condition (assignment + comparison); Clarify expression with parentheses.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +An assignment or a bitwise operator sits next to a comparison in a way whose precedence is easy to +misread (`x = a < 0`, `a & b == c`) - cppcheck suggests adding parentheses to make the intended meaning +explicit. + +## Motivation + +`x = a < 0` really means `x = (a < 0)` (assign a boolean), not `(x = a) < 0` - but a reader skimming the +line can easily assume the opposite. Adding parentheses costs nothing and removes any ambiguity about +which reading was intended. + +## How to fix + +Before: +```cpp +void f(int a) { + int x; + if (x = a < 0) {} // <- is this '(x = a) < 0' or 'x = (a < 0)'? +} +``` + +After: +```cpp +void f(int a) { + int x; + x = (a < 0); + if (x) {} +} +``` + +## Related checkers + +- [assignmentInCondition.md](assignmentInCondition.md) - a related but distinct mistake: a container or + iterator assignment written where a comparison was probably meant. diff --git a/man/checkers/clarifyStatement.md b/man/checkers/clarifyStatement.md new file mode 100644 index 00000000000..728b5f9dd53 --- /dev/null +++ b/man/checkers/clarifyStatement.md @@ -0,0 +1,43 @@ +# clarifyStatement + +**Message**: In expression like '*A++' the result of '*' is unused. Did you intend to write '(*A)++;'?
+**Category**: Code Quality
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A statement like `*p++;` looks like it dereferences the incremented pointer, but `++` binds tighter +than the dereference - the pointer is incremented and the dereferenced (old) value is simply discarded. + +## Motivation + +`*p++` is parsed as `*(p++)`: the pointer itself is incremented, and the value that was pointed to +before the increment is read and then thrown away, since the statement doesn't do anything with it. A +reader skimming the code can easily assume the intent was to modify what the pointer points to +(`(*p)++`), which is a different operation entirely. + +## How to fix + +Add parentheses to make the intended operation explicit. + +Before: +```cpp +char* f(char* c) { + *c++; // <- increments 'c' and discards the old *c, doesn't touch what 'c' points to + return c; +} +``` + +After: +```cpp +char* f(char* c) { + (*c)++; + return c; +} +``` + +## Related checkers + +- [clarifyCalculation.md](clarifyCalculation.md) - a different easy-to-misread-precedence pitfall, about + a calculation next to `?` rather than `*p++`. diff --git a/man/checkers/commaSeparatedReturn.md b/man/checkers/commaSeparatedReturn.md new file mode 100644 index 00000000000..faa1981c894 --- /dev/null +++ b/man/checkers/commaSeparatedReturn.md @@ -0,0 +1,48 @@ +# commaSeparatedReturn + +**Message**: Comma is used in return statement. The comma can easily be misread as a ';'.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A comma appears inside a `return` statement (`return a + 1, b++;`) - this is a single statement using +the comma operator, easily misread as `return a + 1;` followed by a separate `b++;`. + +**This check is currently switched off in cppcheck and never produces a warning**, even for the exact +code pattern it's meant to catch - it's kept in the error list only so its ID stays recognized (for +example in older suppression files), not because it's active. Don't rely on it to catch this mistake. + +## Motivation + +The comma operator lets one statement evaluate several expressions in sequence, discarding all but the +last one's value - `return a + 1, b++;` actually returns the value of `b++`, not `a + 1`. This reads +exactly like two separate statements with a typo'd semicolon, which is a very easy way to misjudge what +value a function returns. + +## How to fix + +Split the comma expression into separate statements, and make the intended return value explicit. + +Before: +```cpp +int f(int x, int a, int b) { + if (x) + return a + 1, // looks like this is returned... + b++; // ...but this is actually returned instead + return 0; +} +``` + +After: +```cpp +int f(int x, int a, int b) { + if (x) { + b++; + return b; + } + return 0; +} +``` + diff --git a/man/checkers/compareBoolExpressionWithInt.md b/man/checkers/compareBoolExpressionWithInt.md new file mode 100644 index 00000000000..5379e5b7d50 --- /dev/null +++ b/man/checkers/compareBoolExpressionWithInt.md @@ -0,0 +1,22 @@ +# compareBoolExpressionWithInt + +**Message**: Comparison of a boolean expression with an integer other than 0 or 1.
+**Category**: Code Quality
+**Severity**: Warning
+**Language**: C++ + +## Description + +A boolean expression (not necessarily a `bool` variable - for example `a && b`, or a comparison) is +compared against a number. + +## Motivation + +A boolean expression only ever evaluates to `0` or `1`, so comparing it against any other number is +always false, and comparing it against `0`/`1` with a relational operator is easy to get backwards - +either way, this is rarely what the code's author actually intended. + +## How to fix + +Compare the boolean expression directly, or with `==`/`!=` against `true`/`false`, instead of against +an arbitrary integer. diff --git a/man/checkers/comparePointers.md b/man/checkers/comparePointers.md new file mode 100644 index 00000000000..57de49f3b1e --- /dev/null +++ b/man/checkers/comparePointers.md @@ -0,0 +1,61 @@ +# comparePointers and subtractPointers + +**Message**: Comparing pointers that point to different objects
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ only + +## Description + +Two pointers that cppcheck can see point into two different, unrelated variables or objects are +compared (`<`, `>`, `<=`, `>=` - `comparePointers`) or subtracted (`-` - `subtractPointers`). Only +pointers into the same array/object have a meaningful order or distance between them. + +## Motivation + +Comparing or subtracting pointers that don't point into the same array or object is undefined +behaviour: there's no guarantee about how unrelated objects are laid out in memory relative to each +other, so the result of `<`/`>`/`-` between them isn't meaningful, even though the code compiles and +often looks reasonable. + +## How to fix + +Only compare or subtract pointers that point into the same array or object. + +Before: +```cpp +bool f() { + int x = 0; + int y = 0; + int* xp = &x; + int* yp = &y; + return xp > yp; // <- comparePointers: 'x' and 'y' are unrelated variables +} +``` + +After: +```cpp +bool f() { + int arr[2] = {0, 0}; + int* xp = &arr[0]; + int* yp = &arr[1]; + return xp > yp; // pointers into the same array can be meaningfully compared +} +``` + +Before: +```cpp +int f() { + int x = 0; + int y = 1; + return &x - &y; // <- subtractPointers: the distance between unrelated variables is meaningless +} +``` + +After: +```cpp +int f() { + int arr[2] = {0, 1}; + return &arr[0] - &arr[1]; +} +``` diff --git a/man/checkers/compareValueOutOfTypeRangeError.md b/man/checkers/compareValueOutOfTypeRangeError.md new file mode 100644 index 00000000000..e94e748a372 --- /dev/null +++ b/man/checkers/compareValueOutOfTypeRangeError.md @@ -0,0 +1,38 @@ +# compareValueOutOfTypeRangeError + +**Message**: Comparing expression of type 'unsigned char' against value 256. Condition is always false.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A variable is compared against a constant that lies outside the range its own type can represent (for +example, an `unsigned char` compared against `256`). + +## Motivation + +An `unsigned char` can never hold `256` - it wraps around before ever reaching it - so the comparison's +result is fixed regardless of the variable's actual value. This is usually a sign the variable's type is +too small for what it's meant to hold, or that the compared-against constant is wrong. + +## How to fix + +Before: +```cpp +void f(unsigned char c) { + if (c == 256) {} // <- 'c' can never reach 256 +} +``` + +After: +```cpp +void f(int c) { + if (c == 256) {} +} +``` + +## Related checkers + +- [knownConditionTrueFalse.md](knownConditionTrueFalse.md) - the more general check for a condition + whose truth value cppcheck already knows in advance. diff --git a/man/checkers/comparisonError.md b/man/checkers/comparisonError.md new file mode 100644 index 00000000000..dc0cab73b6c --- /dev/null +++ b/man/checkers/comparisonError.md @@ -0,0 +1,37 @@ +# comparisonError + +**Message**: Expression '(X & 0x7) == 0x8' is always false.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A bitwise `&`/`|` expression is compared against a constant that mask could never actually produce. + +## Motivation + +Masking a value with `& 0x07` can only ever produce a result in `0..7`; comparing that result against +`8` (or any value the mask can't produce) is either dead code or a sign the mask or the compared-against +constant is wrong. + +## How to fix + +Before: +```cpp +void f(int a) { + if ((a & 0x07) == 8) {} // <- masking with 0x07 can never produce 8 +} +``` + +After: +```cpp +void f(int a) { + if ((a & 0x0f) == 8) {} +} +``` + +## Related checkers + +- [badBitmaskCheck.md](badBitmaskCheck.md) - `|` used where `&` was probably meant. +- [mismatchingBitAnd.md](mismatchingBitAnd.md) - chained `&` masks that share no bits. diff --git a/man/checkers/comparisonFunctionIsAlwaysTrueOrFalse.md b/man/checkers/comparisonFunctionIsAlwaysTrueOrFalse.md new file mode 100644 index 00000000000..7ee231c0650 --- /dev/null +++ b/man/checkers/comparisonFunctionIsAlwaysTrueOrFalse.md @@ -0,0 +1,37 @@ +# comparisonFunctionIsAlwaysTrueOrFalse + +**Message**: Comparison of two identical variables with isless(x,x) always evaluates to false.
+**Category**: Code Quality
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +One of the C99 comparison macros (`isgreater`, `isless`, `islessgreater`, `isgreaterequal`, +`islessequal`) is called with the exact same variable as both arguments, which always evaluates to the +same result. + +## Motivation + +Comparing a value against itself with a strict-ordering macro always produces the same, fixed answer - +so the call doesn't test anything, and is almost always a copy-paste mistake where the second argument +should have been a different variable. + +## How to fix + +Before: +```cpp +#include +bool f(int x) { + return isless(x,x); // <- always false +} +``` + +After: +```cpp +#include +bool f(int x, int y) { + return isless(x,y); +} +``` + diff --git a/man/checkers/comparisonOfBoolWithBoolError.md b/man/checkers/comparisonOfBoolWithBoolError.md new file mode 100644 index 00000000000..918b60f8a28 --- /dev/null +++ b/man/checkers/comparisonOfBoolWithBoolError.md @@ -0,0 +1,37 @@ +# comparisonOfBoolWithBoolError + +**Message**: Comparison of a variable having boolean value using relational (<, >, <= or >=) operator.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +Two `bool` variables are compared with `<`, `>`, `<=` or `>=`. + +## Motivation + +`bool` only has two values, so ordering two of them doesn't express anything `==`/`!=` wouldn't say +more clearly, and is easy to get backwards since `false < true` is not always the intuitive direction a +reader expects. + +## How to fix + +Before: +```cpp +void f(bool a, bool b) { + if (a < b) {} // <- relational comparison between two bools +} +``` + +After: +```cpp +void f(bool a, bool b) { + if (a != b) {} +} +``` + +## Related checkers + +- [comparisonOfBoolWithInvalidComparator.md](comparisonOfBoolWithInvalidComparator.md) - the same kind + of relational comparison, but against a `true`/`false` literal instead of another `bool` variable. diff --git a/man/checkers/comparisonOfBoolWithInvalidComparator.md b/man/checkers/comparisonOfBoolWithInvalidComparator.md new file mode 100644 index 00000000000..4fc9dafd9c9 --- /dev/null +++ b/man/checkers/comparisonOfBoolWithInvalidComparator.md @@ -0,0 +1,40 @@ +# comparisonOfBoolWithInvalidComparator + +**Message**: Comparison of a boolean value using relational operator (<, >, <= or >=).
+**Category**: Code Quality
+**Severity**: Warning
+**Language**: C++ + +## Description + +A boolean literal (`true`/`false`) is compared to something using `<`, `>`, `<=` or `>=`. + +## Motivation + +`<`, `>`, `<=` and `>=` are well-defined against a `bool` literal (`false` is `0`, `true` is `1`), so +this code already compiles and evaluates correctly - there is no functional problem to fix. The reason +to flag it is readability: `bool` only has two values, so an ordering comparison against `true`/`false` +says nothing that `==`/`!=` wouldn't say more directly, and it forces the reader to work out which of +`false`/`true` is "smaller" instead of just reading the equality check. Preferring `==`/`!=` for +two-valued types is the clearer, more idiomatic style. + +## How to fix + +Before: +```cpp +void f(bool x) { + if (x > false) {} // <- relational comparison against a bool literal +} +``` + +After: +```cpp +void f(bool x) { + if (x == true) {} +} +``` + +## Related checkers + +- [comparisonOfBoolWithBoolError.md](comparisonOfBoolWithBoolError.md) - the same kind of relational + comparison, but between two `bool` variables instead of against a literal. diff --git a/man/checkers/comparisonOfFuncReturningBoolError.md b/man/checkers/comparisonOfFuncReturningBoolError.md new file mode 100644 index 00000000000..b01a4a09e25 --- /dev/null +++ b/man/checkers/comparisonOfFuncReturningBoolError.md @@ -0,0 +1,42 @@ +# comparisonOfFuncReturningBoolError and comparisonOfTwoFuncsReturningBoolError + +**Message**: Comparison of a function returning boolean value using relational (<, >, <= or >=) operator.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +The result of a function known to return `bool` is compared with `<`, `>`, `<=` or `>=` - +`comparisonOfFuncReturningBoolError` when one side is such a call, `comparisonOfTwoFuncsReturningBoolError` +when both sides are. + +## Motivation + +`<`, `>`, `<=` and `>=` are well-defined for `bool` results (`false` is `0`, `true` is `1`), so this code +already compiles and evaluates correctly - there is no functional problem to fix. The reason to flag it +is readability: `bool` only has two values, so ordering the result of a bool-returning function says +nothing that `==`/`!=` wouldn't say more directly, and it forces the reader to work out which of +`false`/`true` is "smaller" instead of just reading the equality/logical check. Preferring `==`/`!=` (or +plain `&&`/`!`) for two-valued results is the clearer, more idiomatic style. + +## How to fix + +Before: +```cpp +bool compare1(int x); +bool compare2(int x); +void f(int x) { + if (compare1(x) > compare2(x)) {} // <- relational comparison between two bool-returning calls +} +``` + +After: +```cpp +bool compare1(int x); +bool compare2(int x); +void f(int x) { + if (compare1(x) && !compare2(x)) {} +} +``` + diff --git a/man/checkers/complexPatternError.md b/man/checkers/complexPatternError.md new file mode 100644 index 00000000000..11f6f994e04 --- /dev/null +++ b/man/checkers/complexPatternError.md @@ -0,0 +1,38 @@ +# complexPatternError + +**Message**: Found complex pattern inside Token::simpleMatch() call: "%type%"
+**Category**: Code Quality
+**Severity**: Error
+**Language**: C++ (cppcheck's own source code only) + +## Description + +A pattern given to `Token::simpleMatch()`/`Token::findsimplematch()` contains wildcard syntax +(`%type%`, `[abc]`, `a|b`, ...) that those "simple" functions don't actually interpret; they only do a +literal, word-by-word text comparison. + +This is not a general-purpose checker for arbitrary C/C++ programs - see +[simplePatternError.md](simplePatternError.md) for the shared background on this internal, +cppcheck-source-only checker. + +## Motivation + +Mixing up `Match()` with `simpleMatch()` silently loses the wildcard matching a pattern was written to +rely on - the code compiles and runs, but the comparison never matches the way the author intended, +turning into a subtle logic bug in cppcheck's own analysis. + +## How to fix + +Before: +```cpp +Token::simpleMatch(tok, "%type%"); // <- simpleMatch() won't interpret this +``` + +After: +```cpp +Token::Match(tok, "%type%"); +``` + +## Related checkers + +- [simplePatternError.md](simplePatternError.md) - the opposite mistake, using `Match()` with a pattern that has no wildcard syntax at all. diff --git a/man/checkers/constParameter.md b/man/checkers/constParameter.md new file mode 100644 index 00000000000..7f5342ea7f2 --- /dev/null +++ b/man/checkers/constParameter.md @@ -0,0 +1,48 @@ +# constParameter + +**Message**: Parameter 'x' can be declared as const
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +An array-typed function parameter is never used to modify its contents, so it could be declared as an +array of `const` elements. + +## Motivation + +A missing `const` hides a guarantee the compiler could otherwise enforce and readers could otherwise +rely on: that the function only reads the array, never modifies it. + +## How to fix + +Before: +```cpp +void f(int n, int v[42]) { // <- 'v' is only read + int j = 0; + for (int i = 0; i < n; ++i) { + j += 1; + if (j == 1) {} + } +} +``` + +After: +```cpp +void f(int n, const int v[42]) { + int j = 0; + for (int i = 0; i < n; ++i) { + j += 1; + if (j == 1) {} + } +} +``` + +## Related checkers + +- [constVariable.md](constVariable.md) - the same idea, for a local array variable instead of a parameter. +- [constParameterReference.md](constParameterReference.md) - the reference-parameter equivalent. +- [constParameterPointer.md](constParameterPointer.md) - the pointer-parameter equivalent. +- [constParameterCallback.md](constParameterCallback.md) - the same idea, but for a parameter of a + function used as a callback. diff --git a/man/checkers/constParameterCallback.md b/man/checkers/constParameterCallback.md new file mode 100644 index 00000000000..bf2009e9846 --- /dev/null +++ b/man/checkers/constParameterCallback.md @@ -0,0 +1,44 @@ +# constParameterCallback + +**Message**: Parameter 'x' can be declared with const, however it seems that 'f' is a callback function.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +Same idea as [constParameter.md](constParameter.md)/[constParameterReference.md](constParameterReference.md)/ +[constParameterPointer.md](constParameterPointer.md): a parameter is never used to modify what it refers +to, so it could be declared `const`. This variant is for when the function is used as a callback (passed +as a function pointer) - fixing it may also require adjusting whatever calls through that function +pointer. + +## Motivation + +A missing `const` hides a guarantee the compiler could otherwise enforce and readers could otherwise +rely on. The callback case is called out separately because the fix isn't purely local: the function +pointer type it's assigned to also needs to change, or the cast at the call site needs adjusting. + +## How to fix + +Before: +```cpp +#include +void dostuff(int (*cb)(std::vector&)); +int callback(std::vector& x) { return x[0]; } // <- 'x' is only read +void f() { dostuff(callback); } +``` + +After: const-ify the parameter, and the function pointer type it's passed through. +```cpp +#include +void dostuff(int (*cb)(const std::vector&)); +int callback(const std::vector& x) { return x[0]; } +void f() { dostuff(callback); } +``` + +## Related checkers + +- [constParameter.md](constParameter.md), [constParameterReference.md](constParameterReference.md), + [constParameterPointer.md](constParameterPointer.md) - the same idea, for a parameter that isn't part + of a callback function's signature. diff --git a/man/checkers/constParameterReference.md b/man/checkers/constParameterReference.md new file mode 100644 index 00000000000..8a6be03d6cc --- /dev/null +++ b/man/checkers/constParameterReference.md @@ -0,0 +1,60 @@ +# constParameterReference + +**Message**: Parameter 'x' can be declared as reference to const
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +A reference parameter is never used to modify what it refers to, so it could be declared as a reference +to `const`. + +## Motivation + +A missing `const` hides a guarantee the compiler could otherwise enforce and readers could otherwise +rely on: that the function only reads through the reference, never modifies the caller's object through +it. + +## How to fix + +Before: +```cpp +#include +auto foo(std::vector& vec, bool flag) { // <- 'vec' is only read + std::vector dummy; + std::vector::iterator iter; + if (flag) + iter = vec.begin(); + else { + dummy.push_back(42); + iter = dummy.begin(); + } + return *iter; +} +``` + +After: +```cpp +#include +auto foo(const std::vector& vec, bool flag) { + std::vector dummy; + std::vector::iterator iter; + if (flag) + iter = dummy.begin(); + else { + dummy.push_back(42); + iter = dummy.begin(); + } + return *iter; +} +``` + +## Related checkers + +- [constParameter.md](constParameter.md) - the array-parameter equivalent. +- [constParameterPointer.md](constParameterPointer.md) - the pointer-parameter equivalent. +- [constVariableReference.md](constVariableReference.md) - the same idea, for a local reference + variable instead of a parameter. +- [constParameterCallback.md](constParameterCallback.md) - the same idea, but for a parameter of a + function used as a callback. diff --git a/man/checkers/constStatement.md b/man/checkers/constStatement.md new file mode 100644 index 00000000000..60c222a12fe --- /dev/null +++ b/man/checkers/constStatement.md @@ -0,0 +1,34 @@ +# constStatement + +**Message**: Redundant code: Found a statement that has no effect.
+**Category**: Code Quality
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A statement is just a constant, a variable name, or another expression with no side effect, and its +result is discarded - the statement does nothing. + +## Motivation + +A statement that's evaluated purely for its result, with that result then thrown away, is either dead +code left over from editing (for example a comparison that was meant to be an `if`) or a missing +function call (a `;` typed where `foo();` was meant). + +## How to fix + +Before: +```cpp +void f(int x) { + x; // <- result discarded, has no effect +} +``` + +After: +```cpp +#include +void f(int x) { + printf("%d", x); +} +``` diff --git a/man/checkers/constVariable.md b/man/checkers/constVariable.md new file mode 100644 index 00000000000..ca85e979228 --- /dev/null +++ b/man/checkers/constVariable.md @@ -0,0 +1,41 @@ +# constVariable + +**Message**: Variable 'x' can be declared as const
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A local array variable is never used to modify its contents, so it could be declared as an array of +`const` elements. + +## Motivation + +A missing `const` hides a guarantee the compiler could otherwise enforce and readers could otherwise +rely on: that this array, once initialized, is never written to again. + +## How to fix + +Before: +```cpp +int f() { + static int i[1] = {}; // <- 'i' is only read + return i[0]; +} +``` + +After: +```cpp +int f() { + static const int i[1] = {}; + return i[0]; +} +``` + +## Related checkers + +- [constParameter.md](constParameter.md) - the same idea, for an array-typed function parameter instead + of a local variable. +- [constVariableReference.md](constVariableReference.md) - the reference-variable equivalent. +- [constVariablePointer.md](constVariablePointer.md) - the pointer-variable equivalent. diff --git a/man/checkers/constVariablePointer.md b/man/checkers/constVariablePointer.md new file mode 100644 index 00000000000..617cf3486bc --- /dev/null +++ b/man/checkers/constVariablePointer.md @@ -0,0 +1,45 @@ +# constVariablePointer + +**Message**: Variable 'x' can be declared as pointer to const
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A local pointer variable is never used to modify what it points to, so it could be declared as a +pointer to `const`. + +## Motivation + +A missing `const` hides a guarantee the compiler could otherwise enforce and readers could otherwise +rely on: that this pointer is only ever used to read the data it points to. + +## How to fix + +Before: +```cpp +#include +void f() { + int x = 5; + int *tm = &x; // <- 'tm' is only used to read '*tm' + printf("%d\n", *tm); +} +``` + +After: +```cpp +#include +void f() { + int x = 5; + const int *tm = &x; + printf("%d\n", *tm); +} +``` + +## Related checkers + +- [constParameterPointer.md](constParameterPointer.md) - the same idea, for a pointer parameter instead + of a local variable. +- [constVariable.md](constVariable.md) - the array-variable equivalent. +- [constVariableReference.md](constVariableReference.md) - the reference-variable equivalent. diff --git a/man/checkers/constVariableReference.md b/man/checkers/constVariableReference.md new file mode 100644 index 00000000000..3c6c1c98f0b --- /dev/null +++ b/man/checkers/constVariableReference.md @@ -0,0 +1,43 @@ +# constVariableReference + +**Message**: Variable 'x' can be declared as reference to const
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +A local reference variable is never used to modify what it refers to, so it could be declared as a +reference to `const`. + +## Motivation + +A missing `const` hides a guarantee the compiler could otherwise enforce and readers could otherwise +rely on: that this reference is only ever used to read the object it refers to. + +## How to fix + +Before: +```cpp +#include +void f(int i) { + int &j = i; // <- 'j' is only read + printf("%d\n", j); +} +``` + +After: +```cpp +#include +void f(int i) { + const int &j = i; + printf("%d\n", j); +} +``` + +## Related checkers + +- [constParameterReference.md](constParameterReference.md) - the same idea, for a reference parameter + instead of a local variable. +- [constVariable.md](constVariable.md) - the array-variable equivalent. +- [constVariablePointer.md](constVariablePointer.md) - the pointer-variable equivalent. diff --git a/man/checkers/containerOutOfBounds.md b/man/checkers/containerOutOfBounds.md new file mode 100644 index 00000000000..06d6e50defe --- /dev/null +++ b/man/checkers/containerOutOfBounds.md @@ -0,0 +1,45 @@ +# containerOutOfBounds + +**Message**: Out of bounds access in 'v\[100\]', if 'v' size is 3 and '100' is 100
+**Category**: Undefined Behaviour
+**Severity**: Error/Warning
+**Language**: C++ + +## Description + +An index or `.at()` call on a container is out of bounds for what cppcheck knows about the container's +size. + +## Motivation + +Accessing a container out of bounds is undefined behaviour - the same class of bug as an out-of-bounds +array access, but easier to introduce by accident because the container's internal storage can move or +resize without any visible syntax change at the call site. + +## How to fix + +Before: +```cpp +#include +void f() { + std::vector v(3); + v[100] = 1; // <- containerOutOfBounds +} +``` + +After: +```cpp +#include +void f() { + std::vector v(3); + v[2] = 1; +} +``` + +## Related checkers + +- [containerOutOfBoundsIndexExpression.md](containerOutOfBoundsIndexExpression.md) - the same idea, but + specifically when the index expression itself provably reaches or exceeds the container's size. +- [stlOutOfBounds.md](stlOutOfBounds.md) - the same idea, but specifically for a loop condition using + `<=` instead of `<`. +- [negativeContainerIndex.md](negativeContainerIndex.md) - the same idea, but for a negative index. diff --git a/man/checkers/containerOutOfBoundsIndexExpression.md b/man/checkers/containerOutOfBoundsIndexExpression.md new file mode 100644 index 00000000000..a435318d92f --- /dev/null +++ b/man/checkers/containerOutOfBoundsIndexExpression.md @@ -0,0 +1,39 @@ +# containerOutOfBoundsIndexExpression + +**Message**: Out of bounds access of s, index 's.size()' is out of bounds.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +An index expression itself (for example `s[s.size()]`) provably reaches or exceeds the container's own +size. + +## Motivation + +Accessing a container out of bounds is undefined behaviour. This particular shape is easy to write by +accident - `s.size()` looks like it should be the last valid position, but it is actually one past it. + +## How to fix + +Before: +```cpp +#include +void f(std::string s) { + s[s.size()] = 1; // <- containerOutOfBoundsIndexExpression: one past the last character +} +``` + +After: +```cpp +#include +void f(std::string s) { + s[s.size() - 1] = 1; +} +``` + +## Related checkers + +- [containerOutOfBounds.md](containerOutOfBounds.md) - the more general out-of-bounds container access + check that this is a special case of. diff --git a/man/checkers/copyCtorAndEqOperator.md b/man/checkers/copyCtorAndEqOperator.md new file mode 100644 index 00000000000..75a706156c9 --- /dev/null +++ b/man/checkers/copyCtorAndEqOperator.md @@ -0,0 +1,28 @@ +# copyCtorAndEqOperator + +**Message**: The class 'A' has 'copy constructor' but lack of 'operator='.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ + +## Description + +A class defines a copy constructor without also defining `operator=`, or vice versa - having a working +copy in one direction and the compiler-generated (possibly wrong) one in the other is an easy way to end +up with a broken half-copy. + +**This check is currently switched off in cppcheck** (its message was found to need clarification), so +it will not appear in current output no matter what code you give it. It's documented here only because +it remains a real, registered ID. + +## Motivation + +If a class needs custom copying logic for its copy constructor, it almost always needs the same logic +for `operator=` (and vice versa) - the compiler-generated version of whichever one is missing just +copies each member's value, which usually doesn't match the custom behaviour the other one implements. + +## Related checkers + +- [noCopyConstructor.md](noCopyConstructor.md) and [noOperatorEq.md](noOperatorEq.md) - the more + commonly-seen checks for a class that's missing one of these special member functions entirely, + rather than defining one without the other. diff --git a/man/checkers/copyCtorNoAllocation.md b/man/checkers/copyCtorNoAllocation.md new file mode 100644 index 00000000000..5797ec775b0 --- /dev/null +++ b/man/checkers/copyCtorNoAllocation.md @@ -0,0 +1,27 @@ +# copyCtorNoAllocation + +**Message**: Copy constructor does not allocate memory for member 'p' although memory has been allocated in other constructors.
+**Category**: Undefined Behaviour
+**Severity**: Warning
+**Language**: C++ + +## Description + +The intended sibling of [copyCtorPointerCopying.md](copyCtorPointerCopying.md): a copy constructor +copies a pointer without allocating new memory, checked from the allocating constructor's side instead +of the copy constructor's side. + +**This check is dead code in current cppcheck.** Its implementation is commented out in the source +(citing an unresolved report about the message needing more work), so it can never actually fire, no +matter what code you give it. It's documented here only because it remains a real, registered ID. + +## Motivation + +If another constructor allocates memory for a pointer member, a copy constructor that doesn't do the +same for that member is expected to share the same shallow-copy/double-free risk that +[copyCtorPointerCopying.md](copyCtorPointerCopying.md) describes. + +## Related checkers + +- [copyCtorPointerCopying.md](copyCtorPointerCopying.md) - the actively-working check for the same + underlying shallow-copy risk, checked from the copy constructor's side. diff --git a/man/checkers/copyCtorPointerCopying.md b/man/checkers/copyCtorPointerCopying.md new file mode 100644 index 00000000000..a397c548394 --- /dev/null +++ b/man/checkers/copyCtorPointerCopying.md @@ -0,0 +1,66 @@ +# copyCtorPointerCopying + +**Message**: Value of pointer 'p', which points to allocated memory, is copied in copy constructor instead of allocating new memory.
+**Category**: Undefined Behaviour
+**Severity**: Warning
+**Language**: C++ + +## Description + +A copy constructor copies a pointer member's *value* (so both objects now point at the same allocated +memory) instead of allocating a fresh block and copying the pointed-to data - a classic shallow-copy +bug, usually followed by a double-free once both objects are destroyed. + +## Motivation + +Once two objects share the same pointer value for a member each believes it owns, whichever is +destroyed first frees the memory out from under the other - leaving the survivor with a dangling +pointer, and destroying both eventually frees the same block twice. + +## How to fix + +Allocate a fresh block in the copy constructor and copy the pointed-to data into it, rather than copying +the pointer itself. + +Before: +```cpp +#include +#include +class F { + char *p; + F(const F &f) { + p = f.p; // <- both objects now share the same allocated block + } +public: + F(char *str) { + p = malloc(strlen(str)+1); + } + ~F(); + F& operator=(const F&f); +}; +``` + +After: +```cpp +#include +#include +class F { + char *p; +public: + F(const F &f) { + p = (char*)malloc(strlen(f.p)+1); + strcpy(p, f.p); + } + F(char *str) { + p = (char*)malloc(strlen(str)+1); + strcpy(p, str); + } + ~F(); + F& operator=(const F&f); +}; +``` + +## Related checkers + +- [noCopyConstructor.md](noCopyConstructor.md) - the related check for when a class allocates a + resource but doesn't define a copy constructor at all. diff --git a/man/checkers/coutCerrMisusage.md b/man/checkers/coutCerrMisusage.md new file mode 100644 index 00000000000..12da5db7dab --- /dev/null +++ b/man/checkers/coutCerrMisusage.md @@ -0,0 +1,34 @@ +# coutCerrMisusage + +**Message**: Invalid usage of output stream: '<< std::cout'.
+**Category**: Correctness
+**Severity**: Error
+**Language**: C++ + +## Description + +`std::cout`/`std::cerr` is streamed into itself (`std::cout << std::cout;`) - almost always a typo for +a variable that was meant to be printed. + +## Motivation + +Streaming `std::cout` into itself doesn't print anything meaningful and is essentially always a typo - +usually a variable name was meant to be there instead of the stream's own name. + +## How to fix + +Before: +```cpp +#include +void f() { + std::cout << std::cout; // <- likely a typo'd variable name +} +``` + +After: +```cpp +#include +void f() { + std::cout << "hello"; +} +``` diff --git a/man/checkers/cstyleCast.md b/man/checkers/cstyleCast.md index f05a192ced8..58c33f06985 100644 --- a/man/checkers/cstyleCast.md +++ b/man/checkers/cstyleCast.md @@ -1,4 +1,3 @@ - # cstyleCast **Message**: C-style pointer casting
@@ -23,7 +22,7 @@ This checker is about C casts that converts to/from a pointer or reference. Dangerous conversions are covered by other warnings so this ID `cstyleCast` is primarily about writing warnings for casts that are currently safe. -# Motivation +## Motivation The motivation of this checker is to modernize c++ code. @@ -38,8 +37,8 @@ Before: ```cpp struct Base{}; struct Derived: public Base {}; -void foo(Base* base) { - Base *p = (Base*)derived; // <- cstyleCast, cast from derived object to base object is safe now +void foo(Derived* derived) { + Base *p = (Base*)derived; // <- cstyleCast: casting up to a base class is always safe } ``` @@ -47,8 +46,9 @@ After: ```cpp struct Base{}; struct Derived: public Base {}; -void foo(Base* base) { - Derived *p = static_cast(base); +void foo(Derived* derived) { + Base *p = static_cast(derived); } ``` -The `static_cast` ensures that there will not be loss of constness in the future. +The `static_cast` documents the intended direction of the cast and will fail to compile if the +class hierarchy ever changes in a way that makes it invalid. diff --git a/man/checkers/ctuArrayIndex.md b/man/checkers/ctuArrayIndex.md new file mode 100644 index 00000000000..1dcde49e62d --- /dev/null +++ b/man/checkers/ctuArrayIndex.md @@ -0,0 +1,49 @@ +# ctuArrayIndex + +**Message**: Array index out of bounds; 'p' buffer size is 4 and it is accessed at offset 40.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +The same idea as [arrayIndexOutOfBounds.md](arrayIndexOutOfBounds.md), but found by cppcheck's +whole-program ("cross translation unit") analysis, which follows a buffer across function calls that +the normal, per-function analysis doesn't always chain together. Because of this, the same bug can +sometimes be reported twice - once under the plain ID and once under this `ctu`-prefixed one - for the +same line. + +## Motivation + +An out-of-bounds array access is undefined behaviour whether it's visible directly inside one function +or only becomes apparent by following a buffer through a call into another function - the whole-program +analysis exists to catch the cases a single function's view can't. + +## How to fix + +Before: +```cpp +void f(char *p) { + p[9] = 0; +} +void g() { + char buf[5]; + f(buf); // <- 'buf' is too small for what f() does with it +} +``` + +After: +```cpp +void f(char *p) { + p[9] = 0; +} +void g() { + char buf[10]; + f(buf); +} +``` + +## Related checkers + +- [arrayIndexOutOfBounds.md](arrayIndexOutOfBounds.md) - the per-function version of this check. +- [ctuPointerArith.md](ctuPointerArith.md) - the whole-program pointer-arithmetic equivalent. diff --git a/man/checkers/ctuPointerArith.md b/man/checkers/ctuPointerArith.md new file mode 100644 index 00000000000..948d5810e7b --- /dev/null +++ b/man/checkers/ctuPointerArith.md @@ -0,0 +1,44 @@ +# ctuPointerArith + +**Message**: Pointer arithmetic overflow; 'p' buffer size is 12
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +The same idea as [pointerOutOfBounds.md](pointerOutOfBounds.md), but found by cppcheck's whole-program +("cross translation unit") analysis, which follows a buffer across function calls that the normal, +per-function analysis doesn't always chain together. Because of this, the same bug can sometimes be +reported twice - once under the plain ID and once under this `ctu`-prefixed one - for the same line. + +## Motivation + +Pointer arithmetic that goes out of bounds is undefined behaviour whether it's visible directly inside +one function or only becomes apparent by following a buffer through a call into another function - the +whole-program analysis exists to catch the cases a single function's view can't. + +## How to fix + +Before: +```cpp +void dostuff(int *p) { int x = *(p + 10); } +int main() { + int arr[3]; + dostuff(arr); // <- 'arr' is too small for what dostuff() does with it +} +``` + +After: +```cpp +void dostuff(int *p) { int x = *(p + 10); } +int main() { + int arr[11]; + dostuff(arr); +} +``` + +## Related checkers + +- [pointerOutOfBounds.md](pointerOutOfBounds.md) - the per-function version of this check. +- [ctuArrayIndex.md](ctuArrayIndex.md) - the whole-program array-indexing equivalent. diff --git a/man/checkers/ctunullpointer.md b/man/checkers/ctunullpointer.md new file mode 100644 index 00000000000..c52228cb6bc --- /dev/null +++ b/man/checkers/ctunullpointer.md @@ -0,0 +1,25 @@ +# ctunullpointer + +**Message**: Null pointer dereference: p
+**Category**: Undefined Behaviour
+**Severity**: Error/Warning
+**Language**: C/C++ + +## Description + +The same idea as [nullPointer](nullPointer.md), but found by cppcheck's whole-program ("cross +translation unit") analysis, which follows a null value across several function calls that the normal, +per-function analysis doesn't always chain together on its own. + +## Motivation + +Some null-pointer bugs only become visible when tracing a value across function boundaries - for +example, a function that's given a null pointer by one of its callers, several call levels away. This +whole-program analysis catches those cases at the cost of needing every relevant source file analyzed +together. Because of this, the same bug can sometimes be reported twice - once as a plain `nullPointer` +and once as `ctunullpointer` - for the same line. + +## Related checkers + +- [nullPointer.md](nullPointer.md) - the per-function analysis this whole-program analysis complements. +- [ctunullpointerOutOfMemory.md](ctunullpointerOutOfMemory.md) - the same idea for a pointer from a failable allocation function. diff --git a/man/checkers/ctunullpointerOutOfMemory.md b/man/checkers/ctunullpointerOutOfMemory.md new file mode 100644 index 00000000000..be69e2ddb2c --- /dev/null +++ b/man/checkers/ctunullpointerOutOfMemory.md @@ -0,0 +1,28 @@ +# ctunullpointerOutOfMemory and ctunullpointerOutOfResources + +**Message**: If memory allocation fails, then there is a possible null pointer dereference: p
+**Category**: Undefined Behaviour
+**Severity**: Error/Warning
+**Language**: C/C++ + +## Description + +- `ctunullpointerOutOfMemory`: the same idea as [nullPointerOutOfMemory](nullPointerOutOfMemory.md), + but found by cppcheck's whole-program ("cross translation unit") analysis, which follows the result + of a failable memory allocation across function calls that the normal, per-function analysis doesn't + always chain together on its own. +- `ctunullpointerOutOfResources`: the same idea for a failable resource-allocating function (`fopen`, + ...) rather than a memory allocator. + +## Motivation + +Some "forgot to check the allocation" bugs only become visible when tracing the allocated pointer/handle +across function boundaries. This whole-program analysis catches those cases at the cost of needing every +relevant source file analyzed together. Because of this, the same bug can sometimes be reported twice - +once as a plain `nullPointerOutOfMemory`/`nullPointerOutOfResources` and once with the `ctu` prefix - for +the same line. + +## Related checkers + +- [nullPointerOutOfMemory.md](nullPointerOutOfMemory.md) - the per-function analysis this whole-program analysis complements. +- [ctunullpointer.md](ctunullpointer.md) - the same whole-program analysis without a specific failable allocation involved. diff --git a/man/checkers/ctuuninitvar.md b/man/checkers/ctuuninitvar.md new file mode 100644 index 00000000000..865ed3e80c4 --- /dev/null +++ b/man/checkers/ctuuninitvar.md @@ -0,0 +1,47 @@ +# ctuuninitvar + +**Message**: Using argument p that points at uninitialized variable x
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +The same idea as [uninitvar.md](uninitvar.md), but found by cppcheck's whole-program ("cross +translation unit") analysis, which follows an uninitialized value across function calls that the +normal, per-function analysis doesn't always chain together. Because of this, the same bug can +sometimes be reported twice - once as `uninitvar` and once as `ctuuninitvar` - for the same line. + +## Motivation + +Reading an uninitialized variable is undefined behaviour whether it's visible directly inside one +function or only becomes apparent by following a pointer through a call into another function - the +whole-program analysis exists to catch the cases a single function's view can't. + +## How to fix + +Before: +```cpp +void f(int *p) { + a = *p; // <- if f() is ever called with an uninitialized argument +} +int main() { + int x; + f(&x); +} +``` + +After: +```cpp +void f(int *p) { + a = *p; +} +int main() { + int x = 0; + f(&x); +} +``` + +## Related checkers + +- [uninitvar.md](uninitvar.md) - the per-function version of this check. diff --git a/man/checkers/dangerousTypeCast.md b/man/checkers/dangerousTypeCast.md index 79538103db5..f4015cd52e5 100644 --- a/man/checkers/dangerousTypeCast.md +++ b/man/checkers/dangerousTypeCast.md @@ -1,4 +1,3 @@ - # dangerousTypeCast **Message**: Potentially invalid type conversion in old-style C cast, clarify/fix with C++ cast
@@ -6,6 +5,14 @@ **Severity**: Warning
**Language**: C++, not applicable for C code +## Description + +An old-style C cast (`(Type)expr`) is used to convert between two pointer or reference types in a way +that could be an invalid conversion - for example casting between two unrelated pointer types, or down +a class hierarchy without any guarantee the object is really of the target type. A C-style cast will +silently do whatever C++ cast is needed to make the code compile (including a `reinterpret_cast`), so it +gives no indication of, or protection against, this. + ## Motivation C style casts can be dangerous in many ways: diff --git a/man/checkers/danglingLifetime.md b/man/checkers/danglingLifetime.md new file mode 100644 index 00000000000..037806b0415 --- /dev/null +++ b/man/checkers/danglingLifetime.md @@ -0,0 +1,55 @@ +# danglingLifetime + +**Message**: Non-local variable 'p' will use pointer to local variable 'x'.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +The address of a local variable/array is assigned to a *global, static, or member* pointer, so it +outlives the local variable it points to. + +## Motivation + +Using a pointer after the local variable it points to has gone out of scope is undefined behaviour. +Because the memory involved is usually still intact for a little while afterwards, this kind of bug +often "works" in testing and then fails unpredictably once something else reuses that memory - which +makes it worth catching at analysis time instead of at runtime. + +## How to fix + +Before: +```cpp +int *p; +void f() { + int x; + p = &x; // <- danglingLifetime: 'p' outlives 'x' +} +``` + +After: +```cpp +int *p; +void f() { + static int x; + p = &x; +} +``` + +Alternatively, if `p` is only meant to point at `x` temporarily and is set back to something else before +`x` disappears, cppcheck already recognizes that as safe and won't warn: +```cpp +int *p; +void f() { + int x; + p = &x; + p = nullptr; // 'p' no longer points to 'x' by the time 'x' is destroyed - not flagged +} +``` + +## Related checkers + +- [autoVariables.md](autoVariables.md) - the same idea, but for a local variable's address escaping + through a function parameter instead of a global/static/member pointer. +- [danglingReference.md](danglingReference.md) - the same idea, for a reference instead of a pointer. diff --git a/man/checkers/danglingReference.md b/man/checkers/danglingReference.md new file mode 100644 index 00000000000..c5f82d7ec7e --- /dev/null +++ b/man/checkers/danglingReference.md @@ -0,0 +1,28 @@ +# danglingReference + +**Message**: Using reference to dangling temporary.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +A reference variable that itself outlives the function (`static`, or a non-local reference parameter) +is bound to a local variable. + +## Motivation + +A reference is just another name for the object it's bound to - it doesn't keep that object alive. If +the reference itself outlives the local variable it was bound to, using it afterwards is undefined +behaviour, in the same way a dangling pointer would be. + +## How to fix + +Bind the long-lived reference to something that actually outlives the function (a `static` variable, a +global, or something the caller owns), not a plain local variable. + +## Related checkers + +- [danglingLifetime.md](danglingLifetime.md) - the same idea, for a pointer instead of a reference. +- [danglingTempReference.md](danglingTempReference.md) - a related mistake where a reference is bound + to a temporary and used after that temporary has been destroyed, within the same function. diff --git a/man/checkers/danglingTempReference.md b/man/checkers/danglingTempReference.md new file mode 100644 index 00000000000..0b85ee2d818 --- /dev/null +++ b/man/checkers/danglingTempReference.md @@ -0,0 +1,31 @@ +# danglingTempReference + +**Message**: Using reference to dangling temporary.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +A reference bound to a temporary is used after that temporary has already been destroyed. + +## Motivation + +A temporary object is normally destroyed at the end of the full expression that created it. A small set +of lifetime-extension rules let a `const&`/`&&` binding stretch that lifetime in some cases, but not all +- using the reference outside the case where extension actually applies is a use of an already-destroyed +object. + +## How to fix + +Only rely on a reference to a temporary within the same full expression that created it, or store the +value itself (by copy or move) instead of a reference to it if it needs to outlive that expression. + +## Related checkers + +- [returnTempReference.md](returnTempReference.md) - the same underlying temporary-lifetime mistake, + specifically when the reference is returned from the function. +- [danglingTemporaryLifetime.md](danglingTemporaryLifetime.md) - the same idea, for a pointer or + iterator into a temporary instead of a reference. +- [danglingReference.md](danglingReference.md) - a related mistake where a long-lived reference is + bound to a local variable (rather than a temporary) that doesn't outlive it. diff --git a/man/checkers/danglingTemporaryLifetime.md b/man/checkers/danglingTemporaryLifetime.md new file mode 100644 index 00000000000..cbc05d7dde3 --- /dev/null +++ b/man/checkers/danglingTemporaryLifetime.md @@ -0,0 +1,28 @@ +# danglingTemporaryLifetime + +**Message**: Using pointer to dangling temporary.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +A pointer or iterator into a temporary object is used after that temporary has already been destroyed. + +## Motivation + +A temporary object is normally destroyed at the end of the full expression that created it. A pointer +or iterator into it doesn't extend its lifetime the way a reference binding sometimes can - so using +one afterwards is a use of an already-destroyed object. + +## How to fix + +Only use a pointer/iterator into a temporary within the same full expression that created it, or store +the value itself (by copy or move) instead if it needs to outlive that expression. + +## Related checkers + +- [danglingTempReference.md](danglingTempReference.md) - the same idea, for a reference instead of a + pointer/iterator. +- [invalidLifetime.md](invalidLifetime.md) - the same underlying "used after its referent's scope + ended" mistake, for a pointer into a named local variable rather than a temporary. diff --git a/man/checkers/deallocret.md b/man/checkers/deallocret.md new file mode 100644 index 00000000000..eab1c68e7ce --- /dev/null +++ b/man/checkers/deallocret.md @@ -0,0 +1,40 @@ +# deallocret + +**Message**: Returning/dereferencing 'p' after it is deallocated / released
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A locally-allocated variable is returned, or dereferenced as part of a `return`, after it has already +been freed. + +## Motivation + +Returning a pointer after it's been freed hands the caller a pointer that's already invalid - using a +pointer after it's been freed is undefined behaviour regardless of which statement does it. + +## How to fix + +Before: +```cpp +int* f() { + int *p = malloc(10); + free(p); + return p; // <- deallocret +} +``` + +After: +```cpp +int* f() { + int *p = malloc(10); + return p; +} +``` + +## Related checkers + +- [deallocuse.md](deallocuse.md) - the same idea, but the freed variable is dereferenced by an ordinary + statement rather than a `return`. diff --git a/man/checkers/deallocuse.md b/man/checkers/deallocuse.md new file mode 100644 index 00000000000..dd895c37294 --- /dev/null +++ b/man/checkers/deallocuse.md @@ -0,0 +1,43 @@ +# deallocuse + +**Message**: Dereferencing 'p' after it is deallocated / released
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A locally-allocated variable is dereferenced after it has already been freed. + +## Motivation + +Using a pointer after it's been freed is undefined behaviour, and one of the more common causes of +memory corruption - the memory often still looks intact for a while afterwards, which is exactly what +makes this bug easy to miss in testing. + +## How to fix + +Before: +```cpp +void f() { + int *ptr = new int; + delete(ptr); + *ptr = 0; // <- deallocuse +} +``` + +After: +```cpp +void f() { + int *ptr = new int; + *ptr = 0; + delete(ptr); +} +``` + +## Related checkers + +- [deallocret.md](deallocret.md) - the same idea, but the freed variable is dereferenced as part of a + `return` statement. +- [doubleFree.md](doubleFree.md) - the related mistake of freeing the same variable again, rather than + dereferencing it. diff --git a/man/checkers/derefInvalidIterator.md b/man/checkers/derefInvalidIterator.md new file mode 100644 index 00000000000..a64ecd182ba --- /dev/null +++ b/man/checkers/derefInvalidIterator.md @@ -0,0 +1,70 @@ +# derefInvalidIterator and derefInvalidIteratorRedundantCheck + +**Message**: Dereference of an invalid iterator: v.begin()-1
+**Category**: Undefined Behaviour
+**Severity**: Error/Warning
+**Language**: C++ + +## Description + +An iterator is dereferenced while it could be `end()`, before `begin()`, or otherwise invalid. + +- `derefInvalidIterator`: cppcheck can tell directly that the iterator may be invalid at the point it's + dereferenced. +- `derefInvalidIteratorRedundantCheck`: there's a nearby validity check on the same iterator, but the + dereference happens where that check doesn't actually apply (for example, after the `if` that checked + it, rather than inside it) - either the check is redundant, or the dereference is a bug. + +## Motivation + +Dereferencing an iterator that doesn't currently point at a real element is undefined behaviour, and is +easy to get subtly wrong when a validity check exists in the code but is written on the wrong side of +the dereference. + +## How to fix + +Before: +```cpp +#include +void f() { + std::vector v{ 1, 2, 3 }; + v.erase(v.begin() - 1); // <- derefInvalidIterator: 'v.begin()-1' is out of bounds +} +``` + +After: +```cpp +#include +void f() { + std::vector v{ 1, 2, 3 }; + v.erase(v.begin()); +} +``` + +Before: +```cpp +#include +#include +int f(std::vector v, int i) { + auto it = std::find(v.begin(), v.end(), i); + if (it != v.end()) {} + return *it; // <- derefInvalidIteratorRedundantCheck: dereferenced outside the 'if' that checked it +} +``` + +After: +```cpp +#include +#include +int f(std::vector v, int i) { + auto it = std::find(v.begin(), v.end(), i); + if (it != v.end()) + return *it; + return -1; +} +``` + +## Related checkers + +- [eraseIteratorOutOfBounds.md](eraseIteratorOutOfBounds.md) - the equivalent check for calling + `erase()` with (rather than dereferencing) an iterator that could be out of bounds. diff --git a/man/checkers/doubleFree.md b/man/checkers/doubleFree.md new file mode 100644 index 00000000000..c732469b99f --- /dev/null +++ b/man/checkers/doubleFree.md @@ -0,0 +1,43 @@ +# doubleFree + +**Message**: Memory pointed to by 'p' is freed twice.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +The same locally-allocated variable is freed/released twice. + +## Motivation + +Freeing the same memory twice is undefined behaviour, and a common way memory-allocator corruption bugs +are introduced - it's easy to miss when the two frees are far apart, or on different branches that both +happen to execute. cppcheck only tracks an allocation through straight-line code: as soon as a loop or +`goto` appears anywhere in the function, it stops checking that function rather than risk a wrong guess, +so silence on a function with a loop in it isn't proof the code is free of double frees. + +## How to fix + +Before: +```cpp +void f() { + char *p = malloc(10); + free(p); + free(p); // <- doubleFree +} +``` + +After: +```cpp +void f() { + char *p = malloc(10); + free(p); +} +``` + +## Related checkers + +- [deallocuse.md](deallocuse.md) - the related mistake of dereferencing (rather than freeing again) + something that's already been freed. +- [memleak.md](memleak.md) - the opposite mistake: an allocation that's never freed at all. diff --git a/man/checkers/duplInheritedMember.md b/man/checkers/duplInheritedMember.md new file mode 100644 index 00000000000..e2b6f4b7d2a --- /dev/null +++ b/man/checkers/duplInheritedMember.md @@ -0,0 +1,46 @@ +# duplInheritedMember + +**Message**: The class 'Derived' defines member variable with name 'x' also defined in its parent class 'Base'.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ + +## Description + +A derived class declares a data member or (non-virtual, non-constructor/destructor) member function +with the same name as one a base class already has - the derived one hides the base one, which is +confusing and easy to do by accident in a large class hierarchy. + +## Motivation + +When a derived class redeclares a name its base class already uses, the derived member hides the base +one for code that operates on the derived type - but code that only sees the base class (through a base +pointer/reference, or inside a base-class member function) still reaches the *base* member. The two +names look identical at each call site, so which one is actually being used depends entirely on the +static type in scope at that point, which is easy to get wrong. + +## How to fix + +Rename one of the two members so there's no ambiguity about which one a given piece of code refers to. + +Before: +```cpp +class Base { + protected: + int x; +}; +struct Derived : Base { + int x; // <- hides 'Base::x' +}; +``` + +After: +```cpp +class Base { + protected: + int x; +}; +struct Derived : Base { + int y; +}; +``` diff --git a/man/checkers/duplicateAssignExpression.md b/man/checkers/duplicateAssignExpression.md new file mode 100644 index 00000000000..07318644398 --- /dev/null +++ b/man/checkers/duplicateAssignExpression.md @@ -0,0 +1,45 @@ +# duplicateAssignExpression + +**Message**: Same expression used in consecutive assignments of 'i' and 'j'.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +Two variables, declared one after another, are assigned the exact same expression - often a copy-paste +mistake where the second line should refer to something else. + +## Motivation + +Two variables initialized to the same expression right next to each other is a common outcome of +copy-pasting one line and forgetting to change one of the operands - if it's not a mistake, computing +the same value twice (rather than reusing it) is also wasteful. + +## How to fix + +Before: +```cpp +int f() __attribute__((pure)); +int g() __attribute__((pure)); +void test() { + int i = f(); + int j = f(); // <- same expression as 'i', is this a copy-paste mistake? +} +``` + +After: +```cpp +int f() __attribute__((pure)); +int g() __attribute__((pure)); +void h(int, int); +void test() { + int i = f(); + int j = g(); + h(i, j); +} +``` + +## Related checkers + +- [duplicateExpression.md](duplicateExpression.md) - the same idea for one expression compared against itself, rather than two separate assignments. diff --git a/man/checkers/duplicateBranch.md b/man/checkers/duplicateBranch.md new file mode 100644 index 00000000000..51aca89f447 --- /dev/null +++ b/man/checkers/duplicateBranch.md @@ -0,0 +1,42 @@ +# duplicateBranch + +**Message**: Found duplicate branches for 'if' and 'else'.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +The `if` and `else` branches of a statement contain identical code, so the condition makes no +difference to what runs. + +## Motivation + +If both branches do the same thing, the condition is pointless - either the branches were meant to +differ and one was copy-pasted by mistake, or the `if`/`else` itself can be removed entirely. + +## How to fix + +Before: +```cpp +void f(int a, int &b) { + if (a) + b = 1; + else + b = 1; // <- identical to the 'if' branch +} +``` + +After: +```cpp +void f(int a, int &b) { + if (a) + b = 1; + else + b = 2; +} +``` + +## Related checkers + +- [duplicateExpression.md](duplicateExpression.md) - the same "is this a copy-paste mistake?" idea, for an expression instead of a whole branch. diff --git a/man/checkers/duplicateBreak.md b/man/checkers/duplicateBreak.md new file mode 100644 index 00000000000..aa1feef6e8e --- /dev/null +++ b/man/checkers/duplicateBreak.md @@ -0,0 +1,45 @@ +# duplicateBreak + +**Message**: Consecutive return, break, continue, goto or throw statements are unnecessary.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +Two `return`/`break`/`continue`/`goto`/`throw` statements appear back to back - the second one can +never be reached. + +## Motivation + +Once the first of these statements runs, control leaves the current block immediately, so nothing +written directly after it (in the same block) can ever execute. It's dead code that's safe to delete. + +## How to fix + +Before: +```cpp +void foo(int a) { + while (1) { + if (a++ >= 100) { + break; + continue; // <- can never be reached + } + } +} +``` + +After: +```cpp +void foo(int a) { + while (1) { + if (a++ >= 100) { + break; + } + } +} +``` + +## Related checkers + +- [unreachableCode.md](unreachableCode.md) - the more general case of any code (not just another jump statement) placed after one of these statements. diff --git a/man/checkers/duplicateCondition.md b/man/checkers/duplicateCondition.md new file mode 100644 index 00000000000..caea8610c9d --- /dev/null +++ b/man/checkers/duplicateCondition.md @@ -0,0 +1,39 @@ +# duplicateCondition + +**Message**: The if condition is the same as the previous if condition
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +Two consecutive `if` statements test the exact same condition - the second one is dead code. + +## Motivation + +If the first `if` didn't change anything the condition depends on, the second, identical `if` can never +newly become true or false compared to the first - it's either always dead code, or a sign that an +assignment that was supposed to happen in between is missing. + +## How to fix + +Before: +```cpp +void f(int x) { + if (x == 1) {} + if (x == 1) {} // <- dead code +} +``` + +After: +```cpp +void f(int x) { + if (x == 1) {} +} +``` + +## Related checkers + +- [multiCondition.md](multiCondition.md) - the same idea, but for `if`/`else if` chains. +- [identicalConditionAfterEarlyExit.md](identicalConditionAfterEarlyExit.md) - the same idea, but the + first check is an early `return`/`throw`/`break`/`continue` instead of a plain `if`. diff --git a/man/checkers/duplicateConditionalAssign.md b/man/checkers/duplicateConditionalAssign.md new file mode 100644 index 00000000000..007edf870eb --- /dev/null +++ b/man/checkers/duplicateConditionalAssign.md @@ -0,0 +1,42 @@ +# duplicateConditionalAssign + +**Message**: Assignment 'x=5' is redundant with condition 'x==5'.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +An `if` assigns a value to a variable that the condition already guarantees it has (`if (x == 5) x = 5;`) - +the assignment has no effect. + +## Motivation + +If the condition already established what the variable equals, assigning that same value again inside +the `if` changes nothing - it's leftover code from an edit, or a sign the assignment was meant to use a +different value or variable. + +This check may need `--check-level=exhaustive` to see every case. + +## How to fix + +Before: +```cpp +void f(int x) { + if (x == 5) + x = 5; // <- 'x' is already 5 here +} +``` + +After: +```cpp +void f(int x) { + if (x == 5) { + } +} +``` + +## Related checkers + +- [knownConditionTrueFalse.md](knownConditionTrueFalse.md) - the more general check for a condition + whose truth value cppcheck already knows in advance. diff --git a/man/checkers/duplicateExpression.md b/man/checkers/duplicateExpression.md new file mode 100644 index 00000000000..4fba20bdbc3 --- /dev/null +++ b/man/checkers/duplicateExpression.md @@ -0,0 +1,38 @@ +# duplicateExpression + +**Message**: Same expression on both sides of '=='.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +The exact same expression appears on both sides of a comparison or other binary operator (`a == a`, +`a - a`, ...), or more than once in a chain of the same operator. + +## Motivation + +Comparing or combining an expression with an identical copy of itself always produces the same, +predetermined result (`true`, `false`, or zero) regardless of the expression's actual value - this is +almost always a typo for what should have been two different variables. + +## How to fix + +Before: +```cpp +void foo(int a) { + if (a == a) { } // <- always true +} +``` + +After: +```cpp +void foo(int a, int b) { + if (a == b) { } +} +``` + +## Related checkers + +- [oppositeExpression.md](oppositeExpression.md) - the mirror-image mistake, comparing an expression against its logical/arithmetic opposite. +- [duplicateAssignExpression.md](duplicateAssignExpression.md) - the same idea across two separate variable assignments. diff --git a/man/checkers/duplicateExpressionTernary.md b/man/checkers/duplicateExpressionTernary.md index 706ad85e2ac..85a4bfeb967 100644 --- a/man/checkers/duplicateExpressionTernary.md +++ b/man/checkers/duplicateExpressionTernary.md @@ -17,26 +17,6 @@ The warning is triggered when: The same expression indicates that there might be some logic error or copy-paste mistake. -## Examples - -### Problematic code - -```cpp -// Same expression in both branches -int result = condition ? x : x; // Warning: duplicateExpressionTernary - -// Same variable referenced through alias -const int c = a; -int result = condition ? a : c; // Warning: duplicateExpressionTernary -``` - -### Fixed code - -```cpp -// Different expressions in branches -int result = condition ? x : y; // OK -``` - ## How to fix 1. **Check for copy-paste errors**: Verify that both branches are supposed to have the same expression diff --git a/man/checkers/duplicateValueTernary.md b/man/checkers/duplicateValueTernary.md index 06e9f8e9be7..a0ae2550293 100644 --- a/man/checkers/duplicateValueTernary.md +++ b/man/checkers/duplicateValueTernary.md @@ -23,30 +23,9 @@ However, no warning is generated when: The same value indicates that there might be some logic error or copy-paste mistake. -## Examples - -### Problematic code - -```cpp -// Different expressions, same value -int result = condition ? (int)1 : 1; // Warning: duplicateValueTernary - -// Different cast syntax, same value -int result = condition ? 1 : (int)1; // Warning: duplicateValueTernary -``` - -### Fixed code - -```cpp -// Different values in branches -int result = condition ? 1 : 2; // OK - -// Simplified - condition doesn't matter -int result = 1; // OK - removed unnecessary ternary - -// Platform-dependent values are allowed -int size = is_64bit ? sizeof(long) : sizeof(int); // OK - may differ on platforms -``` +Note that a ternary whose branches merely *could* differ depending on the platform, such as +`is_64bit ? sizeof(long) : sizeof(int)`, is not flagged: cppcheck only warns when it can tell both +branches are the same value on the platform being analyzed. ## How to fix diff --git a/man/checkers/eraseDereference.md b/man/checkers/eraseDereference.md new file mode 100644 index 00000000000..35312320b74 --- /dev/null +++ b/man/checkers/eraseDereference.md @@ -0,0 +1,51 @@ +# eraseDereference + +**Message**: Iterator 'iter' used after element has been erased.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +An iterator is dereferenced (or compared) after the element it pointed to has already been erased. + +## Motivation + +Once the element an iterator points to has been erased, the iterator itself is invalid - dereferencing +or comparing it afterwards is undefined behaviour, even though it often still appears to "work" because +the underlying memory hasn't been reused yet. + +## How to fix + +Before: +```cpp +#include +#include +void f() { + std::map ints; + std::map::iterator iter; + iter = ints.begin(); + ints.erase(iter); + std::cout << iter->first << std::endl; // <- eraseDereference: 'iter' was just erased +} +``` + +After: +```cpp +#include +#include +void f() { + std::map ints = {{1, 2}}; + std::map::iterator iter; + iter = ints.begin(); + std::cout << iter->first << std::endl; + ints.erase(iter); +} +``` + +## Related checkers + +- [invalidIterator1.md](invalidIterator1.md) - a related mistake where an already-erased iterator is + passed to `erase()`/`insert()` again, rather than dereferenced. +- [derefInvalidIterator.md](derefInvalidIterator.md) - the more general check for dereferencing an + iterator that could be invalid for any reason, not specifically because it was just erased. diff --git a/man/checkers/eraseIteratorOutOfBounds.md b/man/checkers/eraseIteratorOutOfBounds.md new file mode 100644 index 00000000000..ac24423fd7f --- /dev/null +++ b/man/checkers/eraseIteratorOutOfBounds.md @@ -0,0 +1,64 @@ +# eraseIteratorOutOfBounds and eraseIteratorOutOfBoundsCond + +**Message**: Calling function 'erase()' on the iterator 'v.begin()' which is out of bounds.
+**Category**: Undefined Behaviour
+**Severity**: Error/Warning
+**Language**: C++ + +## Description + +`erase()` is called with an iterator that is known to be `end()`, before `begin()`, or otherwise out of +the container's range. + +- `eraseIteratorOutOfBounds`: the iterator is unconditionally known to be out of bounds. +- `eraseIteratorOutOfBoundsCond`: the iterator is only out of bounds on one branch of a condition + tested nearby - either that condition is redundant, or this `erase()` call is a bug. + +## Motivation + +Calling `erase()` with an iterator that doesn't point at an actual element in the container (`end()`, +or the result of moving an iterator before `begin()` or past `end()`) is undefined behaviour. + +## How to fix + +Before: +```cpp +#include +void f() { + std::vector v; + v.erase(v.begin()); // <- eraseIteratorOutOfBounds: 'v' is empty, begin() == end() +} +``` + +After: +```cpp +#include +void f() { + std::vector v = {1, 2, 3}; + v.erase(v.begin()); +} +``` + +Before: +```cpp +#include +void f(std::vector& v, std::vector::iterator it) { + if (it == v.end()) {} + v.erase(it); // <- eraseIteratorOutOfBoundsCond: 'it' can be end() here +} +``` + +After: +```cpp +#include +void f(std::vector& v, std::vector::iterator it) { + if (it == v.end()) + return; + v.erase(it); +} +``` + +## Related checkers + +- [derefInvalidIterator.md](derefInvalidIterator.md) - the equivalent check for dereferencing (rather + than erasing through) an iterator that could be out of bounds. diff --git a/man/checkers/exceptDeallocThrow.md b/man/checkers/exceptDeallocThrow.md new file mode 100644 index 00000000000..53571ffc8f1 --- /dev/null +++ b/man/checkers/exceptDeallocThrow.md @@ -0,0 +1,42 @@ +# exceptDeallocThrow + +**Message**: Exception thrown in invalid state, 'p' points at deallocated memory.
+**Category**: Undefined Behaviour
+**Severity**: Warning
+**Language**: C++ only + +## Description + +A global/static pointer is `delete`d and an exception can then be thrown before the pointer is given a +new value, leaving a dangling pointer for any surviving code to use. + +## Motivation + +If an exception is thrown between freeing a pointer and resetting it, any exception handler (or later +code, if the exception is caught and execution continues) that touches the same global/static pointer +sees a dangling value - a use-after-free that's easy to miss because the code "looks" like it cleans up +properly, just not in a safe order. + +## How to fix + +Before: +```cpp +static int* p = nullptr; +void f(bool someCondition) { + delete p; + if (someCondition) + throw 1; // <- 'p' is left dangling if this throws + p = nullptr; +} +``` + +After: +```cpp +static int* p = nullptr; +void f(bool someCondition) { + delete p; + p = nullptr; + if (someCondition) + throw 1; +} +``` diff --git a/man/checkers/exceptRethrowCopy.md b/man/checkers/exceptRethrowCopy.md new file mode 100644 index 00000000000..99ca02987fe --- /dev/null +++ b/man/checkers/exceptRethrowCopy.md @@ -0,0 +1,50 @@ +# exceptRethrowCopy + +**Message**: Throwing a copy of the caught exception instead of rethrowing the original exception.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ only + +## Description + +A `catch` block rethrows the caught exception with `throw x;` instead of a bare `throw;`, which makes +an unnecessary copy (and can slice a derived exception down to its base type). + +## Motivation + +`throw x;` constructs a new exception object from `x`, sliced down to whatever type `x` was declared +as - if the actual thrown object was a more-derived type, that extra information is lost. A bare +`throw;` rethrows the original exception object exactly as it was, with no copy and no slicing. + +## How to fix + +Before: +```cpp +#include +void doWork(); +void f() { + try { + doWork(); + } catch (const std::exception& err) { + throw err; // <- copies (and can slice) the exception + } +} +``` + +After: +```cpp +#include +void doWork(); +void f() { + try { + doWork(); + } catch (const std::exception& err) { + throw; + } +} +``` + +## Related checkers + +- [catchExceptionByValue.md](catchExceptionByValue.md) - a related `catch`-clause mistake: catching by + value instead of by reference. diff --git a/man/checkers/exceptThrowInDestructor.md b/man/checkers/exceptThrowInDestructor.md new file mode 100644 index 00000000000..a529320267c --- /dev/null +++ b/man/checkers/exceptThrowInDestructor.md @@ -0,0 +1,42 @@ +# exceptThrowInDestructor + +**Message**: Class X is not safe, destructor throws exception
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ only + +## Description + +A destructor throws an exception. + +## Motivation + +Destructors are implicitly `noexcept` since C++11, and even before that, throwing from a destructor +during stack unwinding (while another exception is already propagating) calls `std::terminate()` +immediately, aborting the program without normal cleanup. + +## How to fix + +Before: +```cpp +class Resource { +public: + ~Resource() { + throw 1; // <- destructors are expected not to throw + } +}; +``` + +After: +```cpp +class Resource { +public: + ~Resource() { + } +}; +``` + +## Related checkers + +- [throwInNoexceptFunction.md](throwInNoexceptFunction.md) - the same underlying problem, for any + function explicitly marked `noexcept` rather than specifically a destructor. diff --git a/man/checkers/extraWhitespaceError.md b/man/checkers/extraWhitespaceError.md new file mode 100644 index 00000000000..5f635a0d776 --- /dev/null +++ b/man/checkers/extraWhitespaceError.md @@ -0,0 +1,25 @@ +# extraWhitespaceError + +**Message**: Found extra whitespace in the pattern.
+**Category**: Code Quality
+**Severity**: Warning
+**Language**: C++ (cppcheck's own source code only) + +## Description + +A pattern string has leading/trailing or doubled whitespace, which doesn't change what it matches but +suggests a typo. + +This is not a general-purpose checker for arbitrary C/C++ programs - see +[simplePatternError.md](simplePatternError.md) for the shared background on this internal, +cppcheck-source-only checker. + +## Motivation + +Stray whitespace in a pattern string is harmless to how the pattern matches, but it's a signal that the +string may have been edited carelessly (for example, a token accidentally deleted without removing its +surrounding space) - worth a second look even though it isn't a functional bug on its own. + +## How to fix + +Remove the extra leading, trailing, or doubled whitespace from the pattern string. diff --git a/man/checkers/fflushOnInputStream.md b/man/checkers/fflushOnInputStream.md new file mode 100644 index 00000000000..b09ea8076b8 --- /dev/null +++ b/man/checkers/fflushOnInputStream.md @@ -0,0 +1,34 @@ +# fflushOnInputStream + +**Message**: fflush() called on input stream 'x' may result in undefined behaviour on non-linux systems.
+**Category**: Portability
+**Severity**: Portability
+**Language**: C/C++ + +## Description + +`fflush()` is called on an input stream (`stdin`, or a file opened for reading). + +## Motivation + +The C standard only defines `fflush()`'s effect for output streams; on an input stream it's +implementation-defined - some platforms discard unread input as a convenience, others don't define +anything useful, so relying on it isn't portable. + +## How to fix + +Before: +```cpp +#include +void f() { + fflush(stdin); // <- undefined/implementation-defined on an input stream +} +``` + +After: +```cpp +#include +void f() { + while (getchar() != '\n') {} +} +``` diff --git a/man/checkers/floatConversionOverflow.md b/man/checkers/floatConversionOverflow.md index efdb95ae2c8..77338965d7b 100644 --- a/man/checkers/floatConversionOverflow.md +++ b/man/checkers/floatConversionOverflow.md @@ -7,10 +7,9 @@ ## Description -This checker uses ValueFlow analysis to detect conversions from a floating point value to an -integer type (via explicit cast, assignment or `return`) where the floating point value is outside -the range that the target integer type can represent, based on the platform's configured integer -widths. +This checker detects conversions from a floating point value to an integer type (via explicit cast, +assignment or `return`) where the floating point value is outside the range that the target integer +type can represent, based on the platform's configured integer widths. ## Motivation @@ -18,30 +17,14 @@ Converting a floating point value to an integer type is undefined behaviour when fit in the target type (for example, it is too large, too small, NaN, or infinite). Unlike integer overflow, this cannot simply be assumed to "wrap around"; the actual result is unpredictable. -## Limitations / false negatives - -- Code that ValueFlow determines is unreachable is not analyzed: a dead branch of a ternary - expression, or an operand of `&&`/`||` that short-circuit evaluation would never reach, is skipped - even though the cast is textually present: - ```cpp - bool f(unsigned short x); - bool g() { - return false && f((unsigned short)75000.0); // not detected, right side never evaluated - } - ``` -- Detection depends on ValueFlow attaching a concrete floating point value to the expression; a value - that comes from a computation ValueFlow cannot bound is not checked. -- The generic (non-fast-path) bit-width check additionally requires a platform to be configured; - only the two `exp2`-based checks for extreme values run regardless of platform. - ## How to fix Make sure the floating point value fits in the target integer type before converting, for example by clamping the value to the valid range, or by using a wider or floating point type instead. -Note: cppcheck only warns when ValueFlow can determine the actual floating point value (or a bound on -it). An unconstrained `double` parameter with no known or derivable value is not checked; the example -below uses a value that ValueFlow can trace so the warning actually fires. +Note: cppcheck only warns when it can determine the actual floating point value (or a bound on it). +An unconstrained `double` parameter with no known or derivable value is not checked; the example +below uses a value cppcheck can trace so the warning actually fires. Before: ```cpp diff --git a/man/checkers/ftellTextModeFile.md b/man/checkers/ftellTextModeFile.md index 6ca6653a6f2..847e23b6580 100644 --- a/man/checkers/ftellTextModeFile.md +++ b/man/checkers/ftellTextModeFile.md @@ -1,8 +1,8 @@ -# ftellModeTextFile +# ftellTextModeFile **Message**: ftell() result is unspecified when file is opened in mode "t".
**Category**: Portability
-**Severity**: Style
+**Severity**: Portability
**Language**: C/C++ ## Description diff --git a/man/checkers/funcArgNamesDifferent.md b/man/checkers/funcArgNamesDifferent.md new file mode 100644 index 00000000000..8e5841a4a38 --- /dev/null +++ b/man/checkers/funcArgNamesDifferent.md @@ -0,0 +1,49 @@ +# funcArgNamesDifferent and funcArgNamesDifferentUnnamed + +**Message**: Function 'f' argument 1 names different: declaration 'a' definition 'b'.
+**Category**: Code Quality
+**Severity**: Style (Inconclusive)
+**Language**: C/C++ + +## Description + +A function is declared in one place (for example a header) and defined in another, and an argument has +a different name between the two (`funcArgNamesDifferent`), or is named in one but not the other +(`funcArgNamesDifferentUnnamed`). + +## Motivation + +Readers of the declaration alone can be misled about what an argument means if the definition uses a +different (or more descriptive) name - the declaration is often the only thing visible from a header, +while the actual logic and its more meaningful names live in the definition. + +## How to fix + +Before: +```cpp +void func2(int a, int b, int c); +void func2(int A, int B, int C) { } // <- names don't match the declaration +``` + +After: +```cpp +void func2(int a, int b, int c); +void func2(int a, int b, int c) { } +``` + +Before: +```cpp +void f(int a); +void f(int) {} // <- the definition drops the declared name +``` + +After: +```cpp +void f(int a); +void f(int a) {} +``` + +## Related checkers + +- [funcArgOrderDifferent.md](funcArgOrderDifferent.md) - the same kind of declaration/definition + mismatch, but for the order of two arguments that keep their names. diff --git a/man/checkers/funcArgOrderDifferent.md b/man/checkers/funcArgOrderDifferent.md new file mode 100644 index 00000000000..0042f6964e4 --- /dev/null +++ b/man/checkers/funcArgOrderDifferent.md @@ -0,0 +1,36 @@ +# funcArgOrderDifferent + +**Message**: Function 'func2' argument order different: declaration 'a, b, c' definition 'c, b, a'
+**Category**: Code Quality
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A function's declaration and its definition give the same set of argument names, but in a different +order - a strong sign that a maintenance edit swapped two parameters in only one of the two places. + +## Motivation + +If the declaration and definition disagree about which parameter is which, calls written against the +declaration pass arguments in the wrong logical order relative to what the definition actually does with +them - a real, easy-to-miss bug, not just a style nit. + +## How to fix + +Before: +```cpp +void func2(int a, int b, int c); +void func2(int c, int b, int a) { } // <- 'a' and 'c' swapped places +``` + +After: +```cpp +void func2(int a, int b, int c); +void func2(int a, int b, int c) { } +``` + +## Related checkers + +- [funcArgNamesDifferent.md](funcArgNamesDifferent.md) - the same kind of declaration/definition + mismatch, but for an argument name changing (or disappearing) rather than two arguments swapping order. diff --git a/man/checkers/globalLockGuard.md b/man/checkers/globalLockGuard.md new file mode 100644 index 00000000000..05b1c4b3a07 --- /dev/null +++ b/man/checkers/globalLockGuard.md @@ -0,0 +1,43 @@ +# globalLockGuard + +**Message**: Lock guard is defined globally. Lock guards are intended to be local. A global lock guard could lead to a deadlock since it won't unlock until the end of the program.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ + +## Description + +A `std::lock_guard` (or similar RAII lock wrapper) is declared `static` or at namespace/global scope - +it's then held for the entire lifetime of the program, since its destructor (which releases the lock) +never runs until the program exits, defeating the purpose of the lock and risking a permanent deadlock. + +## Motivation + +A `lock_guard`'s whole purpose is to release the lock automatically when it goes out of scope. Giving +it static storage duration means it never goes out of scope until the program ends - the lock is taken +once and never released, so any other code that later tries to lock the same mutex blocks forever. + +## How to fix + +Before: +```cpp +#include +void f() { + static std::mutex m; + static std::lock_guard g(m); // <- never unlocked until program exit +} +``` + +After: +```cpp +#include +void f() { + static std::mutex m; + std::lock_guard g(m); +} +``` + +## Related checkers + +- [localMutex.md](localMutex.md) - the opposite mistake: a mutex and its lock declared in the same, + too-narrow scope, so the lock has no effect. diff --git a/man/checkers/identicalConditionAfterEarlyExit.md b/man/checkers/identicalConditionAfterEarlyExit.md new file mode 100644 index 00000000000..72974ef3612 --- /dev/null +++ b/man/checkers/identicalConditionAfterEarlyExit.md @@ -0,0 +1,43 @@ +# identicalConditionAfterEarlyExit + +**Message**: Identical condition 'x==1', second condition is always false
+**Category**: Code Quality
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +After `if (cond) return;` (or `throw`/`continue`/`break`), the same condition is tested again later in +the function - by that point it can only be false. + +## Motivation + +Once execution passes the early exit, the condition that would have triggered it is already known to be +false for the rest of the function - re-testing it is always false, so it's either dead code or a sign +the two conditions were meant to check different things. + +This check may need `--check-level=exhaustive` to see every case. + +## How to fix + +Before: +```cpp +void f(int x) { + if (x == 1) + return; + if (x == 1) {} // <- always false here +} +``` + +After: +```cpp +void f(int x) { + if (x == 1) + return; +} +``` + +## Related checkers + +- [duplicateCondition.md](duplicateCondition.md) - the same idea, but for two consecutive plain `if` + statements with no early exit in between. diff --git a/man/checkers/identicalInnerCondition.md b/man/checkers/identicalInnerCondition.md new file mode 100644 index 00000000000..c2fa1e0d681 --- /dev/null +++ b/man/checkers/identicalInnerCondition.md @@ -0,0 +1,47 @@ +# identicalInnerCondition + +**Message**: Identical inner 'if' condition is always true.
+**Category**: Code Quality
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +An `if` nested directly inside another `if` repeats the exact same condition as the outer one, so the +inner condition is always true. + +## Motivation + +If the outer `if` already tested the condition, testing it again immediately inside is redundant - the +inner `if` can never be false, so it adds nothing but confusion (and cost of a reader wondering whether +something subtle was intended). + +This check may need `--check-level=exhaustive` to see every case. + +## How to fix + +Before: +```cpp +void f(int x) { + if (x == 1) { + if (x == 1) {} // <- always true + } +} +``` + +After: remove the redundant inner check. +```cpp +void f(int x) { + if (x == 1) { + } +} +``` + +## Related checkers + +- [oppositeInnerCondition.md](oppositeInnerCondition.md) - the same idea, but the inner condition + contradicts the outer one instead of repeating it. +- [overlappingInnerCondition.md](overlappingInnerCondition.md) - the same idea, but the inner condition + is merely implied by (not identical to) the outer one. +- [multiCondition.md](multiCondition.md) - the same idea, but for an `if`/`else if` chain instead of + nested `if`s. diff --git a/man/checkers/ignoredReturnValue.md b/man/checkers/ignoredReturnValue.md new file mode 100644 index 00000000000..244888e4625 --- /dev/null +++ b/man/checkers/ignoredReturnValue.md @@ -0,0 +1,39 @@ +# ignoredReturnValue and ignoredReturnErrorCode + +**Message**: Return value of function $symbol() is not used.
+**Category**: Correctness
+**Severity**: Warning/Style
+**Language**: C/C++ + +## Description + +The return value of a function that must be checked is discarded: + +- `ignoredReturnValue`: the function is marked `[[nodiscard]]`, or is known to be pure/const, or + returns newly allocated memory that would otherwise leak. +- `ignoredReturnErrorCode`: the function's return value follows a "returns an error code" convention + (per its library configuration), so discarding it means a failure could go unnoticed. + +## Motivation + +If a function's whole purpose is its return value (a pure calculation), or its return value is the only +way to learn it failed, throwing that value away either wastes the call entirely or hides a possible +failure that will only surface later, in a more confusing way. + +## How to fix + +Before: +```cpp +#include +void f(char* a, char* b) { + strcmp(a, b); // <- the comparison result is thrown away +} +``` + +After: +```cpp +#include +void f(char* a, char* b) { + if (strcmp(a, b) == 0) {} +} +``` diff --git a/man/checkers/incompatibleFileOpen.md b/man/checkers/incompatibleFileOpen.md new file mode 100644 index 00000000000..bc0e7fd52d6 --- /dev/null +++ b/man/checkers/incompatibleFileOpen.md @@ -0,0 +1,44 @@ +# incompatibleFileOpen + +**Message**: The file 'a.txt' is opened for read and write access at the same time on different streams
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +The same filename is opened for reading on one stream while another stream already has it open for +writing (or vice versa). + +## Motivation + +Two independent streams onto the same file, one reading and one writing, can observe an inconsistent +view of the file's contents depending on buffering and timing - a portability and correctness hazard +that's easy to introduce without noticing, since each stream on its own looks fine. + +## How to fix + +Before: +```cpp +#include +void f() { + FILE *f1 = fopen("a.txt", "w"); + FILE *f2 = fopen("a.txt", "r"); // <- 'a.txt' is already open for writing +} +``` + +After: +```cpp +#include +void f() { + FILE *f1 = fopen("a.txt", "r"); + if (f1) fclose(f1); +} +``` + +## Related checkers + +- [useClosedFile.md](useClosedFile.md), [readWriteOnlyFile.md](readWriteOnlyFile.md), + [writeReadOnlyFile.md](writeReadOnlyFile.md), [IOWithoutPositioning.md](IOWithoutPositioning.md), + [seekOnAppendedFile.md](seekOnAppendedFile.md) - other checks that follow the same `FILE*` through a + function. diff --git a/man/checkers/incompleteArrayFill.md b/man/checkers/incompleteArrayFill.md new file mode 100644 index 00000000000..41f3ce4f584 --- /dev/null +++ b/man/checkers/incompleteArrayFill.md @@ -0,0 +1,45 @@ +# incompleteArrayFill + +**Message**: Array 'a[5]' might be filled incompletely. Did you forget to multiply the size given to 'memset()' with 'sizeof(*a)'?
+**Category**: Correctness
+**Severity**: Warning/Portability
+**Language**: C/C++ + +## Description + +`memset()`/`memcpy()`/`memmove()` is given a byte count equal to the *number of elements* in an array, +rather than their combined size in bytes - unless each element happens to be exactly one byte, this only +fills part of the array. When the array's element type is specifically `bool`, this is reported at +`portability` severity instead: `sizeof(bool)` happens to be `1` on most common platforms (so the code +"works" there), but the C/C++ standard doesn't guarantee it, so the exact same code is a real, silent +bug on a platform where `bool` is wider. + +## Motivation + +These functions take their size argument in bytes, not element count. Passing the element count directly +is an easy mistake - it compiles fine, and on typical platforms with 1-byte types it can even happen to +be correct, but for any array element wider than one byte it silently leaves most of the array +untouched. + +## How to fix + +Multiply the element count by `sizeof(*array)` (or the element type's size) to get the correct byte +count. + +Before: +```cpp +#include +void f() { + int a[5]; + memset(a, 123, 5); // <- fills 5 bytes, not 5 ints +} +``` + +After: +```cpp +#include +void f() { + int a[5]; + memset(a, 123, 5 * sizeof(*a)); +} +``` diff --git a/man/checkers/incorrectLogicOperator.md b/man/checkers/incorrectLogicOperator.md new file mode 100644 index 00000000000..7acd2d8c735 --- /dev/null +++ b/man/checkers/incorrectLogicOperator.md @@ -0,0 +1,42 @@ +# incorrectLogicOperator + +**Message**: Logical disjunction always evaluates to true: x != 1 || x != 2.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +Two comparisons on the same variable, joined by `&&`/`||`, combine to something that's always true or +always false, regardless of the variable's actual value. + +## Motivation + +`x == 1 && x == 2` can never be true (a variable can't equal both 1 and 2 at once), and +`x != 1 || x != 2` can never be false (it's always at least one of those). Writing this is a strong +signal the wrong logical operator was used - `&&` instead of `||`, or vice versa - or that one of the +compared values is wrong. + +This check may need `--check-level=exhaustive` to see every case. + +## How to fix + +Before: +```cpp +void f(int x) { + if (x == 1 && x == 2) {} // <- can never be true +} +``` + +After: +```cpp +void f(int x) { + if (x == 1 || x == 2) {} +} +``` + +## Related checkers + +- [redundantCondition.md](redundantCondition.md) - the same style of two-comparisons-on-one-variable + analysis, but for when one comparison is already implied by the other rather than making the whole + expression always true/false. diff --git a/man/checkers/incorrectStringBooleanError.md b/man/checkers/incorrectStringBooleanError.md new file mode 100644 index 00000000000..cdfc503ea7f --- /dev/null +++ b/man/checkers/incorrectStringBooleanError.md @@ -0,0 +1,36 @@ +# incorrectStringBooleanError and incorrectCharBooleanError + +**Message**: Conversion of string literal "Hello" to bool always evaluates to true.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A string or char literal is used directly where a boolean is expected (`if ("Hello")`, +`x ? "a" : "b"` as the whole condition) - this is always `true` unless the literal is exactly +`""`/`'\0'`. + +## Motivation + +A non-empty string or character literal converts to `true` every time - if the intent was to check a +variable's value, using the literal itself instead is a typo that silently makes the condition +constant, so the code inside always (or never) runs regardless of any real input. + +## How to fix + +Before: +```cpp +int f() { + if ("Hello") {} // <- always true + return 0; +} +``` + +After: +```cpp +int f(bool flag) { + if (flag) {} + return 0; +} +``` diff --git a/man/checkers/incorrectStringCompare.md b/man/checkers/incorrectStringCompare.md new file mode 100644 index 00000000000..a03b379d62a --- /dev/null +++ b/man/checkers/incorrectStringCompare.md @@ -0,0 +1,35 @@ +# incorrectStringCompare + +**Message**: String literal "Hello" doesn't match length argument for substr().
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +`x.substr(pos, len) == "literal"` where `len` doesn't match the length of `"literal"`. + +## Motivation + +`substr(pos, len)` only ever returns a string of (at most) `len` characters, so comparing it against a +literal of a different length can never be true (if `len` is too short) or is comparing more characters +than were actually extracted (if `len` is too long) - either way, the comparison doesn't test what it +looks like it tests. + +## How to fix + +Before: +```cpp +#include +int f(std::string test) { + return test.substr(0, 4) == "Hello" ? 0 : 1; // <- "Hello" is 5 chars +} +``` + +After: +```cpp +#include +int f(std::string test) { + return test.substr(0, 5) == "Hello" ? 0 : 1; +} +``` diff --git a/man/checkers/incrementboolean.md b/man/checkers/incrementboolean.md new file mode 100644 index 00000000000..a096446d0a1 --- /dev/null +++ b/man/checkers/incrementboolean.md @@ -0,0 +1,34 @@ +# incrementboolean + +**Message**: Incrementing a variable of type 'bool' with postfix operator++ is deprecated by the C++ Standard. You should assign it the value 'true' instead.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +A `bool` variable is incremented with `++`. + +## Motivation + +Incrementing a `bool` is deprecated by the C++ standard (Annex D) and always just sets it to `true` - +writing `= true` says the same thing without relying on deprecated, integer-flavored behaviour of a +type that isn't really a number. + +## How to fix + +Before: +```cpp +bool ready = true; +void f() { + ready++; // <- deprecated +} +``` + +After: +```cpp +bool ready = true; +void f() { + ready = true; +} +``` diff --git a/man/checkers/initializerList.md b/man/checkers/initializerList.md new file mode 100644 index 00000000000..989627d1f71 --- /dev/null +++ b/man/checkers/initializerList.md @@ -0,0 +1,77 @@ +# initializerList + +**Message**: Member variable 'c' is in the wrong order in the initialization list.
+**Category**: Correctness
+**Severity**: Style
+**Language**: C++ + +## Description + +A constructor's member-initializer list either lists members in a different order than they're declared +in the class (misleading, since members are always initialized in declaration order, not +initializer-list order), or initializes one member using another member as a source value when that +source member is declared *after* it - so, despite appearing earlier in the list, the source hasn't +actually been initialized yet at that point. + +## Motivation + +C++ always initializes members in the order they're *declared* in the class, regardless of what order +they're written in the initializer list. cppcheck flags any mismatch between the two orders purely as a +defensive-programming measure - most of the time (as in the first example below, where every member is +just initialized from a constant) writing the list out of order is only misleading to a reader, with no +real consequence. But because members are genuinely initialized in declaration order, a list written out +of order can also hide a real bug: if one member's initializer reads another member that's declared +*later*, the value it reads is indeterminate (that member's own storage hasn't been given a value yet), +and reading an indeterminate scalar value is undefined behaviour - the same underlying problem as reading +any other uninitialized variable, just harder to spot because the initializer list makes it look like the +source was already set up. cppcheck doesn't distinguish the two cases - it always suggests reordering the +list to match declaration order, which prevents the harmful case from ever being possible. + +## How to fix + +Write the initializer list in the same order the members are declared in the class. + +Before: +```cpp +class Fred { + int a, b, c; +public: + Fred() : c(0), b(0), a(0) { } // <- listed in the reverse of declaration order +}; +``` + +After: +```cpp +class Fred { + int a, b, c; +public: + Fred() : a(0), b(0), c(0) { } +}; +``` + +Before: +```cpp +class Foo { +public: + Foo(int arg) : a(b), b(arg) {} // <- 'a' is initialized from 'b', but 'b' isn't set yet + int a; + int b; +}; +``` + +After: +```cpp +class Foo { +public: + explicit Foo(int arg) : a(arg), b(arg) {} + int a; + int b; +}; +``` + +## Related checkers + +- [useInitializationList.md](useInitializationList.md) - a different constructor-initializer-list + pitfall, about whether the list is used at all rather than the order members appear in it. +- [selfInitialization.md](selfInitialization.md) - the more specific case where a member is initialized + from itself. diff --git a/man/checkers/intToPointerCast.md b/man/checkers/intToPointerCast.md new file mode 100644 index 00000000000..b72749a994e --- /dev/null +++ b/man/checkers/intToPointerCast.md @@ -0,0 +1,44 @@ +# intToPointerCast + +**Message**: Casting non-zero decimal integer literal to pointer.
+**Category**: Portability
+**Severity**: Portability
+**Language**: C/C++ + +## Description + +A non-zero integer literal (not a hex address like `0x7000`, which is common in embedded code) is cast +directly to a pointer. + +## Motivation + +A decimal, octal, or binary integer literal cast to a pointer looks like it's meant to be a specific +memory address, but writing addresses in those bases is unusual and easy to mistype or misread compared +to the conventional hexadecimal form - a small typo can silently produce a very different address. + +## How to fix + +Write the address in hexadecimal, which is the conventional and clearer way to express a raw address. + +Before: +```cpp +#include +uint8_t* f() { + uint8_t* ptr = (uint8_t*)7; // <- 7 isn't a real address + return ptr; +} +``` + +After: +```cpp +#include +uint8_t* f() { + uint8_t* ptr = (uint8_t*)0x7000; // an actual address is fine + return ptr; +} +``` + +## Related checkers + +- [invalidPointerCast.md](invalidPointerCast.md) - a different pointer-cast portability issue, about + casting between two pointer types whose values aren't laid out the same way in memory. diff --git a/man/checkers/integerOverflow.md b/man/checkers/integerOverflow.md index d21c547c78c..1c426f5a7ca 100644 --- a/man/checkers/integerOverflow.md +++ b/man/checkers/integerOverflow.md @@ -1,4 +1,4 @@ -# integerOverflow +# integerOverflow and integerOverflowCond **Message**: Signed integer overflow for expression 'x*y'.
**Category**: Undefined Behaviour
@@ -7,12 +7,15 @@ ## Description -This checker uses ValueFlow analysis to detect when a signed integer arithmetic expression -(`+`, `-`, `*`, `/`, `<<`, etc.) can overflow or underflow the range of its result type, based on -the platform's configured integer widths (`int_bit`, `long_bit`, `long_long_bit`). +This checker detects when a signed integer arithmetic expression (`+`, `-`, `*`, `/`, `<<`, etc.) can +overflow or underflow the range of its result type, based on the platform's configured integer +widths. -When the overflow/underflow only happens under a certain condition, the message explains that -"Either the condition ... is redundant or there is signed integer overflow/underflow ...". +- `integerOverflow`: the overflow/underflow is unconditional - it happens for every value cppcheck can + see reaching that point. +- `integerOverflowCond`: the overflow/underflow only happens under a certain condition elsewhere in the + code; the message explains that "Either the condition ... is redundant or there is signed integer + overflow/underflow ...". As a special case, left-shifting into the sign bit (for example `1 << 31` for a 32-bit int) is not reported, since this is common practice even though it is technically undefined behaviour; that is @@ -25,27 +28,6 @@ calculation silently produces a wrong (wrapped or truncated) result, and with op the compiler is allowed to assume overflow never happens, which can eliminate or reorder code in surprising ways. -## Limitations / false negatives - -- This checker only looks at expressions whose result type is `int`, `long` or `long long` **and** - signed. Unsigned overflow (wraparound) is well-defined behaviour in C/C++ and is intentionally not - reported here. -- **Left-shifts into the sign bit are deliberately not reported by this checker**, even though they - are technically a signed integer overflow, because this is common practice (for example - `1 << 31` for a 32-bit `int`). Such shifts are instead the responsibility of the - [shiftTooManyBits](shiftTooManyBits.md) checker family. This means the same expression can trigger - `shiftTooManyBitsSigned` without also triggering `integerOverflow`: - ```cpp - int f(int i) { - return (i == 31) ? 1 << i : 0; // only reported as shiftTooManyBitsSigned, not integerOverflow - } - ``` -- This checker requires a platform to be configured, and is skipped when the platform's `int` width - is already as wide as cppcheck's internal integer representation. -- Detection depends on ValueFlow computing a concrete or condition-derived out-of-range value for the - expression; not every expression that can overflow gets such a value, so some real overflows can be - missed. - ## How to fix You can fix these warnings by: @@ -53,10 +35,10 @@ You can fix these warnings by: 2. Rewriting the calculation to avoid the overflow (for example checking bounds before multiplying) 3. Using an unsigned type, if wraparound behaviour is actually intended -Note: cppcheck only warns when ValueFlow can actually determine that the calculation overflows - -either from a known value (as below) or from a condition elsewhere in the code (see -`integerOverflowCond` above). A plain `a * b` of two otherwise-unconstrained parameters does not by -itself give ValueFlow anything to prove an overflow with, so it is not reported. +Note: cppcheck only warns when it can actually determine that the calculation overflows - either from +a known value (as below) or from a condition elsewhere in the code (see `integerOverflowCond` above). +A plain `a * b` of two otherwise-unconstrained parameters gives cppcheck nothing to prove an overflow +with, so it is not reported. Before: ```cpp diff --git a/man/checkers/invalidContainer.md b/man/checkers/invalidContainer.md new file mode 100644 index 00000000000..402ff69f4bb --- /dev/null +++ b/man/checkers/invalidContainer.md @@ -0,0 +1,71 @@ +# invalidContainer and invalidContainerReference + +**Message**: Using pointer to local variable 'v' that may be invalid.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +A pointer or reference taken from inside a container (an element, `.front()`, an iterator, ...) is used +after a call (`push_back()`, `insert()`, `clear()`, ...) that may have invalidated it. + +- `invalidContainer`: the invalidated thing is a pointer or iterator value. +- `invalidContainerReference`: the invalidated thing is a reference. + +## Motivation + +Many container operations can reallocate or otherwise invalidate previously-obtained pointers, +references, and iterators into that container's data, without any visible change at the call site of +the operation that invalidates them. Using one afterwards is undefined behaviour. + +## How to fix + +Before: +```cpp +#include +#include +void f(std::vector &v) { + int *v0 = &v[0]; + v.push_back(123); + std::cout << *v0 << std::endl; // <- invalidContainer: push_back() may have reallocated 'v' +} +``` + +After: +```cpp +#include +#include +void f(std::vector &v) { + v.push_back(123); + std::cout << v[0] << std::endl; +} +``` + +Before: +```cpp +#include +#include +void f() { + std::vector v = {1}; + int &v0 = v.front(); + v.push_back(123); + std::cout << v0 << std::endl; // <- invalidContainerReference: push_back() may have reallocated 'v' +} +``` + +After: +```cpp +#include +#include +void f() { + std::vector v = {1}; + v.push_back(123); + std::cout << v.front() << std::endl; +} +``` + +## Related checkers + +- [invalidContainerLoop.md](invalidContainerLoop.md) - the equivalent problem when the invalidating + call happens while a loop is actively iterating over the same container. diff --git a/man/checkers/invalidContainerLoop.md b/man/checkers/invalidContainerLoop.md new file mode 100644 index 00000000000..d6bce6e098a --- /dev/null +++ b/man/checkers/invalidContainerLoop.md @@ -0,0 +1,52 @@ +# invalidContainerLoop + +**Message**: Calling 'push_back' while iterating the container is invalid.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +A container is modified with a call that can invalidate iterators (such as `push_back()` on a +`vector`) while a loop is iterating over that same container. + +## Motivation + +Modifying a container while a range-based (or iterator-based) loop is walking over it risks invalidating +the loop's own iterator mid-iteration - for `push_back()` on a `vector`, this happens whenever the call +needs to reallocate storage, which isn't certain from any single call but is guaranteed to happen +eventually as the container grows. Once that happens, the loop machinery's next step (comparing, +dereferencing, or incrementing the now-invalid iterator) is undefined behaviour - even though it often +appears to "work" for a while before the container's storage actually needs to move. + +## How to fix + +Before: +```cpp +#include +void f(std::vector v) { + for (auto i : v) { + if (i < 5) + v.push_back(i * 2); // <- invalidContainerLoop: modifying 'v' while iterating over it + } +} +``` + +After: +```cpp +#include +void f(std::vector v) { + std::vector toAdd; + for (auto i : v) { + if (i < 5) + toAdd.push_back(i * 2); + } + v.insert(v.end(), toAdd.begin(), toAdd.end()); +} +``` + +## Related checkers + +- [invalidContainer.md](invalidContainer.md) - the more general check for using a pointer, reference, + or iterator into a container after some other call may have invalidated it (not necessarily while + iterating). diff --git a/man/checkers/invalidFree.md b/man/checkers/invalidFree.md new file mode 100644 index 00000000000..57bff5c0198 --- /dev/null +++ b/man/checkers/invalidFree.md @@ -0,0 +1,43 @@ +# invalidFree + +**Message**: Mismatching address is not returned from malloc(). The address you get from malloc() must be freed without offset.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +`free()`/`delete`/`delete[]` is called on a pointer that has been moved by pointer arithmetic since it +was allocated - the allocator only recognizes the exact address it originally returned. + +## Motivation + +Freeing an address other than the exact one an allocator returned is undefined behaviour - typically a +crash, but potentially memory corruption that surfaces somewhere else entirely. This is easy to +introduce by accident once a pointer is advanced (for example while parsing or iterating through a +buffer) and then freed without first restoring it to the original address. cppcheck only tracks a +pointer as long as it can be sure nothing else changed it; passing it to another function is enough +uncertainty that cppcheck stops checking it rather than guess, so this catches only the mismatches it +can prove, not every one that exists. + +## How to fix + +Free the exact pointer the allocator returned, not one that's been offset since. + +Before: +```cpp +#include +void foo() { + char *a; a = malloc(1024); + free(a + 10); // <- this isn't the address malloc() returned +} +``` + +After: +```cpp +#include +void foo() { + char *a; a = malloc(1024); + free(a); +} +``` diff --git a/man/checkers/invalidFunctionArg.md b/man/checkers/invalidFunctionArg.md new file mode 100644 index 00000000000..d102bfdce9d --- /dev/null +++ b/man/checkers/invalidFunctionArg.md @@ -0,0 +1,40 @@ +# invalidFunctionArg + +**Message**: Invalid $symbol() argument nr 1. The value is 0 but the valid values are '1:'.
+**Category**: Correctness
+**Severity**: Error/Warning
+**Language**: C/C++ + +## Description + +An argument value falls outside the numeric range a function actually accepts, based on what cppcheck +knows about the function (from a library configuration) and about the value passed. + +## Motivation + +Many standard library functions only behave correctly for a specific range of an argument (a base for +`strtol()`, a mode for a file function, ...); passing a value outside that range is undefined or +implementation-defined behaviour that a compiler will not catch. + +## How to fix + +Before: +```cpp +void f(char *a, char **b) { + strtol(a, b, 1); // <- base 1 is not valid +} +``` + +After: +```cpp +void f(char *a, char **b) { + strtol(a, b, 10); +} +``` + +## Related checkers + +- [invalidFunctionArgBool.md](invalidFunctionArgBool.md) - the same idea, but for an argument that + needs a real boolean rather than a plain `0`/`1`. +- [invalidFunctionArgStr.md](invalidFunctionArgStr.md) - the same idea, but for an argument that needs + a null-terminated C-string. diff --git a/man/checkers/invalidFunctionArgBool.md b/man/checkers/invalidFunctionArgBool.md new file mode 100644 index 00000000000..af6e3d691dd --- /dev/null +++ b/man/checkers/invalidFunctionArgBool.md @@ -0,0 +1,41 @@ +# invalidFunctionArgBool + +**Message**: Invalid $symbol() argument nr 1. A non-boolean value is required.
+**Category**: Correctness
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A plain `0`/`1` is passed where a function (per its library configuration) actually requires a real +boolean argument. + +## Motivation + +Some functions distinguish a genuine boolean argument from an arbitrary integer that happens to be `0` +or `1` - passing the wrong kind of value can be accepted silently but not do what's intended. + +## How to fix + +Before: +```cpp +void setFlag(bool enabled); +void f() { + setFlag(1); // <- treat this as a real boolean, not an arbitrary int +} +``` + +After: +```cpp +void setFlag(bool enabled); +void f() { + setFlag(true); +} +``` + +## Related checkers + +- [invalidFunctionArg.md](invalidFunctionArg.md) - the same idea, but for an argument whose numeric + value must fall in a specific range. +- [invalidFunctionArgStr.md](invalidFunctionArgStr.md) - the same idea, but for an argument that needs + a null-terminated C-string. diff --git a/man/checkers/invalidFunctionArgStr.md b/man/checkers/invalidFunctionArgStr.md new file mode 100644 index 00000000000..a68e95ad57a --- /dev/null +++ b/man/checkers/invalidFunctionArgStr.md @@ -0,0 +1,46 @@ +# invalidFunctionArgStr + +**Message**: Invalid $symbol() argument nr 1. A NUL-terminated string is required.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A `char`/`wchar_t` buffer that isn't null-terminated (for example, the address of a single character) +is passed where a function (per its library configuration) requires a null-terminated C-string. + +## Motivation + +Functions like `strlen()`/`strcmp()` scan forward until they find a `'\0'` byte - if the argument isn't +actually a null-terminated string, whether this reads past the end of whatever memory was passed depends +on what byte values happen to follow it. If a `'\0'` byte never turns up before the buffer's real end, +the scan reads out of bounds, which is undefined behaviour; there's no way to guarantee that in advance +from the code alone, which is exactly why passing a non-null-terminated buffer is dangerous even when a +particular run happens not to crash. + +## How to fix + +Before: +```cpp +#include +size_t f(char x) { + return strlen(&x); // <- 'x' is a single char, not a null-terminated string +} +``` + +After: +```cpp +#include +size_t f() { + char x[] = "a"; + return strlen(x); +} +``` + +## Related checkers + +- [invalidFunctionArg.md](invalidFunctionArg.md) - the same idea, but for an argument whose numeric + value must fall in a specific range. +- [invalidFunctionArgBool.md](invalidFunctionArgBool.md) - the same idea, but for an argument that + needs a real boolean rather than a plain `0`/`1`. diff --git a/man/checkers/invalidIterator1.md b/man/checkers/invalidIterator1.md new file mode 100644 index 00000000000..811bf31d678 --- /dev/null +++ b/man/checkers/invalidIterator1.md @@ -0,0 +1,43 @@ +# invalidIterator1 + +**Message**: Invalid iterator: aIt
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +An iterator that hasn't been given a value yet (or is no longer valid) is passed to +`insert()`/`erase()`. + +## Motivation + +Passing an iterator that doesn't currently point anywhere valid into a container method is undefined +behaviour, and is easy to miss when the same iterator was previously used (and invalidated) earlier in +the function. + +## How to fix + +Before: +```cpp +#include +void f(const std::list& m) { + std::list::iterator aIt = m.begin(); + m.erase(*aIt); + m.erase(aIt); // <- invalidIterator1: 'aIt' was already erased by value above +} +``` + +After: +```cpp +#include +void f(std::list& m) { + std::list::iterator aIt = m.begin(); + m.erase(aIt); +} +``` + +## Related checkers + +- [eraseDereference.md](eraseDereference.md) - a related mistake where an iterator is dereferenced, + rather than passed to `erase()`, after the element it pointed to was already erased. diff --git a/man/checkers/invalidLengthModifierError.md b/man/checkers/invalidLengthModifierError.md new file mode 100644 index 00000000000..55c339067e2 --- /dev/null +++ b/man/checkers/invalidLengthModifierError.md @@ -0,0 +1,38 @@ +# invalidLengthModifierError + +**Message**: 'I' in format string (no. 1) is a length modifier and cannot be used without a conversion specifier.
+**Category**: Undefined Behaviour
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A length modifier (`h`, `l`, `I64`, ...) appears in the format string with no conversion specifier +after it, so it doesn't actually modify anything. + +## Motivation + +A length modifier only means something when it's immediately followed by a conversion specifier like +`d` or `u` - on its own it's either a leftover from editing the format string, or a sign that a +specifier letter was accidentally dropped. It also leaves the format string with no valid conversion +specification at that point, and the C standard says using an invalid conversion specification is +undefined behaviour - in practice this usually just misreads the argument list, but nothing prevents a +library from doing something worse with it. + +## How to fix + +Before: +```cpp +#include +void f() { + printf("%I", 5); // <- 'I' with no conversion specifier after it +} +``` + +After: +```cpp +#include +void f() { + printf("%Id", (ptrdiff_t)5); +} +``` diff --git a/man/checkers/invalidLifetime.md b/man/checkers/invalidLifetime.md new file mode 100644 index 00000000000..938b531fe70 --- /dev/null +++ b/man/checkers/invalidLifetime.md @@ -0,0 +1,48 @@ +# invalidLifetime + +**Message**: Using object that is out of scope.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A pointer (or a lambda capturing one) that refers to a variable is used after that variable's scope has +already ended - for example, it was only set inside an `if` block, and is used after the block closes. + +## Motivation + +A variable declared inside a nested block stops existing once that block ends, even though the pointer +that referred to it is still sitting in an outer scope. Using that pointer afterwards is undefined +behaviour, and it's easy to overlook because the pointer variable itself is still very much "in scope." + +## How to fix + +Before: +```cpp +void f(bool cond) { + int* p; + if (cond) { + int x = 1; + p = &x; + } + *p = 2; // <- invalidLifetime: 'x' is out of scope here on the 'cond' path +} +``` + +After: +```cpp +void f(bool cond) { + int local = 1; + int* p = &local; + if (cond) { + *p = 1; + } + *p = 2; +} +``` + +## Related checkers + +- [danglingTemporaryLifetime.md](danglingTemporaryLifetime.md) - the same idea, but for a pointer or + iterator into a temporary object instead of a named local variable. diff --git a/man/checkers/invalidPointerCast.md b/man/checkers/invalidPointerCast.md new file mode 100644 index 00000000000..6da56bf09a9 --- /dev/null +++ b/man/checkers/invalidPointerCast.md @@ -0,0 +1,47 @@ +# invalidPointerCast + +**Message**: Casting between float * and double * which have an incompatible binary data representation.
+**Category**: Portability
+**Severity**: Portability
+**Language**: C/C++ + +## Description + +A pointer is cast to a pointer of another type whose values aren't laid out the same way in memory (for +example `float*` to `double*`, or a pointer to a floating-point type cast to/from an integer pointer) - +reading through the new pointer doesn't reinterpret the same bytes the same way on every platform. + +## Motivation + +Reinterpreting the bytes of one type as if they were another only makes sense when both types share the +same binary layout. Floating-point types in particular can have very different sizes and bit layouts +across platforms, so code that happens to "work" during development can silently misbehave once built +for a different target. + +cppcheck flags the cast itself, based only on the two pointer types involved - it doesn't check whether +the resulting pointer is ever actually read through (or otherwise used in a way that depends on the +bytes matching up). Doing so - reading an object through a pointer to an incompatible type - is where +the undefined behaviour actually is; forming the pointer alone is not. + +## How to fix + +Before: +```cpp +void test() { + float *f = new float[10]; + delete [] (double*)f; // <- float and double aren't stored the same way +} +``` + +After: +```cpp +void test() { + float *f = new float[10]; + delete [] f; +} +``` + +## Related checkers + +- [intToPointerCast.md](intToPointerCast.md) - a different pointer-cast portability issue, about casting + a plain (non-hex) integer literal directly to a pointer. diff --git a/man/checkers/invalidPrintfArgType_float.md b/man/checkers/invalidPrintfArgType_float.md new file mode 100644 index 00000000000..939dfc9c987 --- /dev/null +++ b/man/checkers/invalidPrintfArgType_float.md @@ -0,0 +1,40 @@ +# invalidPrintfArgType_float + +**Message**: %f in format string (no. 1) requires 'double' but the argument type is 'signed int'
+**Category**: Undefined Behaviour
+**Severity**: Warning/Portability
+**Language**: C/C++ + +## Description + +The argument for a floating-point specifier (`%f`, `%e`, `%g`, ...) isn't a floating-point value. + +## Motivation + +Because `printf` is variadic, a `float` argument is promoted to `double` and read back out as +`double` by the corresponding specifier - passing an integer instead means `printf` reads the wrong +number of bytes and reinterprets them as a floating-point value. That mismatched read is undefined +behaviour in its own right, not just a meaningless printed number. + +## How to fix + +Before: +```cpp +#include +void f(int x) { + printf("%f", x); // <- needs 'double', not 'int' +} +``` + +After: +```cpp +#include +void f(double x) { + printf("%f", x); +} +``` + +## Related checkers + +- [invalidPrintfArgType_uint.md](invalidPrintfArgType_uint.md) / [invalidPrintfArgType_sint.md](invalidPrintfArgType_sint.md) - the integer equivalents. +- [invalidScanfArgType_float.md](invalidScanfArgType_float.md) - the `scanf`-side equivalent. diff --git a/man/checkers/invalidPrintfArgType_n.md b/man/checkers/invalidPrintfArgType_n.md new file mode 100644 index 00000000000..c569da99501 --- /dev/null +++ b/man/checkers/invalidPrintfArgType_n.md @@ -0,0 +1,40 @@ +# invalidPrintfArgType_n + +**Message**: %n in format string (no. 1) requires 'int \*' but the argument type is 'signed int'
+**Category**: Undefined Behaviour
+**Severity**: Warning/Portability
+**Language**: C/C++ + +## Description + +For a `printf`-family call, a `%n` specifier is given something that clearly isn't a writable pointer at +all - a plain integer, or a `const`-qualified pointer. It checks for *some* writable pointer, not +specifically a pointer to `int`, which is what `%n` actually requires. + +## Motivation + +`%n` writes the number of characters printed so far back through the pointer it's given - if the +argument isn't actually a pointer, this writes to whatever address that value happens to represent, +which is undefined behaviour (and is disabled outright by some platforms/libraries as a security risk). + +## How to fix + +Before: +```cpp +#include +void f(int x) { + printf("%n", x); // <- needs an 'int *', not 'int' +} +``` + +After: +```cpp +#include +void f(int *x) { + printf("%n", x); +} +``` + +## Related checkers + +- [invalidPrintfArgType_s.md](invalidPrintfArgType_s.md) / [invalidPrintfArgType_p.md](invalidPrintfArgType_p.md) - the same idea for `%s` and `%p`. diff --git a/man/checkers/invalidPrintfArgType_p.md b/man/checkers/invalidPrintfArgType_p.md new file mode 100644 index 00000000000..26cdff2ef33 --- /dev/null +++ b/man/checkers/invalidPrintfArgType_p.md @@ -0,0 +1,39 @@ +# invalidPrintfArgType_p + +**Message**: %p in format string (no. 1) requires an address but the argument type is 'signed int'
+**Category**: Undefined Behaviour
+**Severity**: Warning/Portability
+**Language**: C/C++ + +## Description + +For a `printf`-family call, a `%p` specifier isn't given a pointer. + +## Motivation + +`%p` is meant to print the value of a pointer. Passing a non-pointer argument means `printf` reads it as +if it were a pointer-sized value from the variadic argument list, which is undefined behaviour the +moment that mismatched read happens - not just a meaningless value, since on some calling conventions it +also reads the wrong argument size entirely, throwing off every argument read after it. + +## How to fix + +Before: +```cpp +#include +void f(int x) { + printf("%p", x); // <- needs an address, not 'int' +} +``` + +After: +```cpp +#include +void f(void *x) { + printf("%p", x); +} +``` + +## Related checkers + +- [invalidPrintfArgType_s.md](invalidPrintfArgType_s.md) / [invalidPrintfArgType_n.md](invalidPrintfArgType_n.md) - the same idea for `%s` and `%n`. diff --git a/man/checkers/invalidPrintfArgType_s.md b/man/checkers/invalidPrintfArgType_s.md new file mode 100644 index 00000000000..7616098dfe4 --- /dev/null +++ b/man/checkers/invalidPrintfArgType_s.md @@ -0,0 +1,39 @@ +# invalidPrintfArgType_s + +**Message**: %s in format string (no. 1) requires 'char \*' but the argument type is 'signed int'
+**Category**: Undefined Behaviour
+**Severity**: Warning/Portability
+**Language**: C/C++ + +## Description + +For a `printf`-family call, a `%s` specifier isn't given a `char*`. + +## Motivation + +`printf` reads a `%s` argument as a pointer to a null-terminated string - if the argument is actually a +plain integer (or any other non-pointer value), `printf` dereferences whatever address that value +happens to represent, which is undefined behaviour. + +## How to fix + +Before: +```cpp +#include +void f(int x) { + printf("%s", x); // <- needs a 'char *', not 'int' +} +``` + +After: +```cpp +#include +void f(const char *x) { + printf("%s", x); +} +``` + +## Related checkers + +- [invalidScanfArgType_s.md](invalidScanfArgType_s.md) - the `scanf`-side equivalent. +- [invalidPrintfArgType_n.md](invalidPrintfArgType_n.md) / [invalidPrintfArgType_p.md](invalidPrintfArgType_p.md) - the same idea for `%n` and `%p`. diff --git a/man/checkers/invalidPrintfArgType_sint.md b/man/checkers/invalidPrintfArgType_sint.md new file mode 100644 index 00000000000..b36668c81f4 --- /dev/null +++ b/man/checkers/invalidPrintfArgType_sint.md @@ -0,0 +1,42 @@ +# invalidPrintfArgType_sint + +**Message**: %d in format string (no. 1) requires 'int' but the argument type is 'unsigned int'
+**Category**: Undefined Behaviour
+**Severity**: Warning/Portability
+**Language**: C/C++ + +## Description + +The argument for a signed-integer specifier (`%d`, `%i`) doesn't match that specifier's expected +type/size (including the `h`/`hh`/`l`/`ll`/`z`/`j`/`t` length modifiers). + +## Motivation + +`printf` reads its arguments according to the type the format specifier implies, not the type the +argument actually has - a signedness or size mismatch means the value is read using the wrong number of +bytes or the wrong interpretation of the sign bit. Reading a variadic argument through a mismatched type +like this is undefined behaviour in its own right, not just a display quirk - the printed value being +nonsensical is only the most visible symptom. + +## How to fix + +Before: +```cpp +#include +void f(unsigned int x) { + printf("%d", x); // <- needs signed 'int', not 'unsigned int' +} +``` + +After: +```cpp +#include +void f(int x) { + printf("%d", x); +} +``` + +## Related checkers + +- [invalidPrintfArgType_uint.md](invalidPrintfArgType_uint.md) - the unsigned-integer equivalent. +- [invalidPrintfArgType_float.md](invalidPrintfArgType_float.md) - the floating-point equivalent. diff --git a/man/checkers/invalidPrintfArgType_uint.md b/man/checkers/invalidPrintfArgType_uint.md new file mode 100644 index 00000000000..72c12476361 --- /dev/null +++ b/man/checkers/invalidPrintfArgType_uint.md @@ -0,0 +1,43 @@ +# invalidPrintfArgType_uint + +**Message**: %u in format string (no. 1) requires 'unsigned int' but the argument type is 'signed int'
+**Category**: Undefined Behaviour
+**Severity**: Warning/Portability
+**Language**: C/C++ + +## Description + +The argument for an unsigned-integer specifier (`%u`, `%x`, `%o`, ...) doesn't match that specifier's +expected type/size (including the `h`/`hh`/`l`/`ll`/`z`/`j`/`t` length modifiers). + +## Motivation + +`printf` reads its arguments according to the type the format specifier implies, not the type the +argument actually has - a signedness or size mismatch means the value is read using the wrong number of +bytes or the wrong interpretation of the sign bit. Reading a variadic argument through a mismatched type +like this is undefined behaviour in its own right, not just a display quirk - the printed value being +nonsensical is only the most visible symptom. + +## How to fix + +Before: +```cpp +#include +void f(int x) { + printf("%u", x); // <- needs 'unsigned int', not signed 'int' +} +``` + +After: +```cpp +#include +void f(unsigned int x) { + printf("%u", x); +} +``` + +## Related checkers + +- [invalidPrintfArgType_sint.md](invalidPrintfArgType_sint.md) - the signed-integer equivalent. +- [invalidPrintfArgType_float.md](invalidPrintfArgType_float.md) - the floating-point equivalent. +- [invalidScanfArgType_int.md](invalidScanfArgType_int.md) - the `scanf`-side equivalent. diff --git a/man/checkers/invalidScanfArgType_float.md b/man/checkers/invalidScanfArgType_float.md new file mode 100644 index 00000000000..a99f12811eb --- /dev/null +++ b/man/checkers/invalidScanfArgType_float.md @@ -0,0 +1,42 @@ +# invalidScanfArgType_float + +**Message**: %f in format string (no. 1) requires 'float \*' but the argument type is 'signed int \*'
+**Category**: Undefined Behaviour
+**Severity**: Warning/Portability
+**Language**: C/C++ + +## Description + +For a `scanf`-family call, the argument for a floating-point specifier (`%f`, `%e`, `%g`, ...) isn't a +pointer to the matching floating-point type. + +## Motivation + +`scanf` writes the parsed floating-point value through the pointer it's given, in the binary +floating-point representation that pointer's type implies - a mismatched pointer type (for example an +`int*`) means the write is the wrong size and reinterprets those bytes incorrectly. Writing through a +pointer typed differently than the object it actually points to is undefined behaviour in its own +right, not just a corrupted value. + +## How to fix + +Before: +```cpp +#include +void f(int *x) { + scanf("%f", x); // <- needs a 'float *', not 'int *' +} +``` + +After: +```cpp +#include +void f(float *x) { + scanf("%f", x); +} +``` + +## Related checkers + +- [invalidScanfArgType_s.md](invalidScanfArgType_s.md) / [invalidScanfArgType_int.md](invalidScanfArgType_int.md) - the same idea for string and integer `scanf` conversions. +- [invalidPrintfArgType_float.md](invalidPrintfArgType_float.md) - the `printf`-side equivalent. diff --git a/man/checkers/invalidScanfArgType_int.md b/man/checkers/invalidScanfArgType_int.md new file mode 100644 index 00000000000..ffc22b9db3d --- /dev/null +++ b/man/checkers/invalidScanfArgType_int.md @@ -0,0 +1,42 @@ +# invalidScanfArgType_int + +**Message**: %d in format string (no. 1) requires 'int \*' but the argument type is 'float \*'
+**Category**: Undefined Behaviour
+**Severity**: Warning/Portability
+**Language**: C/C++ + +## Description + +For a `scanf`-family call, the argument for an integer specifier (`%d`, `%u`, `%x`, ...) isn't a +pointer to the matching integer type (including the `h`/`hh`/`l`/`ll`/`z`/`j`/`t` length modifiers). + +## Motivation + +`scanf` writes the parsed integer through the pointer it's given, using exactly as many bytes as the +specifier's type implies - a mismatched pointer type means the write is the wrong size for what it +actually points to. Writing through a pointer typed differently than the object it actually points to is +undefined behaviour in its own right, not just a matter of corrupting adjacent memory or only partially +updating the intended variable. + +## How to fix + +Before: +```cpp +#include +void f(float *x) { + scanf("%d", x); // <- needs an 'int *', not 'float *' +} +``` + +After: +```cpp +#include +void f(int *x) { + scanf("%d", x); +} +``` + +## Related checkers + +- [invalidScanfArgType_s.md](invalidScanfArgType_s.md) / [invalidScanfArgType_float.md](invalidScanfArgType_float.md) - the same idea for string and floating-point `scanf` conversions. +- [invalidPrintfArgType_uint.md](invalidPrintfArgType_uint.md) / [invalidPrintfArgType_sint.md](invalidPrintfArgType_sint.md) - the `printf`-side equivalents. diff --git a/man/checkers/invalidScanfArgType_s.md b/man/checkers/invalidScanfArgType_s.md new file mode 100644 index 00000000000..be631260712 --- /dev/null +++ b/man/checkers/invalidScanfArgType_s.md @@ -0,0 +1,42 @@ +# invalidScanfArgType_s + +**Message**: %s in format string (no. 1) requires a 'char \*' but the argument type is 'signed int \*'
+**Category**: Undefined Behaviour
+**Severity**: Warning/Portability
+**Language**: C/C++ + +## Description + +For a `scanf`-family call, the argument for a `%s`/`%[...]` specifier isn't a pointer to the matching +type (remember every `scanf` conversion writes through a pointer). + +## Motivation + +`scanf` writes the characters it reads directly through the pointer it's given - if that pointer +doesn't actually point to a `char`/`wchar_t` buffer, the write corrupts whatever memory it does point +to. Writing through a pointer typed differently than the object it actually points to is undefined +behaviour in its own right, not just a corrupted value. + +## How to fix + +Before: +```cpp +#include +void f(int x) { + scanf("%s", &x); // <- needs a 'char *', not 'int *' +} +``` + +After: +```cpp +#include +void f() { + char x[32]; + scanf("%31s", x); +} +``` + +## Related checkers + +- [invalidScanfArgType_int.md](invalidScanfArgType_int.md) / [invalidScanfArgType_float.md](invalidScanfArgType_float.md) - the same idea for numeric `scanf` conversions. +- [invalidPrintfArgType_s.md](invalidPrintfArgType_s.md) - the `printf`-side equivalent for `%s`. diff --git a/man/checkers/invalidScanfFormatWidth.md b/man/checkers/invalidScanfFormatWidth.md new file mode 100644 index 00000000000..8b65103f95a --- /dev/null +++ b/man/checkers/invalidScanfFormatWidth.md @@ -0,0 +1,61 @@ +# invalidScanfFormatWidth and invalidScanfFormatWidth_smaller + +**Message**: Width 9 given in format string (no. 1) is larger than destination buffer 'str[8]', use %8c to prevent overflowing it.
+**Category**: Undefined Behaviour
+**Severity**: Error/Warning
+**Language**: C/C++ + +## Description + +- `invalidScanfFormatWidth`: a `%s`/`%c` field width is *larger* than the destination + array - `scanf` doesn't stop at the array's bound, only at the field width, so this overflows the + buffer. +- `invalidScanfFormatWidth_smaller`: the field width is *smaller* than the destination array - not a + bug, but a hint that the width might have been computed wrong (this variant needs `--inconclusive` to + show, since it's just a suspicious-looking number, not a proven mistake). + +## Motivation + +`scanf` writes up to as many characters as the field width says, regardless of how big the destination +buffer actually is - if the width is larger than the buffer, the read overflows it as soon as the input +actually supplies that many characters (given short enough input, it may not overflow on a particular +run, which is what makes this easy to miss in testing); if the width is smaller than the buffer, it +just leaves the buffer holding less than expected. Neither mistake is visible from the call site alone. + +## How to fix + +Before: +```cpp +#include +void f() { + char str[8]; + scanf("%9c", str); // <- invalidScanfFormatWidth: 9 > 8, overflows 'str' +} +``` + +After: +```cpp +#include +void f() { + char str[8]; + scanf("%8c", str); +} +``` + +Before: +```cpp +#include +void f() { + char str[10]; + scanf("%5s", str); // <- invalidScanfFormatWidth_smaller: 5 is well under 10, worth double-checking +} +``` + +After: +```cpp +#include +void f() { + char str[10]; + scanf("%9s", str); +} +``` diff --git a/man/checkers/invalidTestForOverflow.md b/man/checkers/invalidTestForOverflow.md new file mode 100644 index 00000000000..769d417d2ba --- /dev/null +++ b/man/checkers/invalidTestForOverflow.md @@ -0,0 +1,40 @@ +# invalidTestForOverflow + +**Message**: Invalid test for overflow 'x+100<x'; signed integer overflow is undefined behavior.
+**Category**: Undefined Behaviour
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A classic overflow check (`x + c < x`) that is itself undefined behaviour for signed integers and +pointers - optimizing compilers are allowed to, and do, remove such checks. + +## Motivation + +Signed integer overflow is undefined behaviour in C/C++, so a compiler is allowed to assume it never +happens - which means it can optimize away a check whose only purpose is to detect that overflow just +occurred. Code that relies on this pattern can pass in a debug build and silently stop working once +optimizations are turned on. + +## How to fix + +Before: +```cpp +void f(int x) { + if (x + 100 < x) {} // <- relies on signed overflow, which is UB +} +``` + +After: +```cpp +#include +void f(int x) { + if (x > std::numeric_limits::max() - 100) {} +} +``` + +## Related checkers + +- [pointerAdditionResultNotNull.md](pointerAdditionResultNotNull.md) - a related undefined-behaviour + trap, relying on pointer overflow instead of signed integer overflow. diff --git a/man/checkers/invalidscanf.md b/man/checkers/invalidscanf.md new file mode 100644 index 00000000000..e1a832509b1 --- /dev/null +++ b/man/checkers/invalidscanf.md @@ -0,0 +1,37 @@ +# invalidscanf + +**Message**: scanf() without field width limits can crash with huge input data.
+**Category**: Undefined Behaviour
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A `scanf`-family call reads a string (`%s`, or a character set `%[...]`) with no maximum field width - +given long enough input, this overflows the destination buffer. + +## Motivation + +Without a field width, `scanf("%s", buf)` (and its relatives) will write as much input as it's given, +with no regard for the size of `buf` - a classic, easily-exploitable buffer overflow driven entirely by +the input data. + +## How to fix + +Before: +```cpp +#include +void f() { + char c[5]; + scanf("%s", c); // <- no width limit, can overflow 'c' +} +``` + +After: +```cpp +#include +void f() { + char c[5]; + scanf("%4s", c); +} +``` diff --git a/man/checkers/iterateByValue.md b/man/checkers/iterateByValue.md new file mode 100644 index 00000000000..ac8881d6704 --- /dev/null +++ b/man/checkers/iterateByValue.md @@ -0,0 +1,47 @@ +# iterateByValue + +**Message**: Variable 'x' is used to iterate by value. It could be declared as a const reference which is usually faster and recommended in C++.
+**Category**: Code Quality
+**Severity**: Performance
+**Language**: C++ + +## Description + +A range-based `for` loop's variable (`for (auto x : container)`) copies each element - copying is +wasteful when a `const` reference would do. + +## Motivation + +Copying every element of a container just to read it wastes time and memory proportional to the +element's size and the container's length, for no benefit over a `const` reference. + +## How to fix + +Before: +```cpp +#include +#include +void f() { + const std::set ss = { "a", "b", "c" }; + for (auto s : ss) // <- each string is copied + (void)s.size(); +} +``` + +After: +```cpp +#include +#include +void f() { + const std::set ss = { "a", "b", "c" }; + for (const auto& s : ss) + (void)s.size(); +} +``` + +## Related checkers + +- [passedByValue.md](passedByValue.md) - the same idea, for a function parameter instead of a loop + variable. +- [iterateByValueCallback.md](iterateByValueCallback.md) - the same idea, when the loop is inside a + callback function. diff --git a/man/checkers/iterateByValueCallback.md b/man/checkers/iterateByValueCallback.md new file mode 100644 index 00000000000..66584610c45 --- /dev/null +++ b/man/checkers/iterateByValueCallback.md @@ -0,0 +1,28 @@ +# iterateByValueCallback + +**Message**: Variable 'x' is used to iterate by value. It could be declared as a const reference which is usually faster and recommended in C++. However it seems that 'f' is a callback function.
+**Category**: Code Quality
+**Severity**: Performance
+**Language**: C++ + +## Description + +Same idea as [iterateByValue.md](iterateByValue.md): a range-based `for` loop copies each element +needlessly. This variant is for when the loop is inside a function used as a callback. + +## Motivation + +Copying every element of a container just to read it wastes time and memory proportional to the +element's size and the container's length, for no benefit over a `const` reference. + +## How to fix + +Declare the loop variable as a `const` reference instead of copying each element, the same as for +[iterateByValue.md](iterateByValue.md). + +## Related checkers + +- [iterateByValue.md](iterateByValue.md) - the same idea, for a loop that isn't inside a callback + function. +- [passedByValueCallback.md](passedByValueCallback.md) - the same idea, for a callback's parameter + instead of a loop variable. diff --git a/man/checkers/iterators1.md b/man/checkers/iterators1.md new file mode 100644 index 00000000000..397b34f794f --- /dev/null +++ b/man/checkers/iterators1.md @@ -0,0 +1,48 @@ +# iterators1 + +**Message**: Same iterator is used with different containers 'l1' and 'l2'.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +An iterator that belongs to one container is passed to an `insert()`/`erase()`-style method of a +different container. + +## Motivation + +An iterator only makes sense in the context of the container it came from. Passing it to a different +container's method is meaningless and undefined behaviour - almost always a copy-paste mistake where +the wrong container variable was used. + +## How to fix + +Before: +```cpp +#include +void foo() { + std::list l1; + std::list l2; + std::list::iterator it = l1.begin(); + l2.insert(it, 0); // <- iterators1: 'it' belongs to l1, not l2 +} +``` + +After: +```cpp +#include +void foo() { + std::list l1; + std::list::iterator it = l1.begin(); + l1.insert(it, 0); +} +``` + +## Related checkers + +- [mismatchingContainerIterator.md](mismatchingContainerIterator.md) - the same code shape is also + caught by this related, differently-worded check; both messages can appear together on the same + line. +- [mismatchingContainers.md](mismatchingContainers.md) - the equivalent mistake when the mismatched + iterators are compared (`!=`/`==`) rather than passed to a container method. diff --git a/man/checkers/iterators3.md b/man/checkers/iterators3.md new file mode 100644 index 00000000000..d37a7b9cdcc --- /dev/null +++ b/man/checkers/iterators3.md @@ -0,0 +1,46 @@ +# iterators3 + +**Message**: Same iterator is used with containers 'l1' that are temporaries or defined in different scopes.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +An iterator is compared against, or used with, a container of the same name that's actually a +different variable - a temporary, or one declared in a different, unrelated scope that happens to +shadow the container the iterator really belongs to. + +## Motivation + +Two variables sharing a name in different scopes are still two entirely different objects. An iterator +taken from one of them has no valid relationship to the other, even though the code reads as if it does +because the name is identical - comparing or using it against the wrong one is undefined behaviour. + +## How to fix + +Before: +```cpp +#include +std::vector f(); +bool foo() { + return f().begin() != f().end(); // <- iterators3: each call to f() returns a different temporary +} +``` + +After: call the function once and compare against the same instance. +```cpp +#include +std::vector f(); +bool foo() { + std::vector v = f(); + return v.begin() != v.end(); +} +``` + +## Related checkers + +- [iterators1.md](iterators1.md) - the same idea, but for two containers that are genuinely + different variables (not just same-named ones in different scopes). +- [mismatchingContainerIterator.md](mismatchingContainerIterator.md) - the same idea, for passing the + iterator into another container's method instead of comparing it. diff --git a/man/checkers/knownArgument.md b/man/checkers/knownArgument.md new file mode 100644 index 00000000000..23eca5dbdfe --- /dev/null +++ b/man/checkers/knownArgument.md @@ -0,0 +1,56 @@ +# knownArgument and knownArgumentHiddenVariableExpression + +**Message**: Argument 'x-x' to function 'func' is always 0. It does not matter what value 'x' has.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +- `knownArgument`: a function/constructor argument is an expression whose value cppcheck can already + compute at compile time, regardless of what a variable inside it holds - so passing that variable in + was pointless. +- `knownArgumentHiddenVariableExpression`: same idea, but specifically when a constant part of the + expression (`&& false`, `* 0`, `|| true`) completely masks a variable that looks like it should have + mattered - a common sign of a stray/leftover condition. + +## Motivation + +When an argument's value doesn't actually depend on a variable that's visibly part of its expression, +that's either dead code (the variable can be removed) or a sign the expression doesn't do what its +author expected - especially in the "hidden variable" case, where a constant operator silently cancels +out something that looks meaningful. + +## How to fix + +Before: +```cpp +void g(int); +void f(int x) { + g((x & 0x01) >> 7); // <- always 0, no matter what 'x' is +} +``` + +After: +```cpp +void g(int); +void f(int x) { + g(x >> 7); +} +``` + +Before: +```cpp +void dostuff(int); +void f(int x) { + dostuff(x * 0); // <- 'x' never actually matters here +} +``` + +After: +```cpp +void dostuff(int); +void f(int x) { + dostuff(x); +} +``` diff --git a/man/checkers/knownConditionTrueFalse.md b/man/checkers/knownConditionTrueFalse.md new file mode 100644 index 00000000000..1e354d9827c --- /dev/null +++ b/man/checkers/knownConditionTrueFalse.md @@ -0,0 +1,68 @@ +# knownConditionTrueFalse + +**Message**: Condition 'x==5' is always true
+**Category**: Correctness
+**Severity**: Style
+**Language**: C/C++ + +## Description + +cppcheck can already work out this condition's value from what it knows about the variables involved, +so the condition is always true or always false. + +## Motivation + +A condition that's always true or always false isn't testing anything - at best it's confusing, +leftover, or dead code; at worst, it means the intended check never actually happens and a real bug (a +wrong comparison, a typo'd variable, a value that was supposed to vary but doesn't) slips through +unnoticed. + +This check may need `--check-level=exhaustive` to see every case. + +## How to fix + +Before: +```cpp +void f() { + int x = 5; + if (x == 5) {} // <- always true +} +``` + +After: use the real variable instead of a fixed value, or remove the redundant check. +```cpp +void f(int x) { + if (x == 5) {} +} +``` + +## False positives to be aware of + +- **This check does not account for a member value changing through a call that reaches it indirectly** + (for example, through a container of pointers the function iterates over). A member read before such + a call can be wrongly assumed to still hold the same value afterwards: + ```cpp + #include + #include + struct S { int i; }; + struct T { + std::map m; + S* get(const std::string& s) { return m[s]; } + void modify() { for (const auto& e : m) e.second->i = 0; } + }; + void f(T& t) { + const S* p = t.get("abc"); + const int o = p->i; + t.modify(); // this can change p->i + if (p->i == o) {} // wrongly reported as always true + } + ``` + +## Related checkers + +- [assignIfError.md](assignIfError.md) - the same idea, specifically for a value just narrowed down by + a bitmask assignment. +- [moduloAlwaysTrueFalse.md](moduloAlwaysTrueFalse.md), [compareValueOutOfTypeRangeError.md](compareValueOutOfTypeRangeError.md) - + the same idea, for a `%` result or a type's value range instead of a general known value. +- [duplicateConditionalAssign.md](duplicateConditionalAssign.md) - a related, narrower case: an + assignment that repeats a value a condition already guarantees. diff --git a/man/checkers/knownEmptyContainer.md b/man/checkers/knownEmptyContainer.md new file mode 100644 index 00000000000..4e2d20c952a --- /dev/null +++ b/man/checkers/knownEmptyContainer.md @@ -0,0 +1,35 @@ +# knownEmptyContainer + +**Message**: Iterating over container 'v' that is always empty.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +A container or iterator that cppcheck knows is always empty is iterated over or otherwise used as if +it might contain elements. + +## Motivation + +Code that iterates over a container cppcheck can prove is empty at that point is dead code - the loop +body never runs, which is usually not what the author intended. + +## How to fix + +Before: +```cpp +#include +void f() { + std::vector v; + for (auto x : v) {} // <- knownEmptyContainer: 'v' is provably empty here +} +``` + +After: +```cpp +#include +void f(std::vector v) { + for (auto x : v) {} +} +``` diff --git a/man/checkers/knownPointerToBool.md b/man/checkers/knownPointerToBool.md new file mode 100644 index 00000000000..9e5cb7f68df --- /dev/null +++ b/man/checkers/knownPointerToBool.md @@ -0,0 +1,37 @@ +# knownPointerToBool + +**Message**: Pointer expression 'p' converted to bool is always true.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A pointer already known to be non-null (its address was just taken, or it was just checked) is +converted to `bool` - the result can only ever be `true`. + +## Motivation + +Converting a pointer that's already known to be non-null to `bool` doesn't test anything - it always +evaluates to `true`, so the code reads as if it's checking something that it isn't. + +## How to fix + +Before: +```cpp +void g(bool); +void f() { + int i = 5; + int* p = &i; + g(p); // <- 'p' can't be null here, so this is always true +} +``` + +After: +```cpp +void g(bool); +void f(int* p) { + g(p); +} +``` + diff --git a/man/checkers/leakNoVarFunctionCall.md b/man/checkers/leakNoVarFunctionCall.md new file mode 100644 index 00000000000..0753e19a0d0 --- /dev/null +++ b/man/checkers/leakNoVarFunctionCall.md @@ -0,0 +1,44 @@ +# leakNoVarFunctionCall + +**Message**: Allocation with malloc, strcpy doesn't release it.
+**Category**: Correctness
+**Severity**: Error
+**Language**: C/C++ + +## Description + +The result of an allocating call is passed directly into another function that is not known to store, +free, or otherwise take ownership of it (`strcpy(dst, strdup(src))`) - once that outer call returns, +nothing holds onto the allocated memory. + +## Motivation + +Passing an allocation straight into another call without ever storing it in a variable only works if +that outer call takes ownership (stores or frees the pointer itself). Most functions - like `strcpy()`, +which only reads through the pointer it's given - don't; once the statement finishes, the allocated +memory is unreachable and leaked. This is mainly reliable for calls to well-known library functions; +cppcheck does not reliably work out ownership for a user-defined function, even one whose body is +visible and plainly doesn't take ownership, so it stays quiet there rather than guess. + +## How to fix + +Before: +```cpp +void f() { + strcpy(a, strdup(p)); // <- strcpy() doesn't free its 2nd argument +} +``` + +After: +```cpp +void f() { + char* tmp = strdup(p); + strcpy(a, tmp); + free(tmp); +} +``` + +## Related checkers + +- [leakReturnValNotUsed.md](leakReturnValNotUsed.md) - the simpler case where the allocation's result + isn't passed anywhere at all, just discarded. diff --git a/man/checkers/leakReturnValNotUsed.md b/man/checkers/leakReturnValNotUsed.md new file mode 100644 index 00000000000..246d8656934 --- /dev/null +++ b/man/checkers/leakReturnValNotUsed.md @@ -0,0 +1,40 @@ +# leakReturnValNotUsed + +**Message**: Return value of allocation function 'malloc' is not stored.
+**Category**: Correctness
+**Severity**: Error
+**Language**: C/C++ + +## Description + +The result of an allocating call (`malloc(10);`, `new Foo;`) is discarded immediately - there was never +anywhere to store it, so it's an instant, guaranteed leak. + +## Motivation + +An allocating call's whole purpose is to hand back a pointer to the new memory/object - discarding that +return value immediately means the memory is allocated and then instantly unreachable, with no way for +anything to ever free it. Unlike most leaks, which depend on some later code path forgetting to free +something, this one is a leak the moment the line runs. + +## How to fix + +Before: +```cpp +void f() { + malloc(10); // <- return value discarded +} +``` + +After: +```cpp +void f() { + char* p = (char*)malloc(10); + free(p); +} +``` + +## Related checkers + +- [memleak.md](memleak.md) - the more general "allocated but never freed" check, for allocations that + are stored somewhere but still never freed. diff --git a/man/checkers/leakUnsafeArgAlloc.md b/man/checkers/leakUnsafeArgAlloc.md new file mode 100644 index 00000000000..fd3f9ba2049 --- /dev/null +++ b/man/checkers/leakUnsafeArgAlloc.md @@ -0,0 +1,42 @@ +# leakUnsafeArgAlloc + +**Message**: Unsafe allocation. If shared_ptr() throws, memory could be leaked. Use make_shared() instead.
+**Category**: Correctness
+**Severity**: Warning (inconclusive)
+**Language**: C++ + +## Description + +A call constructs a `shared_ptr`/`unique_ptr` around a `new` expression as one argument, while another +argument is a function call that might throw - if that other call throws before the smart pointer is +constructed, the raw `new` can leak (a classic pre-C++17 evaluation-order hazard). This is only reported +with `--inconclusive`, since cppcheck can't tell whether the other argument can actually throw. + +## Motivation + +Before C++17, the order in which a function call's arguments (and the work needed to construct them) +run relative to each other was unspecified. If `new int(42)` runs, and then the *other* argument's +function call throws before `shared_ptr(...)` gets to wrap it, the raw pointer from `new` is never +adopted by anything and leaks when the exception propagates. `make_shared`/`make_unique` avoid the +hazard entirely, since there's no separate raw pointer that can be orphaned this way. + +## How to fix + +Before: +```cpp +void g(); +void f(shared_ptr p, int x); +void x() { + f(shared_ptr(new int(42)), g()); // <- leaks if g() throws +} +``` + +After: +```cpp +void g(); +void f(shared_ptr p, int x); +void x() { + f(make_shared(42), g()); +} +``` + diff --git a/man/checkers/literalWithCharPtrCompare.md b/man/checkers/literalWithCharPtrCompare.md new file mode 100644 index 00000000000..ff7e724b518 --- /dev/null +++ b/man/checkers/literalWithCharPtrCompare.md @@ -0,0 +1,39 @@ +# literalWithCharPtrCompare and charLiteralWithCharPtrCompare + +**Message**: String literal compared with variable 'c'. Did you intend to use strcmp() instead?
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A `char*`/`wchar_t*` variable is compared directly against a string or character literal with `==`/`!=` +- this compares the pointer itself (its address), not the string's contents. + +## Motivation + +`c == "x"` compiles fine in C/C++ but almost never does what it looks like: it asks whether `c` happens +to point at the exact same memory as the literal `"x"`, not whether the characters match. This is one of +the most common C/C++ beginner mistakes, and the compiler gives no warning of its own. + +## How to fix + +Before: +```cpp +bool foo(char* c) { + return c == "x"; // <- compares addresses, not contents +} +``` + +After: +```cpp +#include +bool foo(char* c) { + return strcmp(c, "x") == 0; +} +``` + +## Related checkers + +- [staticStringCompare.md](staticStringCompare.md) - a related string-comparison mistake: comparing two + string literals (or two identical variables) with a string-comparison function. diff --git a/man/checkers/localMutex.md b/man/checkers/localMutex.md new file mode 100644 index 00000000000..e7b50cc0092 --- /dev/null +++ b/man/checkers/localMutex.md @@ -0,0 +1,44 @@ +# localMutex + +**Message**: The lock is ineffective because the mutex is locked at the same scope as the mutex itself.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ + +## Description + +A `std::mutex` and the `lock_guard`/`.lock()` call that locks it are declared in the very same scope - +the mutex is a fresh, unshared object every time that scope is entered, so nothing else can ever be +contending for it and the lock has no effect. + +## Motivation + +A mutex only protects shared data if the same mutex instance is reachable from every place that might +access that data concurrently. A mutex that's a local variable, locked in the same function it's +declared in, is a brand-new object on every call - no other thread (or even another call to the same +function) can ever see or contend for that exact instance, so the "protection" is illusory. + +## How to fix + +Before: +```cpp +#include +void f() { + std::mutex m; + std::lock_guard g(m); // <- 'm' is a fresh, private mutex every call +} +``` + +After: +```cpp +#include +std::mutex m; +void f() { + std::lock_guard g(m); +} +``` + +## Related checkers + +- [globalLockGuard.md](globalLockGuard.md) - the opposite mistake: a lock guard given static/global + storage, so it never releases the lock. diff --git a/man/checkers/mallocOnClassError.md b/man/checkers/mallocOnClassError.md new file mode 100644 index 00000000000..cc3cb0e0a5c --- /dev/null +++ b/man/checkers/mallocOnClassError.md @@ -0,0 +1,62 @@ +# mallocOnClassError and mallocOnClassWarning + +**Message**: Memory for class instance allocated with malloc(), but class contains a virtual function.
+**Category**: Undefined Behaviour
+**Severity**: Error/Warning
+**Language**: C++ + +## Description + +A C++ class/struct instance is allocated with `malloc()`/`calloc()`/`realloc()` instead of `new` - the +constructor never runs. + +- `mallocOnClassError`: the class clearly needs its constructor to run (it has a virtual function, so + its vtable pointer is never set up, or a non-trivial member type that itself needs construction). +- `mallocOnClassWarning`: a constructor exists but the class might still happen to work out by luck + (for example if the constructor only sets members to values that also happen to match zeroed memory). + +## Motivation + +`malloc()` only allocates raw memory - it never calls a C++ constructor, so no object of that class type +actually exists in that memory yet. The `malloc()` call itself doesn't crash or misbehave; the risk is +in what happens next - calling any member function through the pointer (especially a virtual one, which +reads a vtable pointer that was never set up), or letting a destructor run on it, is undefined behaviour, +because the standard's rules for using an object of a type only apply once that object's lifetime has +actually begun via a constructor call. + +## How to fix + +Before: +```cpp +#include +struct C { virtual void bar(); }; +void foo(C*& p) { + p = malloc(sizeof(C)); // <- mallocOnClassError: no constructor/vtable set up +} +``` + +After: +```cpp +struct C { virtual void bar(); }; +void foo(C*& p) { + p = new C(); +} +``` + +Before: +```cpp +#include +class C { public: C() {} }; +void foo(C*& p) { + p = malloc(sizeof(C)); // <- mallocOnClassWarning: C()'s body never runs +} +``` + +After: +```cpp +class C { public: C() {} }; +void foo(C*& p) { + p = new C(); +} +``` + diff --git a/man/checkers/memleak.md b/man/checkers/memleak.md new file mode 100644 index 00000000000..2fde8a78d43 --- /dev/null +++ b/man/checkers/memleak.md @@ -0,0 +1,62 @@ +# memleak and resourceLeak + +**Message**: Memory leak: p
+**Category**: Correctness
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A locally-allocated variable is allocated (`malloc`/`new`/`fopen`/and other functions described the +same way in the library configuration), but no path frees/releases it before it goes out of scope, is +reassigned, or the function returns. + +- `memleak`: the allocation is memory (`malloc`, `new`, ...). +- `resourceLeak`: the allocation is some other kind of resource (a file handle from `fopen`, ...). + +## Motivation + +A memory or resource leak wastes memory/handles until the process exits, which can degrade or crash +long-running programs. This is easy for a human reviewer to miss, especially across multiple `if`/`else` +branches, but mechanical enough for cppcheck to track precisely in straightforward code. cppcheck only +tracks an allocation through straight-line code: once a loop or `goto` appears anywhere in the function, +it stops checking that function entirely rather than risk a wrong guess, so its silence on a function +with a loop in it isn't proof the code has no leak. + +## How to fix + +Before: +```cpp +void f() { + char *p = malloc(10); // <- memleak: never freed +} +``` + +After: +```cpp +void f() { + char *p = malloc(10); + free(p); +} +``` + +## False positives to be aware of + +- **A false positive is possible when a variable is freed only through a reference alias to it.** The + checker doesn't always recognize that freeing the alias also frees the original variable, and can + report a leak that doesn't actually exist: + ```cpp + void f() { + char *p; + char *&ref = p; + p = malloc(10); + free(ref); // frees 'p', but is reported as "Memory leak: p" anyway + } + ``` + +## Related checkers + +- [mismatchAllocDealloc.md](mismatchAllocDealloc.md) - for when the allocation *is* freed, but with the + wrong deallocation function. +- [doubleFree.md](doubleFree.md) / [deallocuse.md](deallocuse.md) / [deallocret.md](deallocret.md) - the + related mistakes of freeing something twice, or using it afterwards, once it has been freed. diff --git a/man/checkers/memleakOnRealloc.md b/man/checkers/memleakOnRealloc.md new file mode 100644 index 00000000000..ba99fab784f --- /dev/null +++ b/man/checkers/memleakOnRealloc.md @@ -0,0 +1,45 @@ +# memleakOnRealloc + +**Message**: Common realloc mistake: 'a' nulled but not freed upon failure
+**Category**: Correctness
+**Severity**: Error
+**Language**: C/C++ + +## Description + +The classic `p = realloc(p, newSize);` mistake - if `realloc()` fails, it returns `NULL` without +freeing the original block, but this code has just overwritten `p` with that `NULL`, losing the only +pointer to the original (still allocated) memory. + +## Motivation + +`realloc()` only frees the original block on success; on failure the original block is untouched and +still needs freeing, but the return value is `NULL`. Assigning the return value straight back onto the +only variable that pointed to the original block throws that pointer away too, permanently leaking the +original allocation whenever `realloc()` fails. + +## How to fix + +Before: +```cpp +void foo() { + char *a = (char *)malloc(10); + a = (char *)realloc(a, 100); // <- original block lost if this fails + free(a); +} +``` + +After: +```cpp +void foo() { + char *a = (char *)malloc(10); + char *tmp = (char *)realloc(a, 100); + if (tmp) + a = tmp; + free(a); +} +``` + +## Related checkers + +- [memleak.md](memleak.md) - the more general "allocated but never freed" check this one complements. diff --git a/man/checkers/memsetClass.md b/man/checkers/memsetClass.md new file mode 100644 index 00000000000..a41159d00e6 --- /dev/null +++ b/man/checkers/memsetClass.md @@ -0,0 +1,53 @@ +# memsetClass + +**Message**: Using 'memset' on class that contains a 'std::string'.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +`memset()`/`memcpy()`/`memmove()` is used on an object of a type that isn't POD (for example it +contains a `std::string`) - the constructor, destructor, and copy semantics that type relies on are all +bypassed. + +## Motivation + +Raw memory functions like `memset()` don't know anything about C++ object semantics - they just +overwrite bytes. Using one on an object that has its own constructor/destructor/internal invariants +(like `std::string`'s internal pointer/length bookkeeping) corrupts that object instead of resetting it. + +## How to fix + +Before: +```cpp +#include +#include +class Fred { +public: + std::string b; +}; +void f() { + Fred fred; + memset(&fred, 0, sizeof(Fred)); // <- memsetClass: bypasses std::string's own management +} +``` + +After: +```cpp +#include +class Fred { +public: + std::string b; +}; +void f() { + Fred fred; +} +``` + +## Related checkers + +- [memsetClassFloat.md](memsetClassFloat.md) - the same underlying mistake, specifically for a type + containing a `float`/`double` member. +- [memsetClassReference.md](memsetClassReference.md) - the same underlying mistake, specifically for a + type containing a reference member. diff --git a/man/checkers/memsetClassFloat.md b/man/checkers/memsetClassFloat.md new file mode 100644 index 00000000000..2fb1c7b8e08 --- /dev/null +++ b/man/checkers/memsetClassFloat.md @@ -0,0 +1,54 @@ +# memsetClassFloat + +**Message**: Using 'memset' on struct which contains a floating point number.
+**Category**: Portability
+**Severity**: Warning
+**Language**: C++ + +## Description + +`memset()`/`memcpy()`/`memmove()` is used on a type containing a `float`/`double` member - whether an +all-zero-bytes pattern actually means `0.0` on the target platform isn't something the C++ standard +guarantees. + +## Motivation + +Zeroing a struct's bytes to reset a floating-point member to `0.0` relies on the platform's +floating-point representation matching all-zero-bytes with the value zero. This is true in practice on +essentially all mainstream hardware (IEEE 754), but it's not guaranteed by the language, so it's a +portability risk rather than a certainty. + +## How to fix + +Before: +```cpp +#include +typedef float realnum; +struct multilevel_data { + realnum *GammaInv; + realnum data[1]; +}; +void f() { + multilevel_data d; + memset(&d, 0, sizeof(multilevel_data)); // <- memsetClassFloat +} +``` + +After: +```cpp +typedef float realnum; +struct multilevel_data { + realnum *GammaInv; + realnum data[1]; +}; +void f() { + multilevel_data d = {}; +} +``` + +## Related checkers + +- [memsetClass.md](memsetClass.md) - the same underlying mistake, specifically for a type that isn't + POD at all (for example it contains a `std::string`). +- [memsetClassReference.md](memsetClassReference.md) - the same underlying mistake, specifically for a + type containing a reference member. diff --git a/man/checkers/memsetClassReference.md b/man/checkers/memsetClassReference.md new file mode 100644 index 00000000000..e9514686462 --- /dev/null +++ b/man/checkers/memsetClassReference.md @@ -0,0 +1,54 @@ +# memsetClassReference + +**Message**: Using 'memset' on class that contains a reference.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +`memset()`/`memcpy()`/`memmove()` is used on a type containing a reference member - a reference can't +be reseated after it's bound, so overwriting its bytes doesn't do what an assignment would. + +## Motivation + +A reference member is bound once, at construction, and can never be made to refer to something else. +Overwriting its bytes with `memset()` doesn't rebind it - it corrupts whatever internal representation +the compiler uses for references, which is undefined behaviour. + +## How to fix + +Before: +```cpp +#include +#include +class A { +public: + std::string &s; + A(std::string &str) : s(str) {} +}; +void f(std::string &str) { + A a(str); + memset(&a, 0, sizeof(a)); // <- memsetClassReference: can't overwrite a bound reference like this +} +``` + +After: +```cpp +#include +class A { +public: + std::string &s; + A(std::string &str) : s(str) {} +}; +void f(std::string &str) { + A a(str); +} +``` + +## Related checkers + +- [memsetClass.md](memsetClass.md) - the same underlying mistake, specifically for a type that isn't + POD at all (for example it contains a `std::string`). +- [memsetClassFloat.md](memsetClassFloat.md) - the same underlying mistake, specifically for a type + containing a `float`/`double` member. diff --git a/man/checkers/memsetFloat.md b/man/checkers/memsetFloat.md new file mode 100644 index 00000000000..4433bcb7195 --- /dev/null +++ b/man/checkers/memsetFloat.md @@ -0,0 +1,58 @@ +# memsetFloat and memsetValueOutOfRange + +**Message**: The 2nd memset() argument 'x' is a float, its representation is implementation defined.
+**Category**: Correctness
+**Severity**: Portability/Warning
+**Language**: C/C++ + +## Description + +`memset()`'s fill value (2nd argument): + +- `memsetFloat`: is a `float`, whose byte representation is implementation-defined - the actual bytes + written will vary across platforms. +- `memsetValueOutOfRange`: is a literal integer that doesn't fit in an `unsigned char` - `memset()` + only ever writes the `unsigned char` conversion of this value, so anything outside `0..255` (or the + platform's signed-char range) doesn't mean what it looks like. + +## Motivation + +`memset()` fills memory byte-by-byte using the value converted to `unsigned char`. A `float` argument +has no portable byte-for-byte meaning in that context, and an integer literal outside a single byte's +range is silently truncated - both make the call's actual effect different from what the source code +suggests. + +## How to fix + +Before: +```cpp +void f(void* p, size_t n) { + memset(p, 1.0f, n); // <- float, implementation-defined byte pattern +} +``` + +After: +```cpp +void f(void* p, size_t n) { + memset(p, 0, n); +} +``` + +Before: +```cpp +void f(void* p, size_t n) { + memset(p, 300, n); // <- 300 doesn't fit in an unsigned char +} +``` + +After: +```cpp +void f(void* p, size_t n) { + memset(p, 0xff, n); +} +``` + +## Related checkers + +- [memsetZeroBytes.md](memsetZeroBytes.md) - a different `memset()` misuse, about the length (3rd) + argument being zero rather than the fill value. diff --git a/man/checkers/memsetZeroBytes.md b/man/checkers/memsetZeroBytes.md new file mode 100644 index 00000000000..d6ecb11d80e --- /dev/null +++ b/man/checkers/memsetZeroBytes.md @@ -0,0 +1,37 @@ +# memsetZeroBytes + +**Message**: memset() called to fill 0 bytes.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +`memset(ptr, value, 0)` - the 2nd and 3rd arguments look swapped, since filling 0 bytes has no effect. + +## Motivation + +`memset()`'s signature is `memset(void *ptr, int value, size_t num)` - a literal `0` as the last +argument means the call does nothing at all, which is almost always a sign the fill value and the +length were written in the wrong order. + +## How to fix + +Before: +```cpp +void f(void* p) { + memset(p, sizeof(p), 0); // <- value and length look swapped +} +``` + +After: +```cpp +void f(void* p, size_t n) { + memset(p, 0, n); +} +``` + +## Related checkers + +- [memsetFloat.md](memsetFloat.md) - other `memset()` misuses about the fill-value (2nd) argument + rather than the length. diff --git a/man/checkers/mismatchAllocDealloc.md b/man/checkers/mismatchAllocDealloc.md new file mode 100644 index 00000000000..4119fa1963e --- /dev/null +++ b/man/checkers/mismatchAllocDealloc.md @@ -0,0 +1,40 @@ +# mismatchAllocDealloc + +**Message**: Mismatching allocation and deallocation: f
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A locally-allocated variable is freed with a function that doesn't match how it was allocated (for +example `new[]` freed with `delete`, or `fopen` freed with `free`). + +## Motivation + +Freeing something with the wrong function is undefined behaviour: `new[]`/`delete` mismatches can +corrupt the heap, and freeing a `FILE*` with `free()` instead of `fclose()` skips flushing/closing the +underlying file descriptor. This is easy for a human reviewer to miss, but mechanical enough for +cppcheck to track precisely in straightforward code. + +## How to fix + +Before: +```cpp +void f() { + FILE *f = fopen(fname, mode); + free(f); // <- mismatchAllocDealloc: fopen() must be matched with fclose() +} +``` + +After: +```cpp +void f() { + FILE *f = fopen(fname, mode); + fclose(f); +} +``` + +## Related checkers + +- [memleak.md](memleak.md) - for the related, simpler case of an allocation that is never freed at all. diff --git a/man/checkers/mismatchingBitAnd.md b/man/checkers/mismatchingBitAnd.md new file mode 100644 index 00000000000..61cf0f780c7 --- /dev/null +++ b/man/checkers/mismatchingBitAnd.md @@ -0,0 +1,43 @@ +# mismatchingBitAnd + +**Message**: Mismatching bitmasks. Result is always 0 (X = Y & 0xf0; Z = X & 0x1; => Z=0).
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A variable is masked with `& 0xf0`, and the result is masked again later with a value that shares no +bits with the first mask - so the final result is always `0`. + +## Motivation + +Chaining two bitmasks that share no bits is either dead code (the second mask can be removed since the +result is always `0`) or a sign that one of the two masks is wrong - either way it's worth a second +look. + +## How to fix + +Before: +```cpp +void f(int x) { + int a = x & 0xf0; + int b = a & 0x1; // <- always 0, no bits are shared with 0xf0 + if (b) {} +} +``` + +After: +```cpp +void f(int x) { + int a = x & 0xf0; + int b = a & 0x10; + if (b) {} +} +``` + +## Related checkers + +- [badBitmaskCheck.md](badBitmaskCheck.md) - `|` used where `&` was probably meant. +- [comparisonError.md](comparisonError.md) - a bitwise expression compared against a constant it can + never produce. diff --git a/man/checkers/mismatchingContainerExpression.md b/man/checkers/mismatchingContainerExpression.md new file mode 100644 index 00000000000..0ff9c8f35e3 --- /dev/null +++ b/man/checkers/mismatchingContainerExpression.md @@ -0,0 +1,46 @@ +# mismatchingContainerExpression + +**Message**: Iterators to containers from different expressions 'f()' and 'g()' are used together.
+**Category**: Undefined Behaviour
+**Severity**: Warning
+**Language**: C++ + +## Description + +Two iterators come from two separate calls to same-looking expressions (for example `begin(f())` and +`end(g())`) that cppcheck can't prove refer to the same container. + +## Motivation + +When the container itself is the result of a function call rather than a plain variable, it's easy to +accidentally call two different functions (or the same function twice, if it returns a different +container each time) for what was meant to be a single container's begin/end pair. + +## How to fix + +Before: +```cpp +#include +#include +std::vector& f(); +std::vector& g(); +void foo() { + (void)std::find(begin(f()), end(g()), 0); // <- mismatchingContainerExpression +} +``` + +After: +```cpp +#include +#include +std::vector& f(); +void foo() { + (void)std::find(begin(f()), end(f()), 0); +} +``` + +## Related checkers + +- [mismatchingContainers.md](mismatchingContainers.md) - the stronger version of this check, for when + cppcheck can tell the two iterators definitely belong to different containers (rather than merely + being unable to prove they're the same one). diff --git a/man/checkers/mismatchingContainerIterator.md b/man/checkers/mismatchingContainerIterator.md new file mode 100644 index 00000000000..60c4a2e7f57 --- /dev/null +++ b/man/checkers/mismatchingContainerIterator.md @@ -0,0 +1,47 @@ +# mismatchingContainerIterator + +**Message**: Iterator 'it' referring to container 'l1' is used with container 'l2'.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +An iterator that belongs to one container is passed to an `insert()`/`erase()`-style method of a +different container. + +## Motivation + +An iterator only makes sense in the context of the container it came from. Passing it to a different +container's method is meaningless and undefined behaviour - almost always a copy-paste mistake where +the wrong container variable was used. + +## How to fix + +Before: +```cpp +#include +void foo() { + std::list l1; + std::list l2; + std::list::iterator it = l1.begin(); + l2.insert(it, 0); // <- mismatchingContainerIterator: 'it' belongs to l1, not l2 +} +``` + +After: +```cpp +#include +void foo() { + std::list l1; + std::list::iterator it = l1.begin(); + l1.insert(it, 0); +} +``` + +## Related checkers + +- [iterators1.md](iterators1.md) - the same code shape is also caught by this related, differently + worded check; both messages can appear together on the same line. +- [mismatchingContainers.md](mismatchingContainers.md) - the equivalent mistake when the mismatched + iterators are compared (`!=`/`==`) rather than passed to a container method. diff --git a/man/checkers/mismatchingContainers.md b/man/checkers/mismatchingContainers.md new file mode 100644 index 00000000000..163c8a1c8c8 --- /dev/null +++ b/man/checkers/mismatchingContainers.md @@ -0,0 +1,47 @@ +# mismatchingContainers + +**Message**: Iterators of different containers 'l1' and 'l2' are used together.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +An iterator that belongs to one container is compared (with `!=`/`==`, or via a relational operator) +against an iterator from a different container - most often seen as the loop condition of a `for` loop +that begins with one container's iterator and ends with another's. + +## Motivation + +Comparing iterators from two different containers is meaningless - there's no relationship between +their positions - and is almost always a copy-paste mistake, typically a loop's end condition that +still refers to the wrong container. + +## How to fix + +Before: +```cpp +#include +void f() { + std::list l1; + std::list l2; + for (std::list::iterator it = l1.begin(); it != l2.end(); ++it) { } // <- mismatchingContainers +} +``` + +After: +```cpp +#include +void f() { + std::list l1; + for (std::list::iterator it = l1.begin(); it != l1.end(); ++it) { } +} +``` + +## Related checkers + +- [iterators1.md](iterators1.md) / [mismatchingContainerIterator.md](mismatchingContainerIterator.md) - + the equivalent mistake when the mismatched iterator is passed to a container method instead of + compared. +- [mismatchingContainerExpression.md](mismatchingContainerExpression.md) - a related, weaker check for + when cppcheck can't prove two iterator-producing expressions refer to the same container at all. diff --git a/man/checkers/missingMemberCopy.md b/man/checkers/missingMemberCopy.md new file mode 100644 index 00000000000..a30223ebd71 --- /dev/null +++ b/man/checkers/missingMemberCopy.md @@ -0,0 +1,46 @@ +# missingMemberCopy + +**Message**: Member variable 'classname::varname' is not initialized in the copy constructor.
+**Category**: Correctness
+**Severity**: Warning (Inconclusive)
+**Language**: C++ + +## Description + +A copy or move constructor is defined (with a body), but one particular member is never assigned +anywhere in it - everything else about the class suggests it should have been copied. + +## Motivation + +A hand-written copy/move constructor that forgets one member produces objects whose copies silently +diverge from the original in that one field - a subtle bug, especially in a class with many members, +since the constructor still compiles and mostly "looks right." + +## How to fix + +Copy (or move) the missing member too. + +Before: +```cpp +struct S { + int i{}; + S() = default; + S(const S& s) {} // <- 'i' isn't copied +}; +``` + +After: +```cpp +struct S { + int i{}; + S() = default; + S(const S& s) : i(s.i) {} +}; +``` + +## Related checkers + +- [operatorEqVarError.md](operatorEqVarError.md) - the same idea, for a hand-written `operator=` instead + of a copy/move constructor. +- [uninitMemberVar.md](uninitMemberVar.md) - the related family of checks for a member that's never + initialized by a regular constructor at all. diff --git a/man/checkers/missingOverride.md b/man/checkers/missingOverride.md new file mode 100644 index 00000000000..5ed810334d5 --- /dev/null +++ b/man/checkers/missingOverride.md @@ -0,0 +1,37 @@ +# missingOverride + +**Message**: The function 'f' overrides a function in a base class but is not marked with a 'override' specifier.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +A derived class's function has the same signature as a virtual function in its base class, but isn't +marked `override`. + +## Motivation + +Without `override`, if a later edit to the base class's signature accidentally stops a derived function +from being an override (a parameter type change, a typo in the name, ...), the derived function quietly +becomes an unrelated new function instead - and nothing warns about it, since it's valid C++ either way. +Marking overrides explicitly turns that silent mismatch into a compile error. + +## How to fix + +Before: +```cpp +class Base { virtual void f(); }; +class Derived : Base { virtual void f(); }; // <- missingOverride: no 'override' +``` + +After: +```cpp +class Base { virtual void f(); }; +class Derived : Base { void f() override; }; +``` + +## Related checkers + +- [uselessOverride.md](uselessOverride.md) - for when a function *is* correctly overriding a base one, + but the override doesn't actually change any behaviour. diff --git a/man/checkers/missingPercentCharacter.md b/man/checkers/missingPercentCharacter.md new file mode 100644 index 00000000000..e89362e67d8 --- /dev/null +++ b/man/checkers/missingPercentCharacter.md @@ -0,0 +1,36 @@ +# missingPercentCharacter + +**Message**: Missing percent character in Token::Match() pattern: "%type"
+**Category**: Code Quality
+**Severity**: Error
+**Language**: C++ (cppcheck's own source code only) + +## Description + +A `%something` placeholder is missing its closing `%`. + +This is not a general-purpose checker for arbitrary C/C++ programs - see +[simplePatternError.md](simplePatternError.md) for the shared background on this internal, +cppcheck-source-only checker. + +## Motivation + +Without the closing `%`, the pattern-matching engine doesn't recognize the text as a placeholder at +all, and instead tries to match it as literal characters - silently breaking the match the pattern was +written to perform. + +## How to fix + +Before: +```cpp +Token::Match(tok, "%type"); // <- missing closing '%' +``` + +After: +```cpp +Token::Match(tok, "%type%"); +``` + +## Related checkers + +- [unknownPattern.md](unknownPattern.md) - a `%something%` placeholder that's correctly closed but isn't a recognized name. diff --git a/man/checkers/missingReturn.md b/man/checkers/missingReturn.md new file mode 100644 index 00000000000..cfaa7fde670 --- /dev/null +++ b/man/checkers/missingReturn.md @@ -0,0 +1,42 @@ +# missingReturn + +**Message**: Found an exit path from function with non-void return type that has missing return statement
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A function with a non-`void` return type has a path that reaches the end of the function (or a +`case`/`if` branch) without returning a value. + +## Motivation + +Falling off the end of a value-returning function without a `return` is undefined behaviour the moment +that code path is actually taken - the standard says so unconditionally, regardless of whether the +caller ever reads the returned value. Since the checker flags the mere existence of such a path, whether +this is reached in practice depends on the arguments/state a real call ends up using; when it is +reached, the caller receives whatever happened to be in the return-value register/location, which is +unpredictable and can differ between builds, platforms, or optimization levels. + +## How to fix + +Before: +```cpp +int f(int x) { + if (x) { + return 1; + } +} // <- no return if 'x' is 0 +``` + +After: +```cpp +int f(int x) { + if (x) { + return 1; + } + return 0; +} +``` + diff --git a/man/checkers/moduloAlwaysTrueFalse.md b/man/checkers/moduloAlwaysTrueFalse.md new file mode 100644 index 00000000000..b82103616d6 --- /dev/null +++ b/man/checkers/moduloAlwaysTrueFalse.md @@ -0,0 +1,36 @@ +# moduloAlwaysTrueFalse + +**Message**: Comparison of modulo result is predetermined, because it is always less than 5.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A `%` result is compared against a constant outside the range that modulo operation could ever produce. + +## Motivation + +`x % 5` can only ever be `0..4` - comparing it against `5` or anything larger makes the comparison +predetermined, which usually means the modulo divisor or the compared-against constant is wrong. + +## How to fix + +Before: +```cpp +void f(int x) { + if (x % 5 == 5) {} // <- 'x % 5' is always 0..4 +} +``` + +After: +```cpp +void f(int x) { + if (x % 5 == 0) {} +} +``` + +## Related checkers + +- [knownConditionTrueFalse.md](knownConditionTrueFalse.md) - the more general check for a condition + whose truth value cppcheck already knows in advance. diff --git a/man/checkers/moduloofone.md b/man/checkers/moduloofone.md new file mode 100644 index 00000000000..6b3d3ee6db8 --- /dev/null +++ b/man/checkers/moduloofone.md @@ -0,0 +1,33 @@ +# moduloofone + +**Message**: Modulo of one is always equal to zero
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +An expression is reduced modulo `1`, which is always `0` no matter what the other operand is. + +## Motivation + +`x % 1` is always `0` for any integer `x` - the computation is pointless and almost always signals a +typo (a `1` that should have been some other number, or a variable that should have been used instead +of a literal `1`). + +## How to fix + +Before: +```cpp +void f(unsigned int x) { + int y = x % 1; // <- always 0 +} +``` + +After: +```cpp +void f(unsigned int x) { + int y = x % 2; + (void)y; +} +``` diff --git a/man/checkers/multiCondition.md b/man/checkers/multiCondition.md new file mode 100644 index 00000000000..1d68492c290 --- /dev/null +++ b/man/checkers/multiCondition.md @@ -0,0 +1,58 @@ +# multiCondition + +**Message**: Expression is always false because 'else if' condition matches previous condition at line 2.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +An `else if` condition is the same as (dead code), or the exact opposite of (always true), the +condition already tested by the `if` before it. + +## Motivation + +By the time an `else if` is reached, the `if` before it is already known to be false. If the `else if` +condition is identical to that `if`, it can never be true either (dead code); if it's the exact +opposite, it's guaranteed to be true (redundant, since `else` alone would do the same thing). + +## How to fix + +Before: +```cpp +void f(int x) { + if (x == 1) {} + else if (x == 1) {} // <- can never be reached +} +``` + +After: +```cpp +void f(int x) { + if (x == 1) {} + else if (x == 2) {} +} +``` + +Before: +```cpp +void f(int x) { + if (x == 1) {} + else if (x != 1) {} // <- always true, we're already past the 'x == 1' branch +} +``` + +After: +```cpp +void f(int x) { + if (x == 1) {} + else {} +} +``` + +## Related checkers + +- [duplicateCondition.md](duplicateCondition.md) - the same idea, but for two consecutive plain `if` + statements rather than an `if`/`else if` chain. +- [oppositeInnerCondition.md](oppositeInnerCondition.md), [identicalInnerCondition.md](identicalInnerCondition.md) - + the same idea again, but for an `if` nested directly inside another `if`. diff --git a/man/checkers/multiplySizeof.md b/man/checkers/multiplySizeof.md new file mode 100644 index 00000000000..8b27e5f16e9 --- /dev/null +++ b/man/checkers/multiplySizeof.md @@ -0,0 +1,60 @@ +# multiplySizeof and divideSizeof + +**Message**: Multiplying sizeof() with sizeof() indicates a logic error.
+**Category**: Correctness
+**Severity**: Warning (inconclusive)
+**Language**: C/C++ + +## Description + +- `multiplySizeof`: `sizeof(a) * sizeof(b)` - multiplying two `sizeof` results together almost never + makes sense dimensionally (the result would be "bytes squared"). +- `divideSizeof`: dividing the result of `sizeof()` on a pointer type by another `sizeof()` - since + `sizeof(pointer)` is the size of the pointer itself, not of the data it points to, this computes a + meaningless ratio. + +Both require `--inconclusive` to be enabled. + +## Motivation + +Both patterns are dimensionally suspicious: multiplying two byte-counts together, or dividing a +pointer's fixed size by an unrelated element size, essentially never produces the number a program +actually needs. They're the kind of typo (an extra `sizeof`, or a `sizeof` applied to the wrong +variable) that's easy to introduce and easy to miss, since the code still compiles. + +## How to fix + +Before: +```cpp +void f() { + int a = 1, b = 1; + int s = sizeof(a) * sizeof(b); // <- multiplying two sizeof() results +} +``` + +After: +```cpp +void f() { + int a = 1, b = 1; + int s = sizeof(a) * b; // multiply by the count, not another sizeof() +} +``` + +Before: +```cpp +void f(int *p) { + int n = 100 / sizeof(p); // <- dividing by the pointer's own size +} +``` + +After: +```cpp +void f(int *p) { + int n = 100 / sizeof(*p); +} +``` + +## Related checkers + +- [pointerSize.md](pointerSize.md) - the same "size of pointer, not of data" mistake, specifically + when used as (or to compute) an argument to `malloc`/`memcpy`/`memset`-family functions. diff --git a/man/checkers/nanInArithmeticExpression.md b/man/checkers/nanInArithmeticExpression.md new file mode 100644 index 00000000000..5f8134be309 --- /dev/null +++ b/man/checkers/nanInArithmeticExpression.md @@ -0,0 +1,42 @@ +# nanInArithmeticExpression + +**Message**: Using NaN/Inf in a computation.
+**Category**: Correctness
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A computation divides by `0.0` and immediately adds or subtracts another value from the result - the +addition/subtraction is pointless once the division has already produced `NaN`/`Inf`. + +## Motivation + +Once an expression is `NaN` or `Inf`, further arithmetic on it (other than a handful of operations +specifically meant to detect/handle that case) can't recover a meaningful value - so code that keeps +computing with it is either dead weight or a sign the `NaN`/`Inf` case wasn't actually intended. + +## How to fix + +Before: +```cpp +#include +void f() { + double x = 3.0 / 0.0 + 1.0; // <- pointless once the division already produced NaN/Inf + printf("%f", x); +} +``` + +After: +```cpp +#include +void f(double divisor) { + double x = 3.0 / divisor + 1.0; + printf("%f", x); +} +``` + +## Related checkers + +- [zerodiv.md](zerodiv.md) - integer division by zero, which is undefined behaviour rather than the + well-defined `NaN`/`Inf` result of floating-point division by zero. diff --git a/man/checkers/negativeArraySize.md b/man/checkers/negativeArraySize.md new file mode 100644 index 00000000000..97ca1b25336 --- /dev/null +++ b/man/checkers/negativeArraySize.md @@ -0,0 +1,60 @@ +# negativeArraySize and negativeMemoryAllocationSize + +**Message**: Declaration of array 'a' with negative size is undefined behaviour
+**Category**: Correctness/Undefined Behaviour
+**Severity**: Error/Warning
+**Language**: C/C++ + +## Description + +Something that determines the size of an array-like allocation is known to be negative: + +- `negativeArraySize`: a variable-length array is declared with a size cppcheck knows is negative. +- `negativeMemoryAllocationSize`: a `new[]` expression is given a size cppcheck knows is negative. + +## Motivation + +- `negativeArraySize`: a variable-length array's size, once converted to the unsigned type it's ultimately + represented as, becomes an enormous positive number - so instead of failing cleanly, the declaration + either crashes outright or succeeds with a wildly wrong, huge size. This is undefined behaviour. +- `negativeMemoryAllocationSize`: in C++11 and later, `new[]` is specifically required to detect this and + throw `std::bad_array_new_length` instead - so this specific case is a well-defined, catchable error + rather than undefined behaviour, but it's still a bug worth flagging: an uncaught exception terminates + the program, and any earlier C++ standard leaves the size negotiation undefined instead. + +## How to fix + +Before: +```cpp +void f() { + int n = -1; + int a[n]; // <- negativeArraySize +} +``` + +After: +```cpp +void f() { + int n = 1; + int a[n]; +} +``` + +Before: +```cpp +void f() { + int n = -1; + int *p = new int[n]; // <- negativeMemoryAllocationSize +} +``` + +After: +```cpp +void f() { + int n = 1; + int *p = new int[n]; + p[0] = 0; + delete[] p; +} +``` + diff --git a/man/checkers/negativeContainerIndex.md b/man/checkers/negativeContainerIndex.md new file mode 100644 index 00000000000..a628bc99c74 --- /dev/null +++ b/man/checkers/negativeContainerIndex.md @@ -0,0 +1,38 @@ +# negativeContainerIndex + +**Message**: Array index -11 is out of bounds.
+**Category**: Undefined Behaviour
+**Severity**: Error/Warning
+**Language**: C++ + +## Description + +A container is indexed with a value known to be negative. + +## Motivation + +Indexing an array-like container with a negative value is undefined behaviour - there is no valid +element "before the beginning" to access. + +## How to fix + +Before: +```cpp +#include +void f(const std::vector &v) { + v[-11] = 123; // <- negativeContainerIndex +} +``` + +After: +```cpp +#include +void f(const std::vector &v) { + v[11] = 123; +} +``` + +## Related checkers + +- [containerOutOfBounds.md](containerOutOfBounds.md) - the more general out-of-bounds container access + check, for indices that are too large rather than negative. diff --git a/man/checkers/negativeIndex.md b/man/checkers/negativeIndex.md new file mode 100644 index 00000000000..2b7477f8af0 --- /dev/null +++ b/man/checkers/negativeIndex.md @@ -0,0 +1,41 @@ +# negativeIndex + +**Message**: Array index -11 is out of bounds.
+**Category**: Undefined Behaviour
+**Severity**: Error/Warning
+**Language**: C/C++ + +## Description + +An array is indexed with a value cppcheck knows is negative. + +## Motivation + +Indexing an array with a negative value reads or writes memory before the start of the array, which is +undefined behaviour - it can silently corrupt unrelated data instead of crashing, making the actual +cause hard to trace back to. + +## How to fix + +Before: +```cpp +void f(int i) { + int a[10]; + if (i == -1) + a[i] = 0; // <- negative index +} +``` + +After: +```cpp +void f(int i) { + int a[10]; + if (i >= 0 && i < 10) + a[i] = 0; +} +``` + +## Related checkers + +- [arrayIndexOutOfBounds.md](arrayIndexOutOfBounds.md) - the general out-of-bounds-index check this + one complements, for indices known to be too large rather than negative. diff --git a/man/checkers/noConstructor.md b/man/checkers/noConstructor.md new file mode 100644 index 00000000000..76616aeaabb --- /dev/null +++ b/man/checkers/noConstructor.md @@ -0,0 +1,51 @@ +# noConstructor + +**Message**: The struct 'x' does not have a constructor although it has private member variables.
+**Category**: Correctness
+**Severity**: Style
+**Language**: C++ + +## Description + +A class/struct has private member variables (of a native type, pointer, or a type that itself needs +initialization) but declares no constructor at all - those members are left with whatever bytes happened +to already be there when the object is created. + +## Motivation + +Without a constructor, a class's native-type, pointer, or reference members are left uninitialized when +an object is created - reading one of them before it's explicitly assigned would be undefined behaviour. +This checker only looks at the class's shape (private members, no constructor, no default member +initializer); it doesn't verify that any member is actually read before being set anywhere in the +program, so it flags classes that are technically at risk even if none of them are ever used that way in +practice. This check is also deliberately reported at `style` severity rather than `warning`: for +performance reasons a constructor might be intentionally left out in some cases, so it's presented as a +suggestion rather than a certain bug. + +## How to fix + +Add a constructor that initializes every member. + +Before: +```cpp +class Fred { + int i; +public: + void setValue(int i_) { i = i_; } // <- no constructor initializes 'i' +}; +``` + +After: +```cpp +class Fred { + int i; +public: + Fred() : i(0) {} + void setValue(int i_) { i = i_; } +}; +``` + +## Related checkers + +- [uninitMemberVar.md](uninitMemberVar.md) - the related family of checks for when a constructor exists + but still misses initializing one or more specific members. diff --git a/man/checkers/noCopyConstructor.md b/man/checkers/noCopyConstructor.md new file mode 100644 index 00000000000..afbd4850f2c --- /dev/null +++ b/man/checkers/noCopyConstructor.md @@ -0,0 +1,59 @@ +# noCopyConstructor + +**Message**: Struct 'F' does not have a copy constructor which is recommended since it has dynamic memory/resource management.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ + +## Description + +The class allocates a resource itself (`new`/`malloc`-family in a constructor, `delete`/`free`-family +in a destructor) but doesn't define its own copy constructor (or only defaults it) - the +compiler-generated version would copy the raw pointer, not the resource. + +## Motivation + +The compiler-generated copy constructor just copies each member's value. For a raw pointer, that means +the copy ends up pointing at the exact same allocated block as the original - both objects now believe +they own it, so destroying either one leaves the other holding a dangling pointer, and destroying both +frees the same memory twice. Using the dangling copy afterward, or freeing the same block twice, is +undefined behaviour - though which of those actually happens, if either, depends on what the rest of the +program goes on to do with the two objects. This check fires on the class's shape alone (it allocates a +resource but has no copy constructor of its own) - it doesn't verify that the class is ever actually +copied anywhere in the analyzed code, so a class that's never copied is flagged just the same as one that +is. + +## How to fix + +Write a copy constructor that allocates a fresh block for the copy (or use a container/smart pointer +that already manages this correctly, avoiding the need for a hand-written one at all). + +Before: +```cpp +class F { + char *p; +public: + F() { p = new char[10]; } // <- no copy constructor + ~F() { delete[] p; } +}; +``` + +After: +```cpp +class F { + char *p; +public: + F() { p = new char[10]; } + F(const F& other) { p = new char[10]; } + F& operator=(const F& other) { p = other.p; return *this; } + ~F() { delete[] p; } +}; +``` + +## Related checkers + +- [noOperatorEq.md](noOperatorEq.md) and [noDestructor.md](noDestructor.md) - the same underlying + "allocates a resource but is missing a special member function" pattern, for `operator=` and the + destructor respectively. +- [copyCtorPointerCopying.md](copyCtorPointerCopying.md) - the more specific case where a copy + constructor does exist, but shallow-copies a pointer instead of allocating a fresh block. diff --git a/man/checkers/noDestructor.md b/man/checkers/noDestructor.md new file mode 100644 index 00000000000..cd9d3df2337 --- /dev/null +++ b/man/checkers/noDestructor.md @@ -0,0 +1,48 @@ +# noDestructor + +**Message**: Class 'F' does not have a destructor which is recommended since it has dynamic memory/resource management.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ + +## Description + +The class allocates a resource itself (`new`/`malloc`-family in a constructor) but doesn't define its +own destructor. + +## Motivation + +Without a destructor, whatever a constructor allocated is never released when the object is destroyed - +a straightforward memory/resource leak every time an object of this class goes out of scope or is +deleted. + +## How to fix + +Add a destructor that releases what the constructor allocated. + +Before: +```cpp +struct F { + char *p; + F() { p = new char[10]; } // <- no destructor + F(const F&); + F& operator=(const F&); +}; +``` + +After: +```cpp +struct F { + char *p; + F() { p = new char[10]; } + F(const F&); + F& operator=(const F&); + ~F() { delete[] p; } +}; +``` + +## Related checkers + +- [noCopyConstructor.md](noCopyConstructor.md) and [noOperatorEq.md](noOperatorEq.md) - the same + underlying "allocates a resource but is missing a special member function" pattern, for the copy + constructor and `operator=` respectively. diff --git a/man/checkers/noExplicitConstructor.md b/man/checkers/noExplicitConstructor.md new file mode 100644 index 00000000000..2d3f4fbbce6 --- /dev/null +++ b/man/checkers/noExplicitConstructor.md @@ -0,0 +1,39 @@ +# noExplicitConstructor + +**Message**: Class 'Class' has a constructor with 1 argument that is not explicit.
+**Category**: Correctness
+**Severity**: Style
+**Language**: C++ + +## Description + +A constructor that takes exactly one argument (and so can be used for an implicit conversion) isn't +marked `explicit` - so a value of that argument's type can silently convert into the class wherever the +class type is expected, which is rarely intended. + +## Motivation + +A single-argument constructor that isn't `explicit` doubles as an implicit conversion: anywhere the +class type is expected, a value of the argument's type is silently accepted and converted, without the +reader seeing any indication a conversion happened at all. This can produce confusing overload +resolution and surprising implicit conversions that the author never intended. + +## How to fix + +Mark the constructor `explicit`, unless the implicit conversion is genuinely intended. + +Before: +```cpp +class Class { +public: + Class(int i) {} // <- not explicit +}; +``` + +After: +```cpp +class Class { +public: + explicit Class(int i) {} +}; +``` diff --git a/man/checkers/noOperatorEq.md b/man/checkers/noOperatorEq.md new file mode 100644 index 00000000000..482455f5d42 --- /dev/null +++ b/man/checkers/noOperatorEq.md @@ -0,0 +1,57 @@ +# noOperatorEq + +**Message**: Struct 'F' does not have a operator= which is recommended since it has dynamic memory/resource management.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ + +## Description + +The class allocates a resource itself (`new`/`malloc`-family in a constructor, `delete`/`free`-family +in a destructor) but doesn't define its own `operator=` (or only defaults it) - the compiler-generated +version would copy the raw pointer, not the resource. + +## Motivation + +The compiler-generated assignment operator just copies each member's value. For a raw pointer, that +means the assigned-to object ends up pointing at the same allocated block as the source - both objects +now believe they own it, and the assigned-to object's original allocation is leaked in the process, +since nothing frees it first. This is more than a leak waiting to happen: if an object of this class is +ever assigned to another, both are eventually destroyed, and their destructors each free the same block, +that is a double-free - undefined behaviour. This check fires on the class's shape alone (it allocates a +resource but has no `operator=` of its own) - it doesn't verify that the class is ever actually assigned +anywhere in the analyzed code, so a class that's never copy-assigned is flagged just the same as one that +is. + +## How to fix + +Write an `operator=` that allocates a fresh block for the assigned-to object (or use a +container/smart pointer that already manages this correctly). + +Before: +```cpp +class F { + char *p; +public: + F() { p = new char[10]; } // <- no operator= + ~F() { delete[] p; } +}; +``` + +After: +```cpp +class F { + char *p; +public: + F() { p = new char[10]; } + F(const F& other) { p = new char[10]; } + F& operator=(const F& other) { p = other.p; return *this; } + ~F() { delete[] p; } +}; +``` + +## Related checkers + +- [noCopyConstructor.md](noCopyConstructor.md) and [noDestructor.md](noDestructor.md) - the same + underlying "allocates a resource but is missing a special member function" pattern, for the copy + constructor and destructor respectively. diff --git a/man/checkers/nullPointer.md b/man/checkers/nullPointer.md new file mode 100644 index 00000000000..b0519b3f46a --- /dev/null +++ b/man/checkers/nullPointer.md @@ -0,0 +1,63 @@ +# nullPointer + +**Message**: Null pointer dereference
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A pointer is definitely, or very likely, null at the point it is dereferenced - for example, it was +just assigned `0`/`nullptr`, or a variable known to be null was passed in. + +## Motivation + +Dereferencing a null pointer is undefined behaviour and one of the most common causes of crashes in +C/C++ programs. cppcheck reports this only when it can actually determine the pointer is null (or very +likely null); it does not attempt to trace every possible path a pointer's value could have taken to +get there, so the absence of a warning does not by itself mean a pointer can never be null at that +point. + +## How to fix + +Before: +```cpp +void f() { + int *p = 0; + *p = 1; // <- nullPointer +} +``` + +After: +```cpp +void f() { + int x = 0; + int *p = &x; + *p = 1; +} +``` + +## False positives to be aware of + +- **A false positive is possible when a guard condition depends on a different parameter than the one + being dereferenced.** If a function only dereferences one pointer when another argument has a + specific value, cppcheck does not always verify that a given call site actually triggers that + value, and can warn even when the guard makes the dereference unreachable for that call: + ```cpp + void f(int* p, const int* q) { + if (*q == -1) + *p = 0; + } + void g() { + int x = -2; + f(nullptr, &x); // reported as a possible null dereference of 'p', even though *q==-1 is false here + } + ``` + +## Related checkers + +- [nullPointerRedundantCheck.md](nullPointerRedundantCheck.md) - the same idea, but where a null check on the same pointer exists elsewhere and either it or the dereference is misplaced. +- [nullPointerDefaultArg.md](nullPointerDefaultArg.md) - the same idea for a pointer parameter that defaults to null. +- [nullPointerOutOfMemory.md](nullPointerOutOfMemory.md) - the same idea for a pointer that came from an allocation function that can fail. +- [nullPointerArithmetic.md](nullPointerArithmetic.md) - the same idea for pointer arithmetic instead of a direct dereference. +- [ctunullpointer.md](ctunullpointer.md) - the same idea, found by whole-program analysis across function calls. diff --git a/man/checkers/nullPointerArithmetic.md b/man/checkers/nullPointerArithmetic.md new file mode 100644 index 00000000000..c2987c4132f --- /dev/null +++ b/man/checkers/nullPointerArithmetic.md @@ -0,0 +1,43 @@ +# nullPointerArithmetic + +**Message**: Pointer addition with NULL pointer.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +Pointer arithmetic (`p + n`, `p - n`, `p++`, `--p`, ...) is performed on a pointer that is null (or +could be). Adding to or subtracting from a null pointer is undefined behaviour even though no memory is +actually touched. + +## Motivation + +It's easy to assume pointer arithmetic is only dangerous once the result is dereferenced, but the C/C++ +standards make forming an out-of-bounds pointer - including any arithmetic on a null pointer - undefined +behaviour in its own right, regardless of whether the result is ever used. + +## How to fix + +Before: +```cpp +void foo(char *s) { + char *p = s + 20; // <- if foo() is ever called with a null 's' +} +void bar() { foo(0); } +``` + +After: +```cpp +void foo(char *s) { + if (s) + char *p = s + 20; +} +void bar() { foo(0); } +``` + +## Related checkers + +- [nullPointer.md](nullPointer.md) - the direct-dereference equivalent of this check. +- [nullPointerArithmeticRedundantCheck.md](nullPointerArithmeticRedundantCheck.md) - the same idea, refined by a nearby null check. +- [nullPointerArithmeticOutOfMemory.md](nullPointerArithmeticOutOfMemory.md) - the same idea for a pointer from a failable allocation function. diff --git a/man/checkers/nullPointerArithmeticOutOfMemory.md b/man/checkers/nullPointerArithmeticOutOfMemory.md new file mode 100644 index 00000000000..7219f2826ef --- /dev/null +++ b/man/checkers/nullPointerArithmeticOutOfMemory.md @@ -0,0 +1,22 @@ +# nullPointerArithmeticOutOfMemory + +**Message**: If memory allocation fails: pointer addition with NULL pointer.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +Pointer arithmetic is performed on a pointer that came from a memory-allocation function that can fail +and return null (`malloc`, `new` in some configurations, ...), without checking for that failure first. + +## Motivation + +Just like [nullPointerOutOfMemory](nullPointerOutOfMemory.md), assuming an allocation always succeeds +means that under memory pressure, the very next operation on the result - even pointer arithmetic that +doesn't dereference anything - is undefined behaviour. + +## Related checkers + +- [nullPointerArithmetic.md](nullPointerArithmetic.md) - the same idea without a specific failable allocation involved. +- [nullPointerOutOfMemory.md](nullPointerOutOfMemory.md) - the direct-dereference equivalent of this check. diff --git a/man/checkers/nullPointerArithmeticRedundantCheck.md b/man/checkers/nullPointerArithmeticRedundantCheck.md new file mode 100644 index 00000000000..d965cabee06 --- /dev/null +++ b/man/checkers/nullPointerArithmeticRedundantCheck.md @@ -0,0 +1,23 @@ +# nullPointerArithmeticRedundantCheck + +**Message**: Either the condition 'p' is redundant or there is overflow in pointer addition.
+**Category**: Undefined Behaviour
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +Pointer arithmetic is performed on a pointer that could be null, and elsewhere in the code there is a +`NULL`/`nullptr` check on the very same pointer - so either that check is redundant, or the arithmetic +is a bug. + +## Motivation + +Just like [nullPointerRedundantCheck](nullPointerRedundantCheck.md), a null check that exists somewhere +in the code but doesn't actually guard the pointer arithmetic is a strong sign of a misplaced check or +an unnecessary one. + +## Related checkers + +- [nullPointerArithmetic.md](nullPointerArithmetic.md) - the same idea without a nearby check to cross-reference. +- [nullPointerRedundantCheck.md](nullPointerRedundantCheck.md) - the direct-dereference equivalent of this check. diff --git a/man/checkers/nullPointerDefaultArg.md b/man/checkers/nullPointerDefaultArg.md new file mode 100644 index 00000000000..5a6b99b1d29 --- /dev/null +++ b/man/checkers/nullPointerDefaultArg.md @@ -0,0 +1,39 @@ +# nullPointerDefaultArg + +**Message**: Possible null pointer dereference if the default parameter value is used.
+**Category**: Undefined Behaviour
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A pointer parameter that defaults to `nullptr`/`0` is dereferenced without a null check, so calling the +function with no argument for that parameter crashes. + +## Motivation + +A default argument value is one every caller is implicitly allowed to rely on - if that default is +null and the function dereferences the parameter unconditionally, calling the function the "normal", +argument-omitted way is itself the bug trigger. + +## How to fix + +Before: +```cpp +void f(int *p = 0) { + *p = 1; // <- crashes if called as f() +} +``` + +After: +```cpp +void f(int *p = 0) { + if (!p) + return; + *p = 1; +} +``` + +## Related checkers + +- [nullPointer.md](nullPointer.md) - the general null-dereference check. diff --git a/man/checkers/nullPointerOutOfMemory.md b/man/checkers/nullPointerOutOfMemory.md new file mode 100644 index 00000000000..964ab3d2f72 --- /dev/null +++ b/man/checkers/nullPointerOutOfMemory.md @@ -0,0 +1,46 @@ +# nullPointerOutOfMemory and nullPointerOutOfResources + +**Message**: Null pointer dereference
+**Category**: Undefined Behaviour
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +- `nullPointerOutOfMemory`: a pointer came from a memory-allocation function that can fail and return + null (`malloc`, `new` in some configurations, ...) and is used without checking for that failure. +- `nullPointerOutOfResources`: the same idea for a handle from a resource-allocating function that can + fail (`fopen`, ...) rather than a memory allocator. + +## Motivation + +`malloc()` and similar functions are documented to return `NULL` on failure - assuming they always +succeed means that, under memory or resource pressure, the very next dereference crashes instead of the +program handling the failure gracefully. + +## How to fix + +Before: +```cpp +void f() { + int *p = malloc(10); + *p = 1; // <- malloc() can return NULL + free(p); +} +``` + +After: +```cpp +void f() { + int *p = malloc(10); + if (p) { + *p = 1; + free(p); + } +} +``` + +## Related checkers + +- [nullPointer.md](nullPointer.md) - the general null-dereference check. +- [ctunullpointer.md](ctunullpointer.md) - the whole-program-analysis counterpart, which has its own `ctunullpointerOutOfMemory`/`ctunullpointerOutOfResources` variants. diff --git a/man/checkers/nullPointerRedundantCheck.md b/man/checkers/nullPointerRedundantCheck.md new file mode 100644 index 00000000000..5b314bfeb11 --- /dev/null +++ b/man/checkers/nullPointerRedundantCheck.md @@ -0,0 +1,47 @@ +# nullPointerRedundantCheck + +**Message**: Either the condition 'p' is redundant or there is possible null pointer dereference.
+**Category**: Undefined Behaviour
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A pointer is dereferenced, and elsewhere in the code there is a `NULL`/`nullptr` check on the very same +pointer - so either that check is redundant, or the dereference is a bug. This is often more useful +than a plain null-dereference warning, since it also catches "check after use" and "check the wrong +branch" mistakes, not just a directly-assigned null value. + +## Motivation + +A null check that exists somewhere in the code, but doesn't actually guard the dereference, is a strong +sign of a logic error: either the check was misplaced relative to the code it was meant to protect, or +the check itself is unnecessary and hides the fact that the pointer can never legitimately be null. +cppcheck only connects a check to a dereference when it can follow that the two really refer to the +same pointer value; once the pointer is reassigned, cached in a `bool`, or handed to another function +in between, that connection can be lost, so this check finding nothing is not proof the pointer is +always guarded correctly. + +## How to fix + +Before: +```cpp +void f(int *p) { + *p = 1; // <- dereferenced here... + if (p) {} // ...but only checked for null here +} +``` + +After: +```cpp +void f(int *p) { + if (!p) + return; + *p = 1; +} +``` + +## Related checkers + +- [nullPointer.md](nullPointer.md) - the general null-dereference check this one refines with a nearby-check cross-reference. +- [nullPointerArithmeticRedundantCheck.md](nullPointerArithmeticRedundantCheck.md) - the same idea for pointer arithmetic instead of a direct dereference. diff --git a/man/checkers/objectIndex.md b/man/checkers/objectIndex.md new file mode 100644 index 00000000000..18766ae527d --- /dev/null +++ b/man/checkers/objectIndex.md @@ -0,0 +1,44 @@ +# objectIndex + +**Message**: The address of variable 's.x' is accessed at non-zero index.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +The address of one specific member of a variable is taken and then indexed with a nonzero value, +reaching past that member into the rest of the object. + +## Motivation + +Indexing past the address of a single member relies on that member happening to be followed by other +data with a compatible layout - this isn't something the language guarantees (padding, member reordering +by the compiler in some cases, and unrelated following members all make it unreliable), so it's +undefined behaviour dressed up as if it worked. + +## How to fix + +Before: +```cpp +struct S { int x; int y; }; +void f() { + S s; + int *p = &s.x; + p[3] = 0; // <- reaches past 'x' into the rest of 's' +} +``` + +After: +```cpp +struct S { int x; int y; }; +void f() { + S s; + s.y = 0; +} +``` + +## Related checkers + +- [arrayIndexOutOfBounds.md](arrayIndexOutOfBounds.md) - the more general out-of-bounds-access check, + for a plain array rather than the address of a single struct member. diff --git a/man/checkers/obsoleteFunctionCalled.md b/man/checkers/obsoleteFunctionCalled.md new file mode 100644 index 00000000000..4c9bd920c84 --- /dev/null +++ b/man/checkers/obsoleteFunctionCalled.md @@ -0,0 +1,40 @@ +# <name>Called (for example getsCalled, bsd_signalCalled, or any other function marked this way in a library configuration) + +**Message**: Obsolete function 'gets' called. It is recommended to use 'fgets' or 'gets_s' instead.
+**Category**: Correctness
+**Severity**: Error/Warning/Style/Portability
+**Language**: C/C++ + +## Description + +A function known to be obsolete, dangerous, or non-reentrant is called. The message names a safer +replacement. The exact error ID is built from the function's own name plus `Called` (`getsCalled`, +`bsd_signalCalled`, ...), and which functions are covered - and at what severity - comes entirely from +the loaded library configuration (`std.cfg`, `posix.cfg`, and similar), not from a fixed list built into +cppcheck itself. `alloca()` is handled separately - see [allocaCalled.md](allocaCalled.md). + +## Motivation + +Some standard library functions are obsolete or unsafe for reasons that aren't visible from their +signature alone: `gets()` can't bound how much it reads and will happily overflow any buffer, some +functions from `` are unreliable across platforms, and so on. A caller has no way to know this +without already being aware of the function's history. + +## How to fix + +Before: +```cpp +#include +void f(char *a) { + char *x = gets(a); // <- gets() cannot bound the input, buffer overflow risk +} +``` + +After: +```cpp +#include +void f(char* buf, int n) { + char *x = fgets(buf, n, stdin); +} +``` + diff --git a/man/checkers/operatorEqMissingReturnStatement.md b/man/checkers/operatorEqMissingReturnStatement.md new file mode 100644 index 00000000000..8a74f7201d1 --- /dev/null +++ b/man/checkers/operatorEqMissingReturnStatement.md @@ -0,0 +1,47 @@ +# operatorEqMissingReturnStatement + +**Message**: No 'return' statement in non-void function causes undefined behavior.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +An `operator=` reaches the end of its body with no `return` at all, and isn't one of the deliberate +"never returns" cases covered by +[operatorEqShouldBeLeftUnimplemented.md](operatorEqShouldBeLeftUnimplemented.md) - undefined behaviour, +since the (non-`void`) function is expected to produce a value. + +## Motivation + +Falling off the end of a value-returning function without a `return` is undefined behaviour in C++ in +its own right, per the standard - it doesn't require the caller to go on and use the "returned" value; +merely reaching the closing `}` without having returned anything is already the undefined action. + +## How to fix + +Add the missing `return *this;`. + +Before: +```cpp +class szp +{ + szp &operator =(int *other) {} // <- no return statement +}; +``` + +After: +```cpp +class szp +{ + szp &operator =(int *other) { return *this; } +}; +``` + +## Related checkers + +- [operatorEqRetRefThis.md](operatorEqRetRefThis.md) - the general check for `operator=` not returning + `*this`. +- [operatorEqShouldBeLeftUnimplemented.md](operatorEqShouldBeLeftUnimplemented.md) - the case where + `operator=` deliberately never returns (for example, always throwing), and should instead be declared + unimplemented/deleted rather than just missing a `return`. diff --git a/man/checkers/operatorEqRetRefThis.md b/man/checkers/operatorEqRetRefThis.md new file mode 100644 index 00000000000..271b89f208a --- /dev/null +++ b/man/checkers/operatorEqRetRefThis.md @@ -0,0 +1,45 @@ +# operatorEqRetRefThis + +**Message**: 'operator=' should return reference to 'this' instance.
+**Category**: Correctness
+**Severity**: Style
+**Language**: C++ + +## Description + +A hand-written `operator=` doesn't `return *this;` - code like `a = b = c;` or chaining `.` after an +assignment then breaks, since the return value isn't what everyone expects. + +## Motivation + +Idiomatic C++ assignment operators return a reference to the assigned-to object so that assignments can +be chained (`a = b = c;`) and the result can be used directly. An `operator=` that returns something +else (or nothing meaningful) silently breaks that convention for any caller who relies on it, which is +easy to miss since a single, non-chained `a = b;` still compiles and works either way. + +## How to fix + +Return `*this` from `operator=`. + +Before: +```cpp +class A { +public: + A & operator=(const A &a) { return a; } // <- returns 'a', not '*this' +}; +``` + +After: +```cpp +class A { +public: + A & operator=(const A &a) { return *this; } +}; +``` + +## Related checkers + +- [operatorEqMissingReturnStatement.md](operatorEqMissingReturnStatement.md) - the more severe sibling + case where `operator=` has no `return` statement at all. +- [operatorEqShouldBeLeftUnimplemented.md](operatorEqShouldBeLeftUnimplemented.md) - the case where + `operator=` deliberately never returns, and should instead be declared unimplemented/deleted. diff --git a/man/checkers/operatorEqShouldBeLeftUnimplemented.md b/man/checkers/operatorEqShouldBeLeftUnimplemented.md new file mode 100644 index 00000000000..9a49a5a3822 --- /dev/null +++ b/man/checkers/operatorEqShouldBeLeftUnimplemented.md @@ -0,0 +1,54 @@ +# operatorEqShouldBeLeftUnimplemented + +**Message**: 'operator=' should either return reference to 'this' instance or be declared private and left unimplemented.
+**Category**: Correctness
+**Severity**: Style
+**Language**: C++ + +## Description + +An `operator=` with no `return` statement, whose entire body is a `throw` (or a call to a function that +never returns) - a common way to try to make an assignment operator uncallable, but the idiomatic way is +to declare it `private`/`= delete` instead of giving it a body that never returns. + +## Motivation + +Giving `operator=` a body that always throws is a roundabout, easy-to-misread way of saying "this class +can't be assigned" - it still compiles as if it were a normal, callable assignment operator, and the +"can't be assigned" part is only enforced at runtime, when it's too late to catch at compile time. The +idiomatic ways (`= delete`, or a private declaration with no definition) reject the attempt to assign at +compile time instead. + +## How to fix + +Declare the assignment operator `= delete` (or `private` with no body) rather than giving it a body +that always throws. + +Before: +```cpp +#include +#include +class A { +public: + A & operator=(const A &a) { + rand(); + throw std::exception(); // <- always throws instead of assigning + } +}; +``` + +After: +```cpp +class A { +public: + A & operator=(const A &a) = delete; +}; +``` + +## Related checkers + +- [operatorEqRetRefThis.md](operatorEqRetRefThis.md) - the general check for `operator=` not returning + `*this`, of which this is a specific variant. +- [operatorEqMissingReturnStatement.md](operatorEqMissingReturnStatement.md) - the sibling case where + `operator=` reaches the end of its body with no return, but isn't one of these deliberate + "never returns" cases. diff --git a/man/checkers/operatorEqToSelf.md b/man/checkers/operatorEqToSelf.md new file mode 100644 index 00000000000..f0b029ed32e --- /dev/null +++ b/man/checkers/operatorEqToSelf.md @@ -0,0 +1,59 @@ +# operatorEqToSelf + +**Message**: 'operator=' should check for assignment to self to avoid problems with dynamic memory.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ + +## Description + +`operator=` allocates/frees a resource but never checks for `a = a;` (self-assignment) - freeing a +resource and then trying to copy from the same, now-freed object corrupts the object. + +## Motivation + +`a = a;` is valid, if unusual, code - and an `operator=` that frees its own current resource before +copying from the source object can, on self-assignment, free the very resource it's about to read from, +leaving the object corrupted (a use of freed memory, which is undefined behaviour) - though this depends +on the new value actually being derived from the freed data; cppcheck flags any allocate-without-a- +self-check pattern in `operator=`, not just the ones it can confirm would read something already freed. +Since self-assignment is rare in normal code (it usually happens indirectly, through an alias or a +container operation), this bug can go unnoticed for a long time. + +## How to fix + +Check for self-assignment (`this == &a`) before freeing anything, and return early if it's true. + +Before: +```cpp +#include +#include +class A { +public: + char *s; + A & operator=(const A &a) + { + free(s); // <- breaks if 'a' is '*this' + s = strdup(a.s); + return *this; + } +}; +``` + +After: +```cpp +#include +#include +class A { +public: + char *s; + A & operator=(const A &a) + { + if (this == &a) + return *this; + free(s); + s = strdup(a.s); + return *this; + } +}; +``` diff --git a/man/checkers/operatorEqVarError.md b/man/checkers/operatorEqVarError.md new file mode 100644 index 00000000000..aef42c8758c --- /dev/null +++ b/man/checkers/operatorEqVarError.md @@ -0,0 +1,45 @@ +# operatorEqVarError + +**Message**: Member variable 'classname::varname' is not assigned a value in 'classname::operator='.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ + +## Description + +Same idea as [missingMemberCopy.md](missingMemberCopy.md), but for a hand-written `operator=` instead +of a copy constructor: one particular member is never assigned anywhere in it, even though everything +else about the class suggests it should have been. + +## Motivation + +A hand-written `operator=` that forgets one member leaves that member holding its old value after an +assignment that's supposed to make the object equal to another - a subtle bug that's easy to miss since +the operator still compiles and mostly "looks right." + +## How to fix + +Assign the missing member too. + +Before: +```cpp +struct S { + int i{}; + S() = default; + S& operator=(const S& s) { return *this; } // <- 'i' isn't assigned +}; +``` + +After: +```cpp +struct S { + int i{}; + S() = default; + S& operator=(const S& s) { i = s.i; return *this; } +}; +``` + +## Related checkers + +- [missingMemberCopy.md](missingMemberCopy.md) - the same idea, for a hand-written copy/move constructor + instead of `operator=`. diff --git a/man/checkers/oppositeExpression.md b/man/checkers/oppositeExpression.md new file mode 100644 index 00000000000..f251057e3f2 --- /dev/null +++ b/man/checkers/oppositeExpression.md @@ -0,0 +1,37 @@ +# oppositeExpression + +**Message**: Opposite expression on both sides of '&&'.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +An expression and its logical/arithmetic opposite (`a` and `!a`, `x` and `-x`, ...) appear on both +sides of the same operator - for `&&`/`||` in particular, this makes the whole condition always +false/true. + +## Motivation + +Combining an expression with its own negation can never depend on the expression's actual value - the +result is fixed in advance, which usually means one side was meant to refer to something else. + +## How to fix + +Before: +```cpp +void f(bool a) { + if (a && !a) {} // <- always false +} +``` + +After: +```cpp +void f(bool a, bool b) { + if (a && !b) {} +} +``` + +## Related checkers + +- [duplicateExpression.md](duplicateExpression.md) - the mirror-image mistake, comparing an expression against an identical copy of itself. diff --git a/man/checkers/oppositeInnerCondition.md b/man/checkers/oppositeInnerCondition.md new file mode 100644 index 00000000000..fe4b7b9453d --- /dev/null +++ b/man/checkers/oppositeInnerCondition.md @@ -0,0 +1,47 @@ +# oppositeInnerCondition + +**Message**: Opposite inner 'if' condition leads to a dead code block.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +An `if` nested directly inside another `if` has a condition that contradicts the outer one, so its body +is dead code. + +## Motivation + +Once the outer condition is true, the value(s) it depends on are already narrowed down; an inner +condition that could only be true when the outer one is false can never actually run. This usually means +either dead code left over from an edit, or a comparison that doesn't say what the author meant. + +This check may need `--check-level=exhaustive` to see every case. + +## How to fix + +Before: +```cpp +void f(int x) { + if (x == 1) { + if (x == 2) {} // <- dead code, x is already 1 here + } +} +``` + +After: remove the dead inner check. +```cpp +void f(int x) { + if (x == 1) { + } +} +``` + +## Related checkers + +- [identicalInnerCondition.md](identicalInnerCondition.md) - the same idea, but the inner condition + repeats the outer one exactly instead of contradicting it. +- [overlappingInnerCondition.md](overlappingInnerCondition.md) - the same idea, but the inner condition + is already implied by (not identical to) the outer one. +- [multiCondition.md](multiCondition.md) - the same idea, but for an `if`/`else if` chain instead of + nested `if`s. diff --git a/man/checkers/orInComplexPattern.md b/man/checkers/orInComplexPattern.md new file mode 100644 index 00000000000..1d8684f6756 --- /dev/null +++ b/man/checkers/orInComplexPattern.md @@ -0,0 +1,31 @@ +# orInComplexPattern + +**Message**: Found \"|\" in complex pattern. You probably intend to use \"%or%\".
+**Category**: Code Quality
+**Severity**: Error
+**Language**: C++ (cppcheck's own source code only) + +## Description + +A pattern uses a literal `|`/`||` instead of the pattern language's `%or%`/`%oror%`. + +This is not a general-purpose checker for arbitrary C/C++ programs - see +[simplePatternError.md](simplePatternError.md) for the shared background on this internal, +cppcheck-source-only checker. + +## Motivation + +A literal `|`/`||` character in a pattern string matches the literal token `|`/`||` in the code being +analyzed - it does not mean "either of these two alternatives" the way `%or%`/`%oror%` does. This is an +easy habit to slip into for anyone used to regular-expression syntax, and it silently changes what the +pattern matches. + +## How to fix + +Replace the literal `|`/`||` with `%or%`/`%oror%` when the intent is "match either of these token +types", keeping the literal form only when the code being analyzed should actually contain a `|`/`||` +character. + +## Related checkers + +- [unknownPattern.md](unknownPattern.md) - a different kind of placeholder mistake in the same pattern language. diff --git a/man/checkers/overlappingInnerCondition.md b/man/checkers/overlappingInnerCondition.md new file mode 100644 index 00000000000..763edbf7512 --- /dev/null +++ b/man/checkers/overlappingInnerCondition.md @@ -0,0 +1,47 @@ +# overlappingInnerCondition + +**Message**: Overlapping inner 'if' condition is always true.
+**Category**: Code Quality
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +An `if` nested directly inside another `if` has a condition that's already guaranteed by the outer one +(a bitwise overlap, for example `x == 1` outside and `x & 7` inside), so the inner condition is always +true. + +## Motivation + +If the outer condition already guarantees the inner one, the inner `if` can never be false - it adds +nothing but a false impression that there's a real extra check happening. + +This check may need `--check-level=exhaustive` to see every case. + +## How to fix + +Before: +```cpp +void f(int x) { + if (x == 1) { + if (x & 7) {} // <- always true, implied by x == 1 + } +} +``` + +After: remove the redundant inner check. +```cpp +void f(int x) { + if (x == 1) { + } +} +``` + +## Related checkers + +- [identicalInnerCondition.md](identicalInnerCondition.md) - the same idea, but the inner condition is + identical to the outer one rather than merely implied by it. +- [oppositeInnerCondition.md](oppositeInnerCondition.md) - the same idea, but the inner condition + contradicts the outer one instead. +- [multiCondition.md](multiCondition.md) - the same idea, but for an `if`/`else if` chain instead of + nested `if`s. diff --git a/man/checkers/overlappingStrcmp.md b/man/checkers/overlappingStrcmp.md new file mode 100644 index 00000000000..e10f84108cb --- /dev/null +++ b/man/checkers/overlappingStrcmp.md @@ -0,0 +1,39 @@ +# overlappingStrcmp + +**Message**: The expression 'strcmp(x, "b") != 0' is suspicious. It overlaps 'strcmp(x, "a") == 0'.
+**Category**: Code Quality
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +`strcmp(x, "a") == 0 || strcmp(x, "b") != 0` - the `!= 0` half is always true whenever the `== 0` half +is false, since `x` can't equal `"a"` and simultaneously differ from `"a"` in the case that makes the +first half false. In other words, the second condition doesn't add anything meaningful and is likely a +copy-paste mistake. + +## Motivation + +An `||` of two `strcmp()` checks like this always evaluates to true regardless of what `x` actually is, +which is easy to miss since each half looks like a reasonable, independent check on its own. + +## How to fix + +Before: +```cpp +void f(const char *str) { + if (strcmp(str, "a") == 0 || strcmp(str, "b") != 0) {} // <- always true +} +``` + +After: +```cpp +void f(const char *str) { + if (strcmp(str, "a") == 0 || strcmp(str, "c") == 0) {} +} +``` + +## Related checkers + +- [sprintfOverlappingData.md](sprintfOverlappingData.md) - an unrelated string-function misuse in the + same checker, about `sprintf()`'s destination and source arguments overlapping. diff --git a/man/checkers/overlappingWriteFunction.md b/man/checkers/overlappingWriteFunction.md new file mode 100644 index 00000000000..44e8ac2f3f5 --- /dev/null +++ b/man/checkers/overlappingWriteFunction.md @@ -0,0 +1,49 @@ +# overlappingWriteFunction + +**Message**: Overlapping read/write in memcpy() is undefined behavior
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A call to a function like `memcpy()` is given source and destination ranges that overlap - only +`memmove()`-family functions are allowed to have overlapping ranges; `memcpy()` and similar are +undefined behaviour if the ranges overlap. + +## Motivation + +`memcpy()` (unlike `memmove()`) is free to copy in whatever order is fastest for the platform - forwards, +backwards, or in chunks - on the assumption that the source and destination never overlap. If they do +overlap, the copy can partially overwrite data it still needs to read, corrupting the result in a way +that can differ between compilers, optimization levels, or standard library implementations. cppcheck +only reports this when it can work out the exact size being copied; it does not warn just because two +ranges look suspiciously close together with a size it can't pin down. + +## How to fix + +Use `memmove()` instead of `memcpy()` when the source and destination ranges might overlap. + +Before: +```cpp +#include +void foo() { + char a[10]; + memcpy(a, a+1, 2u); // <- source and destination overlap +} +``` + +After: +```cpp +#include +void foo() { + char a[10]; + memmove(a, a+1, 2u); // memmove() is defined to handle overlap safely +} +``` + +## Related checkers + +- [overlappingWriteUnion.md](overlappingWriteUnion.md) - the same underlying overlapping-read/write + hazard, but for reading and writing two overlapping members of a union in one expression instead of a + function call. diff --git a/man/checkers/overlappingWriteUnion.md b/man/checkers/overlappingWriteUnion.md new file mode 100644 index 00000000000..f9adca82f0f --- /dev/null +++ b/man/checkers/overlappingWriteUnion.md @@ -0,0 +1,51 @@ +# overlappingWriteUnion + +**Message**: Overlapping read/write of union is undefined behavior
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A union member is written using the value of a different, overlapping member of the same union in the +same expression (for example `u.i = u.f;`) - the read and write overlap in memory in a way the compiler +isn't required to sequence safely. + +## Motivation + +All members of a union share the same storage. Reading one member while writing a different, overlapping +one in the same expression is undefined behaviour: the compiler is free to assume the read and write +don't alias, and can reorder or optimize the expression in ways that don't match what the code visually +appears to do. + +## How to fix + +Read the source member into a separate variable first, then write the destination member from that +variable. + +Before: +```cpp +void foo() { + union { int i; float f; } u; + u.i = 0; + u.i = u.f; // <- reads and writes the same union storage in one expression +} +``` + +After: +```cpp +void foo() { + union { int i; float f; } u; + u.i = 0; + float g = u.f; + u.i = (int)g; +} +``` + +## Related checkers + +- [overlappingWriteFunction.md](overlappingWriteFunction.md) - the same underlying overlapping-read/write + hazard, but for a function call (like `memcpy()`) given overlapping source/destination ranges instead + of a union member access. +- [UnionZeroInit.md](UnionZeroInit.md) - a different union-related pitfall, about a union not being + fully zeroed by its initializer. diff --git a/man/checkers/passedByValue.md b/man/checkers/passedByValue.md new file mode 100644 index 00000000000..b48a9a0f13c --- /dev/null +++ b/man/checkers/passedByValue.md @@ -0,0 +1,43 @@ +# passedByValue + +**Message**: Parameter 'x' should be passed by const reference.
+**Category**: Code Quality
+**Severity**: Performance
+**Language**: C++ + +## Description + +A function parameter of a class/struct/container type (not a small built-in type) is passed by value +and never modified, so it's copied for nothing - it could be a `const` reference instead. + +## Motivation + +Passing a non-trivial object by value makes an unnecessary copy every time the function is called; a +`const` reference avoids the copy while still preventing the function from modifying the caller's +object. + +## How to fix + +Before: +```cpp +#include +#include +void f(std::string str) { // <- an unnecessary copy of 'str' is made + printf("%s\n", str.c_str()); +} +``` + +After: +```cpp +#include +#include +void f(const std::string& str) { + printf("%s\n", str.c_str()); +} +``` + +## Related checkers + +- [passedByValueCallback.md](passedByValueCallback.md) - the same idea, but for a parameter of a + function used as a callback. +- [iterateByValue.md](iterateByValue.md) - the same idea, for a range-based `for` loop's variable. diff --git a/man/checkers/passedByValueCallback.md b/man/checkers/passedByValueCallback.md new file mode 100644 index 00000000000..46561cd4c2c --- /dev/null +++ b/man/checkers/passedByValueCallback.md @@ -0,0 +1,43 @@ +# passedByValueCallback + +**Message**: Parameter 'x' should be passed by const reference. However it seems that 'f' is a callback function.
+**Category**: Code Quality
+**Severity**: Performance
+**Language**: C++ + +## Description + +Same idea as [passedByValue.md](passedByValue.md): a non-trivial parameter is passed by value and never +modified, so it's copied for nothing. This variant is for when the function is used as a callback - +fixing it may also require adjusting the function pointer type it's assigned to. + +## Motivation + +Passing a non-trivial object by value makes an unnecessary copy every time the function is called. The +callback case is called out separately because the fix isn't purely local: the function pointer type +also needs to change. + +## How to fix + +Before: +```cpp +#include +#include +void setCb(void (*cb)(std::string)); +void cb(std::string s) { printf("%s", s.c_str()); } // <- an unnecessary copy of 's' is made +void f() { setCb(cb); } +``` + +After: +```cpp +#include +#include +void setCb(void (*cb)(const std::string&)); +void cb(const std::string& s) { printf("%s", s.c_str()); } +void f() { setCb(cb); } +``` + +## Related checkers + +- [passedByValue.md](passedByValue.md) - the same idea, for a parameter that isn't part of a callback + function's signature. diff --git a/man/checkers/pointerAdditionResultNotNull.md b/man/checkers/pointerAdditionResultNotNull.md new file mode 100644 index 00000000000..35e1971a102 --- /dev/null +++ b/man/checkers/pointerAdditionResultNotNull.md @@ -0,0 +1,38 @@ +# pointerAdditionResultNotNull + +**Message**: Comparison is wrong. Result of 'ptr+1' can't be 0 unless there is pointer overflow, and pointer overflow is undefined behaviour.
+**Category**: Undefined Behaviour
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +Pointer arithmetic is compared against `NULL`/`0`, which can only be true through undefined-behaviour +pointer overflow. + +## Motivation + +Adding a positive offset to a valid, non-null pointer can never legitimately produce a null pointer - the +only way the comparison could be true is via pointer overflow, which is itself undefined behaviour. A +check written this way doesn't do what it looks like it does. + +## How to fix + +Before: +```cpp +void f(char *p) { + if (p + 12 == 0) {} // <- relies on pointer overflow, which is UB +} +``` + +After: +```cpp +void f(char *p) { + if (p == nullptr) {} +} +``` + +## Related checkers + +- [invalidTestForOverflow.md](invalidTestForOverflow.md) - a related undefined-behaviour trap, relying + on signed integer overflow instead of pointer overflow. diff --git a/man/checkers/pointerArithBool.md b/man/checkers/pointerArithBool.md new file mode 100644 index 00000000000..a14042e59a9 --- /dev/null +++ b/man/checkers/pointerArithBool.md @@ -0,0 +1,33 @@ +# pointerArithBool + +**Message**: Converting pointer arithmetic result to bool. The bool is always true unless there is undefined behaviour.
+**Category**: Correctness
+**Severity**: Error
+**Language**: C++ + +## Description + +The result of pointer arithmetic (`p + 1`, `p - 1`, ...) is used directly as a boolean condition. + +## Motivation + +A pointer produced by arithmetic is essentially always non-null (unless the arithmetic itself is +undefined behaviour), so converting it straight to `bool` is always `true` and rarely what the code's +author meant - usually a dereference was intended instead. + +## How to fix + +Before: +```cpp +void f(char *p) { + if (p + 1) {} // <- always true unless undefined behaviour +} +``` + +After: +```cpp +void f(char *p) { + if (p && *(p + 1)) {} +} +``` + diff --git a/man/checkers/pointerLessThanZero.md b/man/checkers/pointerLessThanZero.md new file mode 100644 index 00000000000..eb696d0d1b4 --- /dev/null +++ b/man/checkers/pointerLessThanZero.md @@ -0,0 +1,52 @@ +# pointerLessThanZero and pointerPositive + +**Message**: A pointer can not be negative so it is either pointless or an error to check if it is.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A pointer is compared against `0` with `<` (`pointerLessThanZero`) or `>=` (`pointerPositive`) - a +pointer value can't be negative, so the first can never be true and the second is always true. + +## Motivation + +Both comparisons are tautological for a pointer, so the branch they guard either never runs or always +runs - not what the code visibly appears to be testing, and usually a sign that `== nullptr`/`!= +nullptr` was intended instead. + +## How to fix + +Before: +```cpp +void foo(const int* x) { + if (x < 0) {} // <- can never be true +} +``` + +After: +```cpp +void foo(const int* x) { + if (x == nullptr) {} +} +``` + +Before: +```cpp +void foo(const int* x) { + if (x >= 0) {} // <- always true +} +``` + +After: +```cpp +void foo(const int* x) { + if (x != nullptr) {} +} +``` + +## Related checkers + +- [unsignedLessThanZero.md](unsignedLessThanZero.md) - the same idea, for an unsigned integer compared + against `0` instead of a pointer. diff --git a/man/checkers/pointerOutOfBounds.md b/man/checkers/pointerOutOfBounds.md new file mode 100644 index 00000000000..850a4926710 --- /dev/null +++ b/man/checkers/pointerOutOfBounds.md @@ -0,0 +1,47 @@ +# pointerOutOfBounds and pointerOutOfBoundsCond + +**Message**: Undefined behaviour, pointer arithmetic 'a+20' is out of bounds.
+**Category**: Undefined Behaviour
+**Severity**: Portability/Warning
+**Language**: C/C++ + +## Description + +Pointer arithmetic (`p + n`, `p - n`, `p += n`, `p++`, ...) produces a pointer that lands outside the +array/buffer `p` points into: + +- `pointerOutOfBounds`: cppcheck knows for certain the result is out of bounds. This is undefined + behaviour even before the resulting pointer is dereferenced. +- `pointerOutOfBoundsCond`: the out-of-bounds result only holds on one branch of a condition checked + elsewhere - so either that condition is redundant, or this arithmetic is a bug. + +## Motivation + +Forming a pointer that lands outside the bounds of the array it points into is undefined behaviour in +C/C++, even if that pointer is never dereferenced - the language only guarantees pointer arithmetic +stays valid up to one-past-the-end of an array. Compilers are allowed to (and do) optimize based on +this assumption, which can make the resulting bug manifest in surprising, hard-to-reproduce ways. + +## How to fix + +Before: +```cpp +void f() { + int a[10]; + int *p = a + 20; // <- pointerOutOfBounds +} +``` + +After: +```cpp +void f() { + int a[20]; + int *p = a + 19; +} +``` + +## Related checkers + +- [arrayIndexOutOfBounds.md](arrayIndexOutOfBounds.md) - the array-indexing equivalent of this check. +- [ctuPointerArith.md](ctuPointerArith.md) - the same idea, found by cppcheck's whole-program analysis + across function calls. diff --git a/man/checkers/pointerSize.md b/man/checkers/pointerSize.md new file mode 100644 index 00000000000..518278655d1 --- /dev/null +++ b/man/checkers/pointerSize.md @@ -0,0 +1,59 @@ +# pointerSize and sizeofDivisionMemfunc + +**Message**: Size of pointer 'x' used instead of size of its data.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +`sizeof(ptr)` (the size of the pointer itself) is used where the size of what it points to was clearly +intended, as an argument to `malloc`/`calloc`/`memset`/`memcpy`/`memmove`/`strncpy`/`strncmp`/`strncat`: + +- `pointerSize`: `sizeof(ptr)` is passed directly as (or as part of) the size argument. +- `sizeofDivisionMemfunc`: the size argument is computed by *dividing* by `sizeof(ptr)`, when + multiplying was clearly intended. + +## Motivation + +`sizeof(ptr)` is the fixed size of the pointer itself (typically 4 or 8 bytes), not the size of the +buffer it points to. Using it to size a `malloc`/`memset`/`memcpy`-family call is a classic copy-paste +bug: it usually still compiles and often still "sort of works" for small, coincidentally-sized cases, +while silently under- or over-sizing the real operation. The line itself is just a miscalculated size - +the actual undefined behaviour comes later, if something then reads or writes the buffer assuming it's +the size that was intended rather than the (usually too small) size it actually got. + +## How to fix + +Before: +```cpp +void f() { + int *x = (int*)malloc(sizeof(x)); // <- size of the pointer, not what it points to +} +``` + +After: +```cpp +void f() { + int *x = (int*)malloc(sizeof(*x)); +} +``` + +Before: +```cpp +void f(char* dst, char* src, int size) { + memcpy(dst, src, size / sizeof(dst)); // <- dividing by the pointer's size, not the data's +} +``` + +After: +```cpp +void f(char* dst, char* src, int size) { + memcpy(dst, src, size); +} +``` + +## Related checkers + +- [multiplySizeof.md](multiplySizeof.md) - a related but more general `sizeof(a) * sizeof(b)` / + `sizeof(a) / sizeof(b)` mistake, not specific to these memory functions. diff --git a/man/checkers/postfixOperator.md b/man/checkers/postfixOperator.md new file mode 100644 index 00000000000..99c249ef73e --- /dev/null +++ b/man/checkers/postfixOperator.md @@ -0,0 +1,51 @@ +# postfixOperator + +**Message**: Prefer prefix ++/-- operators for non-primitive types.
+**Category**: Performance
+**Severity**: Performance
+**Language**: C++ only + +## Description + +This checker suggests using the prefix `++`/`--` operator instead of postfix on a variable (or member +variable) of a non-primitive type - a class, struct or union instance, or a type whose name ends in +`iterator`, `const_iterator`, `reverse_iterator` or `const_reverse_iterator` - whenever the old value +produced by the postfix operator is not actually used, for example as a whole statement (`k++;`) or in +a loop's increment clause (`for (...; ...; i++)`). + +It does not warn when the postfix result is genuinely used, for example when passed as a function +argument (`foo(a++)`), since switching to prefix there would change the program's behavior, not just +its performance. + +This checker only runs when the `performance` severity is enabled, and only applies to C++ (there are +no classes in C). + +## Motivation + +For a non-primitive type, the postfix operator typically has to make a copy of the object's previous +value before modifying it, so that the old value can be returned - even when nothing uses that old +value. The prefix operator does not need this copy. Built-in types like `int` and pointers don't pay +for this copy, which is why the checker never warns about those. + +## How to fix + +Use the prefix operator when the previous value isn't needed. + +Before: +```cpp +class BigNumber { /* ... */ }; + +void f(BigNumber& n) { + n++; // <- postfixOperator: the returned old value is discarded +} +``` + +After: +```cpp +class BigNumber { /* ... */ }; + +void f(BigNumber& n) { + ++n; +} +``` + diff --git a/man/checkers/preprocessorErrorDirective.md b/man/checkers/preprocessorErrorDirective.md index 44fcb4c1a28..c1a9a2c49c2 100644 --- a/man/checkers/preprocessorErrorDirective.md +++ b/man/checkers/preprocessorErrorDirective.md @@ -1,4 +1,3 @@ - # preprocessorErrorDirective **Message**: #error message
diff --git a/man/checkers/publicAllocationError.md b/man/checkers/publicAllocationError.md new file mode 100644 index 00000000000..8426a7eba4c --- /dev/null +++ b/man/checkers/publicAllocationError.md @@ -0,0 +1,49 @@ +# publicAllocationError + +**Message**: Possible leak in public function. The pointer 'x' is not deallocated before it is allocated.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ + +## Description + +A public member function's first action is to allocate a new value directly into a member pointer, +without checking or freeing whatever it already pointed to - calling this function on an +already-initialized object leaks the old value. + +## Motivation + +A public function can be called at any time, including on an object that already holds a previous +allocation in that member. Overwriting the member with a fresh allocation, without freeing what it +pointed to first, leaks the old value every time the function is called on an object that already has +one - which working code calling this function more than once will do. + +## How to fix + +Before: +```cpp +class A { + int *p; +public: + A() : p(nullptr) {} + void init() { p = new int; } // <- leaks the old 'p' if init() is called twice +}; +``` + +After: +```cpp +class A { + int *p; +public: + A() : p(nullptr) {} + void init() { + delete p; + p = new int; + } +}; +``` + +## Related checkers + +- [unsafeClassCanLeak.md](unsafeClassCanLeak.md) - a related class-ownership leak, where nothing ever + frees an allocated member at all. diff --git a/man/checkers/raceAfterInterlockedDecrement.md b/man/checkers/raceAfterInterlockedDecrement.md new file mode 100644 index 00000000000..f2d24288143 --- /dev/null +++ b/man/checkers/raceAfterInterlockedDecrement.md @@ -0,0 +1,49 @@ +# raceAfterInterlockedDecrement + +**Message**: Race condition: non-interlocked access after InterlockedDecrement(). Use InterlockedDecrement() return value instead.
+**Category**: Correctness
+**Severity**: Error
+**Language**: Windows platform only + +## Description + +Code checks a variable's value directly right after calling the Windows `InterlockedDecrement()` API on +it - between the API call and the check, another thread could have already changed the variable again, +so only the value `InterlockedDecrement()` itself returned can be trusted. + +## Motivation + +`InterlockedDecrement()` exists specifically to make the decrement-and-check atomic across threads. If +the code decrements the variable and then reads it again as a separate step, another thread can run in +between and change the value - the whole point of using the interlocked API is defeated, reintroducing +the exact race condition it was meant to prevent. cppcheck recognizes this purely from the code shape (an +`InterlockedDecrement()` call immediately followed by a plain re-read of the same variable) - it has no +way to confirm another thread genuinely touches that variable, so this is a strong hint of a real race, +not a proof that one exists in every case it's reported. + +## How to fix + +Use the value `InterlockedDecrement()` itself returns, instead of re-reading the variable afterwards. + +Before: +```cpp +void destroy(); +void f() { + int counter = 0; + InterlockedDecrement(&counter); + if (counter) // <- another thread could change 'counter' in between + return; + destroy(); +} +``` + +After: +```cpp +void destroy(); +void f() { + int counter = 0; + if (InterlockedDecrement(&counter) == 0) + return; + destroy(); +} +``` diff --git a/man/checkers/readWriteOnlyFile.md b/man/checkers/readWriteOnlyFile.md new file mode 100644 index 00000000000..2b4f3f5da30 --- /dev/null +++ b/man/checkers/readWriteOnlyFile.md @@ -0,0 +1,52 @@ +# readWriteOnlyFile + +**Message**: Read operation on a file that was opened only for writing.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +The file was opened in a mode (`"w"`) that only allows writing, and the code then reads from it. + +## Motivation + +Reading from a stream that was opened write-only is undefined behaviour in the C standard - even where +an implementation happens to do something predictable with it, code relying on that isn't portable. +cppcheck follows a `FILE*`'s open mode only through straight-line code in a single function; once it's +passed to another function, or is a global/member variable, the tracking is dropped rather than +guessed at, so this check finding nothing does not mean a file is necessarily being used consistently +with how it was opened. + +## How to fix + +Before: +```cpp +#include +void f() { + FILE *fp = fopen("a.txt", "w"); + if (!fp) return; + char buf[10]; + fread(buf, 1, 10, fp); // <- 'fp' was only opened for writing + fclose(fp); +} +``` + +After: +```cpp +#include +void f() { + FILE *fp = fopen("a.txt", "r"); + if (!fp) return; + char buf[10]; + fread(buf, 1, 10, fp); + fclose(fp); +} +``` + +## Related checkers + +- [writeReadOnlyFile.md](writeReadOnlyFile.md) - the opposite mismatch: writing to a read-only file. +- [useClosedFile.md](useClosedFile.md), [IOWithoutPositioning.md](IOWithoutPositioning.md), + [seekOnAppendedFile.md](seekOnAppendedFile.md), [incompatibleFileOpen.md](incompatibleFileOpen.md) - + other checks that follow the same `FILE*` through a function. diff --git a/man/checkers/redundantAssignInSwitch.md b/man/checkers/redundantAssignInSwitch.md new file mode 100644 index 00000000000..b033ccabd95 --- /dev/null +++ b/man/checkers/redundantAssignInSwitch.md @@ -0,0 +1,55 @@ +# redundantAssignInSwitch + +**Message**: Variable 'x' is reassigned a value before the old one has been used.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A variable is assigned a value in one `switch` `case`, and that value is overwritten by another +assignment in a later `case` that it falls through into, without the value ever being read in between. + +## Motivation + +This is the `switch`-fallthrough flavor of a redundant assignment, and it's usually a sign of a missing +`break;` rather than an intentional fallthrough - if the fallthrough really is intentional, the first +assignment is still pointless and worth removing for clarity. + +## How to fix + +Before: +```cpp +void bar(int); +void foo(int a) { + int y = 1; + switch (a) { + case 2: + y = 2; // <- falls through into case 3, missing 'break;'? + case 3: + y = 3; + } + bar(y); +} +``` + +After: +```cpp +void bar(int); +void foo(int a) { + int y = 1; + switch (a) { + case 2: + y = 2; + break; + case 3: + y = 3; + } + bar(y); +} +``` + +## Related checkers + +- [redundantAssignment.md](redundantAssignment.md) - the same idea outside of a `switch`. +- [redundantBitwiseOperationInSwitch.md](redundantBitwiseOperationInSwitch.md) - the bitwise-assignment equivalent in a `switch`. diff --git a/man/checkers/redundantAssignment.md b/man/checkers/redundantAssignment.md new file mode 100644 index 00000000000..7952393b49e --- /dev/null +++ b/man/checkers/redundantAssignment.md @@ -0,0 +1,41 @@ +# redundantAssignment + +**Message**: Variable 'x' is reassigned a value before the old one has been used.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A variable is given a value, but that value is completely overwritten by another assignment before +it's ever read. + +## Motivation + +This isn't wrong in the sense of producing an incorrect result today, but it signals that something +didn't happen the way the author intended: a value that was supposed to be used got clobbered first. +It's also a prime candidate for dead-code cleanup, and occasionally hides an outright bug (for example +a copy-pasted assignment that should have targeted a different variable). + +## How to fix + +Before: +```cpp +void f(int i) { + i = 1; + i = 1; // <- the first assignment is pointless +} +``` + +After: +```cpp +void f(int i) { + i = 1; +} +``` + +## Related checkers + +- [redundantAssignInSwitch.md](redundantAssignInSwitch.md) - the same idea, but across fall-through `switch` cases. +- [redundantInitialization.md](redundantInitialization.md) - the same idea, but for a variable's initializer value. +- [redundantCopyLocalConst.md](redundantCopyLocalConst.md) - a related redundant-copy check for `const` variables. diff --git a/man/checkers/redundantBitwiseOperationInSwitch.md b/man/checkers/redundantBitwiseOperationInSwitch.md new file mode 100644 index 00000000000..23d61a5d227 --- /dev/null +++ b/man/checkers/redundantBitwiseOperationInSwitch.md @@ -0,0 +1,52 @@ +# redundantBitwiseOperationInSwitch + +**Message**: Redundant bitwise operation on 'x' in 'switch' statement. 'break;' missing?
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +The same bitwise assignment (`x |= 1`, `x &= 1`, ...) is applied to a variable twice across +fall-through `switch` cases, with nothing reading the value in between. + +## Motivation + +Just like a redundant plain assignment, this is usually a sign of a missing `break;` between two +`case` labels - the first bitwise operation has no effect once the identical one runs again in the +next case. + +## How to fix + +Before: +```cpp +void foo(int a) { + int y = 1; + switch (a) { + case 2: + y |= 3; // <- same operation repeated in case 3 + case 3: + y |= 3; + break; + } +} +``` + +After: +```cpp +void foo(int a) { + int y = 1; + switch (a) { + case 2: + y |= 3; + break; + case 3: + y |= 3; + break; + } +} +``` + +## Related checkers + +- [redundantAssignInSwitch.md](redundantAssignInSwitch.md) - the plain-assignment equivalent in a `switch`. diff --git a/man/checkers/redundantCondition.md b/man/checkers/redundantCondition.md new file mode 100644 index 00000000000..e6066811f32 --- /dev/null +++ b/man/checkers/redundantCondition.md @@ -0,0 +1,42 @@ +# redundantCondition + +**Message**: Redundant condition: The condition 'x != 4' is redundant since 'x == 3' is sufficient.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +Two comparisons on the same variable, joined by `&&`/`||`, where one comparison is already implied by +the other and adds nothing. + +## Motivation + +If `x == 3` is true, `x != 4` is automatically true too - so `(x == 3) && (x != 4)` behaves exactly like +`x == 3` alone. The extra comparison doesn't change what the code does, only makes it longer and harder +to read, and can suggest a stricter check was intended than what's actually enforced. + +This check may need `--check-level=exhaustive` to see every case. + +## How to fix + +Before: +```cpp +void f(int x, int a) { + if ((x==3) && (x!=4)) // <- 'x != 4' adds nothing once 'x == 3' is true + a++; +} +``` + +After: +```cpp +void f(int x, int a) { + if (x==3) + a++; +} +``` + +## Related checkers + +- [incorrectLogicOperator.md](incorrectLogicOperator.md) - the same style of two-comparisons-on-one-variable + analysis, but for when the combination is always entirely true or entirely false. diff --git a/man/checkers/redundantContinue.md b/man/checkers/redundantContinue.md new file mode 100644 index 00000000000..fe5208c340e --- /dev/null +++ b/man/checkers/redundantContinue.md @@ -0,0 +1,44 @@ +# redundantContinue + +**Message**: 'continue' is redundant since it is the last statement in a loop.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A `continue;` is the last statement in a loop body - the loop was about to move to its next iteration +anyway, so it has no effect. + +## Motivation + +A `continue;` in this position doesn't change what the code does, but it can mislead a reader into +thinking it matters, or that it's guarding something that comes after it in the loop body (when nothing +does). + +## How to fix + +Before: +```cpp +#include +void f() { + for (int i = 0; i < 10; ++i) { + printf("i = %d\n", i); + continue; // <- this is already the end of the loop body + } +} +``` + +After: +```cpp +#include +void f() { + for (int i = 0; i < 10; ++i) { + printf("i = %d\n", i); + } +} +``` + +## Related checkers + +- [unreachableCode.md](unreachableCode.md) - code that can never run at all, rather than a no-op statement. diff --git a/man/checkers/redundantCopy.md b/man/checkers/redundantCopy.md new file mode 100644 index 00000000000..909454d8747 --- /dev/null +++ b/man/checkers/redundantCopy.md @@ -0,0 +1,41 @@ +# redundantCopy + +**Message**: Buffer 'x' is being written before its old content has been used.
+**Category**: Code Quality
+**Severity**: Performance
+**Language**: C/C++ + +## Description + +A buffer is written, then written again before its first content is ever read - the buffer equivalent +of a redundant variable assignment. In current versions of cppcheck, this specific message is not +actually produced for any input: the code path that would emit it exists in the source but isn't +reachable in practice, so don't expect a warning here even for code that matches the pattern below. + +## Motivation + +Writing to a buffer twice with nothing reading it in between wastes the work of the first write, which +is the real, intended idea behind this check - a buffer written twice (by `memset`/`strcpy`/similar) +with no read in between, including across fall-through `switch` cases. If you're looking for the +closest actively-working equivalent, see [redundantCopyLocalConst.md](redundantCopyLocalConst.md) (for +`const` variables) or [redundantAssignment.md](redundantAssignment.md) (the same idea for a plain +variable instead of a buffer). + +## How to fix + +Remove or combine the redundant write so the buffer is only written once before it's read: + +```cpp +#include +void bar(); +void f() { + char a[10]; + memset(a, 0, 10); + bar(); +} +``` + +## Related checkers + +- [redundantAssignment.md](redundantAssignment.md) - the same idea for a plain variable instead of a buffer. +- [redundantCopyLocalConst.md](redundantCopyLocalConst.md) - a different, currently-working redundant-copy check for `const` variables. diff --git a/man/checkers/redundantCopyLocalConst.md b/man/checkers/redundantCopyLocalConst.md new file mode 100644 index 00000000000..3ffb3f4214f --- /dev/null +++ b/man/checkers/redundantCopyLocalConst.md @@ -0,0 +1,39 @@ +# redundantCopyLocalConst + +**Message**: Use const reference for 'x' to avoid unnecessary data copying.
+**Category**: Performance
+**Severity**: Performance
+**Language**: C/C++ + +## Description + +A local variable is declared `const` and initialized as a copy of an existing object that outlives it - +since the copy is never modified, a `const` reference would do the same job without copying. + +## Motivation + +Copying an object (a `std::string`, a container, any non-trivial type) costs time and memory for no +benefit when the copy is only ever read. A `const&` avoids the copy entirely while behaving identically +from the reader's point of view. + +## How to fix + +Before: +```cpp +#include +void f(std::string str) { + std::string s2 = str; // <- 's2' is never modified +} +``` + +After: +```cpp +#include +void f(const std::string& str) { + const std::string& s2 = str; +} +``` + +## Related checkers + +- [redundantCopy.md](redundantCopy.md) - the buffer-write equivalent of this idea. diff --git a/man/checkers/redundantIfRemove.md b/man/checkers/redundantIfRemove.md new file mode 100644 index 00000000000..7fc75a16340 --- /dev/null +++ b/man/checkers/redundantIfRemove.md @@ -0,0 +1,43 @@ +# redundantIfRemove + +**Message**: Redundant checking of STL container element existence before removing it.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +`container.find(x) != container.end()` (or `container.find(x) != container.npos`) is checked before +calling `container.remove(x)` - `remove()` on an absent element is a harmless no-op, so the check adds +nothing. + +## Motivation + +The existence check reads as if it's guarding against a problem, but `remove()` on an element that +isn't there simply does nothing - it's not an error condition that needs to be avoided. The check is +pure overhead: an extra lookup, and an extra branch for a reader to understand, for no behavioral +difference. + +## How to fix + +Before: +```cpp +#include +void f(std::string haystack, std::string needle) { + if (haystack.find(needle) != haystack.end()) // <- remove() is safe on a miss + haystack.remove(needle); +} +``` + +After: +```cpp +#include +void f(std::string haystack, std::string needle) { + haystack.remove(needle); +} +``` + +## Related checkers + +- [stlFindInsert.md](stlFindInsert.md) - the same kind of redundant-check-before-a-safe-operation + pattern, for `insert()` instead of `remove()`. diff --git a/man/checkers/redundantInitialization.md b/man/checkers/redundantInitialization.md new file mode 100644 index 00000000000..734012a41a5 --- /dev/null +++ b/man/checkers/redundantInitialization.md @@ -0,0 +1,41 @@ +# redundantInitialization + +**Message**: Redundant initialization for 'x'. The initialized value is overwritten before it is read.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A variable's initializer value is overwritten before it's read - the same idea as a redundant +assignment, but for the value given at declaration. + +## Motivation + +Giving a variable an initial value only for it to be immediately replaced wastes the work of computing +that initial value, and can mislead a reader into thinking the initializer matters. + +## How to fix + +Before: +```cpp +#include +std::string f() { + std::string s = "abc"; + s = "def"; // <- the "abc" initializer is never read + return s; +} +``` + +After: +```cpp +#include +std::string f() { + std::string s = "def"; + return s; +} +``` + +## Related checkers + +- [redundantAssignment.md](redundantAssignment.md) - the same idea for a later assignment instead of the initializer. diff --git a/man/checkers/redundantNextPrevious.md b/man/checkers/redundantNextPrevious.md new file mode 100644 index 00000000000..78790345927 --- /dev/null +++ b/man/checkers/redundantNextPrevious.md @@ -0,0 +1,35 @@ +# redundantNextPrevious + +**Message**: Statement is redundant, code can be simplified.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ (cppcheck's own source code only) + +## Description + +A chain like `tok->next()->previous()` can be simplified (here, to just `tok`). + +This is not a general-purpose checker for arbitrary C/C++ programs - see +[simplePatternError.md](simplePatternError.md) for the shared background on this internal, +cppcheck-source-only checker. + +## Motivation + +Walking forward and then immediately back (or vice versa) always returns to the starting token - the +extra calls add nothing but noise and a small amount of wasted work. + +## How to fix + +Before: +```cpp +return tok->next()->previous(); // <- redundant +``` + +After: +```cpp +return tok; +``` + +## Related checkers + +- [redundantTokCheck.md](redundantTokCheck.md) - a different kind of redundant code involving the same `Token`/pattern-matching API. diff --git a/man/checkers/redundantPointerOp.md b/man/checkers/redundantPointerOp.md new file mode 100644 index 00000000000..716d09d1995 --- /dev/null +++ b/man/checkers/redundantPointerOp.md @@ -0,0 +1,32 @@ +# redundantPointerOp + +**Message**: Redundant pointer operation on 'p' - it's already a pointer.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +`&*p` or `*&p` is used where `p` is already exactly what's needed - taking the address of a dereference +(or vice versa) just to undo it. + +## Motivation + +This pattern has no effect beyond what writing `p` directly would achieve, and only adds noise that +makes a reader wonder if there's a subtlety being expressed that isn't actually there. + +## How to fix + +Before: +```cpp +int *f(int *x) { + return &*x; // <- 'x' is already a pointer +} +``` + +After: +```cpp +int *f(int *x) { + return x; +} +``` diff --git a/man/checkers/redundantTokCheck.md b/man/checkers/redundantTokCheck.md new file mode 100644 index 00000000000..73ce134ed3e --- /dev/null +++ b/man/checkers/redundantTokCheck.md @@ -0,0 +1,36 @@ +# redundantTokCheck + +**Message**: Unnecessary check of token; 'Token::Match()' already checks if it is a nullptr.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ (cppcheck's own source code only) + +## Description + +Code like `if (tok && Token::Match(tok, "..."))` - the `tok &&` part is redundant, since these +`Token::*` functions already return false/no-match when given a null token. + +This is not a general-purpose checker for arbitrary C/C++ programs - see +[simplePatternError.md](simplePatternError.md) for the shared background on this internal, +cppcheck-source-only checker. + +## Motivation + +Since `Token::Match()` and its relatives already handle a null token safely by returning "no match", an +explicit null check right before calling one of them adds nothing but visual clutter. + +## How to fix + +Before: +```cpp +if (tok && Token::Match(tok, "foo")) {} // <- 'tok &&' is unnecessary +``` + +After: +```cpp +if (Token::Match(tok, "foo")) {} +``` + +## Related checkers + +- [redundantNextPrevious.md](redundantNextPrevious.md) - a different kind of redundant code involving the same `Token` API. diff --git a/man/checkers/rethrowNoCurrentException.md b/man/checkers/rethrowNoCurrentException.md new file mode 100644 index 00000000000..8fd0e859bae --- /dev/null +++ b/man/checkers/rethrowNoCurrentException.md @@ -0,0 +1,39 @@ +# rethrowNoCurrentException + +**Message**: Rethrowing current exception with 'throw;', it seems there is no current exception to rethrow. If there is no current exception this calls std::terminate().
+**Category**: Correctness
+**Severity**: Error
+**Language**: C++ only + +## Description + +A bare `throw;` appears somewhere that isn't inside a `catch` block. If there's no exception currently +being handled, this calls `std::terminate()`. + +## Motivation + +A bare `throw;` only makes sense while an exception is actively being handled (inside a `catch` block, +or a function called from one, with the exception still propagating) - used anywhere else, there's no +exception to rethrow, and the C++ standard says the result is a call to `std::terminate()`, aborting the +program. + +## How to fix + +Before: +```cpp +void f() { + throw; // <- not inside a catch block +} +``` + +After: +```cpp +void doWork(); +void f() { + try { + doWork(); + } catch (...) { + throw; // fine here: there is a current exception being handled + } +} +``` diff --git a/man/checkers/returnByReference.md b/man/checkers/returnByReference.md new file mode 100644 index 00000000000..8909020f844 --- /dev/null +++ b/man/checkers/returnByReference.md @@ -0,0 +1,37 @@ +# returnByReference + +**Message**: Function returns a copy instead of returning by const reference.
+**Category**: Performance
+**Severity**: Performance
+**Language**: C++ + +## Description + +A getter function returns a large member (or a container/string) by value, making an unnecessary copy +on every call, when it could return a `const` reference instead. + +## Motivation + +Returning a container or string by value copies its entire contents on every call, even though the +caller almost always just wants to look at the member that's already sitting inside the object. +Returning a `const&` instead avoids that copy entirely. + +## How to fix + +Before: +```cpp +#include +struct S { + std::string s; + std::string getS() const { return s; } // <- returnByReference: copies 's' on every call +}; +``` + +After: +```cpp +#include +struct S { + std::string s; + const std::string& getS() const { return s; } +}; +``` diff --git a/man/checkers/returnDanglingLifetime.md b/man/checkers/returnDanglingLifetime.md new file mode 100644 index 00000000000..9d2cb31e02a --- /dev/null +++ b/man/checkers/returnDanglingLifetime.md @@ -0,0 +1,43 @@ +# returnDanglingLifetime + +**Message**: Returning pointer to local variable 'num' that will be invalid when returning.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A function returns a pointer, iterator, or a lambda that captures one, which refers to a local +variable, array, or container. + +## Motivation + +Using a pointer after the local variable it points to has gone out of scope is undefined behaviour. +Because the memory involved is usually still intact for a little while afterwards, this kind of bug +often "works" in testing and then fails unpredictably once something else reuses that memory - which +makes it worth catching at analysis time instead of at runtime. + +## How to fix + +Before: +```cpp +int* foo() { + int num = 2; + return # // <- returnDanglingLifetime +} +``` + +After: +```cpp +int* foo() { + static int num = 2; + return # +} +``` + +## Related checkers + +- [returnReference.md](returnReference.md) - the same idea, but for a function whose declared return + type is a reference rather than a pointer. +- [autoVariables.md](autoVariables.md) - the same idea, but escaping through a function parameter + instead of `return`. diff --git a/man/checkers/returnNonBoolInBooleanFunction.md b/man/checkers/returnNonBoolInBooleanFunction.md new file mode 100644 index 00000000000..6e882f490ce --- /dev/null +++ b/man/checkers/returnNonBoolInBooleanFunction.md @@ -0,0 +1,33 @@ +# returnNonBoolInBooleanFunction + +**Message**: Non-boolean value returned from function returning bool
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +A function declared to return `bool` returns a value that cppcheck can determine is not `0` or `1`. + +## Motivation + +A `bool`-returning function is expected to always produce `true`/`false`; returning a value outside +that range (after the implicit conversion, anything nonzero collapses to `true` anyway) usually signals +that the wrong expression was returned, or that the function's intent was actually to return a count or +status code. + +## How to fix + +Before: +```cpp +bool f() { + return 2; // <- collapses to 'true', likely not intended +} +``` + +After: +```cpp +bool f() { + return true; +} +``` diff --git a/man/checkers/returnReference.md b/man/checkers/returnReference.md new file mode 100644 index 00000000000..874afee7887 --- /dev/null +++ b/man/checkers/returnReference.md @@ -0,0 +1,41 @@ +# returnReference + +**Message**: Reference to local variable returned.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +A function declared to return a reference returns a reference to a local variable. + +## Motivation + +A reference to a local variable becomes invalid the moment the function returns. Using the returned +reference afterwards is undefined behaviour, even though the memory is often still intact for a little +while, which is why this bug can appear to "work" in casual testing. + +## How to fix + +Before: +```cpp +int& foo() { + int num = 2; + return num; // <- returnReference +} +``` + +After: +```cpp +int foo() { + int num = 2; + return num; +} +``` + +## Related checkers + +- [returnTempReference.md](returnTempReference.md) - the same idea, but returning a reference to a + temporary object instead of a named local variable. +- [returnDanglingLifetime.md](returnDanglingLifetime.md) - the equivalent problem for a function that + returns a pointer/iterator instead of a reference. diff --git a/man/checkers/returnStdMoveLocal.md b/man/checkers/returnStdMoveLocal.md new file mode 100644 index 00000000000..00ac3f318cc --- /dev/null +++ b/man/checkers/returnStdMoveLocal.md @@ -0,0 +1,44 @@ +# returnStdMoveLocal + +**Message**: Using std::move for returning object by-value from function will affect copy elision optimization.
+**Category**: Code Quality
+**Severity**: Performance
+**Language**: C++ only + +## Description + +`return std::move(x);` is used for a local variable or temporary that's returned by value - this +defeats the compiler's copy elision (RVO/NRVO), which would otherwise avoid the copy entirely without +`std::move`. + +## Motivation + +When a local variable is returned by value, the compiler is allowed (and, since C++17 in many cases, +required) to construct it directly in the caller's storage, skipping any copy or move entirely. Wrapping +the return in `std::move()` defeats this optimization by forcing a move instead, which is strictly worse +than the elision the compiler would have done on its own. + +## How to fix + +Before: +```cpp +struct A{}; +A f() { + A var; + return std::move(var); // <- defeats copy elision +} +``` + +After: +```cpp +struct A{}; +A f() { + A var; + return var; +} +``` + +## Related checkers + +- [useStandardLibrary.md](useStandardLibrary.md) - an unrelated performance suggestion in the same + checker: replacing a hand-written copy loop with a standard library call. diff --git a/man/checkers/returnTempReference.md b/man/checkers/returnTempReference.md new file mode 100644 index 00000000000..13b3842108d --- /dev/null +++ b/man/checkers/returnTempReference.md @@ -0,0 +1,42 @@ +# returnTempReference + +**Message**: Reference to temporary returned.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +A function declared to return a reference returns a reference to a temporary object. + +## Motivation + +A temporary object is destroyed at the end of the full expression that created it (with some narrow +lifetime-extension exceptions that don't apply once the reference is returned out of the function). +Returning a reference to one, and then using it, is a use of an already-destroyed object. + +## How to fix + +Before: +```cpp +int get_value(); +const int &get_reference() { + const int &x = get_value(); // binds to a temporary + return x; // <- returnTempReference +} +``` + +After: +```cpp +int get_value(); +int get_reference() { + return get_value(); +} +``` + +## Related checkers + +- [returnReference.md](returnReference.md) - the same idea, but returning a reference to a named local + variable instead of a temporary. +- [danglingTempReference.md](danglingTempReference.md) - the same underlying temporary-lifetime + mistake, used within the same function rather than returned from it. diff --git a/man/checkers/sameIteratorExpression.md b/man/checkers/sameIteratorExpression.md new file mode 100644 index 00000000000..60181cd66c4 --- /dev/null +++ b/man/checkers/sameIteratorExpression.md @@ -0,0 +1,40 @@ +# sameIteratorExpression + +**Message**: Same iterators expression are used for algorithm.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +The exact same iterator expression is passed as both the "begin" and "end" argument of an algorithm, +making the range empty. + +## Motivation + +A range whose begin and end are the same iterator is always empty, so the algorithm call does nothing - +this is normally a sign that one of the two arguments was meant to be different (for example the end of +the container, not its beginning again). + +## How to fix + +Before: +```cpp +#include +void f(int a[10]) { + if (std::any_of(&a[0], &a[0], [](int x){return x > 0;})) {} // <- sameIteratorExpression: an empty range +} +``` + +After: +```cpp +#include +void f(int a[10]) { + if (std::any_of(&a[0], &a[10], [](int x){return x > 0;})) {} +} +``` + +## Related checkers + +- [mismatchingContainers.md](mismatchingContainers.md) - the opposite mistake: two iterators that + belong to different containers being used together. diff --git a/man/checkers/seekOnAppendedFile.md b/man/checkers/seekOnAppendedFile.md new file mode 100644 index 00000000000..869c589a0ce --- /dev/null +++ b/man/checkers/seekOnAppendedFile.md @@ -0,0 +1,49 @@ +# seekOnAppendedFile + +**Message**: Repositioning operation performed on a file opened in append mode has no effect.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A repositioning call (`fseek()`, `fsetpos()`, `rewind()`) is made on a file that was opened in append +mode (`"a"`) - every write in append mode always goes to the end of the file regardless, so +repositioning has no effect. + +## Motivation + +Code that repositions before writing to an append-mode file is misleading: the reposition is silently +ignored, so the code doesn't do what it looks like it does, and the call is dead weight. + +## How to fix + +Before: +```cpp +#include +void f() { + FILE *fp = fopen("a.txt", "a"); + if (!fp) return; + fseek(fp, 0, SEEK_SET); // <- has no effect in append mode + fwrite("x", 1, 1, fp); + fclose(fp); +} +``` + +After: +```cpp +#include +void f() { + FILE *fp = fopen("a.txt", "a"); + if (!fp) return; + fwrite("x", 1, 1, fp); + fclose(fp); +} +``` + +## Related checkers + +- [useClosedFile.md](useClosedFile.md), [readWriteOnlyFile.md](readWriteOnlyFile.md), + [writeReadOnlyFile.md](writeReadOnlyFile.md), [IOWithoutPositioning.md](IOWithoutPositioning.md), + [incompatibleFileOpen.md](incompatibleFileOpen.md) - other checks that follow the same `FILE*` through + a function. diff --git a/man/checkers/selfAssignment.md b/man/checkers/selfAssignment.md new file mode 100644 index 00000000000..1023a59297c --- /dev/null +++ b/man/checkers/selfAssignment.md @@ -0,0 +1,34 @@ +# selfAssignment + +**Message**: Redundant assignment of 'x' to itself.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A variable is assigned to itself (`x = x;`), which has no effect. + +## Motivation + +An assignment that reads and writes the exact same variable does nothing useful, and is often a +leftover from a refactor or a typo for an assignment that was meant to involve a different variable. + +## How to fix + +Before: +```cpp +void foo() { + int x = 1; + x = x; // <- has no effect +} +``` + +After: +```cpp +#include +void foo() { + int x = 1; + printf("%d", x); +} +``` diff --git a/man/checkers/selfInitialization.md b/man/checkers/selfInitialization.md new file mode 100644 index 00000000000..1fecc4c2a79 --- /dev/null +++ b/man/checkers/selfInitialization.md @@ -0,0 +1,47 @@ +# selfInitialization + +**Message**: Member variable 'i' is initialized by itself.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +A member is initialized with itself in the initializer list (`Fred() : i(i) {}` with no same-named +constructor parameter) - it reads its own not-yet-initialized value. + +## Motivation + +`i(i)` in a member-initializer list refers to the member `i` on both sides (there's no constructor +parameter with that name to shadow it) - the member ends up "initialized" by reading its own +uninitialized value, which is undefined behaviour and almost certainly a typo for a differently-named +constructor parameter. + +## How to fix + +Give the constructor parameter a distinct name, or otherwise supply a real initial value. + +Before: +```cpp +class Fred { + int i; +public: + Fred() : i(i) { // <- reads its own uninitialized value + } +}; +``` + +After: +```cpp +class Fred { + int i; +public: + Fred() : i(0) { + } +}; +``` + +## Related checkers + +- [initializerList.md](initializerList.md) - the related, broader family of member-initializer-list + ordering pitfalls. diff --git a/man/checkers/shadowArgument.md b/man/checkers/shadowArgument.md new file mode 100644 index 00000000000..6556f2449d6 --- /dev/null +++ b/man/checkers/shadowArgument.md @@ -0,0 +1,36 @@ +# shadowArgument + +**Message**: Local variable 'x' shadows outer argument
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +A local variable has the same name as one of the function's own parameters, which is still in scope - +the local one hides the parameter for the rest of its scope. + +## Motivation + +A shadowed name invites confusion about which variable a later line actually refers to, and is a +standing invitation to edit the wrong one. + +## How to fix + +Before: +```cpp +#include +void f(int x) { { int x = 1; printf("%d", x); } } // <- hides the parameter 'x' +``` + +After: +```cpp +#include +void f(int x) { { int y = 1; printf("%d", y); } } +``` + +## Related checkers + +- [shadowVariable.md](shadowVariable.md) - the same idea, when the outer name is a variable. +- [shadowFunction.md](shadowFunction.md) - the same idea, when the outer name is a function. +- [shadowMember.md](shadowMember.md) - the same idea, when the outer name is a class/struct member. diff --git a/man/checkers/shadowFunction.md b/man/checkers/shadowFunction.md new file mode 100644 index 00000000000..39ee40e7385 --- /dev/null +++ b/man/checkers/shadowFunction.md @@ -0,0 +1,39 @@ +# shadowFunction + +**Message**: Local variable 'x' shadows outer function
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +A local variable (or parameter) has the same name as a function that is still visible - the local one +hides the function for the rest of its scope. + +## Motivation + +A shadowed name invites confusion about which one a later line actually refers to, and is a standing +invitation to edit the wrong thing - or to be surprised that the function can no longer be called by +its plain name in that scope. + +## How to fix + +Before: +```cpp +#include +int getA(); +void f() { int getA = 1; printf("%d", getA); } // <- hides the function 'getA' +``` + +After: +```cpp +#include +int getA(); +void f() { int result = 1; printf("%d", result); } +``` + +## Related checkers + +- [shadowVariable.md](shadowVariable.md) - the same idea, when the outer name is a variable. +- [shadowArgument.md](shadowArgument.md) - the same idea, when the outer name is a function parameter. +- [shadowMember.md](shadowMember.md) - the same idea, when the outer name is a class/struct member. diff --git a/man/checkers/shadowMember.md b/man/checkers/shadowMember.md new file mode 100644 index 00000000000..e85f9f66829 --- /dev/null +++ b/man/checkers/shadowMember.md @@ -0,0 +1,42 @@ +# shadowMember + +**Message**: Local variable 'x' shadows outer member
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +A local variable inside a member function has the same name as a class/struct member - the local one +hides the member for the rest of its scope. + +## Motivation + +A shadowed name invites confusion about which one a later line actually refers to, and is a standing +invitation to edit the local variable when the member was intended, or vice versa. + +## How to fix + +Before: +```cpp +#include +struct S { + int i{}; + void f() { int i = 1; printf("%d", i); } // <- hides the member 'i' +}; +``` + +After: +```cpp +#include +struct S { + int i{}; + void f() { int localCount = 1; printf("%d", localCount); } +}; +``` + +## Related checkers + +- [shadowVariable.md](shadowVariable.md) - the same idea, when the outer name is a variable. +- [shadowArgument.md](shadowArgument.md) - the same idea, when the outer name is a function parameter. +- [shadowFunction.md](shadowFunction.md) - the same idea, when the outer name is a function. diff --git a/man/checkers/shadowVariable.md b/man/checkers/shadowVariable.md new file mode 100644 index 00000000000..ff589e7b428 --- /dev/null +++ b/man/checkers/shadowVariable.md @@ -0,0 +1,38 @@ +# shadowVariable + +**Message**: Local variable 'x' shadows outer variable
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +A local variable (or parameter) has the same name as an outer variable that is still in scope - the +inner one hides the outer one for the rest of its scope. + +## Motivation + +A shadowed name invites confusion about which variable a later line actually refers to, and is a +standing invitation to edit the wrong one. + +## How to fix + +Before: +```cpp +#include +int x; +void f() { int x = 1; printf("%d", x); } // <- hides the global 'x' +``` + +After: +```cpp +#include +int x; +void f() { int y = 1; printf("%d", y); } +``` + +## Related checkers + +- [shadowArgument.md](shadowArgument.md) - the same idea, when the outer name is a function parameter. +- [shadowFunction.md](shadowFunction.md) - the same idea, when the outer name is a function. +- [shadowMember.md](shadowMember.md) - the same idea, when the outer name is a class/struct member. diff --git a/man/checkers/shiftNegative.md b/man/checkers/shiftNegative.md new file mode 100644 index 00000000000..403812b86c5 --- /dev/null +++ b/man/checkers/shiftNegative.md @@ -0,0 +1,38 @@ +# shiftNegative + +**Message**: Shifting by a negative value is undefined behaviour
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A bitwise shift (`<<`/`>>`) is done by a negative amount, which is undefined behaviour. + +## Motivation + +The C/C++ standards leave the result of shifting by a negative amount undefined - the program's actual +behaviour is not guaranteed and can change with the compiler, optimization level, or platform. + +## How to fix + +Before: +```cpp +void foo() { + int a; a = 123; + (void)(a << -1); // <- shifting by a negative amount +} +``` + +After: +```cpp +void foo() { + int a; a = 123; + (void)(a << 1); +} +``` + +## Related checkers + +- [shiftNegativeLHS.md](shiftNegativeLHS.md) - the same kind of undefined behaviour, but for shifting a + negative value rather than shifting by a negative amount. diff --git a/man/checkers/shiftNegativeLHS.md b/man/checkers/shiftNegativeLHS.md new file mode 100644 index 00000000000..0b2e0ecf7da --- /dev/null +++ b/man/checkers/shiftNegativeLHS.md @@ -0,0 +1,40 @@ +# shiftNegativeLHS + +**Message**: Shifting a negative value is technically undefined behaviour
+**Category**: Undefined Behaviour
+**Severity**: Portability
+**Language**: C/C++ + +## Description + +The value being shifted (rather than the shift amount) is negative, which is technically undefined +behaviour, even though many compilers implement it as a predictable sign-preserving shift. + +## Motivation + +The C/C++ standards leave the result of shifting a negative value undefined, even though in practice +most compilers implement a predictable arithmetic (sign-preserving) shift - code relying on that isn't +guaranteed to behave the same way on every compiler or platform. + +## How to fix + +Before: +```cpp +void foo() { + int x; + x = (-10+2) << 3; // <- shifting a negative value +} +``` + +After: +```cpp +void foo() { + int x; + x = (10-2) << 3; +} +``` + +## Related checkers + +- [shiftNegative.md](shiftNegative.md) - the same kind of undefined behaviour, but for shifting by a + negative amount rather than shifting a negative value. diff --git a/man/checkers/shiftTooManyBits.md b/man/checkers/shiftTooManyBits.md index 1db7dceb85a..8b33fb08a67 100644 --- a/man/checkers/shiftTooManyBits.md +++ b/man/checkers/shiftTooManyBits.md @@ -2,7 +2,7 @@ **Message**: Shifting 32-bit value by 40 bits is undefined behaviour
**Category**: Undefined Behaviour
-**Severity**: Error/Warning
+**Severity**: Error/Warning/Portability
**Language**: C/C++ ## Description @@ -15,7 +15,9 @@ There are two related warnings: undefined behaviour according to the C/C++ standard. - `shiftTooManyBitsSigned`: the left-hand side type is signed and the shift amount is exactly `bits - 1`. Shifting a signed type this far is undefined behaviour before C++14, and - implementation-defined behaviour from C++14 onwards. + implementation-defined behaviour from C++14 onwards - in the C++14-and-later case this is reported + as a `portability` message instead of a `warning`, since the behaviour is now defined, just not the + same on every platform. The number of bits of the left-hand side type is determined from the platform settings (`int_bit`, `long_bit`, `long_long_bit`), so this checker requires a platform to be configured. @@ -25,24 +27,10 @@ The number of bits of the left-hand side type is determined from the platform se Shifting a value by more bits than its type contains is undefined behaviour. The result is unpredictable and can vary between compilers, compiler versions and optimization settings. -## Limitations / false negatives - -- **Uppercase macro-like calls are skipped entirely.** A statement of the form `NAME(...)` where - `NAME` is all-uppercase and not a known function is treated as a macro invocation and the whole - call is skipped, so a bad shift inside it is not detected, for example: - ```cpp - void f(unsigned int x) { - UINFO(x << 1234); // not detected - } - ``` -- Only applies when the left-hand side type is a non-pointer integral type that resolves to `int`, - `long` or `long long` width; other cases (for example pointer types) are not checked. -- This checker relies on ValueFlow to prove that the shift amount is out of range. When the shift - amount is guarded by several combined conditions, ValueFlow may not be able to derive a tight - enough bound, and the warning can be missed even though the underlying issue is real. -- Code that ValueFlow determines is unreachable (for example a branch that can never be taken due to - a constant/template condition) is not analyzed, so a bad shift in genuinely dead code is not - reported. +cppcheck only warns when it can work out the shift amount directly from the code - a literal value, or +a condition earlier in the function that pins it down. If the amount can't be determined that way (for +example it's guarded by several combined conditions, or hidden inside a macro-like call), no warning is +given even though the shift could still be too large at runtime. ## How to fix diff --git a/man/checkers/signConversion.md b/man/checkers/signConversion.md index 973a03653e8..d371b35ac3e 100644 --- a/man/checkers/signConversion.md +++ b/man/checkers/signConversion.md @@ -7,13 +7,14 @@ ## Description -This checker uses ValueFlow analysis to detect arithmetic expressions (other than `+`/`-`) whose -result type is unsigned, where one of the operands can have a negative value. When that happens, -the negative operand is implicitly converted to an unsigned value before the calculation, which can -produce a very large value instead of the intended negative one. +This checker detects arithmetic expressions (other than `+`/`-`) whose result type is unsigned, where +one of the operands can have a negative value. When that happens, the negative operand is implicitly +converted to an unsigned value before the calculation, which can produce a very large value instead +of the intended negative one. If the negative value is a known constant, the message states the operand "has" a negative value; -otherwise it states the operand "can have" a negative value, based on ValueFlow analysis. +otherwise it states the operand "can have" a negative value, based on cppcheck's analysis of the +surrounding code. This checker only runs when the `warning` severity is enabled. @@ -42,14 +43,6 @@ nothing for an explicit cast to fix or clarify, so a warning here would not be a arithmetic operators (`*`, `/`, `%`, shifts, etc.) do not have this property in the same way and are still checked. -## Limitations / false negatives - -- Only the direct operands of the unsigned arithmetic operator are examined; a negative value that - is only reachable through a deeper subexpression is not specifically traced beyond what ValueFlow - already attaches to that immediate operand. -- Detection depends on ValueFlow having a possible or known negative value for the operand; an - unconstrained parameter with no usable value information will not be flagged. - ## How to fix You can fix these warnings by: @@ -57,10 +50,10 @@ You can fix these warnings by: 2. Using a signed type for the calculation 3. Adding an explicit check or cast to make the intended behaviour clear -Note: cppcheck only warns when ValueFlow can actually determine that the operand can be negative - -either from a known/possible value at the call site (as below), or from a condition earlier in the -same function. A plain `int` parameter with no callers and no surrounding condition gives ValueFlow -no evidence that it can be negative, so it is not reported. +Note: cppcheck only warns when it can actually determine that the operand can be negative - either +from a known value at the call site (as below), or from a condition earlier in the same function. A +plain `int` parameter with no callers and no surrounding condition gives cppcheck no evidence that it +can be negative, so it is not reported. Before: ```cpp diff --git a/man/checkers/signedCharArrayIndex.md b/man/checkers/signedCharArrayIndex.md new file mode 100644 index 00000000000..9f42f4c4e3f --- /dev/null +++ b/man/checkers/signedCharArrayIndex.md @@ -0,0 +1,55 @@ +# signedCharArrayIndex and unknownSignCharArrayIndex + +**Message**: Signed 'char' type used as array index.
+**Category**: Undefined Behaviour/Portability
+**Severity**: Warning/Portability
+**Language**: C/C++ + +## Description + +- `signedCharArrayIndex` (warning): an array is indexed with a `char` known to be signed, and the index + can be 128 or more - once sign-extended, a value like `200` becomes negative, so the access lands + before the start of the array instead of near its expected position. This ID specifically covers the + case where cppcheck knows the index is a signed `char` that *can* reach 128 or more, but can't work + out its exact wrapped-around value; when the concrete wrapped value (or a condition it depends on) is + known, the same bug is instead reported as the more specific [negativeIndex.md](negativeIndex.md) or + [arrayIndexOutOfBounds.md](arrayIndexOutOfBounds.md), which is what happens in most real code. +- `unknownSignCharArrayIndex` (portability): the same situation, but for a plain `char` whose signedness + the C/C++ standard leaves up to the compiler - the exact same source code indexes the array + differently depending on which platform/compiler it's built with. + +## Motivation + +On many platforms `char` can hold negative values (typically -128..127 instead of 0..255). A byte value +of 128 or more, once treated as a signed `char`, becomes negative - so using it directly as an array +index produces a negative index instead of the expected large-but-positive one, reading or writing +before the start of the array. + +## How to fix + +Use `unsigned char` for a value that's meant to index into a table by its raw byte value. + +Before: +```cpp +int buf[256]; +void foo() { + char ch = 0x80; + buf[ch] = 0; // <- negative on platforms where 'char' is signed +} +``` + +After: +```cpp +int buf[256]; +void foo() { + unsigned char ch = 0x80; + buf[ch] = 0; +} +``` + +## Related checkers + +- [negativeIndex.md](negativeIndex.md) and [arrayIndexOutOfBounds.md](arrayIndexOutOfBounds.md) - the + more specific IDs this checker's finding is usually superseded by. +- [charBitOp.md](charBitOp.md) - the same signed-`char` sign-extension pitfall, but for a bitwise + operation instead of an array index. diff --git a/man/checkers/simplePatternError.md b/man/checkers/simplePatternError.md new file mode 100644 index 00000000000..96e4a9aac26 --- /dev/null +++ b/man/checkers/simplePatternError.md @@ -0,0 +1,42 @@ +# simplePatternError + +**Message**: Found simple pattern inside Token::Match() call: ";"
+**Category**: Code Quality
+**Severity**: Warning
+**Language**: C++ (cppcheck's own source code only) + +## Description + +A pattern given to `Token::Match()`/`Token::findmatch()` doesn't actually use any wildcard syntax, so +the cheaper `Token::simpleMatch()`/`Token::findsimplematch()` could be used instead. + +This is not a general-purpose checker for arbitrary C/C++ programs. It only looks for calls to +cppcheck's own internal pattern-matching API, and reports style/correctness issues in how that API is +used. It exists to help cppcheck's own contributors write correct and efficient code in the cppcheck +codebase, not to check any project being analyzed by cppcheck. + +It is only compiled in when cppcheck itself is built with the `CHECK_INTERNAL` macro defined - a normal +end-user build of cppcheck does not have it at all, and even a build that has it still needs +`--enable=internal` to turn it on. + +## Motivation + +`Token::Match()` interprets a small wildcard syntax in its pattern string, which costs more than a +plain text comparison. When a pattern doesn't use any of that syntax, `Token::simpleMatch()` does the +identical comparison faster. + +## How to fix + +Before: +```cpp +Token::Match(tok, ";"); // <- no wildcard syntax used +``` + +After: +```cpp +Token::simpleMatch(tok, ";"); +``` + +## Related checkers + +- [complexPatternError.md](complexPatternError.md) - the opposite mistake, using `simpleMatch()` with a pattern that needs wildcard interpretation. diff --git a/man/checkers/sizeofCalculation.md b/man/checkers/sizeofCalculation.md new file mode 100644 index 00000000000..012a6d4855d --- /dev/null +++ b/man/checkers/sizeofCalculation.md @@ -0,0 +1,34 @@ +# sizeofCalculation + +**Message**: Found calculation inside sizeof().
+**Category**: Correctness
+**Severity**: Warning (inconclusive)
+**Language**: C/C++ + +## Description + +An arithmetic/increment/decrement calculation appears inside `sizeof`, e.g. `sizeof(a + b)` or +`sizeof(x++)` - it looks like it runs, but it never does. + +## Motivation + +The operand of `sizeof` is normally not evaluated at all - only its type is used to compute the result. +A calculation written inside `sizeof` reads as if it executes (especially `x++`, which looks like it +must have a side effect), but it silently never runs, which is confusing for anyone who later expects +that side effect to have happened. + +## How to fix + +Before: +```cpp +void f(int a, int b) { + int s = sizeof(a + b); // <- the addition never actually happens +} +``` + +After: +```cpp +void f(int a, int b) { + int s = sizeof(a); +} +``` diff --git a/man/checkers/sizeofFunctionCall.md b/man/checkers/sizeofFunctionCall.md new file mode 100644 index 00000000000..9f3a55e320b --- /dev/null +++ b/man/checkers/sizeofFunctionCall.md @@ -0,0 +1,34 @@ +# sizeofFunctionCall + +**Message**: Found function call inside sizeof().
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A function call appears inside `sizeof`, e.g. `sizeof(foo())` - `foo()` is never actually called. + +## Motivation + +The operand of `sizeof` is normally not evaluated at all - only its type is used to compute the result. +A function call written inside `sizeof` reads as if it runs (and, worse, hides a call whose return +value or side effect might have been genuinely needed), but it silently never executes. + +## How to fix + +Before: +```cpp +int compute(); +void f() { + int s = sizeof(compute()); // <- compute() is never called +} +``` + +After: +```cpp +int compute(); +void f() { + int s = sizeof(decltype(compute())); +} +``` diff --git a/man/checkers/sizeofVoid.md b/man/checkers/sizeofVoid.md new file mode 100644 index 00000000000..a8fc7c79899 --- /dev/null +++ b/man/checkers/sizeofVoid.md @@ -0,0 +1,40 @@ +# sizeofVoid and sizeofDereferencedVoidPointer + +**Message**: Behaviour of 'sizeof(void)' is not covered by the ISO C standard.
+**Category**: Portability
+**Severity**: Portability
+**Language**: C/C++ + +## Description + +- `sizeofVoid`: `sizeof(void)` is used directly - this isn't defined by the C/C++ standard (only by a + GNU extension, which defines it as `1`). +- `sizeofDereferencedVoidPointer`: `sizeof(*p)` where `p` is `void*` - the same underlying problem, + reached by dereferencing a `void*` instead of naming `void` directly. + +## Motivation + +`void` has no size in standard C/C++ - `sizeof(void)` is only meaningful under a GNU extension (where +it's defined as `1`), so code relying on it is not portable to a strictly-standard-conforming compiler, +even though gcc/clang will happily accept it. + +## How to fix + +Before: +```cpp +void f(void *p) { + int s = sizeof(*p); // <- 'p' is void*, so this is sizeof(void) +} +``` + +After: +```cpp +void f(char *p) { + int s = sizeof(*p); +} +``` + +## Related checkers + +- [arithOperationsOnVoidPointer.md](arithOperationsOnVoidPointer.md) - another `void*`-specific, + non-standard construct (pointer arithmetic directly on `void*`). diff --git a/man/checkers/sizeofsizeof.md b/man/checkers/sizeofsizeof.md new file mode 100644 index 00000000000..86e2c17c4a9 --- /dev/null +++ b/man/checkers/sizeofsizeof.md @@ -0,0 +1,34 @@ +# sizeofsizeof + +**Message**: Calling 'sizeof' on 'sizeof'.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +`sizeof(sizeof(x))` - always the same as `sizeof(size_t)`, regardless of what `x` is. + +## Motivation + +`sizeof(...)` itself evaluates to a `size_t` value, so `sizeof` of that result is always just +`sizeof(size_t)` - a fixed, platform-defined constant that has nothing to do with `x`. Writing this +almost always means one `sizeof` too many was typed. + +## How to fix + +Before: +```cpp +void f() { + int a; + int s = sizeof(sizeof(a)); // <- always sizeof(size_t), regardless of 'a' +} +``` + +After: +```cpp +void f() { + int a; + int s = sizeof(a); +} +``` diff --git a/man/checkers/sizeofwithnumericparameter.md b/man/checkers/sizeofwithnumericparameter.md new file mode 100644 index 00000000000..5dabab68532 --- /dev/null +++ b/man/checkers/sizeofwithnumericparameter.md @@ -0,0 +1,33 @@ +# sizeofwithnumericparameter + +**Message**: Suspicious usage of 'sizeof' with a numeric constant as parameter.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +`sizeof` is applied to a plain numeric constant, e.g. `sizeof(10)`. + +## Motivation + +`sizeof` is nearly always resolved at compile time from the *type* of its operand, not its value - a +numeric literal's type is whatever the compiler infers for it (usually `int`), not something meaningful +to size a buffer or count elements by. This is almost always a mistake for a type name, or a variable +whose size was actually intended. + +## How to fix + +Before: +```cpp +void f() { + int size = sizeof(10); // <- probably meant a type or a variable, not the literal 10 +} +``` + +After: +```cpp +void f() { + int size = sizeof(int); +} +``` diff --git a/man/checkers/sizeofwithsilentarraypointer.md b/man/checkers/sizeofwithsilentarraypointer.md new file mode 100644 index 00000000000..edcd3674a65 --- /dev/null +++ b/man/checkers/sizeofwithsilentarraypointer.md @@ -0,0 +1,34 @@ +# sizeofwithsilentarraypointer + +**Message**: Using 'sizeof' on array given as function argument returns size of a pointer.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +`sizeof` is applied to an array that was received as a function parameter - which has already decayed +to a plain pointer, so the result is the pointer's size, not the array's. + +## Motivation + +An array parameter (`int a[10]`) is really just a pointer as far as the function body is concerned - the +size information from the declaration is not available at runtime. `sizeof(a)` inside the function +silently returns the size of the pointer, not the number of bytes the caller's array actually occupies, +which is easy to miss since the declaration still visually looks like an array. + +## How to fix + +Before: +```cpp +void f(int a[10]) { + int n = sizeof(a) / sizeof(int); // <- 'a' decayed to int*, this is not 10 +} +``` + +After: +```cpp +void f(int a[10], int n) { + int m = n / sizeof(int); // pass the real element count instead +} +``` diff --git a/man/checkers/sprintfOverlappingData.md b/man/checkers/sprintfOverlappingData.md new file mode 100644 index 00000000000..ed710f46009 --- /dev/null +++ b/man/checkers/sprintfOverlappingData.md @@ -0,0 +1,43 @@ +# sprintfOverlappingData + +**Message**: Undefined behavior: Variable is used as parameter and destination in s[n]printf().
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +The same variable is used as both the destination and a source argument of `sprintf`/`snprintf`/ +`swprintf`, which is undefined behaviour. + +## Motivation + +`sprintf()` and its relatives may write to the destination buffer while still reading from it if a +source argument aliases the destination - the C standard leaves this undefined, so the actual result +(correct output, garbled output, or a crash) depends on the specific library implementation. + +## How to fix + +Before: +```cpp +#include +void foo() { + char buf[100]; + sprintf(buf, "%s", buf); // <- source and destination overlap +} +``` + +After: +```cpp +#include +void foo() { + char buf[100] = "hi"; + char tmp[100]; + sprintf(tmp, "%s", buf); // write to a different buffer +} +``` + +## Related checkers + +- [overlappingStrcmp.md](overlappingStrcmp.md) - an unrelated string-function misuse in the same + checker, about a redundant/contradictory pair of `strcmp()` checks. diff --git a/man/checkers/staticStringCompare.md b/man/checkers/staticStringCompare.md new file mode 100644 index 00000000000..b2e5a49bc95 --- /dev/null +++ b/man/checkers/staticStringCompare.md @@ -0,0 +1,41 @@ +# staticStringCompare and stringCompare + +**Message**: Unnecessary comparison of static strings.
+**Category**: Code Quality
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +Two string literals (`staticStringCompare`), or two identical-looking string variables +(`stringCompare`), are compared with a string-comparison function (`strcmp`, `stricmp`, `wcscmp`, ...) - +the result is already known at compile time either way. + +## Motivation + +Comparing two string literals, or a variable against itself under a different name, can never vary at +runtime - the comparison's result is fixed regardless of any input, which usually means a variable name +was typed wrong, or the comparison is leftover from earlier code that used to compare something real. + +## How to fix + +Before: +```cpp +#include +int main() { + if (strcmp("00FF00", "00FF00") == 0) {} // <- always true +} +``` + +After: +```cpp +#include +int main(const char* value) { + if (strcmp(value, "00FF00") == 0) {} // compare the actual variable +} +``` + +## Related checkers + +- [literalWithCharPtrCompare.md](literalWithCharPtrCompare.md) - a related string-comparison mistake: + comparing a pointer against a literal with `==` instead of a string-comparison function. diff --git a/man/checkers/stlBoundaries.md b/man/checkers/stlBoundaries.md new file mode 100644 index 00000000000..0174b5abb67 --- /dev/null +++ b/man/checkers/stlBoundaries.md @@ -0,0 +1,36 @@ +# stlBoundaries + +**Message**: Dangerous comparison using operator< on iterator.
+**Category**: Correctness
+**Severity**: Error
+**Language**: C++ + +## Description + +An iterator that doesn't support ordering (for example a `std::list` or `std::set` iterator) is +compared with `<`/`>` instead of `!=`/`==` - the relative order of two such iterators isn't guaranteed +to mean anything. + +## Motivation + +Only random-access iterators (like a `std::vector` or `std::deque` iterator) have a well-defined +"less than" relationship. For other container types, the underlying storage isn't laid out in a way +that makes iterator order meaningful, so comparing with `<`/`>` is not portable and not reliable. + +## How to fix + +Before: +```cpp +#include +bool foo(std::list::iterator it1, std::list::iterator it2) { + return it1 < it2; // <- stlBoundaries: order of list iterators isn't guaranteed +} +``` + +After: +```cpp +#include +bool foo(std::list::iterator it1, std::list::iterator it2) { + return it1 != it2; +} +``` diff --git a/man/checkers/stlFindInsert.md b/man/checkers/stlFindInsert.md new file mode 100644 index 00000000000..af4a47e0ea2 --- /dev/null +++ b/man/checkers/stlFindInsert.md @@ -0,0 +1,46 @@ +# stlFindInsert + +**Message**: Searching before insertion is not necessary.
+**Category**: Performance
+**Severity**: Performance
+**Language**: C++ + +## Description + +An associative container is searched with `.find()`, and if nothing was found, the same key is +inserted right afterwards - the search is redundant since `insert()`/`emplace()` already report whether +the key existed. + +## Motivation + +Searching a container and then inserting into it based on the result does the lookup twice: once +explicitly with `.find()`, and again internally inside `insert()`/`emplace()` to find the right place +(or detect the key is already present). Going straight to `insert()`/`emplace()` gets the same behavior +in one lookup instead of two. + +## How to fix + +Before: +```cpp +#include +void f1(std::set& s, unsigned x) { + if (s.find(x) == s.end()) { // <- redundant, insert() already knows if x is present + s.insert(x); + } +} +``` + +After: +```cpp +#include +void f1(std::set& s, unsigned x) { + s.insert(x); +} +``` + +## Related checkers + +- [redundantIfRemove.md](redundantIfRemove.md) - a similar redundant-check-before-a-safe-operation + pattern, for `remove()` instead of `insert()`. +- [stlIfFind.md](stlIfFind.md) - a different `find()`-related mistake, where the result is misread as a + boolean. diff --git a/man/checkers/stlIfFind.md b/man/checkers/stlIfFind.md new file mode 100644 index 00000000000..a3e022b8db8 --- /dev/null +++ b/man/checkers/stlIfFind.md @@ -0,0 +1,63 @@ +# stlIfFind and stlIfStrFind + +**Message**: Suspicious condition. The result of find() is an iterator, but it is not properly checked.
+**Category**: Correctness
+**Severity**: Warning/Performance
+**Language**: C++ + +## Description + +The result of a container's `.find()` is used directly as a boolean condition: + +- `stlIfFind`: this tests whether the resulting iterator happens to be "truthy", not whether the + element was found, which is virtually never what's intended. +- `stlIfStrFind`: the same mistake specifically for `std::string::find()`, which returns a *position*, + not an iterator - comparing it directly as a boolean is wrong far more often than not (position 0 - + a match at the very start of the string - is falsy). With a C++20-or-later standard, this is instead + reported as a performance suggestion to use `string::starts_with()`. + +## Motivation + +`find()` on most containers returns an iterator (or, for `std::string`, a position) that must be +compared against `.end()` (or `std::string::npos`) to know whether something was actually found. Using +the raw result as a boolean condition compiles cleanly and can even look like it works in quick testing, +but it is testing the wrong thing - a genuine logic bug hiding behind code that reads as correct. + +## How to fix + +Before: +```cpp +#include +void f(std::set s) { + if (s.find(12)) { } // <- stlIfFind: this checks the iterator's "truthiness", not whether 12 was found +} +``` + +After: +```cpp +#include +void f(std::set s) { + if (s.find(12) != s.end()) { } +} +``` + +Before: +```cpp +#include +void f(const std::string &s) { + if (s.find("abc")) { } // <- stlIfStrFind: position 0 (a match at the start) is falsy here +} +``` + +After: +```cpp +#include +void f(const std::string &s) { + if (s.find("abc") != std::string::npos) { } +} +``` + +## Related checkers + +- [stlFindInsert.md](stlFindInsert.md) - a different `find()`-related redundancy, checking before + inserting into an associative container. diff --git a/man/checkers/stlOutOfBounds.md b/man/checkers/stlOutOfBounds.md new file mode 100644 index 00000000000..e8cf07a6ac8 --- /dev/null +++ b/man/checkers/stlOutOfBounds.md @@ -0,0 +1,44 @@ +# stlOutOfBounds + +**Message**: When ii==foo.size(), foo.at(ii) is out of bounds.
+**Category**: Correctness
+**Severity**: Error
+**Language**: C++ + +## Description + +A loop of the form `for (i = 0; i <= container.size(); ++i)` uses `<=` instead of `<`, so the last +iteration indexes one past the end. + +## Motivation + +This is one of the most common off-by-one mistakes in loops that walk a container by index: the loop +condition looks like a natural "up to and including the size" check, but the valid indices only go up +to `size() - 1`. + +## How to fix + +Before: +```cpp +#include +void f(std::vector foo) { + for (unsigned int ii = 0; ii <= foo.size(); ++ii) { // <- stlOutOfBounds + foo.at(ii) = 0; + } +} +``` + +After: +```cpp +#include +void f(std::vector foo) { + for (unsigned int ii = 0; ii < foo.size(); ++ii) { + foo.at(ii) = 0; + } +} +``` + +## Related checkers + +- [containerOutOfBounds.md](containerOutOfBounds.md) - the more general out-of-bounds container access + check that this loop-condition mistake is a specific cause of. diff --git a/man/checkers/stlSize.md b/man/checkers/stlSize.md new file mode 100644 index 00000000000..8dddc868132 --- /dev/null +++ b/man/checkers/stlSize.md @@ -0,0 +1,45 @@ +# stlSize + +**Message**: Possible inefficient checking for 'x' emptiness.
+**Category**: Performance
+**Severity**: Performance
+**Language**: C++ (only checked with a pre-C++11 standard, and only for `std::list`) + +## Description + +`.size()` is compared against `0`/`1` on a container whose `.size()` isn't guaranteed to be +constant-time (only checked pre-C++11, and only for `std::list`) - `.empty()` is always at least as +fast. + +## Motivation + +Before C++11, `std::list::size()` was allowed to take time proportional to the number of elements +(implementations were free to compute it by walking the list), while `.empty()` is always O(1). +Comparing `.size()` against `0`/`1` where `.empty()` would do is therefore a real, if usually small, +performance cost, and easy to write out of habit from code that works with other containers. + +## How to fix + +Before: +```cpp +#include +struct Fred { + void foo(); + std::list x; +}; +void Fred::foo() { + if (x.size() == 0) {} // <- only with a pre-C++11 standard: size() isn't guaranteed O(1) +} +``` + +After: +```cpp +#include +struct Fred { + void foo(); + std::list x; +}; +void Fred::foo() { + if (x.empty()) {} +} +``` diff --git a/man/checkers/stlcstr.md b/man/checkers/stlcstr.md new file mode 100644 index 00000000000..9e823ae922e --- /dev/null +++ b/man/checkers/stlcstr.md @@ -0,0 +1,69 @@ +# stlcstr and stlcstrthrow + +**Message**: Dangerous usage of c_str(). The value returned by c_str() is invalid after this call.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +The pointer from `.c_str()` (or an implicit conversion to `std::string` followed by `.c_str()`) escapes +the function it was taken in, in a way that leaves it dangling: + +- `stlcstr`: the pointer is returned from a function - the string it points into was a temporary or + local variable that no longer exists once the function returns. +- `stlcstrthrow`: the pointer is thrown as an exception - the string it points into is destroyed as the + stack unwinds, so the caught pointer is immediately dangling. + +## Motivation + +`std::string::c_str()` only stays valid for as long as the `std::string` it came from is still alive +(and hasn't been modified). Returning or throwing that pointer instead of the string itself is a +dangling-pointer bug: the pointer looks fine at the point it's produced, and only misbehaves later, +wherever it's eventually used. + +## How to fix + +Before: +```cpp +#include +const char *get_msg() { + std::string errmsg; + return errmsg.c_str(); // <- stlcstr: dangling as soon as 'errmsg' is destroyed +} +``` + +After: +```cpp +#include +std::string get_msg() { + std::string errmsg; + return errmsg; +} +``` + +Before: +```cpp +#include +void f() { + std::string errmsg; + throw errmsg.c_str(); // <- stlcstrthrow: dangling once the exception propagates past 'errmsg' +} +``` + +After: +```cpp +#include +void f() { + std::string errmsg; + throw errmsg; +} +``` + +## Related checkers + +- [stlcstrReturn.md](stlcstrReturn.md), [stlcstrParam.md](stlcstrParam.md), + [stlcstrConstructor.md](stlcstrConstructor.md), [stlcstrAssignment.md](stlcstrAssignment.md), + [stlcstrConcat.md](stlcstrConcat.md), [stlcstrStream.md](stlcstrStream.md) - the same + `.c_str()`-when-a-`std::string`-would-do idea, in other call-site shapes; those are inefficient + rather than dangerous. diff --git a/man/checkers/stlcstrAssignment.md b/man/checkers/stlcstrAssignment.md new file mode 100644 index 00000000000..44460758c79 --- /dev/null +++ b/man/checkers/stlcstrAssignment.md @@ -0,0 +1,45 @@ +# stlcstrAssignment + +**Message**: Assigning the result of c_str() to a std::string is slow and redundant.
+**Category**: Performance
+**Severity**: Performance
+**Language**: C++ + +## Description + +A `std::string` is assigned the result of `.c_str()` called on another `std::string` - going through +`c_str()` forces an unnecessary `strlen()` call that assigning directly from the source `std::string` +would avoid. + +## Motivation + +Assigning a `const char*` to a `std::string` requires calling `strlen()` to find its length, even +though the source `std::string` already knows its own length. Assigning from the `std::string` directly +skips that redundant scan. + +## How to fix + +Before: +```cpp +#include +std::string f(const std::string& a) { + std::string b = a.c_str(); // <- forces a strlen() that's already known + return b; +} +``` + +After: +```cpp +#include +std::string f(const std::string& a) { + std::string b = a; + return b; +} +``` + +## Related checkers + +- [stlcstr.md](stlcstr.md) - the dangerous (not just inefficient) version of this mistake. +- [stlcstrReturn.md](stlcstrReturn.md), [stlcstrParam.md](stlcstrParam.md), + [stlcstrConstructor.md](stlcstrConstructor.md), [stlcstrConcat.md](stlcstrConcat.md), + [stlcstrStream.md](stlcstrStream.md) - the same idea in other call-site shapes. diff --git a/man/checkers/stlcstrConcat.md b/man/checkers/stlcstrConcat.md new file mode 100644 index 00000000000..d06a1b1c9cd --- /dev/null +++ b/man/checkers/stlcstrConcat.md @@ -0,0 +1,43 @@ +# stlcstrConcat + +**Message**: Concatenating the result of c_str() and a std::string is slow and redundant.
+**Category**: Performance
+**Severity**: Performance
+**Language**: C++ + +## Description + +`.c_str()` called on a `std::string` is concatenated (`+`) with another `std::string` - going through +`c_str()` forces an unnecessary `strlen()` call that concatenating the two `std::string`s directly +would avoid. + +## Motivation + +Concatenating a `const char*` with a `std::string` requires calling `strlen()` on the `const char*` to +find its length, even though the `std::string` it came from already knew its own length. Concatenating +the two `std::string`s directly skips that redundant scan. + +## How to fix + +Before: +```cpp +#include +std::string g(const std::string& a, const std::string& b) { + return a + b.c_str(); // <- forces a strlen() that's already known +} +``` + +After: +```cpp +#include +std::string g(const std::string& a, const std::string& b) { + return a + b; +} +``` + +## Related checkers + +- [stlcstr.md](stlcstr.md) - the dangerous (not just inefficient) version of this mistake. +- [stlcstrReturn.md](stlcstrReturn.md), [stlcstrParam.md](stlcstrParam.md), + [stlcstrConstructor.md](stlcstrConstructor.md), [stlcstrAssignment.md](stlcstrAssignment.md), + [stlcstrStream.md](stlcstrStream.md) - the same idea in other call-site shapes. diff --git a/man/checkers/stlcstrConstructor.md b/man/checkers/stlcstrConstructor.md new file mode 100644 index 00000000000..0985d821688 --- /dev/null +++ b/man/checkers/stlcstrConstructor.md @@ -0,0 +1,45 @@ +# stlcstrConstructor + +**Message**: Constructing a std::string from the result of c_str() is slow and redundant.
+**Category**: Performance
+**Severity**: Performance
+**Language**: C++ + +## Description + +A `std::string` is constructed from the result of `.c_str()` called on another `std::string` - going +through `c_str()` forces an unnecessary `strlen()` call that constructing directly from the source +`std::string` would avoid. + +## Motivation + +Constructing a `std::string` from a `const char*` requires calling `strlen()` to find its length, even +though the source `std::string` already knows its own length. Constructing from the `std::string` +directly skips that redundant scan. + +## How to fix + +Before: +```cpp +#include +std::string f(const std::string& a) { + std::string b(a.c_str()); // <- forces a strlen() that's already known + return b; +} +``` + +After: +```cpp +#include +std::string f(const std::string& a) { + std::string b(a); + return b; +} +``` + +## Related checkers + +- [stlcstr.md](stlcstr.md) - the dangerous (not just inefficient) version of this mistake. +- [stlcstrReturn.md](stlcstrReturn.md), [stlcstrParam.md](stlcstrParam.md), + [stlcstrAssignment.md](stlcstrAssignment.md), [stlcstrConcat.md](stlcstrConcat.md), + [stlcstrStream.md](stlcstrStream.md) - the same idea in other call-site shapes. diff --git a/man/checkers/stlcstrParam.md b/man/checkers/stlcstrParam.md new file mode 100644 index 00000000000..2345c14c784 --- /dev/null +++ b/man/checkers/stlcstrParam.md @@ -0,0 +1,49 @@ +# stlcstrParam + +**Message**: Passing the result of c_str() to a function that takes std::string as argument no. 1 is slow and redundant.
+**Category**: Performance
+**Severity**: Performance
+**Language**: C++ + +## Description + +`.c_str()` is used purely to feed a `const char*` into a function parameter that's typed `std::string` +- going through `c_str()` forces an unnecessary `strlen()`/copy that passing the `std::string` directly +would avoid. + +## Motivation + +Calling `.c_str()` just to hand the result to a parameter that would have happily accepted the +`std::string` itself throws away the length information the `std::string` already had, forcing a +`strlen()` scan (and a copy) to reconstruct it. Passing the `std::string` directly is both simpler and +faster. + +## How to fix + +Before: +```cpp +#include +void Foo1(const std::string& s); +void f() { + std::string str = "bar"; + Foo1(str.c_str()); // <- Foo1() already accepts a std::string +} +``` + +After: +```cpp +#include +void Foo1(const std::string& s); +void f() { + std::string str = "bar"; + Foo1(str); +} +``` + +## Related checkers + +- [stlcstr.md](stlcstr.md) - the dangerous (not just inefficient) version of this mistake, where the + pointer ends up dangling. +- [stlcstrReturn.md](stlcstrReturn.md), [stlcstrConstructor.md](stlcstrConstructor.md), + [stlcstrAssignment.md](stlcstrAssignment.md), [stlcstrConcat.md](stlcstrConcat.md), + [stlcstrStream.md](stlcstrStream.md) - the same idea in other call-site shapes. diff --git a/man/checkers/stlcstrReturn.md b/man/checkers/stlcstrReturn.md new file mode 100644 index 00000000000..f74f72ff420 --- /dev/null +++ b/man/checkers/stlcstrReturn.md @@ -0,0 +1,47 @@ +# stlcstrReturn + +**Message**: Returning the result of c_str() in a function that returns std::string is slow and redundant.
+**Category**: Performance
+**Severity**: Performance
+**Language**: C++ + +## Description + +`.c_str()` is used purely to feed a `const char*` into a `return` statement of a function that returns +`std::string` - going through `c_str()` forces an unnecessary `strlen()`/copy that constructing the +`std::string` result directly would avoid. + +## Motivation + +Calling `.c_str()` just to hand the result back to something that would have happily accepted the +`std::string` itself throws away the length information the `std::string` already had, forcing a +`strlen()` scan (and a copy) to reconstruct it. Passing/returning the `std::string` directly is both +simpler and faster. + +## How to fix + +Before: +```cpp +#include +std::string get_msg() { + std::string errmsg; + return errmsg.c_str(); // <- forces an unnecessary strlen()/copy +} +``` + +After: +```cpp +#include +std::string get_msg() { + std::string errmsg; + return errmsg; +} +``` + +## Related checkers + +- [stlcstr.md](stlcstr.md) - the dangerous (not just inefficient) version of this mistake, where the + function returns `const char*` and the pointer ends up dangling. +- [stlcstrParam.md](stlcstrParam.md), [stlcstrConstructor.md](stlcstrConstructor.md), + [stlcstrAssignment.md](stlcstrAssignment.md), [stlcstrConcat.md](stlcstrConcat.md), + [stlcstrStream.md](stlcstrStream.md) - the same idea in other call-site shapes. diff --git a/man/checkers/stlcstrStream.md b/man/checkers/stlcstrStream.md new file mode 100644 index 00000000000..2653e328853 --- /dev/null +++ b/man/checkers/stlcstrStream.md @@ -0,0 +1,44 @@ +# stlcstrStream + +**Message**: Passing the result of c_str() to a stream is slow and redundant.
+**Category**: Performance
+**Severity**: Performance
+**Language**: C++ + +## Description + +`.c_str()` called on a `std::string` is streamed (`<<`) into an output stream - going through `c_str()` +forces an unnecessary `strlen()` call that streaming the `std::string` directly would avoid. + +## Motivation + +Streaming a `const char*` requires the stream to call `strlen()` on it to find its length, even though +the `std::string` it came from already knew its own length. Streaming the `std::string` directly skips +that redundant scan. + +## How to fix + +Before: +```cpp +#include +#include +void f(std::stringstream& strm, const std::string& s) { + strm << s.c_str(); // <- forces a strlen() that's already known +} +``` + +After: +```cpp +#include +#include +void f(std::stringstream& strm, const std::string& s) { + strm << s; +} +``` + +## Related checkers + +- [stlcstr.md](stlcstr.md) - the dangerous (not just inefficient) version of this mistake. +- [stlcstrReturn.md](stlcstrReturn.md), [stlcstrParam.md](stlcstrParam.md), + [stlcstrConstructor.md](stlcstrConstructor.md), [stlcstrAssignment.md](stlcstrAssignment.md), + [stlcstrConcat.md](stlcstrConcat.md) - the same idea in other call-site shapes. diff --git a/man/checkers/strPlusChar.md b/man/checkers/strPlusChar.md new file mode 100644 index 00000000000..97ceed0388b --- /dev/null +++ b/man/checkers/strPlusChar.md @@ -0,0 +1,45 @@ +# strPlusChar + +**Message**: Unusual pointer arithmetic. A value of type 'char' is added to a string literal.
+**Category**: Correctness
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A string literal has a `char`/`wchar_t` value added to it with `+`, with the literal written on the +left-hand side (`"text" + ch`) - the same expression with the operands swapped (`ch + "text"`) is not +covered by this check. + +## Motivation + +`"/usr" + '/'` doesn't concatenate the character onto the string - a string literal decays to a +`const char*`, so `+` here is pointer arithmetic: it adds the character's numeric value to the pointer, +producing a pointer into the middle of (or past the end of) the literal. This looks like string +concatenation but is nothing like it. + +This can lead to undefined behaviour by itself, separate from whatever happens if the pointer is later +used: the C++ standard only allows pointer arithmetic to land inside the array or exactly one past its +end, and a character value large enough to land outside that range (as `'/'` does against the 5-byte +`"/usr"`) makes the addition itself undefined behaviour, whether or not the resulting pointer is ever +dereferenced. cppcheck doesn't check whether the specific character value would actually stay in range - +this is flagged purely because the syntactic pattern (a string literal plus a `char`) is essentially +always a concatenation mistake, not because cppcheck has determined that this particular addition goes +out of bounds. + +## How to fix + +Before: +```cpp +void foo() { + const char *p = "/usr" + '/'; // <- pointer arithmetic on a string literal +} +``` + +After: +```cpp +#include +void foo() { + std::string p = std::string("/usr") + '/'; +} +``` diff --git a/man/checkers/stringLiteralWrite.md b/man/checkers/stringLiteralWrite.md new file mode 100644 index 00000000000..ee38a753098 --- /dev/null +++ b/man/checkers/stringLiteralWrite.md @@ -0,0 +1,35 @@ +# stringLiteralWrite + +**Message**: Modifying string literal "abc" directly or indirectly is undefined behaviour.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A string literal is modified through a pointer to it (directly, or via a pointer passed to another +function). + +## Motivation + +String literals are typically stored in read-only memory, so writing to one crashes or corrupts memory +unpredictably depending on the platform. `char *p = "abc";` compiles without warning even though `p` +points at memory that must not be written to. + +## How to fix + +Before: +```cpp +void f() { + char *abc = "abc"; + abc[0] = 'a'; // <- undefined behaviour +} +``` + +After: +```cpp +void f() { + char abc[] = "abc"; // a real, modifiable array + abc[0] = 'a'; +} +``` diff --git a/man/checkers/suspiciousCase.md b/man/checkers/suspiciousCase.md new file mode 100644 index 00000000000..1dbd9932ddc --- /dev/null +++ b/man/checkers/suspiciousCase.md @@ -0,0 +1,45 @@ +# suspiciousCase + +**Message**: Found suspicious case label in switch(). Operator '&&' probably doesn't work as intended.
+**Category**: Correctness
+**Severity**: Warning (Inconclusive)
+**Language**: C/C++ + +## Description + +A `switch` `case` label contains `&&`/`||` (`case A&&B:`) - a case label must be a single constant, so +this doesn't test both `A` and `B`; it's normally a sign that `if`/`else` or several `case` labels were +intended instead. + +## Motivation + +A `case` label is required to be a single constant expression - `A && B` is still just one expression +(evaluating to `0` or `1`), not a test of "when `A` and `B`." Code written this way almost never means +what a reader would guess from the `&&`/`||` at a glance, and usually indicates the author meant to test +multiple values or conditions in a way `switch` doesn't directly support. + +## How to fix + +Use separate `case` labels (to match several values) or an `if`/`else` chain (to test a real logical +condition) instead. + +Before: +```cpp +void foo(int a, int A, int B) { + switch(a) { + case A&&B: // <- a case label must be one constant, not a logical expression + foo(a, A, B); + } +} +``` + +After: +```cpp +void foo(int a, int A, int B) { + switch(a) { + case A: + case B: + foo(a, A, B); + } +} +``` diff --git a/man/checkers/suspiciousFloatingPointCast.md b/man/checkers/suspiciousFloatingPointCast.md new file mode 100644 index 00000000000..9e405c42efa --- /dev/null +++ b/man/checkers/suspiciousFloatingPointCast.md @@ -0,0 +1,34 @@ +# suspiciousFloatingPointCast + +**Message**: Floating-point cast causes loss of precision.
+**Category**: Correctness
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A `double`/`long double` value is cast down to a narrower floating type (`float`, or `double` from +`long double`) and then used somewhere that expected the original, wider type back - the narrowing +silently throws away precision for no benefit. + +## Motivation + +Casting to a narrower floating type and then immediately using the result as the wider type again +gains nothing - the precision lost in the cast can't come back, and the code would behave identically +(with better precision) if the cast were simply removed. + +## How to fix + +Before: +```cpp +double f(double a, double b, float c) { + return a + (float)b + c; // <- 'b' loses precision for no reason +} +``` + +After: +```cpp +double f(double a, double b, float c) { + return a + b + c; +} +``` diff --git a/man/checkers/suspiciousSemicolon.md b/man/checkers/suspiciousSemicolon.md new file mode 100644 index 00000000000..0de8287cb67 --- /dev/null +++ b/man/checkers/suspiciousSemicolon.md @@ -0,0 +1,43 @@ +# suspiciousSemicolon + +**Message**: Suspicious use of ; at the end of 'if' statement.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A stray `;` immediately follows an `if`/`for`/`while` condition, right before a `{ ... }` block - the +block is then unconditional, but reads as if it were controlled by the condition. + +## Motivation + +`if (cond);` is a complete statement on its own (an empty statement, executed only when `cond` is true), +so a `{ ... }` block written right after it is a separate, always-executed statement - not the body of +the `if` at all. This is a classic typo that silently turns a conditional block into unconditional code, +and it's easy to miss on a quick read since the block still looks indented as if it belonged to the +`if`. + +## How to fix + +Remove the stray semicolon. + +Before: +```cpp +void do_something(); +void foo(bool quit) { + while (!quit); { // <- the loop body is just ';' - this block always runs once + do_something(); + } +} +``` + +After: +```cpp +void do_something(); +void foo(bool quit) { + while (!quit) { + do_something(); + } +} +``` diff --git a/man/checkers/terminateStrncpy.md b/man/checkers/terminateStrncpy.md new file mode 100644 index 00000000000..c554ade8d81 --- /dev/null +++ b/man/checkers/terminateStrncpy.md @@ -0,0 +1,40 @@ +# terminateStrncpy + +**Message**: The buffer 'dest' may not be null-terminated after the call to strncpy().
+**Category**: Undefined Behaviour
+**Severity**: Warning (inconclusive)
+**Language**: C/C++ + +## Description + +`strncpy()` is called with a length equal to (or larger than) the destination buffer's size, and the +source may be at least that long - in that case `strncpy()` does not null-terminate the destination, so +it may not be a valid C string afterwards. Since this is a "may not be null-terminated" finding rather +than a certainty, it is only reported when `--inconclusive` is enabled. + +## Motivation + +`strncpy()` only writes a trailing `'\0'` if the source string is shorter than the given length; if the +source is at least as long as the length, the destination is left completely full with no terminator. +Any later code that treats the buffer as a normal null-terminated C string then reads past its end. + +## How to fix + +Before: +```cpp +#include +void f() { + char dest[10]; + strncpy(dest, "abcdefghijklmnop", sizeof(dest)); // <- may not be null-terminated +} +``` + +After: +```cpp +#include +void f() { + char dest[10]; + strncpy(dest, "abcdefghijklmnop", sizeof(dest) - 1); + dest[sizeof(dest) - 1] = '\0'; +} +``` diff --git a/man/checkers/thisSubtraction.md b/man/checkers/thisSubtraction.md new file mode 100644 index 00000000000..ec12d91dbf9 --- /dev/null +++ b/man/checkers/thisSubtraction.md @@ -0,0 +1,48 @@ +# thisSubtraction + +**Message**: Suspicious pointer subtraction. Did you forget to dereference it?
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ + +## Description + +The code contains `this-x` - almost always a typo for `this->x`, since subtracting an arbitrary value +from `this` as a pointer is rarely meaningful. + +## Motivation + +`this-x` and `this->x` look almost identical but mean completely different things: one does pointer +arithmetic on `this` itself (rarely intended), the other accesses member `x`. This is an easy typo to +make and an easy one to miss when reading over code quickly. + +It's also not merely a readability problem: `this` points to a single object, not an array, so subtracting +any nonzero value from it produces a pointer outside that object's bounds, which the standard's pointer +arithmetic rules make undefined behaviour to even form, before it's ever dereferenced or compared. cppcheck +doesn't evaluate `x` here, though - it flags the `this - x` syntax on sight, regardless of what `x` actually +is, so this is reported purely as a likely typo for `this->x`, not because cppcheck has confirmed the +subtraction itself is unsafe. + +## How to fix + +Before: +```cpp +class C { +public: + int x; + void f() { + this-x; // <- thisSubtraction: likely meant 'this->x' + } +}; +``` + +After: +```cpp +class C { +public: + int x; + void f() { + this->x; + } +}; +``` diff --git a/man/checkers/thisUseAfterFree.md b/man/checkers/thisUseAfterFree.md new file mode 100644 index 00000000000..0713de349b8 --- /dev/null +++ b/man/checkers/thisUseAfterFree.md @@ -0,0 +1,42 @@ +# thisUseAfterFree + +**Message**: Class member 'x' is accessed after deleting 'this' (or a smart pointer holding it).
+**Category**: Undefined Behaviour
+**Severity**: Warning
+**Language**: C++ + +## Description + +A member function `delete`s (or `.reset()`s) a pointer/smart-pointer that holds `this` itself, and then +goes on to call another method or use another member - by that point the object has already been +destroyed. + +## Motivation + +Once an object has deleted itself (typically via a `static` "instance" pointer, or a smart pointer that +owns it), continuing to use `this` - or anything reached through it, including other member functions - +is a use-after-free, even though the memory may still look intact for a little while afterwards. + +## How to fix + +Before: +```cpp +class C { +public: + void dostuff() { delete mInstance; hello(); } // <- thisUseAfterFree: 'this' is gone after the delete +private: + static C *mInstance; + void hello() {} +}; +``` + +After: +```cpp +class C { +public: + void dostuff() { hello(); delete mInstance; } + void hello() {} +private: + static C *mInstance; +}; +``` diff --git a/man/checkers/throwInEntryPoint.md b/man/checkers/throwInEntryPoint.md new file mode 100644 index 00000000000..e4c1f9d3acd --- /dev/null +++ b/man/checkers/throwInEntryPoint.md @@ -0,0 +1,47 @@ +# throwInEntryPoint + +**Message**: Unhandled exception thrown in function that is an entry point.
+**Category**: Correctness
+**Severity**: Error
+**Language**: C++ only + +## Description + +An exception can escape a recognized program entry point (`main`, or others such as `_init`/`_fini` +when the matching library configuration is loaded). + +## Motivation + +An exception that escapes `main()` (or another entry point) is not caught by anything, so the C++ +runtime calls `std::terminate()` - the program aborts, typically without the cleanup a caught, +handled exception would have allowed. + +## How to fix + +Before: +```cpp +void doWork(); +int main() { + doWork(); // <- if this throws, nothing will catch it + return 0; +} +``` + +After: +```cpp +#include +void doWork(); +int main() { + try { + doWork(); + } catch (const std::exception&) { + return 1; + } + return 0; +} +``` + +## Related checkers + +- [throwInNoexceptFunction.md](throwInNoexceptFunction.md) - the same underlying problem, for a + function explicitly marked `noexcept` instead of a recognized entry point. diff --git a/man/checkers/throwInNoexceptFunction.md b/man/checkers/throwInNoexceptFunction.md new file mode 100644 index 00000000000..d69cddfb7a8 --- /dev/null +++ b/man/checkers/throwInNoexceptFunction.md @@ -0,0 +1,39 @@ +# throwInNoexceptFunction + +**Message**: Unhandled exception thrown in function declared not to throw exceptions.
+**Category**: Correctness
+**Severity**: Error
+**Language**: C++ only + +## Description + +A function declared `noexcept`, `throw()`, or with an equivalent `__attribute__((nothrow))`/ +`__declspec(nothrow)`, throws (or calls something that throws) anyway. + +## Motivation + +If an exception escapes a function promised not to throw, `std::terminate()` is called immediately - +the program aborts without any of the normal stack-unwinding cleanup (destructors further up the call +stack are not guaranteed to run). + +## How to fix + +Before: +```cpp +void f() noexcept { + throw 1; // <- contradicts the noexcept promise +} +``` + +After: +```cpp +void f() noexcept { +} +``` + +## Related checkers + +- [exceptThrowInDestructor.md](exceptThrowInDestructor.md) - the same underlying problem, specifically + for a destructor (implicitly `noexcept`). +- [throwInEntryPoint.md](throwInEntryPoint.md) - the same underlying problem, for a recognized program + entry point instead of an explicitly `noexcept` function. diff --git a/man/checkers/truncLongCast.md b/man/checkers/truncLongCast.md index 47a9819288f..01d9400d0dc 100644 --- a/man/checkers/truncLongCast.md +++ b/man/checkers/truncLongCast.md @@ -7,12 +7,17 @@ ## Description -This checker warns when a calculation has type 'int' and it could potentially overflow that and the result is implicitly or explicitly converted to a larger -integer type after the loss of information has already occurred. +A multiplication (`*`) or left-shift (`<<`) is computed using `int` arithmetic, and only afterwards - +once any overflow has already happened - is the result widened to a larger integer type by an implicit +or explicit conversion. Widening after the fact doesn't recover information that `int` arithmetic already +lost. ## Motivation -The motivation of this checker is to catch bugs. Unintentional loss of information. +Declaring a wider result type (`long`, `int64_t`, ...) is often meant to give a calculation more room, so +it doesn't overflow. That only works if the wider type is used for the calculation itself; if the +multiplication or shift is still done in `int` and only the final result is widened, the overflow already +happened before the conversion, and the wider type just carries forward a truncated value. ## How to fix diff --git a/man/checkers/unassignedVariable.md b/man/checkers/unassignedVariable.md new file mode 100644 index 00000000000..cec8da2a0ee --- /dev/null +++ b/man/checkers/unassignedVariable.md @@ -0,0 +1,44 @@ +# unassignedVariable + +**Message**: Variable 'i' is not assigned a value.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A variable is read (or its address is taken) without ever having been given a value first. + +## Motivation + +Reading a variable that's never been assigned is a sign of a bug: a forgotten initialization, or a +variable name that was meant to refer to something else. (Note this checker flags the code pattern +itself; see [uninitvar.md](uninitvar.md) for the related, more precise check on genuinely reading an +uninitialized value.) + +## How to fix + +Give the variable a value before reading it. + +Before: +```cpp +int foo() { + int i; // <- read below without ever being assigned + return i; +} +``` + +After: +```cpp +int foo() { + int i = 0; + return i; +} +``` + +## Related checkers + +- [uninitvar.md](uninitvar.md) - the closely related, more precise family of checks specifically about + reading an uninitialized value. +- [unusedVariable.md](unusedVariable.md) and [unreadVariable.md](unreadVariable.md) - the sibling + findings for a variable that's never used at all, or assigned but never read. diff --git a/man/checkers/unhandledExceptionSpecification.md b/man/checkers/unhandledExceptionSpecification.md new file mode 100644 index 00000000000..a63abe03e69 --- /dev/null +++ b/man/checkers/unhandledExceptionSpecification.md @@ -0,0 +1,39 @@ +# unhandledExceptionSpecification + +**Message**: Unhandled exception specification when calling function thrower().
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ only + +## Description + +A function with no exception specification of its own calls another function that has an (old-style, +C++17-removed) `throw(SomeType)` dynamic exception specification, without any `try`/`catch` in between. + +## Motivation + +A dynamic exception specification like `throw(int)` documents that a function can only throw that +particular type - calling it without handling that possibility is worth a second look, especially since +this old-style specification was removed from the language in C++17 and callers may not realize it's +still meaningful in the code they're reading. + +## How to fix + +Before: +```cpp +void thrower() throw(int); +void f() { + thrower(); // <- unhandled 'throw(int)' specification +} +``` + +After: +```cpp +void thrower() throw(int); +void f() { + try { + thrower(); + } catch (int) { + } +} +``` diff --git a/man/checkers/uninitMemberVar.md b/man/checkers/uninitMemberVar.md new file mode 100644 index 00000000000..b7d498df8a2 --- /dev/null +++ b/man/checkers/uninitMemberVar.md @@ -0,0 +1,142 @@ +# uninitMemberVar, uninitMemberVarPrivate, uninitDerivedMemberVar, uninitDerivedMemberVarPrivate, uninitMemberVarNoCtor, uninitMemberVarPrivateNoCtor, uninitDerivedMemberVarNoCtor and uninitDerivedMemberVarPrivateNoCtor + +**Message**: Member variable 'x' is not initialized in the constructor.
+**Category**: Undefined Behaviour
+**Severity**: Warning
+**Language**: C++ + +## Description + +A constructor (or copy/move constructor) doesn't give a member variable any value. All eight IDs here +are the exact same underlying finding, distinguished only by three independent, combinable details of +the situation: + +- **`Derived`** appears in the ID when the uninitialized member actually belongs to a *base* class + rather than the class whose constructor is being looked at. +- **`Private`** appears in the ID when either there's no constructor at all and only some of the + class's members happen to have in-class default values (so the ones without one are flagged this + way), or the specific constructor that's missing the initialization is itself `private`. +- **`NoCtor`** appears in the ID when the class has no constructor at all, but *some* of its members do + have in-class default initializers - which means cppcheck can tell that whoever wrote the class was + thinking about initialization, making it worth flagging any other member that has no default. + +Of the eight combinations, three (`uninitMemberVarPrivateNoCtor`, `uninitDerivedMemberVarNoCtor`, +`uninitDerivedMemberVarPrivateNoCtor`) are only theoretically possible - in the current version of +cppcheck, none of the code paths that would build these particular three ID strings are actually +reachable, so you will not see them in practice. + +## Motivation + +Reading a member before it's ever been given a value is undefined behaviour, whether or not the class +manages any resource - the member's contents are simply whatever bytes happened to already be in that +memory. + +## How to fix + +Give every member a value, either via an in-class default initializer or in every constructor's +member-initializer list. + +Before: +```cpp +class Fred { +public: + Fred() {} // <- 'i' is never given a value + int i; +}; +``` + +After: +```cpp +class Fred { +public: + Fred() : i(0) {} + int i; +}; +``` + +Before: +```cpp +class C { +private: + int i1 = 0; + int i2; // <- no constructor, and 'i2' has no default like 'i1' does +}; +``` + +After: +```cpp +class C { +private: + int i1 = 0; + int i2 = 0; +}; +``` + +Before: +```cpp +class Base { +public: + virtual void foo() = 0; + int x; // <- left uninitialized by every class that derives from Base +}; +class Derived: public Base { +public: + Derived() {} + void foo() override; +}; +``` + +After: +```cpp +class Base { +public: + Base() : x(0) {} + virtual void foo() = 0; + int x; +}; +class Derived: public Base { +public: + Derived() {} + void foo() override; +}; +``` + +Before: +```cpp +class B { int i; }; // <- B's own (private) constructor doesn't init 'i' +class D : B { + explicit D(int) {} +}; +``` + +After: +```cpp +class B { + int i; +public: + B() : i(0) {} +}; +class D : B { + explicit D(int) {} +}; +``` + +Before: +```cpp +struct S { + int a = 0, b; // <- 'a' has a default, 'b' doesn't +}; +``` + +After: +```cpp +struct S { + int a = 0, b = 0; +}; +``` + +## Related checkers + +- [noConstructor.md](noConstructor.md) - the related check for when a class has no constructor at all. +- [missingMemberCopy.md](missingMemberCopy.md) - the analogous finding for a copy/move constructor that + simply forgets to copy one particular member, rather than never initializing it. diff --git a/man/checkers/uninitStructMember.md b/man/checkers/uninitStructMember.md new file mode 100644 index 00000000000..f705c1acede --- /dev/null +++ b/man/checkers/uninitStructMember.md @@ -0,0 +1,47 @@ +# uninitStructMember + +**Message**: Uninitialized struct member: x
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A specific struct/class member is read before that particular member has been set, even if other +members of the same variable have been. + +## Motivation + +Initializing some members of a struct doesn't initialize the rest - reading a member that was never +assigned is undefined behaviour, the same as reading an uninitialized plain variable, and easy to miss +when other, nearby members of the same variable were set correctly. + +## How to fix + +Before: +```cpp +#include +#include +struct ABC { int a; int b; }; +void f() { + struct ABC *abc = (struct ABC*)malloc(sizeof(struct ABC)); + printf("%d", abc->a); // <- uninitStructMember +} +``` + +After: +```cpp +#include +#include +struct ABC { int a; int b; }; +void f() { + struct ABC *abc = (struct ABC*)malloc(sizeof(struct ABC)); + abc->a = 0; + printf("%d", abc->a); +} +``` + +## Related checkers + +- [uninitvar.md](uninitvar.md) - the general "read before assignment" check this one specializes for + one particular struct member. diff --git a/man/checkers/uninitdata.md b/man/checkers/uninitdata.md new file mode 100644 index 00000000000..66e0b74ecd0 --- /dev/null +++ b/man/checkers/uninitdata.md @@ -0,0 +1,48 @@ +# uninitdata + +**Message**: Memory is allocated but not initialized: x
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +Like [uninitvar.md](uninitvar.md), but specifically for memory obtained from an allocation function +(`malloc`, etc.) - the block exists, but its contents haven't been written yet. + +## Motivation + +Memory returned by `malloc()` and similar functions is not zero-initialized - it holds whatever bytes +happened to already be there. Reading through it before writing to it is undefined behaviour, just like +reading an uninitialized plain variable, but easier to overlook since the allocation itself looks like +it "created" the value. + +## How to fix + +Before: +```cpp +#include +#include +struct ABC { int a; int b; }; +void f() { + struct ABC *abc = (struct ABC*)malloc(sizeof(struct ABC)); + printf("%d", abc->a); // <- uninitdata +} +``` + +After: +```cpp +#include +#include +struct ABC { int a; int b; }; +void f() { + struct ABC *abc = (struct ABC*)malloc(sizeof(struct ABC)); + abc->a = 0; + printf("%d", abc->a); +} +``` + +## Related checkers + +- [uninitvar.md](uninitvar.md) - the general "read before assignment" check this one specializes for + allocated memory. diff --git a/man/checkers/uninitvar.md b/man/checkers/uninitvar.md new file mode 100644 index 00000000000..507c368a47c --- /dev/null +++ b/man/checkers/uninitvar.md @@ -0,0 +1,82 @@ +# uninitvar and legacyUninitvar + +**Message**: Uninitialized variable: x
+**Category**: Undefined Behaviour
+**Severity**: Error/Warning
+**Language**: C/C++ + +## Description + +A local variable, pointer, or struct member is read (or its uninitialized value is passed along) +before any assignment reaches that point: + +- `uninitvar`: the main check. +- `legacyUninitvar`: an older, separate analysis that catches some patterns the main `uninitvar` check + doesn't (and vice versa) - both run, so the same kind of bug can be reported under either ID depending + on the exact code shape. + +## Motivation + +Reading a variable before it has been given a value is undefined behaviour in C/C++: the variable's +contents are whatever bytes happened to already be in that memory, which is unpredictable and can +differ between runs, builds, or optimization levels. This is one of the most common sources of subtle, +hard-to-reproduce bugs. + +## How to fix + +Before: +```cpp +#include +void f() { + int x; + printf("%d", x); // <- uninitvar +} +``` + +After: +```cpp +#include +void f() { + int x = 0; + printf("%d", x); +} +``` + +## False positives to be aware of + +- **Passing a member-access expression as an argument to an unresolved, all-uppercase macro-like call** + can be wrongly flagged, since cppcheck can't tell whether the macro actually evaluates its argument + at runtime (many such macros, like size- or offset-computing ones, don't): + ```cpp + void f() { + struct SData * s; + TYPEOF(s->status); // flagged as reading uninitialized 's', even if TYPEOF never evaluates it + } + ``` +- **A cast applied to `&variable` can be misread as reading the variable itself**, when it's actually + ambiguous whether `&` means "address of" (which never requires the variable to hold a value) or a + bitwise AND: + ```cpp + int main() { + int done; + dostuff(1, (AuPointer) &done); // flagged, even though '&done' alone doesn't read 'done' + } + ``` +- **Casting a variable of a type cppcheck doesn't recognize to a pointer type can be wrongly treated as + reading it**, particularly in C code using an opaque/typedef'd type: + ```c + void f() { + DES_cblock d; // unknown type + char *dp; + dp = (char *)d; // flagged as reading uninitialized 'd', even though the type is opaque to cppcheck + } + ``` + +## Related checkers + +- [uninitdata.md](uninitdata.md) - the same idea, for memory obtained from an allocation function + rather than a plain variable. +- [uninitStructMember.md](uninitStructMember.md) - the same idea, narrowed to one specific struct + member. +- [ctuuninitvar.md](ctuuninitvar.md) - the same idea, found by whole-program analysis across function + calls. diff --git a/man/checkers/unknownEvaluationOrder.md b/man/checkers/unknownEvaluationOrder.md new file mode 100644 index 00000000000..20684d90228 --- /dev/null +++ b/man/checkers/unknownEvaluationOrder.md @@ -0,0 +1,49 @@ +# unknownEvaluationOrder + +**Message**: Expression 'x = x++;' depends on order of evaluation of side effects
+**Category**: Undefined Behaviour/Portability
+**Severity**: Error/Portability
+**Language**: C/C++ + +## Description + +An expression reads and modifies the same variable more than once with no defined order between the +two (for example `x = x++;` or `x++, x++`). This is reported at two different severities because the +language rules genuinely differ between the two shapes: + +- **Error** (for example `x = x++;`): the read and the modification aren't just unordered, they actively + conflict - this is undefined behaviour in both C and C++. +- **Portability**: the specific expression shape is one where C++17 tightened the sequencing rules + enough that the order is merely *unspecified* (one of a few valid outcomes, not anything-goes) - not + undefined behaviour under C++17, but still worth flagging since older compilers/standards, or C, may + treat the same code as undefined instead. + +## Motivation + +When the same variable is both read and modified more than once in one expression with no sequencing +between the two, different compilers (or the same compiler at different optimization levels) can +legally produce different results. For the `Error` shape this is undefined behaviour outright; for the +`Portability` shape, C++17 guarantees the result is at least one of a small set of valid outcomes, but +which one is still compiler-dependent, so the code isn't portable even though it's not undefined. + +## How to fix + +Before: +```cpp +int dostuff(); +void f() { + int x = dostuff(); + return x + x++; // <- depends on evaluation order +} +``` + +After: +```cpp +int dostuff(); +int f() { + int x = dostuff(); + int y = x++; + return x + y; +} +``` + diff --git a/man/checkers/unknownMacro.md b/man/checkers/unknownMacro.md index 52599215d89..e0b352eab63 100644 --- a/man/checkers/unknownMacro.md +++ b/man/checkers/unknownMacro.md @@ -1,8 +1,6 @@ - # unknownMacro -**Message**: There is an unknown macro here somewhere. Configuration is required. If AAA is a macro then please configure it. [unknownMacro] -
+**Message**: There is an unknown macro here somewhere. Configuration is required. If AAA is a macro then please configure it.
**Category**: Configuration
**Severity**: Error
**Language**: C and C++ diff --git a/man/checkers/unknownPattern.md b/man/checkers/unknownPattern.md new file mode 100644 index 00000000000..f8748e03fb2 --- /dev/null +++ b/man/checkers/unknownPattern.md @@ -0,0 +1,29 @@ +# unknownPattern + +**Message**: Unknown pattern used: "%typex%"
+**Category**: Code Quality
+**Severity**: Error
+**Language**: C++ (cppcheck's own source code only) + +## Description + +A `%something%` placeholder isn't one of the pattern language's recognized names. + +This is not a general-purpose checker for arbitrary C/C++ programs - see +[simplePatternError.md](simplePatternError.md) for the shared background on this internal, +cppcheck-source-only checker. + +## Motivation + +An unrecognized placeholder name is almost always a typo for a real one (`%type%` misspelled as +`%typex%`, for example) - since the pattern engine doesn't know what it means, the match silently fails +to do what was intended. + +## How to fix + +Correct the placeholder name to one the pattern language actually recognizes (`%type%`, `%var%`, +`%num%`, ...). + +## Related checkers + +- [missingPercentCharacter.md](missingPercentCharacter.md) - a placeholder that's missing its closing `%` entirely, rather than misspelled. diff --git a/man/checkers/unpreciseMathCall.md b/man/checkers/unpreciseMathCall.md new file mode 100644 index 00000000000..daf657114bd --- /dev/null +++ b/man/checkers/unpreciseMathCall.md @@ -0,0 +1,40 @@ +# unpreciseMathCall + +**Message**: Expression 'exp(x) - 1' can be replaced by 'expm1(x)' to avoid loss of precision.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +An expression like `exp(x) - 1` or `log(1 + x)` should be written as `expm1(x)`/`log1p(x)` to avoid +losing precision for small `x`. + +## Motivation + +For small `x`, `exp(x)` is very close to `1`, so `exp(x) - 1` subtracts two nearly-equal +floating-point numbers - a classic way to lose most of the significant digits of the result. +`expm1()`/`log1p()` are designed to compute the same mathematical result without this cancellation. + +## How to fix + +Before: +```cpp +#include +void f() { + print(exp(3.5) - 1); // <- loses precision for small arguments +} +``` + +After: +```cpp +#include +void f() { + print(expm1(3.5)); +} +``` + +## Related checkers + +- [wrongmathcall.md](wrongmathcall.md) - a related but distinct math-function issue: passing a literal + value outside a function's valid domain. diff --git a/man/checkers/unreachableCode.md b/man/checkers/unreachableCode.md new file mode 100644 index 00000000000..4ed841196fb --- /dev/null +++ b/man/checkers/unreachableCode.md @@ -0,0 +1,42 @@ +# unreachableCode + +**Message**: Statements following return, break, continue, goto or throw will never be executed.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +Code appears right after a `return`/`break`/`continue`/`goto`/`throw`, or after a call to a function +that never returns - none of it can ever execute. + +## Motivation + +This is dead code by definition: control leaves the enclosing block before reaching it, so it can be +deleted with no change in behavior. It's also worth double-checking that the surrounding logic is +actually correct, since unreachable code sometimes reveals a misplaced statement. + +## How to fix + +Before: +```cpp +void bar(); +void foo() { + return; + bar(); // <- can never run +} +``` + +After: +```cpp +void bar(); +void foo() { + bar(); + return; +} +``` + +## Related checkers + +- [duplicateBreak.md](duplicateBreak.md) - the specific case where the unreachable statement is itself another `return`/`break`/`continue`/`goto`/`throw`. +- [unreachableSwitchCase.md](unreachableSwitchCase.md) - a `switch` `case` that can never be selected, rather than code placed after an exit statement. diff --git a/man/checkers/unreachableSwitchCase.md b/man/checkers/unreachableSwitchCase.md new file mode 100644 index 00000000000..1a1f61a53b0 --- /dev/null +++ b/man/checkers/unreachableSwitchCase.md @@ -0,0 +1,51 @@ +# unreachableSwitchCase + +**Message**: Switch case 'x' can never be selected because the switch condition is known to be 'y'.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A `switch` is given a value cppcheck already knows, and a `case` label in it can never match that +value. + +## Motivation + +A `case` that can never be reached is dead code, and can indicate a mistake nearby - for example, that +the `switch`'s value was meant to still be variable at this point but has already been narrowed down by +an earlier check. + +## How to fix + +Before: +```cpp +enum T { A, B }; +void f(const T &t) { + if (t == A) { + switch (t) { + case A: + break; + case B: // <- 't' is known to be A here + break; + } + } +} +``` + +After: +```cpp +enum T { A, B }; +void f(const T &t) { + switch (t) { + case A: + break; + case B: + break; + } +} +``` + +## Related checkers + +- [unreachableCode.md](unreachableCode.md) - code placed after an exit statement, rather than a `case` that can't be selected. diff --git a/man/checkers/unreadVariable.md b/man/checkers/unreadVariable.md new file mode 100644 index 00000000000..b90d870e3ad --- /dev/null +++ b/man/checkers/unreadVariable.md @@ -0,0 +1,55 @@ +# unreadVariable + +**Message**: Variable 'x' is assigned a value that is never used.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A variable is assigned a value (or modified), but that value is never read afterwards - so the +assignment has no effect. + +## Motivation + +An assignment whose value is never read is pointless code that adds noise for readers, and is often a +sign of a bug: a forgotten follow-up use of the variable, a typo'd variable name on the next line, or +dead code left over from a refactor. + +## How to fix + +Remove the pointless assignment, or actually read the value afterwards. + +Before: +```cpp +void f() { + int x = 5; // <- never read afterwards +} +``` + +After: +```cpp +void f() { + int x = 5; + print(x); +} +``` + +## False positives to be aware of + +- **An RAII guard object bound through `auto&&` can be wrongly reported as an unused assignment.** An + object whose entire purpose is its constructor/destructor side effect (for example a + `std::lock_guard`) has no "value" to read back, but is not just an unused assignment either: + ```cpp + #include + void f(std::mutex& mutex) { + auto&& g = std::lock_guard{ mutex }; // reported as unread, but 'g' locks/unlocks the mutex + } + ``` + +## Related checkers + +- [unusedVariable.md](unusedVariable.md) - the sibling finding for a variable that's never read *or* + written at all. +- [unassignedVariable.md](unassignedVariable.md) - the opposite situation: a variable read before it's + ever assigned. diff --git a/man/checkers/unsafeClassCanLeak.md b/man/checkers/unsafeClassCanLeak.md new file mode 100644 index 00000000000..4b3cd53823b --- /dev/null +++ b/man/checkers/unsafeClassCanLeak.md @@ -0,0 +1,41 @@ +# unsafeClassCanLeak + +**Message**: Class 'A' is unsafe, 'A::b' can leak by wrong usage.
+**Category**: Correctness
+**Severity**: Style
+**Language**: C++ + +## Description + +A class allocates a member (typically in its constructor) but has no visible destructor logic that +frees it - so every place that creates one of these objects has to remember to clean it up manually, +which is easy to get wrong. + +## Motivation + +A class that owns an allocation but doesn't free it in its own destructor pushes the cleanup +responsibility out onto every single place that uses the class - if even one of those places forgets, or +an exception skips the manual cleanup, the allocation leaks. Freeing what the class owns, inside its own +destructor, makes the class safe to use without every caller having to think about it. + +## How to fix + +Before: +```cpp +class A { + int *b; +public: + A() { b = new int; } // <- no destructor frees 'b' +}; +``` + +After: +```cpp +class A { + int *b; +public: + A() { b = new int; } + ~A() { delete b; } +}; +``` + diff --git a/man/checkers/unsafeClassRefMember.md b/man/checkers/unsafeClassRefMember.md new file mode 100644 index 00000000000..2c1381d993a --- /dev/null +++ b/man/checkers/unsafeClassRefMember.md @@ -0,0 +1,31 @@ +# unsafeClassRefMember + +**Message**: Storing reference to member argument in the class member is unsafe.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ + +## Description + +A `const&` member is initialized directly from a `const&` constructor argument - if a caller ever +passes a temporary or a short-lived local variable there, the member ends up referring to something +that's already been destroyed. + +This check only runs when analysis is configured through a Cppcheck GUI/project file that turns on +"safe checks" for classes (``) - there is no plain +command-line flag for it, so it will not appear in an ordinary `--enable=...` run. + +## Motivation + +Storing a reference member straight from a constructor's reference parameter looks harmless, but it +makes the object's validity depend entirely on the lifetime of whatever the caller happened to pass in - +if that argument was a temporary, the member becomes a dangling reference the moment the constructor +call's statement finishes, and every later use of the member is undefined behaviour. This check fires on +the constructor's signature alone - it doesn't (and can't, from the class definition by itself) know what +every caller actually passes in, so a class that's only ever constructed with a long-lived object is +flagged just the same as one that's handed a temporary. + +## How to fix + +Store a copy of the value instead of a reference to it, unless the class's contract explicitly requires +the referred-to object to outlive it. diff --git a/man/checkers/unsignedLessThanZero.md b/man/checkers/unsignedLessThanZero.md new file mode 100644 index 00000000000..26a93cc129c --- /dev/null +++ b/man/checkers/unsignedLessThanZero.md @@ -0,0 +1,52 @@ +# unsignedLessThanZero and unsignedPositive + +**Message**: Checking if unsigned expression 'x' is less than zero.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +An unsigned expression is compared against `0` in a way that can never be true (`unsignedLessThanZero`: +`x < 0`) or is always true (`unsignedPositive`: `x >= 0`), since an unsigned value can't be negative. + +## Motivation + +Both comparisons are tautological for an unsigned type, so the branch they guard either never runs or +always runs - not what the code visibly appears to be testing, and a common leftover from a variable +that used to be signed. + +## How to fix + +Before: +```cpp +void foo(unsigned int x) { + if (x < 0) {} // <- can never be true +} +``` + +After: +```cpp +void foo(int x) { + if (x < 0) {} +} +``` + +Before: +```cpp +void foo() { + for(unsigned char i = 10; i >= 0; i--) {} // <- always true, infinite loop risk +} +``` + +After: +```cpp +void foo() { + for(int i = 10; i >= 0; i--) {} +} +``` + +## Related checkers + +- [pointerLessThanZero.md](pointerLessThanZero.md) - the same idea, for a pointer compared against `0` + instead of an unsigned integer. diff --git a/man/checkers/unusedAllocatedMemory.md b/man/checkers/unusedAllocatedMemory.md new file mode 100644 index 00000000000..5f1d053694d --- /dev/null +++ b/man/checkers/unusedAllocatedMemory.md @@ -0,0 +1,42 @@ +# unusedAllocatedMemory + +**Message**: Variable 'p' is allocated memory that is never used.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A variable is assigned the result of an allocation (`malloc`, `new`, ...), but the allocated memory +itself is never read or written - only, at most, freed again. + +## Motivation + +Allocating memory that's never actually used is pointless, and usually a sign that some code that was +meant to fill or use the buffer was never written, or was removed during a refactor without noticing the +allocation. + +## How to fix + +Actually use the allocated memory, or remove the allocation. + +Before: +```cpp +void f() { + char* p = (char*)malloc(10); // <- allocated but never touched +} +``` + +After: +```cpp +void f() { + char* p = (char*)malloc(10); + p[0] = 'x'; + free(p); +} +``` + +## Related checkers + +- [unusedVariable.md](unusedVariable.md) - the general form of this finding, for any variable that's + never read or written. diff --git a/man/checkers/unusedFunction.md b/man/checkers/unusedFunction.md new file mode 100644 index 00000000000..0b878137dd0 --- /dev/null +++ b/man/checkers/unusedFunction.md @@ -0,0 +1,75 @@ +# unusedFunction and staticFunction + +**Message**: The function 'foo' is never used.
+**Category**: Unused Code
+**Severity**: Style
+**Language**: C/C++ + +## Description + +- `unusedFunction`: a function is defined but never called (and its address is never taken) anywhere + in the code cppcheck was given. +- `staticFunction` (C only): a function is only ever called from within its own file, and could be + given internal linkage by declaring it `static`. + +Both are only enabled through `--enable=unusedFunction` - not through `--enable=style`, even though +the messages are reported at `style` severity - and cppcheck explicitly recommends only enabling this +check when the whole program (every source file of the project) is being analyzed together, not a +single file in isolation. + +Both only consider a function "used" through a direct, by-name call (or its address being taken) +somewhere in that whole-program analysis. A virtual function is never checked, since it may only ever +be reached polymorphically through a base-class pointer/reference, which cppcheck doesn't attempt to +trace; operator overloads are likewise never checked, since they're often invoked implicitly through +operator syntax or required by generic code, making usage hard to establish reliably by name alone. + +## Motivation + +An unused function is dead code: it adds to what has to be read and maintained without doing +anything. For `staticFunction`, giving an internal-only function `static` linkage documents that it +is not part of the file's public interface, and can also help the compiler optimize it. + +## How to fix + +Remove a function that's genuinely unused, or make sure the code that calls it is included in the +same whole-program analysis. Add `static` to a C function that's only called from its own file. + +Before: +```cpp +void helper() { /* ... */ } // <- unusedFunction: never called anywhere in the analyzed code + +int main() { + return 0; +} +``` + +After: +```cpp +void helper() { /* ... */ } + +int main() { + helper(); + return 0; +} +``` + +Before (C): +```c +void helper(void) { /* ... */ } // <- staticFunction: only called from this file + +int main(void) { + helper(); + return 0; +} +``` + +After: +```c +static void helper(void) { /* ... */ } + +int main(void) { + helper(); + return 0; +} +``` + diff --git a/man/checkers/unusedLabel.md b/man/checkers/unusedLabel.md new file mode 100644 index 00000000000..3452ee3f0b4 --- /dev/null +++ b/man/checkers/unusedLabel.md @@ -0,0 +1,94 @@ +# unusedLabel, unusedLabelSwitch, unusedLabelConfiguration and unusedLabelSwitchConfiguration + +**Message**: Label 'x' is not used.
+**Category**: Code Quality
+**Severity**: Style/Warning
+**Language**: C/C++ + +## Description + +A `goto` label is declared but no `goto` anywhere in the file jumps to it. The four IDs are the same +finding, refined by context: + +- `unusedLabel`: the plain case - style severity. +- `unusedLabelSwitch`: the label sits directly where a `case` inside a `switch` was probably meant - + warning severity, since this is a more likely sign of an actual typo. +- `unusedLabelConfiguration` / `unusedLabelSwitchConfiguration`: the same two situations, but the file + also contains `#if`/`#ifdef`, so the missing `goto` might simply be in a preprocessor branch that + wasn't analyzed this time. + +## Motivation + +An unused label is either dead code left over from a refactor, or - especially when it appears right +where a `case` would fit inside a `switch` - a typo for a different keyword entirely. Either way it's +worth a second look: removing genuinely dead labels keeps the code honest about what it does, and +catching a `case`/label typo can fix a real logic bug. + +## How to fix + +Either remove the label, or actually jump to it - or, if it was meant to be a `case`, fix the typo. + +Before: +```cpp +void f() { + label: // <- nothing 'goto's here +} +``` + +After: either remove the label, or actually jump to it. +```cpp +void f() { + label: + ; + goto label; +} +``` + +Before: +```cpp +int test(char art) { + switch (art) { + caseZERO: // <- looks like a typo for 'case 0:' + return 0; + case 2: + return 2; + } + return -1; +} +``` + +After: +```cpp +int test(char art) { + switch (art) { + case 0: + return 0; + case 2: + return 2; + } + return -1; +} +``` + +Before: +```cpp +void f() { +#ifdef X + goto END; +#endif +END: // <- only reachable through code hidden behind '#ifdef X' + ; +} +``` + +After: keep the label's only `goto` in the same preprocessor branch as the label, or remove the label if +it's genuinely unused. +```cpp +void f() { +#ifdef X + goto END; +END: +#endif + ; +} +``` diff --git a/man/checkers/unusedPrivateFunction.md b/man/checkers/unusedPrivateFunction.md new file mode 100644 index 00000000000..e4809534a2c --- /dev/null +++ b/man/checkers/unusedPrivateFunction.md @@ -0,0 +1,42 @@ +# unusedPrivateFunction + +**Message**: Unused private function: 'Fred::f'
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +A private member function is never called from anywhere in the class (or a friend of it). + +## Motivation + +Since a private function can only ever be called from inside its own class (or a friend), one that's +never called anywhere in the visible code is dead code - it can be removed without affecting any +caller outside the class. + +## How to fix + +Before: +```cpp +class Fred { +private: + unsigned int f(); // <- unusedPrivateFunction: never called +public: + Fred(); +}; +Fred::Fred() { } +unsigned int Fred::f() { return 1; } +``` + +After: +```cpp +class Fred { +private: + unsigned int f(); +public: + Fred(); +}; +Fred::Fred() { f(); } +unsigned int Fred::f() { return 1; } +``` diff --git a/man/checkers/unusedScopedObject.md b/man/checkers/unusedScopedObject.md new file mode 100644 index 00000000000..6a29c74e621 --- /dev/null +++ b/man/checkers/unusedScopedObject.md @@ -0,0 +1,52 @@ +# unusedScopedObject + +**Message**: Instance of 'x' object is destroyed immediately.
+**Category**: Correctness
+**Severity**: Style
+**Language**: C++ + +## Description + +An object is constructed as a temporary (`Lock(mutex);` instead of `Lock lock(mutex);`) and destroyed +again immediately at the end of the same statement - if the class's constructor/destructor pair matters +for its side effects (as with a scope guard), this defeats the purpose, since the "lock" is released +before the very next statement runs. + +## Motivation + +RAII types like lock guards, scoped timers, or transaction guards rely on their destructor running at +the end of a *scope*, not at the end of the single statement that constructed them. Forgetting to give +the object a name accidentally destroys it right away, silently disabling whatever protection it was +supposed to provide for the following code. + +## How to fix + +Before: +```cpp +#include +class Lock { +public: + Lock(int i) { std::cout << "Lock " << i << std::endl; } + ~Lock() { std::cout << "~Lock" << std::endl; } +}; +int main() { + Lock(123); // <- constructed and destroyed on this line alone + std::cout << "hello" << std::endl; + return 0; +} +``` + +After: +```cpp +#include +class Lock { +public: + explicit Lock(int i) { std::cout << "Lock " << i << std::endl; } + ~Lock() { std::cout << "~Lock" << std::endl; } +}; +int main() { + Lock lock(123); + std::cout << "hello" << std::endl; + return 0; +} +``` diff --git a/man/checkers/unusedStructMember.md b/man/checkers/unusedStructMember.md new file mode 100644 index 00000000000..5c08ef7b65f --- /dev/null +++ b/man/checkers/unusedStructMember.md @@ -0,0 +1,45 @@ +# unusedStructMember + +**Message**: struct member 'Point::unusedField' is never used.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A struct/class/union member is never referenced anywhere in the file. + +This checker performs a single-file analysis, it checks structs declared in the source file. This +checker does not work properly if you analyze a header file directly. You are not recommended to +run cppcheck on header files directly. + +## Motivation + +An unused struct member usually means the field can be removed, or - just as often - that it *should* +be used somewhere and isn't, which is worth a second look. + +## How to fix + +Remove the field, or actually use it. + +Before: +```cpp +struct Point { + int x; + int y; + int unusedField; // <- never referenced anywhere +}; +``` + +After: +```cpp +struct Point { + int x; + int y; +}; +``` + +## Related checkers + +- [unusedVariable.md](unusedVariable.md) - the same idea, for a local variable instead of a struct + member. diff --git a/man/checkers/unusedVariable.md b/man/checkers/unusedVariable.md new file mode 100644 index 00000000000..858875ecca7 --- /dev/null +++ b/man/checkers/unusedVariable.md @@ -0,0 +1,46 @@ +# unusedVariable + +**Message**: Unused variable: x
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A variable is declared but never read or written at all. + +## Motivation + +A variable that's never touched after being declared is dead code: it adds to what has to be read and +maintained without doing anything, and is often a sign of a bug - a typo'd variable name, or code left +over from a refactor that no longer does what it used to. + +## How to fix + +Remove the variable, or actually use it. + +Before: +```cpp +void f() { + int x = 5; // <- declared and never read or written again +} +``` + +After: +```cpp +void f() { + int x = 5; + print(x); +} +``` + +## Related checkers + +- [unreadVariable.md](unreadVariable.md) - the sibling finding for a variable that *is* assigned a + value, but that value is never read afterwards. +- [unusedAllocatedMemory.md](unusedAllocatedMemory.md) - the same idea, specifically for memory obtained + from an allocation function. +- [unassignedVariable.md](unassignedVariable.md) - the opposite situation: a variable read before it's + ever assigned. +- [unusedStructMember.md](unusedStructMember.md) - the same idea, for a struct/class/union member + instead of a local variable. diff --git a/man/checkers/useClosedFile.md b/man/checkers/useClosedFile.md new file mode 100644 index 00000000000..aa01de27a37 --- /dev/null +++ b/man/checkers/useClosedFile.md @@ -0,0 +1,48 @@ +# useClosedFile + +**Message**: Used file that is not opened.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A read, write, or positioning call is made on a `FILE*` after it's already been closed. + +## Motivation + +Using a file handle after `fclose()` is undefined behaviour - the underlying resource is gone, so any +further operation on it can't be relied on to do anything sensible. + +## How to fix + +Before: +```cpp +#include +void f() { + FILE *f1 = fopen("a.txt", "r"); + if (!f1) return; + fclose(f1); + char buf[1]; + fread(buf, 1, 1, f1); // <- 'f1' is already closed +} +``` + +After: +```cpp +#include +void f() { + FILE *f1 = fopen("a.txt", "r"); + if (!f1) return; + char buf[1]; + fread(buf, 1, 1, f1); + fclose(f1); +} +``` + +## Related checkers + +- [readWriteOnlyFile.md](readWriteOnlyFile.md), [writeReadOnlyFile.md](writeReadOnlyFile.md), + [IOWithoutPositioning.md](IOWithoutPositioning.md), [seekOnAppendedFile.md](seekOnAppendedFile.md), + [incompatibleFileOpen.md](incompatibleFileOpen.md) - other checks that follow the same `FILE*` through + a function to catch a different kind of open-mode/state mismatch. diff --git a/man/checkers/useInitializationList.md b/man/checkers/useInitializationList.md new file mode 100644 index 00000000000..94975d622e0 --- /dev/null +++ b/man/checkers/useInitializationList.md @@ -0,0 +1,49 @@ +# useInitializationList + +**Message**: Variable 's' is assigned in constructor body. Consider performing initialization in initialization list.
+**Category**: Performance
+**Severity**: Performance
+**Language**: C++ + +## Description + +A constructor assigns a member a value in its body, where the exact same value could have been passed +via the member-initializer list instead - doing it in the list avoids first default-constructing the +member and then immediately overwriting it. + +## Motivation + +Assigning a member in the constructor body means the member is first default-constructed and then +immediately reassigned - for a type with a nontrivial default constructor (like `std::string`), that's +extra, avoidable work done on every single object construction. + +## How to fix + +Move the assignment into the member-initializer list. + +Before: +```cpp +#include +class C { + std::string s; +public: + explicit C(const std::string& str) { + s = str; // <- could be done in the initializer list instead + } +}; +``` + +After: +```cpp +#include +class C { + std::string s; +public: + explicit C(const std::string& str) : s(str) {} +}; +``` + +## Related checkers + +- [initializerList.md](initializerList.md) - a different constructor-initializer-list pitfall, about + the *order* members are listed in, rather than whether the list is used at all. diff --git a/man/checkers/useStandardLibrary.md b/man/checkers/useStandardLibrary.md new file mode 100644 index 00000000000..6eac1d7aa66 --- /dev/null +++ b/man/checkers/useStandardLibrary.md @@ -0,0 +1,41 @@ +# useStandardLibrary + +**Message**: Consider using std::memcpy instead of loop.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ only + +## Description + +A hand-written byte-by-byte copy or fill loop that could be replaced with `memcpy`/`memset`. + +## Motivation + +A standard library function like `memcpy()` is at least as fast as a hand-written loop (the library or +compiler can vectorize/optimize it directly), is shorter to read, and doesn't need to be double-checked +for off-by-one mistakes the way a raw loop does. + +## How to fix + +Before: +```cpp +#include +void f(void* dst, const void* src, const size_t count) { + size_t i; + for (i = 0; count > i; ++i) // <- hand-written copy loop + (reinterpret_cast(dst))[i] = (reinterpret_cast(src))[i]; +} +``` + +After: +```cpp +#include +void f(void* dst, const void* src, const size_t count) { + std::memcpy(dst, src, count); +} +``` + +## Related checkers + +- [returnStdMoveLocal.md](returnStdMoveLocal.md) - an unrelated performance suggestion in the same + checker: avoiding a `std::move()` that defeats copy elision. diff --git a/man/checkers/useStlAlgorithm.md b/man/checkers/useStlAlgorithm.md new file mode 100644 index 00000000000..c5fe6431be9 --- /dev/null +++ b/man/checkers/useStlAlgorithm.md @@ -0,0 +1,49 @@ +# useStlAlgorithm + +**Message**: Consider using std::any_of algorithm instead of a raw loop.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +A hand-written loop follows a shape (accumulate a value, count matches, find the first/last match, copy +matching elements, ...) that a single call to a standard `` function would do more clearly +and with less room for an off-by-one mistake. + +## Motivation + +A raw loop that reimplements a standard algorithm is more code to read and more room for a subtle bug +(an off-by-one, a missed edge case) than calling the algorithm that already exists for exactly this +purpose. Naming the intent directly (`std::any_of`, `std::count_if`, `std::find_if`, `std::copy_if`, +`std::transform`, ...) also tells a reader what the loop is *for* without having to trace through its +body. + +## How to fix + +Before: +```cpp +#include +bool f(bool b) { + std::vector v; + if (b) + v.push_back(0); + for (auto i : v) // <- consider std::any_of instead of this raw loop + if (v[i] > 0) + return true; + return false; +} +``` + +After: +```cpp +#include +#include +bool f(bool b) { + std::vector v; + if (b) + v.push_back(0); + return std::any_of(v.begin(), v.end(), [](int i){ return v[i] > 0; }); +} +``` + diff --git a/man/checkers/uselessAssignmentArg.md b/man/checkers/uselessAssignmentArg.md new file mode 100644 index 00000000000..e19d6f5be23 --- /dev/null +++ b/man/checkers/uselessAssignmentArg.md @@ -0,0 +1,38 @@ +# uselessAssignmentArg and uselessAssignmentPtrArg + +**Message**: Assignment of function parameter has no effect outside the function.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A function parameter (plain or pointer) is assigned a new value that is never used afterwards - since +parameters are passed by value in C/C++, this change is invisible to the caller and has no effect. + +- `uselessAssignmentArg`: the parameter is an ordinary by-value parameter. +- `uselessAssignmentPtrArg`: the parameter is a pointer itself being reassigned (not what it points to). + +## Motivation + +Assigning to a by-value parameter without reading it again afterwards looks like it's meant to +communicate something back to the caller, but it can't - parameters are local copies. This usually +means either the assignment is pointless leftover code, or the author actually needed a pointer/ +reference parameter (or a return value) to get the result back out. + +## How to fix + +Before: +```cpp +void foo(int b) { + b = 5; // <- uselessAssignmentArg: caller never sees this +} +``` + +After: change the return type instead of the parameter, or drop the assignment. +```cpp +int foo() { + return 5; +} +``` + diff --git a/man/checkers/uselessCallsCompare.md b/man/checkers/uselessCallsCompare.md new file mode 100644 index 00000000000..cf0bcd6677a --- /dev/null +++ b/man/checkers/uselessCallsCompare.md @@ -0,0 +1,43 @@ +# uselessCallsCompare + +**Message**: It is inefficient to call 'x.compare(x)' as it always returns 0.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ + +## Description + +`x.compare(x)` - calling `.compare()` with the object itself as the argument always returns `0`, which +looks like a meaningful check but never is - usually a copy-paste mistake where the wrong variable was +used for one side. + +## Motivation + +Comparing a value against itself is always true (or, for `.compare()`, always `0`) - the call is dead +code that adds nothing, and the fact that it was written at all suggests the intent was to compare two +different values, one of which was mistyped. + +## How to fix + +Before: +```cpp +#include +void f() { + std::string s1, s2; + s2.compare(s2); // <- always returns 0 +} +``` + +After: +```cpp +#include +void f() { + std::string s1, s2; + s1.compare(s2); +} +``` + +## Related checkers + +- [uselessCallsSwap.md](uselessCallsSwap.md) - the same self-argument mistake, for `.swap()` instead of + `.compare()`. diff --git a/man/checkers/uselessCallsConstructor.md b/man/checkers/uselessCallsConstructor.md new file mode 100644 index 00000000000..8643abf433c --- /dev/null +++ b/man/checkers/uselessCallsConstructor.md @@ -0,0 +1,38 @@ +# uselessCallsConstructor + +**Message**: Inefficient constructor call: container 'x' is assigned a partial copy of itself. Use erase() or resize() instead.
+**Category**: Performance
+**Severity**: Performance
+**Language**: C++ + +## Description + +A container is assigned a range constructed from its own begin/(begin+offset) - this makes an +unnecessary temporary copy of part of the container's own data, when `erase()`/`resize()` would do the +same trim in place. + +## Motivation + +Building a whole new range-constructed container from a slice of a container's own elements, just to +assign it back over the original, copies data that's already sitting in the right place - an in-place +trim (`erase()`/`resize()`) achieves the same final content without the temporary copy. + +## How to fix + +Before: +```cpp +#include +std::string f(std::string s, std::size_t end) { + s = { s.begin(), s.begin() + end }; // <- an unnecessary partial self-copy + return s; +} +``` + +After: +```cpp +#include +std::string f(std::string s, std::size_t end) { + s.resize(end); + return s; +} +``` diff --git a/man/checkers/uselessCallsEmpty.md b/man/checkers/uselessCallsEmpty.md new file mode 100644 index 00000000000..50c7d9479b2 --- /dev/null +++ b/man/checkers/uselessCallsEmpty.md @@ -0,0 +1,36 @@ +# uselessCallsEmpty + +**Message**: Ineffective call of function 'empty()'. Did you intend to call 'clear()' instead?
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ + +## Description + +`.empty()` is called and its result is thrown away - since `.empty()` has no side effect, this almost +certainly should have been `.clear()`. + +## Motivation + +`.empty()` only reports whether a container has any elements; it never removes anything. Calling it and +discarding the result does nothing at all, which is a strong sign the intended call was `.clear()` +(which does have the effect the code was presumably trying to achieve). + +## How to fix + +Before: +```cpp +#include +bool foo(std::vector& v) { + v.empty(); // <- result discarded, has no effect + return v.empty(); +} +``` + +After: +```cpp +#include +bool foo(std::vector& v) { + return v.empty(); +} +``` diff --git a/man/checkers/uselessCallsRemove.md b/man/checkers/uselessCallsRemove.md new file mode 100644 index 00000000000..753c3bb2e99 --- /dev/null +++ b/man/checkers/uselessCallsRemove.md @@ -0,0 +1,41 @@ +# uselessCallsRemove + +**Message**: Return value of std::remove() ignored. Elements remain in container.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ + +## Description + +The return value of `std::remove()`/`std::remove_if()`/`std::unique()` is ignored - these algorithms +don't actually shrink the container, they only move the elements to keep to the front and return the +new logical end; without also calling the container's `erase()` with that return value, the "removed" +elements are still there. + +## Motivation + +`std::remove()`/`std::remove_if()`/`std::unique()` cannot resize a container themselves (they only see +a pair of iterators, not the container), so they can only rearrange elements and report where the "new +end" is. Ignoring that return value leaves the container at its original size, with the elements that +were supposed to be removed still physically present (just in an unspecified state) between the new +logical end and the true end - the classic "erase-remove idiom" exists precisely to complete this. + +## How to fix + +Before: +```cpp +#include +#include +void f(std::vector a, int val) { + std::remove(a.begin(), a.end(), val); // <- elements aren't actually removed +} +``` + +After: +```cpp +#include +#include +void f(std::vector a, int val) { + a.erase(std::remove(a.begin(), a.end(), val), a.end()); +} +``` diff --git a/man/checkers/uselessCallsSubstr.md b/man/checkers/uselessCallsSubstr.md new file mode 100644 index 00000000000..a5c0825a6dc --- /dev/null +++ b/man/checkers/uselessCallsSubstr.md @@ -0,0 +1,39 @@ +# uselessCallsSubstr + +**Message**: Ineffective call of function 'substr' because it returns a copy of the object. Use operator= instead.
+**Category**: Performance
+**Severity**: Performance
+**Language**: C++ + +## Description + +A `substr()` call whose arguments make it a no-op (returns an unmodified copy of the whole string), +always return an empty string, or (when assigned back to the same string) just a slower way to write +`resize()`/`pop_back()`/`replace()`. + +## Motivation + +`substr()` copies characters into a new string - when the requested range happens to be the whole +string, an empty range, or a prefix/suffix of the string it's being assigned back into, that copy is +pure overhead compared to the more direct operation (`operator=`, `resize()`, `pop_back()`, `replace()`) +that achieves the same result. + +## How to fix + +Before: +```cpp +#include +void f() { + std::string s1, s2; + s2 = s1.substr(); // <- substr() with no arguments just copies the whole string +} +``` + +After: +```cpp +#include +void f() { + std::string s1, s2; + s2 = s1; +} +``` diff --git a/man/checkers/uselessCallsSwap.md b/man/checkers/uselessCallsSwap.md new file mode 100644 index 00000000000..d34c5cb1a55 --- /dev/null +++ b/man/checkers/uselessCallsSwap.md @@ -0,0 +1,42 @@ +# uselessCallsSwap + +**Message**: It is inefficient to swap a object with itself by calling 'x.swap(x)'
+**Category**: Performance
+**Severity**: Performance
+**Language**: C++ + +## Description + +`x.swap(x)` - calling `.swap()` with the object itself as the argument is an inefficient no-op - +usually a copy-paste mistake where the wrong variable was used for one side. + +## Motivation + +Swapping a value with itself has no effect: the call still does the work of a swap (temporaries, +moves) for a result that's guaranteed to be identical to not calling it at all. The fact that it was +written at all suggests the intent was to swap with a different variable, one of which was mistyped. + +## How to fix + +Before: +```cpp +#include +void f() { + std::string s1, s2; + s2.swap(s2); // <- no effect, just wasted work +} +``` + +After: +```cpp +#include +void f() { + std::string s1, s2; + s1.swap(s2); +} +``` + +## Related checkers + +- [uselessCallsCompare.md](uselessCallsCompare.md) - the same self-argument mistake, for `.compare()` + instead of `.swap()`. diff --git a/man/checkers/uselessOverride.md b/man/checkers/uselessOverride.md new file mode 100644 index 00000000000..3907a5b01a9 --- /dev/null +++ b/man/checkers/uselessOverride.md @@ -0,0 +1,39 @@ +# uselessOverride + +**Message**: The function 'f' is an unnecessary overload; it is identical to the parent function
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +An overriding function's body is either functionally identical to the base version, or just calls the +base version and returns its result - the override adds a function call for no behavioural difference. + +## Motivation + +An override that does nothing differently from the base class is dead weight: it adds an indirection +and a place for the two versions to accidentally drift apart later, without changing what the program +does today. + +## How to fix + +Before: +```cpp +struct B { virtual int f() { return 5; } }; +struct D : B { + int f() override { return B::f(); } // <- uselessOverride: identical to the base version +}; +``` + +After: remove the pointless override entirely. +```cpp +struct B { virtual int f() { return 5; } }; +struct D : B { +}; +``` + +## Related checkers + +- [missingOverride.md](missingOverride.md) - for a function that overrides a base one without being + marked `override`, regardless of whether the override is useless. diff --git a/man/checkers/va_end_missing.md b/man/checkers/va_end_missing.md new file mode 100644 index 00000000000..80ac6e0ea26 --- /dev/null +++ b/man/checkers/va_end_missing.md @@ -0,0 +1,42 @@ +# va_end_missing + +**Message**: va_list 'args' was opened but not closed by va_end().
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A local `va_list` variable is opened with `va_start()`/`va_copy()`, but the function can finish without +it being closed by `va_end()`. + +## Motivation + +Every `va_list` opened with `va_start()`/`va_copy()` must be matched with exactly one `va_end()` call +before it goes out of scope. Leaving one open is undefined behaviour on some implementations, and can +also leak resources the implementation associated with iterating the variadic arguments. + +## How to fix + +Call `va_end()` on every path that leaves the function after `va_start()`. + +Before: +```cpp +void log(const char* fmt, ...) { + va_list args; + va_start(args, fmt); +} // <- va_end() never called +``` + +After: +```cpp +void log(const char* fmt, ...) { + va_list args; + va_start(args, fmt); + va_end(args); +} +``` + +## Related checkers + +- [va_start_wrongParameter.md](va_start_wrongParameter.md), [va_start_referencePassed.md](va_start_referencePassed.md), [va_list_usedBeforeStarted.md](va_list_usedBeforeStarted.md), [va_start_subsequentCalls.md](va_start_subsequentCalls.md) - other misuses of the same `va_list`/`va_start()`/`va_end()` facility. diff --git a/man/checkers/va_list_usedBeforeStarted.md b/man/checkers/va_list_usedBeforeStarted.md new file mode 100644 index 00000000000..7ddb4844cce --- /dev/null +++ b/man/checkers/va_list_usedBeforeStarted.md @@ -0,0 +1,46 @@ +# va_list_usedBeforeStarted + +**Message**: va_list 'args' used before va_start() was called.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A `va_list` variable is read - passed to `va_arg()`, `va_copy()` or `va_end()` - before `va_start()` +(or `va_copy()`) was called on it, or after it was already closed with `va_end()`. + +## Motivation + +A `va_list` only refers to a valid argument sequence between a matching `va_start()`/`va_copy()` and the +`va_end()` that closes it. Reading it outside that window - too early, or again after it's already been +closed - is undefined behaviour, since there's no argument sequence for it to actually point to. + +## How to fix + +Only read a `va_list` after it has been opened with `va_start()`/`va_copy()`, and before it is closed +with `va_end()`. + +Before: +```cpp +void log(const char* fmt, ...) { + va_list args; + int first = va_arg(args, int); // <- used before va_start() + va_start(args, fmt); + va_end(args); +} +``` + +After: +```cpp +void log(const char* fmt, ...) { + va_list args; + va_start(args, fmt); + int first = va_arg(args, int); + va_end(args); +} +``` + +## Related checkers + +- [va_start_wrongParameter.md](va_start_wrongParameter.md), [va_start_referencePassed.md](va_start_referencePassed.md), [va_start_subsequentCalls.md](va_start_subsequentCalls.md), [va_end_missing.md](va_end_missing.md) - other misuses of the same `va_list`/`va_start()`/`va_end()` facility. diff --git a/man/checkers/va_start_referencePassed.md b/man/checkers/va_start_referencePassed.md new file mode 100644 index 00000000000..f877bb76df4 --- /dev/null +++ b/man/checkers/va_start_referencePassed.md @@ -0,0 +1,25 @@ +# va_start_referencePassed + +**Message**: Using reference 'x' as parameter for va_start() results in undefined behaviour.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A reference parameter is passed as the second argument to `va_start()`. This is undefined behaviour. + +## Motivation + +`va_start()` needs the actual last named parameter as it's stored on the stack, not a reference to it - +a reference is a different object from what the calling convention expects at that point, so using one +here doesn't reliably locate the variadic arguments that follow. + +## How to fix + +Avoid declaring the last named parameter before `...` as a reference, or otherwise ensure `va_start()` +is given the real parameter rather than a reference to it. + +## Related checkers + +- [va_start_wrongParameter.md](va_start_wrongParameter.md), [va_list_usedBeforeStarted.md](va_list_usedBeforeStarted.md), [va_start_subsequentCalls.md](va_start_subsequentCalls.md), [va_end_missing.md](va_end_missing.md) - other misuses of the same `va_list`/`va_start()`/`va_end()` facility. diff --git a/man/checkers/va_start_subsequentCalls.md b/man/checkers/va_start_subsequentCalls.md new file mode 100644 index 00000000000..6cd8fb14dac --- /dev/null +++ b/man/checkers/va_start_subsequentCalls.md @@ -0,0 +1,25 @@ +# va_start_subsequentCalls + +**Message**: va_start() or va_copy() called subsequently on 'args' without va_end() in between.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +`va_start()` or `va_copy()` is called again on a `va_list` that is already open, without an intervening +`va_end()`. + +## Motivation + +Reopening a `va_list` that's still open, without closing it first, is undefined behaviour on many +implementations - the two calls to `va_start()`/`va_copy()` can leave the `va_list` in an inconsistent +state, and whatever `va_arg()` reads afterward is unreliable. + +## How to fix + +Call `va_end()` on a `va_list` before opening it again with `va_start()`/`va_copy()`. + +## Related checkers + +- [va_start_wrongParameter.md](va_start_wrongParameter.md), [va_start_referencePassed.md](va_start_referencePassed.md), [va_list_usedBeforeStarted.md](va_list_usedBeforeStarted.md), [va_end_missing.md](va_end_missing.md) - other misuses of the same `va_list`/`va_start()`/`va_end()` facility. diff --git a/man/checkers/va_start_wrongParameter.md b/man/checkers/va_start_wrongParameter.md new file mode 100644 index 00000000000..32ecd73ce4c --- /dev/null +++ b/man/checkers/va_start_wrongParameter.md @@ -0,0 +1,44 @@ +# va_start_wrongParameter + +**Message**: 'level' given to va_start() is not last named argument of the function. Did you intend to pass 'fmt'?
+**Category**: Undefined Behaviour
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +The parameter passed as the second argument to `va_start()` is not the function's actual last named +parameter (the one right before `...`). + +## Motivation + +`va_start()` needs the last named parameter to locate where the variadic arguments begin on the stack. +Passing any other parameter is undefined behaviour - `va_arg()` calls that follow can then read garbage +instead of the actual variadic arguments, and the exact symptom depends on the compiler and calling +convention. + +## How to fix + +Pass the function's true last named parameter to `va_start()`. + +Before: +```cpp +void log(const char* fmt, const char* tag, int level, ...) { + va_list args; + va_start(args, fmt); // <- 'level' is the actual last named parameter + va_end(args); +} +``` + +After: +```cpp +void log(const char* fmt, const char* tag, int level, ...) { + va_list args; + va_start(args, level); + va_end(args); +} +``` + +## Related checkers + +- [va_start_referencePassed.md](va_start_referencePassed.md), [va_list_usedBeforeStarted.md](va_list_usedBeforeStarted.md), [va_start_subsequentCalls.md](va_start_subsequentCalls.md), [va_end_missing.md](va_end_missing.md) - other misuses of the same `va_list`/`va_start()`/`va_end()` facility. diff --git a/man/checkers/varFuncNullUB.md b/man/checkers/varFuncNullUB.md new file mode 100644 index 00000000000..41694c22cb0 --- /dev/null +++ b/man/checkers/varFuncNullUB.md @@ -0,0 +1,37 @@ +# varFuncNullUB + +**Message**: Passing NULL after the last typed argument to a variadic function leads to undefined behaviour.
+**Category**: Undefined Behaviour
+**Severity**: Portability
+**Language**: C/C++ + +## Description + +`NULL` is passed as the last argument to a variadic function (`...`) - on platforms where `NULL` is +defined as a plain `0` rather than a pointer-sized constant, the function reading its arguments through +`va_arg()` may misinterpret it, since the two constants aren't guaranteed to be the same size. + +## Motivation + +`NULL`'s definition (`0`, `0L`, or `(void*)0`) is implementation-defined. On a platform where +`sizeof(int) != sizeof(void*)` and `NULL` expands to a plain integer `0`, a variadic function expecting +to read a pointer-sized sentinel through `va_arg()` reads the wrong number of bytes - which can crash or +silently read garbage for the following arguments. The bug is invisible on platforms where `NULL` +happens to be pointer-sized, so it can go unnoticed for a long time. + +## How to fix + +Cast the sentinel to the pointer type the function actually expects, instead of relying on `NULL`'s +platform-specific definition. + +Before: +```cpp +void a(...); +void b() { a(NULL); } // <- passing NULL as the last variadic argument +``` + +After: +```cpp +void a(...); +void b() { a((void*)0); } +``` diff --git a/man/checkers/variableScope.md b/man/checkers/variableScope.md new file mode 100644 index 00000000000..6a89c5e8748 --- /dev/null +++ b/man/checkers/variableScope.md @@ -0,0 +1,41 @@ +# variableScope + +**Message**: The scope of the variable 'x' can be reduced.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A variable is declared in an outer scope than where it's actually used - narrowing its declaration to +the innermost block that needs it makes the code easier to follow. + +## Motivation + +A variable declared too early forces a reader to track its lifetime across code that doesn't use it, +and makes it unclear at a glance which block actually depends on it. + +## How to fix + +Before: +```cpp +#include +void f(bool x) { + int i = 0; // <- 'i' is only used inside the 'if' below + if (x) { + i = 10; + printf("%d\n", i); + } +} +``` + +After: +```cpp +#include +void f(bool x) { + if (x) { + int i = 10; + printf("%d\n", i); + } +} +``` diff --git a/man/checkers/virtualCallInConstructor.md b/man/checkers/virtualCallInConstructor.md new file mode 100644 index 00000000000..8e8a2a55aa6 --- /dev/null +++ b/man/checkers/virtualCallInConstructor.md @@ -0,0 +1,74 @@ +# virtualCallInConstructor and pureVirtualCall + +**Message**: Call of pure virtual function 'pure' in constructor.
+**Category**: Correctness/Undefined Behaviour
+**Severity**: Warning/Error
+**Language**: C++ + +## Description + +A constructor or destructor calls a virtual function on `this`. During construction/destruction the +object's dynamic type is only ever the class currently running, so this never dispatches to a derived +override the way it looks like it should. + +- `virtualCallInConstructor`: the called function does have a body in the current class, so the call is + well-defined - it's just probably not calling what the author expected. +- `pureVirtualCall`: the called function has no implementation at all (pure virtual), so the call is + undefined behaviour outright. + +## Motivation + +Code that calls a virtual function from a constructor or destructor, expecting a derived class's +override to run, is a common misunderstanding of C++'s object-construction model. `pureVirtualCall` in +particular is a crash waiting to happen, since there is no function body to call at all at that point. + +## How to fix + +Before: +```cpp +class A { + virtual int f() { return 1; } +public: + A(); +}; +A::A() { + f(); // <- virtualCallInConstructor: dynamic binding doesn't apply here +} +``` + +After: +```cpp +class A { + virtual int f() { return 1; } + int init() { return 1; } +public: + A(); +}; +A::A() { + init(); +} +``` + +Before: +```cpp +class A { + virtual void pure() = 0; +public: + A(); +}; +A::A() { + pure(); // <- pureVirtualCall: 'pure' has no body to call +} +``` + +After: +```cpp +class A { + virtual void pure() = 0; +public: + A(); +}; +A::A() { +} +``` + diff --git a/man/checkers/virtualDestructor.md b/man/checkers/virtualDestructor.md new file mode 100644 index 00000000000..0990b45c78d --- /dev/null +++ b/man/checkers/virtualDestructor.md @@ -0,0 +1,54 @@ +# virtualDestructor + +**Message**: Class 'Base' which is inherited by class 'Derived' does not have a virtual destructor.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +A base class has virtual member functions (so it's clearly meant to be used polymorphically) but a +non-virtual destructor - deleting a derived object through a base-class pointer then only runs the base +class's destructor, leaking whatever the derived part owned. + +## Motivation + +This is outright undefined behaviour that can appear to "work" (the memory for the derived part is +still freed by the base's `delete`, even though its destructor never ran) until an unrelated change - +adding a derived class that owns a resource, for instance - makes it actually leak or corrupt memory. + +## How to fix + +Before: +```cpp +class Base { +public: + virtual void f() {} + ~Base() {} // <- virtualDestructor: not virtual +}; +class Derived : public Base { +public: + ~Derived() { (void)11; } +}; +void f() { + Base *base = new Derived; + delete base; +} +``` + +After: +```cpp +class Base { +public: + virtual void f() {} + virtual ~Base() {} +}; +class Derived : public Base { +public: + ~Derived() override { (void)11; } +}; +void f() { + Base *base = new Derived; + delete base; +} +``` diff --git a/man/checkers/writeReadOnlyFile.md b/man/checkers/writeReadOnlyFile.md new file mode 100644 index 00000000000..2a1c2c167bf --- /dev/null +++ b/man/checkers/writeReadOnlyFile.md @@ -0,0 +1,46 @@ +# writeReadOnlyFile + +**Message**: Write operation on a file that was opened only for reading.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +The file was opened in a mode (`"r"`) that only allows reading, and the code then writes to it. + +## Motivation + +Writing to a stream that was opened read-only is undefined behaviour in the C standard - even where an +implementation happens to do something predictable with it, code relying on that isn't portable. + +## How to fix + +Before: +```cpp +#include +void f() { + FILE *fp = fopen("a.txt", "r"); + if (!fp) return; + fwrite("x", 1, 1, fp); // <- 'fp' was only opened for reading + fclose(fp); +} +``` + +After: +```cpp +#include +void f() { + FILE *fp = fopen("a.txt", "w"); + if (!fp) return; + fwrite("x", 1, 1, fp); + fclose(fp); +} +``` + +## Related checkers + +- [readWriteOnlyFile.md](readWriteOnlyFile.md) - the opposite mismatch: reading from a write-only file. +- [useClosedFile.md](useClosedFile.md), [IOWithoutPositioning.md](IOWithoutPositioning.md), + [seekOnAppendedFile.md](seekOnAppendedFile.md), [incompatibleFileOpen.md](incompatibleFileOpen.md) - + other checks that follow the same `FILE*` through a function. diff --git a/man/checkers/wrongPrintfScanfArgNum.md b/man/checkers/wrongPrintfScanfArgNum.md new file mode 100644 index 00000000000..1455678b6b6 --- /dev/null +++ b/man/checkers/wrongPrintfScanfArgNum.md @@ -0,0 +1,40 @@ +# wrongPrintfScanfArgNum + +**Message**: printf format string requires 2 parameters but only 1 is given.
+**Category**: Undefined Behaviour
+**Severity**: Error/Warning
+**Language**: C/C++ + +## Description + +The format string's conversion specifiers (`%d`, `%s`, ...) don't match the number of arguments +actually given to a `printf`/`scanf`-family call - too many or too few. + +## Motivation + +`printf`/`scanf` format strings are not type- or count-checked by the C or C++ language itself - the +compiler trusts that whatever conversion specifiers you wrote match whatever arguments follow. Too few +arguments means a conversion reads garbage memory as if it were a real argument; too many just means +wasted arguments, but often signals a forgotten `%` specifier. + +## How to fix + +Before: +```cpp +#include +void f() { + printf("%d%s", 1); // <- format string needs 2 arguments, only 1 given +} +``` + +After: +```cpp +#include +void f() { + printf("%d%s", 1, "x"); +} +``` + +## Related checkers + +- [wrongPrintfScanfParameterPositionError.md](wrongPrintfScanfParameterPositionError.md) - a related mistake with POSIX positional specifiers (`%2$d`). diff --git a/man/checkers/wrongPrintfScanfParameterPositionError.md b/man/checkers/wrongPrintfScanfParameterPositionError.md new file mode 100644 index 00000000000..c3ea86c32b3 --- /dev/null +++ b/man/checkers/wrongPrintfScanfParameterPositionError.md @@ -0,0 +1,39 @@ +# wrongPrintfScanfParameterPositionError + +**Message**: printf: referencing parameter 4 while 3 arguments given.
+**Category**: Undefined Behaviour
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A POSIX positional specifier (`%2$d`) references an argument number that doesn't exist, or numbering +starts at `0` instead of `1`. + +## Motivation + +POSIX positional format specifiers let a format string reference its arguments out of order, but that +also means a typo in the position number silently reads the wrong argument, or - as checked here - one +that isn't even there. + +## How to fix + +Before: +```cpp +#include +void foo() { + printf("%1$d, %d, %4$d\n", 1, 2, 3); // <- no 4th argument +} +``` + +After: +```cpp +#include +void foo() { + printf("%1$d, %d, %3$d\n", 1, 2, 3); +} +``` + +## Related checkers + +- [wrongPrintfScanfArgNum.md](wrongPrintfScanfArgNum.md) - the plain (non-positional) argument-count mismatch. diff --git a/man/checkers/wrongmathcall.md b/man/checkers/wrongmathcall.md new file mode 100644 index 00000000000..7aff324d117 --- /dev/null +++ b/man/checkers/wrongmathcall.md @@ -0,0 +1,42 @@ +# wrongmathcall + +**Message**: Passing value -2 to log() leads to implementation-defined result.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A literal value outside a math function's valid domain is passed directly (for example a negative +number to `log()`, or `0` as the divisor to `fmod()`). This check only looks at a literal numeric value +written directly in the call - it does not evaluate variables or computed expressions, even when their +value is otherwise known to be out of range. + +## Motivation + +Math functions like `log()`, `sqrt()`, or `asin()` are only defined for part of the real numbers; calling +them with a value outside that domain is undefined or implementation-defined and typically produces +`NaN` or a platform-specific result rather than a clean error. + +## How to fix + +Before: +```cpp +#include +void f() { + double y = log(-2); // <- log() of a negative number +} +``` + +After: +```cpp +#include +void f(double x) { + double y = (x > 0) ? log(x) : 0.0; +} +``` + +## Related checkers + +- [unpreciseMathCall.md](unpreciseMathCall.md) - a related but distinct math-function issue: a + precision-losing way of writing a calculation that a more precise function already covers. diff --git a/man/checkers/zerodiv.md b/man/checkers/zerodiv.md new file mode 100644 index 00000000000..5585bf188ec --- /dev/null +++ b/man/checkers/zerodiv.md @@ -0,0 +1,60 @@ +# zerodiv and zerodivcond + +**Message**: Division by zero.
+**Category**: Undefined Behaviour
+**Severity**: Error/Warning
+**Language**: C/C++ + +## Description + +- `zerodiv`: an integer division or `%` has a divisor that's known to be zero. +- `zerodivcond`: the same problem, but the zero divisor only holds on one branch of a condition + tested elsewhere - so either that condition is redundant, or this is a genuine division by zero. + +## Motivation + +Integer division by zero is undefined behaviour: the program's actual behaviour (a crash, a trap, or +something else) is not guaranteed by the language standard and can change with the compiler, +optimization level, or platform. + +## How to fix + +Before: +```cpp +#include +void foo() { + std::cout << 42 / (int)0; // <- zerodiv +} +``` + +After: +```cpp +#include +void foo(int divisor) { + std::cout << 42 / divisor; +} +``` + +Before: +```cpp +int f(int x, int y) { + if (x == y) {} + return 1 / (x-y); // <- zerodivcond: division by zero if x == y +} +``` + +After: +```cpp +int f(int x, int y) { + if (x == y) + return 0; + return 1 / (x-y); +} +``` + +## Design notes + +- **Dividing a floating-point value by `0.0` is not reported as `zerodiv`.** Unlike integer division, + floating-point division by zero is well-defined by IEEE 754 (it produces `Inf` or `NaN` rather than + undefined behaviour), so cppcheck only flags it separately, and only in the narrow shape described in + [nanInArithmeticExpression.md](nanInArithmeticExpression.md).