Line data Source code
1 : /*
2 : * Cppcheck - A tool for static C/C++ code analysis
3 : * Copyright (C) 2007-2024 Cppcheck team.
4 : *
5 : * This program is free software: you can redistribute it and/or modify
6 : * it under the terms of the GNU General Public License as published by
7 : * the Free Software Foundation, either version 3 of the License, or
8 : * (at your option) any later version.
9 : *
10 : * This program is distributed in the hope that it will be useful,
11 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : * GNU General Public License for more details.
14 : *
15 : * You should have received a copy of the GNU General Public License
16 : * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 : */
18 :
19 :
20 : //---------------------------------------------------------------------------
21 : #include "checkbool.h"
22 :
23 : #include "astutils.h"
24 : #include "errortypes.h"
25 : #include "settings.h"
26 : #include "symboldatabase.h"
27 : #include "token.h"
28 : #include "tokenize.h"
29 : #include "vfvalue.h"
30 :
31 : #include <list>
32 : #include <vector>
33 : //---------------------------------------------------------------------------
34 :
35 : // Register this check class (by creating a static instance of it)
36 : namespace {
37 : CheckBool instance;
38 : }
39 :
40 : static const CWE CWE398(398U); // Indicator of Poor Code Quality
41 : static const CWE CWE571(571U); // Expression is Always True
42 : static const CWE CWE587(587U); // Assignment of a Fixed Address to a Pointer
43 : static const CWE CWE704(704U); // Incorrect Type Conversion or Cast
44 :
45 87 : static bool isBool(const Variable* var)
46 : {
47 87 : return (var && Token::Match(var->typeEndToken(), "bool|_Bool"));
48 : }
49 :
50 : //---------------------------------------------------------------------------
51 2671 : void CheckBool::checkIncrementBoolean()
52 : {
53 2671 : if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("incrementboolean"))
54 2321 : return;
55 :
56 350 : logChecker("CheckBool::checkIncrementBoolean"); // style
57 :
58 350 : const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
59 6227 : for (const Scope * scope : symbolDatabase->functionScopes) {
60 210129 : for (const Token* tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) {
61 204252 : if (astIsBool(tok) && tok->astParent() && tok->astParent()->str() == "++") {
62 4 : incrementBooleanError(tok);
63 : }
64 : }
65 : }
66 : }
67 :
68 8 : void CheckBool::incrementBooleanError(const Token *tok)
69 : {
70 8 : reportError(
71 : tok,
72 : Severity::style,
73 : "incrementboolean",
74 : "Incrementing a variable of type 'bool' with postfix operator++ is deprecated by the C++ Standard. You should assign it the value 'true' instead.\n"
75 : "The operand of a postfix increment operator may be of type bool but it is deprecated by C++ Standard (Annex D-1) and the operand is always set to true. You should assign it the value 'true' instead.",
76 : CWE398, Certainty::normal
77 16 : );
78 8 : }
79 :
80 14 : static bool isConvertedToBool(const Token* tok)
81 : {
82 14 : if (!tok->astParent())
83 0 : return false;
84 14 : return astIsBool(tok->astParent()) || Token::Match(tok->astParent()->previous(), "if|while (");
85 : }
86 :
87 : //---------------------------------------------------------------------------
88 : // if (bool & bool) -> if (bool && bool)
89 : // if (bool | bool) -> if (bool || bool)
90 : //---------------------------------------------------------------------------
91 2671 : void CheckBool::checkBitwiseOnBoolean()
92 : {
93 2671 : if (!mSettings->severity.isEnabled(Severity::style))
94 2321 : return;
95 :
96 : // danmar: this is inconclusive because I don't like that there are
97 : // warnings for calculations. Example: set_flag(a & b);
98 350 : if (!mSettings->certainty.isEnabled(Certainty::inconclusive))
99 1 : return;
100 :
101 349 : logChecker("CheckBool::checkBitwiseOnBoolean"); // style,inconclusive
102 :
103 349 : const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
104 6226 : for (const Scope * scope : symbolDatabase->functionScopes) {
105 210129 : for (const Token* tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) {
106 204252 : if (tok->isBinaryOp()) {
107 40800 : bool isCompound{};
108 40800 : if (tok->str() == "&" || tok->str() == "|")
109 173 : isCompound = false;
110 40627 : else if (tok->str() == "&=" || tok->str() == "|=")
111 6 : isCompound = true;
112 : else
113 40782 : continue;
114 179 : const bool isBoolOp1 = astIsBool(tok->astOperand1());
115 179 : const bool isBoolOp2 = astIsBool(tok->astOperand2());
116 179 : if (!tok->astOperand1()->valueType() || !tok->astOperand2()->valueType())
117 116 : continue;
118 63 : if (!(isBoolOp1 || isBoolOp2))
119 37 : continue;
120 26 : if (isCompound && (!isBoolOp1 || isBoolOp2))
121 3 : continue;
122 23 : if (tok->str() == "|" && !isConvertedToBool(tok) && !(isBoolOp1 && isBoolOp2))
123 3 : continue;
124 : // first operand will always be evaluated
125 20 : if (!isConstExpression(tok->astOperand2(), mSettings->library))
126 2 : continue;
127 18 : if (tok->astOperand2()->variable() && tok->astOperand2()->variable()->nameToken() == tok->astOperand2())
128 0 : continue;
129 36 : const std::string expression = (isBoolOp1 ? tok->astOperand1() : tok->astOperand2())->expressionString();
130 18 : bitwiseOnBooleanError(tok, expression, tok->str() == "&" ? "&&" : "||", isCompound);
131 : }
132 : }
133 : }
134 : }
135 :
136 22 : void CheckBool::bitwiseOnBooleanError(const Token* tok, const std::string& expression, const std::string& op, bool isCompound)
137 : {
138 44 : std::string msg = "Boolean expression '" + expression + "' is used in bitwise operation.";
139 22 : if (!isCompound)
140 20 : msg += " Did you mean '" + op + "'?";
141 22 : reportError(tok,
142 : Severity::style,
143 : "bitwiseOnBoolean",
144 : msg,
145 : CWE398,
146 44 : Certainty::inconclusive);
147 22 : }
148 :
149 : //---------------------------------------------------------------------------
150 : // if (!x==3) <- Probably meant to be "x!=3"
151 : //---------------------------------------------------------------------------
152 :
153 2671 : void CheckBool::checkComparisonOfBoolWithInt()
154 : {
155 2671 : if (!mSettings->severity.isEnabled(Severity::warning) || !mTokenizer->isCPP())
156 2402 : return;
157 :
158 269 : logChecker("CheckBool::checkComparisonOfBoolWithInt"); // warning,c++
159 :
160 269 : const SymbolDatabase* const symbolDatabase = mTokenizer->getSymbolDatabase();
161 3542 : for (const Scope * scope : symbolDatabase->functionScopes) {
162 108729 : for (const Token* tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) {
163 105456 : if (!tok->isComparisonOp() || !tok->isBinaryOp())
164 104999 : continue;
165 457 : const Token* const left = tok->astOperand1();
166 457 : const Token* const right = tok->astOperand2();
167 457 : if (left->isBoolean() && right->varId()) { // Comparing boolean constant with variable
168 5 : if (tok->str() != "==" && tok->str() != "!=") {
169 4 : comparisonOfBoolWithInvalidComparator(right, left->str());
170 : }
171 452 : } else if (left->varId() && right->isBoolean()) { // Comparing variable with boolean constant
172 6 : if (tok->str() != "==" && tok->str() != "!=") {
173 5 : comparisonOfBoolWithInvalidComparator(right, left->str());
174 : }
175 : }
176 : }
177 : }
178 : }
179 :
180 13 : void CheckBool::comparisonOfBoolWithInvalidComparator(const Token *tok, const std::string &expression)
181 : {
182 13 : reportError(tok, Severity::warning, "comparisonOfBoolWithInvalidComparator",
183 : "Comparison of a boolean value using relational operator (<, >, <= or >=).\n"
184 26 : "The result of the expression '" + expression + "' is of type 'bool'. "
185 : "Comparing 'bool' value using relational (<, >, <= or >=)"
186 26 : " operator could cause unexpected results.");
187 13 : }
188 :
189 : //-------------------------------------------------------------------------------
190 : // Comparing functions which are returning value of type bool
191 : //-------------------------------------------------------------------------------
192 :
193 304 : static bool tokenIsFunctionReturningBool(const Token* tok)
194 : {
195 304 : const Function* func = tok ? tok->function() : nullptr;
196 304 : if (func && Token::Match(tok, "%name% (")) {
197 22 : if (func->tokenDef && Token::Match(func->tokenDef->previous(), "bool|_Bool")) {
198 17 : return true;
199 : }
200 : }
201 287 : return false;
202 : }
203 :
204 2671 : void CheckBool::checkComparisonOfFuncReturningBool()
205 : {
206 2671 : if (!mSettings->severity.isEnabled(Severity::style))
207 2401 : return;
208 :
209 350 : if (!mTokenizer->isCPP())
210 80 : return;
211 :
212 270 : logChecker("CheckBool::checkComparisonOfFuncReturningBool"); // style,c++
213 :
214 270 : const SymbolDatabase * const symbolDatabase = mTokenizer->getSymbolDatabase();
215 317 : auto getFunctionTok = [](const Token* tok) -> const Token* {
216 317 : while (Token::simpleMatch(tok, "!") || (tok && tok->isCast() && !isCPPCast(tok)))
217 13 : tok = tok->astOperand1();
218 304 : if (isCPPCast(tok))
219 2 : tok = tok->astOperand2();
220 304 : if (tok)
221 304 : return tok->previous();
222 0 : return nullptr;
223 : };
224 :
225 3543 : for (const Scope * scope : symbolDatabase->functionScopes) {
226 108729 : for (const Token* tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) {
227 105456 : if (!tok->isComparisonOp() || tok->str() == "==" || tok->str() == "!=")
228 105304 : continue;
229 :
230 152 : const Token* firstToken = getFunctionTok(tok->astOperand1());
231 152 : const Token* secondToken = getFunctionTok(tok->astOperand2());
232 152 : if (!firstToken || !secondToken)
233 0 : continue;
234 :
235 152 : const bool firstIsFunctionReturningBool = tokenIsFunctionReturningBool(firstToken);
236 152 : const bool secondIsFunctionReturningBool = tokenIsFunctionReturningBool(secondToken);
237 152 : if (firstIsFunctionReturningBool && secondIsFunctionReturningBool) {
238 6 : comparisonOfTwoFuncsReturningBoolError(firstToken->next(), firstToken->str(), secondToken->str());
239 146 : } else if (firstIsFunctionReturningBool) {
240 4 : comparisonOfFuncReturningBoolError(firstToken->next(), firstToken->str());
241 142 : } else if (secondIsFunctionReturningBool) {
242 1 : comparisonOfFuncReturningBoolError(secondToken->previous(), secondToken->str());
243 : }
244 : }
245 : }
246 : }
247 :
248 9 : void CheckBool::comparisonOfFuncReturningBoolError(const Token *tok, const std::string &expression)
249 : {
250 9 : reportError(tok, Severity::style, "comparisonOfFuncReturningBoolError",
251 : "Comparison of a function returning boolean value using relational (<, >, <= or >=) operator.\n"
252 18 : "The return type of function '" + expression + "' is 'bool' "
253 : "and result is of type 'bool'. Comparing 'bool' value using relational (<, >, <= or >=)"
254 18 : " operator could cause unexpected results.", CWE398, Certainty::normal);
255 9 : }
256 :
257 10 : void CheckBool::comparisonOfTwoFuncsReturningBoolError(const Token *tok, const std::string &expression1, const std::string &expression2)
258 : {
259 10 : reportError(tok, Severity::style, "comparisonOfTwoFuncsReturningBoolError",
260 : "Comparison of two functions returning boolean value using relational (<, >, <= or >=) operator.\n"
261 20 : "The return type of function '" + expression1 + "' and function '" + expression2 + "' is 'bool' "
262 : "and result is of type 'bool'. Comparing 'bool' value using relational (<, >, <= or >=)"
263 20 : " operator could cause unexpected results.", CWE398, Certainty::normal);
264 10 : }
265 :
266 : //-------------------------------------------------------------------------------
267 : // Comparison of bool with bool
268 : //-------------------------------------------------------------------------------
269 :
270 2671 : void CheckBool::checkComparisonOfBoolWithBool()
271 : {
272 2671 : if (!mSettings->severity.isEnabled(Severity::style))
273 2321 : return;
274 :
275 350 : if (!mTokenizer->isCPP())
276 80 : return;
277 :
278 270 : logChecker("CheckBool::checkComparisonOfBoolWithBool"); // style,c++
279 :
280 270 : const SymbolDatabase* const symbolDatabase = mTokenizer->getSymbolDatabase();
281 :
282 3543 : for (const Scope * scope : symbolDatabase->functionScopes) {
283 108729 : for (const Token* tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) {
284 105456 : if (!tok->isComparisonOp() || tok->str() == "==" || tok->str() == "!=")
285 105304 : continue;
286 152 : bool firstTokenBool = false;
287 :
288 152 : const Token *firstToken = tok->previous();
289 152 : if (firstToken->varId()) {
290 82 : if (isBool(firstToken->variable())) {
291 11 : firstTokenBool = true;
292 : }
293 : }
294 152 : if (!firstTokenBool)
295 141 : continue;
296 :
297 11 : bool secondTokenBool = false;
298 11 : const Token *secondToken = tok->next();
299 11 : if (secondToken->varId()) {
300 5 : if (isBool(secondToken->variable())) {
301 5 : secondTokenBool = true;
302 : }
303 : }
304 11 : if (secondTokenBool) {
305 5 : comparisonOfBoolWithBoolError(firstToken->next(), secondToken->str());
306 : }
307 : }
308 : }
309 : }
310 :
311 9 : void CheckBool::comparisonOfBoolWithBoolError(const Token *tok, const std::string &expression)
312 : {
313 9 : reportError(tok, Severity::style, "comparisonOfBoolWithBoolError",
314 : "Comparison of a variable having boolean value using relational (<, >, <= or >=) operator.\n"
315 18 : "The variable '" + expression + "' is of type 'bool' "
316 : "and comparing 'bool' value using relational (<, >, <= or >=)"
317 18 : " operator could cause unexpected results.", CWE398, Certainty::normal);
318 9 : }
319 :
320 : //-----------------------------------------------------------------------------
321 2671 : void CheckBool::checkAssignBoolToPointer()
322 : {
323 2671 : logChecker("CheckBool::checkAssignBoolToPointer");
324 2671 : const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
325 10859 : for (const Scope * scope : symbolDatabase->functionScopes) {
326 256243 : for (const Token* tok = scope->bodyStart; tok != scope->bodyEnd; tok = tok->next()) {
327 248055 : if (tok->str() == "=" && astIsPointer(tok->astOperand1()) && astIsBool(tok->astOperand2())) {
328 8 : assignBoolToPointerError(tok);
329 : }
330 : }
331 : }
332 2671 : }
333 :
334 12 : void CheckBool::assignBoolToPointerError(const Token *tok)
335 : {
336 12 : reportError(tok, Severity::error, "assignBoolToPointer",
337 24 : "Boolean value assigned to pointer.", CWE587, Certainty::normal);
338 12 : }
339 :
340 : //-----------------------------------------------------------------------------
341 : //-----------------------------------------------------------------------------
342 2671 : void CheckBool::checkComparisonOfBoolExpressionWithInt()
343 : {
344 2671 : if (!mSettings->severity.isEnabled(Severity::warning))
345 2322 : return;
346 :
347 349 : logChecker("CheckBool::checkComparisonOfBoolExpressionWithInt"); // warning
348 :
349 349 : const SymbolDatabase* symbolDatabase = mTokenizer->getSymbolDatabase();
350 :
351 6226 : for (const Scope * scope : symbolDatabase->functionScopes) {
352 210129 : for (const Token* tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) {
353 204252 : if (!tok->isComparisonOp())
354 203457 : continue;
355 :
356 795 : const Token* numTok = nullptr;
357 795 : const Token* boolExpr = nullptr;
358 : bool numInRhs;
359 795 : if (astIsBool(tok->astOperand1())) {
360 71 : boolExpr = tok->astOperand1();
361 71 : numTok = tok->astOperand2();
362 71 : numInRhs = true;
363 724 : } else if (astIsBool(tok->astOperand2())) {
364 14 : boolExpr = tok->astOperand2();
365 14 : numTok = tok->astOperand1();
366 14 : numInRhs = false;
367 : } else {
368 710 : continue;
369 : }
370 :
371 85 : if (!numTok || !boolExpr)
372 0 : continue;
373 :
374 85 : if (boolExpr->isOp() && numTok->isName() && Token::Match(tok, "==|!="))
375 : // there is weird code such as: ((a<b)==c)
376 : // but it is probably written this way by design.
377 4 : continue;
378 :
379 81 : if (astIsBool(numTok))
380 29 : continue;
381 :
382 52 : const ValueFlow::Value *minval = numTok->getValueLE(0, *mSettings);
383 62 : if (minval && minval->intvalue == 0 &&
384 8 : (numInRhs ? Token::Match(tok, ">|==|!=")
385 2 : : Token::Match(tok, "<|==|!=")))
386 8 : minval = nullptr;
387 :
388 52 : const ValueFlow::Value *maxval = numTok->getValueGE(1, *mSettings);
389 59 : if (maxval && maxval->intvalue == 1 &&
390 4 : (numInRhs ? Token::Match(tok, "<|==|!=")
391 3 : : Token::Match(tok, ">|==|!=")))
392 4 : maxval = nullptr;
393 :
394 52 : if (minval || maxval) {
395 24 : const bool not0or1 = (minval && minval->intvalue < 0) || (maxval && maxval->intvalue > 1);
396 24 : comparisonOfBoolExpressionWithIntError(tok, not0or1);
397 : }
398 : }
399 : }
400 : }
401 :
402 28 : void CheckBool::comparisonOfBoolExpressionWithIntError(const Token *tok, bool not0or1)
403 : {
404 28 : if (not0or1)
405 23 : reportError(tok, Severity::warning, "compareBoolExpressionWithInt",
406 46 : "Comparison of a boolean expression with an integer other than 0 or 1.", CWE398, Certainty::normal);
407 : else
408 5 : reportError(tok, Severity::warning, "compareBoolExpressionWithInt",
409 10 : "Comparison of a boolean expression with an integer.", CWE398, Certainty::normal);
410 28 : }
411 :
412 :
413 2671 : void CheckBool::pointerArithBool()
414 : {
415 2671 : logChecker("CheckBool::pointerArithBool");
416 :
417 2671 : const SymbolDatabase* symbolDatabase = mTokenizer->getSymbolDatabase();
418 :
419 14556 : for (const Scope &scope : symbolDatabase->scopeList) {
420 11885 : if (scope.type != Scope::eIf && !scope.isLoopScope())
421 11206 : continue;
422 679 : const Token* tok = scope.classDef->next()->astOperand2();
423 679 : if (scope.type == Scope::eFor) {
424 76 : tok = Token::findsimplematch(scope.classDef->tokAt(2), ";");
425 76 : if (tok)
426 76 : tok = tok->astOperand2();
427 76 : if (tok)
428 71 : tok = tok->astOperand1();
429 603 : } else if (scope.type == Scope::eDo)
430 3 : tok = (scope.bodyEnd->tokAt(2)) ? scope.bodyEnd->tokAt(2)->astOperand2() : nullptr;
431 :
432 679 : pointerArithBoolCond(tok);
433 : }
434 2671 : }
435 :
436 755 : void CheckBool::pointerArithBoolCond(const Token *tok)
437 : {
438 755 : if (!tok)
439 7 : return;
440 748 : if (Token::Match(tok, "&&|%oror%")) {
441 38 : pointerArithBoolCond(tok->astOperand1());
442 38 : pointerArithBoolCond(tok->astOperand2());
443 38 : return;
444 : }
445 710 : if (tok->str() != "+" && tok->str() != "-")
446 701 : return;
447 :
448 9 : if (tok->isBinaryOp() &&
449 18 : tok->astOperand1()->isName() &&
450 18 : tok->astOperand1()->variable() &&
451 27 : tok->astOperand1()->variable()->isPointer() &&
452 6 : tok->astOperand2()->isNumber())
453 6 : pointerArithBoolError(tok);
454 : }
455 :
456 10 : void CheckBool::pointerArithBoolError(const Token *tok)
457 : {
458 10 : reportError(tok,
459 : Severity::error,
460 : "pointerArithBool",
461 : "Converting pointer arithmetic result to bool. The bool is always true unless there is undefined behaviour.\n"
462 20 : "Converting pointer arithmetic result to bool. The boolean result is always true unless there is pointer arithmetic overflow, and overflow is undefined behaviour. Probably a dereference is forgotten.", CWE571, Certainty::normal);
463 10 : }
464 :
465 2671 : void CheckBool::checkAssignBoolToFloat()
466 : {
467 2671 : if (!mTokenizer->isCPP())
468 81 : return;
469 2590 : if (!mSettings->severity.isEnabled(Severity::style))
470 2320 : return;
471 270 : logChecker("CheckBool::checkAssignBoolToFloat"); // style,c++
472 270 : const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
473 3543 : for (const Scope * scope : symbolDatabase->functionScopes) {
474 112002 : for (const Token* tok = scope->bodyStart; tok != scope->bodyEnd; tok = tok->next()) {
475 108729 : if (tok->str() == "=" && astIsFloat(tok->astOperand1(), false) && astIsBool(tok->astOperand2())) {
476 5 : assignBoolToFloatError(tok);
477 : }
478 : }
479 : }
480 : }
481 :
482 9 : void CheckBool::assignBoolToFloatError(const Token *tok)
483 : {
484 9 : reportError(tok, Severity::style, "assignBoolToFloat",
485 18 : "Boolean value assigned to floating point variable.", CWE704, Certainty::normal);
486 9 : }
487 :
488 2671 : void CheckBool::returnValueOfFunctionReturningBool()
489 : {
490 2671 : if (!mSettings->severity.isEnabled(Severity::style))
491 2321 : return;
492 :
493 350 : logChecker("CheckBool::returnValueOfFunctionReturningBool"); // style
494 :
495 350 : const SymbolDatabase * const symbolDatabase = mTokenizer->getSymbolDatabase();
496 :
497 6227 : for (const Scope * scope : symbolDatabase->functionScopes) {
498 5877 : if (!(scope->function && Token::Match(scope->function->retDef, "bool|_Bool")))
499 5752 : continue;
500 :
501 2055 : for (const Token* tok = scope->bodyStart->next(); tok && (tok != scope->bodyEnd); tok = tok->next()) {
502 : // Skip lambdas
503 1930 : const Token* tok2 = findLambdaEndToken(tok);
504 1930 : if (tok2)
505 4 : tok = tok2;
506 1926 : else if (tok->scope() && tok->scope()->isClassOrStruct())
507 3 : tok = tok->scope()->bodyEnd;
508 2059 : else if (Token::simpleMatch(tok, "return") && tok->astOperand1() &&
509 2076 : (tok->astOperand1()->getValueGE(2, *mSettings) || tok->astOperand1()->getValueLE(-1, *mSettings)) &&
510 17 : !(tok->astOperand1()->astOperand1() && Token::Match(tok->astOperand1(), "&|%or%")))
511 12 : returnValueBoolError(tok);
512 : }
513 : }
514 : }
515 :
516 16 : void CheckBool::returnValueBoolError(const Token *tok)
517 : {
518 16 : reportError(tok, Severity::style, "returnNonBoolInBooleanFunction", "Non-boolean value returned from function returning bool");
519 16 : }
|