Skip to content

Commit 41feaae

Browse files
authored
Fix #13876 (Document cstyleCast) [ci skip] (danmar#7539)
1 parent 187d6ab commit 41feaae

File tree

8 files changed

+362
-56
lines changed

8 files changed

+362
-56
lines changed

lib/checkother.cpp

Lines changed: 121 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,44 @@ void CheckOther::suspiciousSemicolonError(const Token* tok)
290290
"Suspicious use of ; at the end of '" + (tok ? tok->str() : std::string()) + "' statement.", CWE398, Certainty::normal);
291291
}
292292

293+
/** @brief would it make sense to use dynamic_cast instead of C style cast? */
294+
static bool isDangerousTypeConversion(const Token* const tok)
295+
{
296+
const Token* from = tok->astOperand1();
297+
if (!from)
298+
return false;
299+
if (!tok->valueType() || !from->valueType())
300+
return false;
301+
if (tok->valueType()->typeScope != nullptr &&
302+
tok->valueType()->typeScope == from->valueType()->typeScope)
303+
return false;
304+
if (tok->valueType()->type == from->valueType()->type &&
305+
tok->valueType()->isPrimitive())
306+
return false;
307+
// cast from derived object to base object is safe..
308+
if (tok->valueType()->typeScope && from->valueType()->typeScope) {
309+
const Type* fromType = from->valueType()->typeScope->definedType;
310+
const Type* toType = tok->valueType()->typeScope->definedType;
311+
if (fromType && toType && fromType->isDerivedFrom(toType->name()))
312+
return false;
313+
}
314+
const bool refcast = (tok->valueType()->reference != Reference::None);
315+
if (!refcast && tok->valueType()->pointer == 0)
316+
return false;
317+
if (!refcast && from->valueType()->pointer == 0)
318+
return false;
319+
320+
if (tok->valueType()->type == ValueType::Type::VOID || from->valueType()->type == ValueType::Type::VOID)
321+
return false;
322+
if (tok->valueType()->pointer == 0 && tok->valueType()->isIntegral())
323+
// ok: (uintptr_t)ptr;
324+
return false;
325+
if (from->valueType()->pointer == 0 && from->valueType()->isIntegral())
326+
// ok: (int *)addr;
327+
return false;
328+
329+
return true;
330+
}
293331

294332
//---------------------------------------------------------------------------
295333
// For C++ code, warn if C-style casts are used on pointer types
@@ -314,8 +352,11 @@ void CheckOther::warningOldStylePointerCast()
314352
tok = scope->bodyStart;
315353
for (; tok && tok != scope->bodyEnd; tok = tok->next()) {
316354
// Old style pointer casting..
317-
if (tok->str() != "(")
355+
if (!tok->isCast() || tok->isBinaryOp())
356+
continue;
357+
if (isDangerousTypeConversion(tok))
318358
continue;
359+
const Token* const errtok = tok;
319360
const Token* castTok = tok->next();
320361
while (Token::Match(castTok, "const|volatile|class|struct|union|%type%|::")) {
321362
castTok = castTok->next();
@@ -332,7 +373,7 @@ void CheckOther::warningOldStylePointerCast()
332373
isRef = true;
333374
castTok = castTok->next();
334375
}
335-
if ((!isPtr && !isRef) || !Token::Match(castTok, ") (| %name%|%num%|%bool%|%char%|%str%|&"))
376+
if ((!isPtr && !isRef) || !Token::Match(castTok, ") (| %name%|%bool%|%char%|%str%|&"))
336377
continue;
337378

338379
if (Token::Match(tok->previous(), "%type%"))
@@ -351,7 +392,7 @@ void CheckOther::warningOldStylePointerCast()
351392
continue;
352393

353394
if (typeTok->tokType() == Token::eType || typeTok->tokType() == Token::eName)
354-
cstyleCastError(tok, isPtr);
395+
cstyleCastError(errtok, isPtr);
355396
}
356397
}
357398
}
@@ -367,6 +408,79 @@ void CheckOther::cstyleCastError(const Token *tok, bool isPtr)
367408
"which kind of cast is expected.", CWE398, Certainty::normal);
368409
}
369410

411+
void CheckOther::warningDangerousTypeCast()
412+
{
413+
// Only valid on C++ code
414+
if (!mTokenizer->isCPP())
415+
return;
416+
if (!mSettings->severity.isEnabled(Severity::warning) && !mSettings->isPremiumEnabled("cstyleCast"))
417+
return;
418+
419+
logChecker("CheckOther::warningDangerousTypeCast"); // warning,c++
420+
421+
const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
422+
for (const Scope * scope : symbolDatabase->functionScopes) {
423+
const Token* tok;
424+
if (scope->function && scope->function->isConstructor())
425+
tok = scope->classDef;
426+
else
427+
tok = scope->bodyStart;
428+
for (; tok && tok != scope->bodyEnd; tok = tok->next()) {
429+
// Old style pointer casting..
430+
if (!tok->isCast() || tok->isBinaryOp())
431+
continue;
432+
433+
if (isDangerousTypeConversion(tok))
434+
dangerousTypeCastError(tok, tok->valueType()->pointer > 0);
435+
}
436+
}
437+
}
438+
439+
void CheckOther::dangerousTypeCastError(const Token *tok, bool isPtr)
440+
{
441+
//const std::string type = isPtr ? "pointer" : "reference";
442+
(void)isPtr;
443+
reportError(tok, Severity::warning, "dangerousTypeCast",
444+
"Potentially invalid type conversion in old-style C cast, clarify/fix with C++ cast",
445+
CWE398, Certainty::normal);
446+
}
447+
448+
void CheckOther::warningIntToPointerCast()
449+
{
450+
if (!mSettings->severity.isEnabled(Severity::portability) && !mSettings->isPremiumEnabled("cstyleCast"))
451+
return;
452+
453+
logChecker("CheckOther::warningIntToPointerCast"); // portability
454+
455+
for (const Token* tok = mTokenizer->tokens(); tok; tok = tok->next()) {
456+
// pointer casting..
457+
if (!tok->isCast())
458+
continue;
459+
const Token* from = tok->astOperand2() ? tok->astOperand2() : tok->astOperand1();
460+
if (!from || !from->isNumber())
461+
continue;
462+
if (!tok->valueType() || tok->valueType()->pointer == 0)
463+
continue;
464+
if (!MathLib::isIntHex(from->str()) && from->getKnownIntValue() != 0) {
465+
std::string format;
466+
if (MathLib::isDec(from->str()))
467+
format = "decimal";
468+
else if (MathLib::isOct(from->str()))
469+
format = "octal";
470+
else
471+
continue;
472+
intToPointerCastError(tok, format);
473+
}
474+
}
475+
}
476+
477+
void CheckOther::intToPointerCastError(const Token *tok, const std::string& format)
478+
{
479+
reportError(tok, Severity::portability, "intToPointerCast",
480+
"Casting non-zero " + format + " integer literal to pointer.",
481+
CWE398, Certainty::normal);
482+
}
483+
370484
void CheckOther::suspiciousFloatingPointCast()
371485
{
372486
if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("suspiciousFloatingPointCast"))
@@ -4393,6 +4507,8 @@ void CheckOther::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger)
43934507

43944508
// Checks
43954509
checkOther.warningOldStylePointerCast();
4510+
checkOther.warningDangerousTypeCast();
4511+
checkOther.warningIntToPointerCast();
43964512
checkOther.suspiciousFloatingPointCast();
43974513
checkOther.invalidPointerCast();
43984514
checkOther.checkCharVariable();
@@ -4459,6 +4575,8 @@ void CheckOther::getErrorMessages(ErrorLogger *errorLogger, const Settings *sett
44594575
c.checkComparisonFunctionIsAlwaysTrueOrFalseError(nullptr, "isless","varName",false);
44604576
c.checkCastIntToCharAndBackError(nullptr, "func_name");
44614577
c.cstyleCastError(nullptr);
4578+
c.dangerousTypeCastError(nullptr, true);
4579+
c.intToPointerCastError(nullptr, "decimal");
44624580
c.suspiciousFloatingPointCastError(nullptr);
44634581
c.passedByValueError(nullptr, false);
44644582
c.constVariableError(nullptr, nullptr);

lib/checkother.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ class CPPCHECKLIB CheckOther : public Check {
8080
/** @brief Are there C-style pointer casts in a c++ file? */
8181
void warningOldStylePointerCast();
8282

83+
/** @brief Dangerous type cast */
84+
void warningDangerousTypeCast();
85+
86+
/** @brief Casting non-hexadecimal integer literal to pointer */
87+
void warningIntToPointerCast();
88+
8389
void suspiciousFloatingPointCast();
8490

8591
/** @brief Check for pointer casts to a type with an incompatible binary data representation */
@@ -198,6 +204,8 @@ class CPPCHECKLIB CheckOther : public Check {
198204
void clarifyCalculationError(const Token *tok, const std::string &op);
199205
void clarifyStatementError(const Token* tok);
200206
void cstyleCastError(const Token *tok, bool isPtr = true);
207+
void dangerousTypeCastError(const Token *tok, bool isPtr);
208+
void intToPointerCastError(const Token *tok, const std::string& format);
201209
void suspiciousFloatingPointCastError(const Token *tok);
202210
void invalidPointerCastError(const Token* tok, const std::string& from, const std::string& to, bool inconclusive, bool toIsInt);
203211
void passedByValueError(const Variable* var, bool inconclusive, bool isRangeBasedFor = false);
@@ -273,6 +281,7 @@ class CPPCHECKLIB CheckOther : public Check {
273281
// warning
274282
"- either division by zero or useless condition\n"
275283
"- access of moved or forwarded variable.\n"
284+
"- potentially dangerous C style type cast of pointer/reference to object.\n"
276285

277286
// performance
278287
"- redundant data copying for const variable\n"
@@ -281,6 +290,7 @@ class CPPCHECKLIB CheckOther : public Check {
281290

282291
// portability
283292
"- Passing NULL pointer to function with variable number of arguments leads to UB.\n"
293+
"- Casting non-zero integer literal in decimal or octal format to pointer.\n"
284294

285295
// style
286296
"- C-style pointer cast in C++ code\n"

man/checkers/cstyleCast.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
2+
# cstyleCast
3+
4+
**Message**: C-style pointer casting<br/>
5+
**Category**: Modernization<br/>
6+
**Severity**: Style<br/>
7+
**Language**: C++, not applicable for C code
8+
9+
## Description
10+
11+
Many developers feel that it's best to replace C casts with C++ casts.
12+
13+
There are several advantages with C++ casts:
14+
* they permit only certain conversions
15+
* they express the intent
16+
* they are easy to identify
17+
18+
This checker is about C casts that converts to/from a pointer or reference.
19+
20+
**Note:** More "noisy" warnings exists that warn about all C casts. For instance Clang has
21+
`-Wold-style-cast` and there is also such checking in Misra C++.
22+
23+
Dangerous conversions are covered by other warnings so this ID `cstyleCast` is primarily about
24+
writing warnings for casts that are currently safe.
25+
26+
## How to fix
27+
28+
You can use C++ casts such as `static_cast` to fix these warnings.
29+
30+
The `dynamic_cast` should rarelly be used to fix these warnings because dangerousTypeCast is
31+
reported when that can be a good idea.
32+
33+
Before:
34+
```cpp
35+
struct Base{};
36+
struct Derived: public Base {};
37+
void foo(Base* base) {
38+
Base *p = (Base*)derived; // <- cstyleCast, cast from derived object to base object is safe now
39+
}
40+
```
41+
42+
After:
43+
```cpp
44+
struct Base{};
45+
struct Derived: public Base {};
46+
void foo(Base* base) {
47+
Derived *p = static_cast<Derived*>(base);
48+
}
49+
```
50+
The `static_cast` ensures that there will not be loss of constness in the future.

man/checkers/dangerousTypeCast.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
2+
# dangerousTypeCast
3+
4+
**Message**: Potentially invalid type conversion in old-style C cast, clarify/fix with C++ cast<br/>
5+
**Category**: Type Safety<br/>
6+
**Severity**: Warning<br/>
7+
**Language**: C++, not applicable for C code
8+
9+
## Motivation
10+
11+
C style casts can be dangerous in many ways:
12+
* loss of precision
13+
* loss of sign
14+
* loss of constness
15+
* invalid type conversion
16+
17+
## Philosophy
18+
19+
This checker warns about old style C casts that perform type conversions that can be invalid.
20+
21+
This checker is not about readability. It is about safety.
22+
23+
## How to fix
24+
25+
You can use `dynamic_cast` or `static_cast` to fix these warnings.
26+
27+
Before:
28+
```cpp
29+
struct Base{};
30+
struct Derived: public Base {};
31+
void foo(Base* base) {
32+
Derived *p = (Derived*)base; // <- can be invalid
33+
}
34+
```
35+
36+
After:
37+
```cpp
38+
struct Base{};
39+
struct Derived: public Base {};
40+
void foo(Base* base) {
41+
Derived *p = dynamic_cast<Derived*>(base);
42+
}
43+
```

releasenotes.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,5 @@ Other:
2525
- CMake will now unconditionally use Boost.Containers if available. If CMake option `USE_BOOST` is specified it will now bail out when it is not found.
2626
- Fix checking a project that contains several project file entries for the same file.
2727
- Fixed --file-filter matching of looked up files in provided paths.
28+
- Split up cstyleCast checker; dangerous casts produce portability/warning reports, safe casts produce style reports.
2829
-

test/cfg/posix.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ void nullPointer_pthread_attr_setstack(const pthread_attr_t *attr) {
144144
(void) pthread_attr_setstack(NULL, NULL, 0);
145145
(void) pthread_attr_setstack(attr, NULL, 0);
146146
// cppcheck-suppress nullPointer
147+
// cppcheck-suppress intToPointerCast
147148
(void) pthread_attr_setstack(NULL, (void*) 1, 0);
148149
}
149150

test/cfg/windows.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -825,7 +825,7 @@ void invalidFunctionArg()
825825
CloseHandle(hMutex);
826826

827827
//Incorrect: 2. parameter to LoadLibraryEx() must be NULL
828-
// cppcheck-suppress [invalidFunctionArg, cstyleCast]
828+
// cppcheck-suppress [invalidFunctionArg, intToPointerCast]
829829
HINSTANCE hInstLib = LoadLibraryEx(L"My.dll", HANDLE(1), 0);
830830
FreeLibrary(hInstLib);
831831

0 commit comments

Comments
 (0)