/src/cppcheck/lib/checkother.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Cppcheck - A tool for static C/C++ code analysis |
3 | | * Copyright (C) 2007-2023 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 "checkother.h" |
22 | | |
23 | | #include "astutils.h" |
24 | | #include "fwdanalysis.h" |
25 | | #include "library.h" |
26 | | #include "mathlib.h" |
27 | | #include "platform.h" |
28 | | #include "settings.h" |
29 | | #include "standards.h" |
30 | | #include "symboldatabase.h" |
31 | | #include "token.h" |
32 | | #include "tokenize.h" |
33 | | #include "tokenlist.h" |
34 | | #include "utils.h" |
35 | | #include "valueflow.h" |
36 | | #include "vfvalue.h" |
37 | | |
38 | | #include <algorithm> // find_if() |
39 | | #include <cctype> |
40 | | #include <list> |
41 | | #include <map> |
42 | | #include <sstream> |
43 | | #include <utility> |
44 | | #include <numeric> |
45 | | |
46 | | //--------------------------------------------------------------------------- |
47 | | |
48 | | // Register this check class (by creating a static instance of it) |
49 | | namespace { |
50 | | CheckOther instance; |
51 | | } |
52 | | |
53 | | static const struct CWE CWE128(128U); // Wrap-around Error |
54 | | static const struct CWE CWE131(131U); // Incorrect Calculation of Buffer Size |
55 | | static const struct CWE CWE197(197U); // Numeric Truncation Error |
56 | | static const struct CWE CWE362(362U); // Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition') |
57 | | static const struct CWE CWE369(369U); // Divide By Zero |
58 | | static const struct CWE CWE398(398U); // Indicator of Poor Code Quality |
59 | | static const struct CWE CWE475(475U); // Undefined Behavior for Input to API |
60 | | static const struct CWE CWE561(561U); // Dead Code |
61 | | static const struct CWE CWE563(563U); // Assignment to Variable without Use ('Unused Variable') |
62 | | static const struct CWE CWE570(570U); // Expression is Always False |
63 | | static const struct CWE CWE571(571U); // Expression is Always True |
64 | | static const struct CWE CWE672(672U); // Operation on a Resource after Expiration or Release |
65 | | static const struct CWE CWE628(628U); // Function Call with Incorrectly Specified Arguments |
66 | | static const struct CWE CWE683(683U); // Function Call With Incorrect Order of Arguments |
67 | | static const struct CWE CWE704(704U); // Incorrect Type Conversion or Cast |
68 | | static const struct CWE CWE758(758U); // Reliance on Undefined, Unspecified, or Implementation-Defined Behavior |
69 | | static const struct CWE CWE768(768U); // Incorrect Short Circuit Evaluation |
70 | | static const struct CWE CWE783(783U); // Operator Precedence Logic Error |
71 | | |
72 | | //---------------------------------------------------------------------------------- |
73 | | // The return value of fgetc(), getc(), ungetc(), getchar() etc. is an integer value. |
74 | | // If this return value is stored in a character variable and then compared |
75 | | // to EOF, which is an integer, the comparison maybe be false. |
76 | | // |
77 | | // Reference: |
78 | | // - Ticket #160 |
79 | | // - http://www.cplusplus.com/reference/cstdio/fgetc/ |
80 | | // - http://www.cplusplus.com/reference/cstdio/getc/ |
81 | | // - http://www.cplusplus.com/reference/cstdio/getchar/ |
82 | | // - http://www.cplusplus.com/reference/cstdio/ungetc/ ... |
83 | | //---------------------------------------------------------------------------------- |
84 | | void CheckOther::checkCastIntToCharAndBack() |
85 | 1.36k | { |
86 | 1.36k | if (!mSettings->severity.isEnabled(Severity::warning)) |
87 | 0 | return; |
88 | | |
89 | 1.36k | logChecker("CheckOther::checkCastIntToCharAndBack"); // warning |
90 | | |
91 | 1.36k | const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); |
92 | 1.73k | for (const Scope * scope : symbolDatabase->functionScopes) { |
93 | 1.73k | std::map<int, std::string> vars; |
94 | 35.9k | for (const Token* tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) { |
95 | | // Quick check to see if any of the matches below have any chances |
96 | 34.1k | if (!Token::Match(tok, "%var%|EOF %comp%|=")) |
97 | 31.5k | continue; |
98 | 2.62k | if (Token::Match(tok, "%var% = fclose|fflush|fputc|fputs|fscanf|getchar|getc|fgetc|putchar|putc|puts|scanf|sscanf|ungetc (")) { |
99 | 0 | const Variable *var = tok->variable(); |
100 | 0 | if (var && var->typeEndToken()->str() == "char" && !var->typeEndToken()->isSigned()) { |
101 | 0 | vars[tok->varId()] = tok->strAt(2); |
102 | 0 | } |
103 | 2.62k | } else if (Token::Match(tok, "EOF %comp% ( %var% = fclose|fflush|fputc|fputs|fscanf|getchar|getc|fgetc|putchar|putc|puts|scanf|sscanf|ungetc (")) { |
104 | 0 | tok = tok->tokAt(3); |
105 | 0 | const Variable *var = tok->variable(); |
106 | 0 | if (var && var->typeEndToken()->str() == "char" && !var->typeEndToken()->isSigned()) { |
107 | 0 | checkCastIntToCharAndBackError(tok, tok->strAt(2)); |
108 | 0 | } |
109 | 2.62k | } else if (mTokenizer->isCPP() && (Token::Match(tok, "EOF %comp% ( %var% = std :: cin . get (") || Token::Match(tok, "EOF %comp% ( %var% = cin . get ("))) { |
110 | 0 | tok = tok->tokAt(3); |
111 | 0 | const Variable *var = tok->variable(); |
112 | 0 | if (var && var->typeEndToken()->str() == "char" && !var->typeEndToken()->isSigned()) { |
113 | 0 | checkCastIntToCharAndBackError(tok, "cin.get"); |
114 | 0 | } |
115 | 2.62k | } else if (mTokenizer->isCPP() && (Token::Match(tok, "%var% = std :: cin . get (") || Token::Match(tok, "%var% = cin . get ("))) { |
116 | 0 | const Variable *var = tok->variable(); |
117 | 0 | if (var && var->typeEndToken()->str() == "char" && !var->typeEndToken()->isSigned()) { |
118 | 0 | vars[tok->varId()] = "cin.get"; |
119 | 0 | } |
120 | 2.62k | } else if (Token::Match(tok, "%var% %comp% EOF")) { |
121 | 0 | if (vars.find(tok->varId()) != vars.end()) { |
122 | 0 | checkCastIntToCharAndBackError(tok, vars[tok->varId()]); |
123 | 0 | } |
124 | 2.62k | } else if (Token::Match(tok, "EOF %comp% %var%")) { |
125 | 0 | tok = tok->tokAt(2); |
126 | 0 | if (vars.find(tok->varId()) != vars.end()) { |
127 | 0 | checkCastIntToCharAndBackError(tok, vars[tok->varId()]); |
128 | 0 | } |
129 | 0 | } |
130 | 2.62k | } |
131 | 1.73k | } |
132 | 1.36k | } |
133 | | |
134 | | void CheckOther::checkCastIntToCharAndBackError(const Token *tok, const std::string &strFunctionName) |
135 | 0 | { |
136 | 0 | reportError( |
137 | 0 | tok, |
138 | 0 | Severity::warning, |
139 | 0 | "checkCastIntToCharAndBack", |
140 | 0 | "$symbol:" + strFunctionName + "\n" |
141 | 0 | "Storing $symbol() return value in char variable and then comparing with EOF.\n" |
142 | 0 | "When saving $symbol() return value in char variable there is loss of precision. " |
143 | 0 | " When $symbol() returns EOF this value is truncated. Comparing the char " |
144 | 0 | "variable with EOF can have unexpected results. For instance a loop \"while (EOF != (c = $symbol());\" " |
145 | 0 | "loops forever on some compilers/platforms and on other compilers/platforms it will stop " |
146 | 0 | "when the file contains a matching character.", CWE197, Certainty::normal |
147 | 0 | ); |
148 | 0 | } |
149 | | |
150 | | |
151 | | //--------------------------------------------------------------------------- |
152 | | // Clarify calculation precedence for ternary operators. |
153 | | //--------------------------------------------------------------------------- |
154 | | void CheckOther::clarifyCalculation() |
155 | 1.36k | { |
156 | 1.36k | if (!mSettings->severity.isEnabled(Severity::style)) |
157 | 0 | return; |
158 | | |
159 | 1.36k | logChecker("CheckOther::clarifyCalculation"); // style |
160 | | |
161 | 1.36k | const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); |
162 | 1.73k | for (const Scope * scope : symbolDatabase->functionScopes) { |
163 | 35.9k | for (const Token* tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) { |
164 | | // ? operator where lhs is arithmetical expression |
165 | 34.1k | if (tok->str() != "?" || !tok->astOperand1() || !tok->astOperand1()->isCalculation()) |
166 | 34.1k | continue; |
167 | 0 | if (!tok->astOperand1()->isArithmeticalOp() && tok->astOperand1()->tokType() != Token::eBitOp) |
168 | 0 | continue; |
169 | | |
170 | | // non-pointer calculation in lhs and pointer in rhs => no clarification is needed |
171 | 0 | if (tok->astOperand1()->isBinaryOp() && Token::Match(tok->astOperand1(), "%or%|&|%|*|/") && tok->astOperand2()->valueType() && tok->astOperand2()->valueType()->pointer > 0) |
172 | 0 | continue; |
173 | | |
174 | | // bit operation in lhs and char literals in rhs => probably no mistake |
175 | 0 | if (tok->astOperand1()->tokType() == Token::eBitOp && Token::Match(tok->astOperand2()->astOperand1(), "%char%") && Token::Match(tok->astOperand2()->astOperand2(), "%char%")) |
176 | 0 | continue; |
177 | | |
178 | | // 2nd operand in lhs has known integer value => probably no mistake |
179 | 0 | if (tok->astOperand1()->isBinaryOp() && tok->astOperand1()->astOperand2()->hasKnownIntValue()) { |
180 | 0 | const Token *op = tok->astOperand1()->astOperand2(); |
181 | 0 | if (op->isNumber()) |
182 | 0 | continue; |
183 | 0 | if (op->valueType() && op->valueType()->isEnum()) |
184 | 0 | continue; |
185 | 0 | } |
186 | | |
187 | | // Is code clarified by parentheses already? |
188 | 0 | const Token *tok2 = tok->astOperand1(); |
189 | 0 | for (; tok2; tok2 = tok2->next()) { |
190 | 0 | if (tok2->str() == "(") |
191 | 0 | tok2 = tok2->link(); |
192 | 0 | else if (tok2->str() == ")") |
193 | 0 | break; |
194 | 0 | else if (tok2->str() == "?") { |
195 | 0 | clarifyCalculationError(tok, tok->astOperand1()->str()); |
196 | 0 | break; |
197 | 0 | } |
198 | 0 | } |
199 | 0 | } |
200 | 1.73k | } |
201 | 1.36k | } |
202 | | |
203 | | void CheckOther::clarifyCalculationError(const Token *tok, const std::string &op) |
204 | 0 | { |
205 | | // suspicious calculation |
206 | 0 | const std::string calc("'a" + op + "b?c:d'"); |
207 | | |
208 | | // recommended calculation #1 |
209 | 0 | const std::string s1("'(a" + op + "b)?c:d'"); |
210 | | |
211 | | // recommended calculation #2 |
212 | 0 | const std::string s2("'a" + op + "(b?c:d)'"); |
213 | |
|
214 | 0 | reportError(tok, |
215 | 0 | Severity::style, |
216 | 0 | "clarifyCalculation", |
217 | 0 | "Clarify calculation precedence for '" + op + "' and '?'.\n" |
218 | 0 | "Suspicious calculation. Please use parentheses to clarify the code. " |
219 | 0 | "The code '" + calc + "' should be written as either '" + s1 + "' or '" + s2 + "'.", CWE783, Certainty::normal); |
220 | 0 | } |
221 | | |
222 | | //--------------------------------------------------------------------------- |
223 | | // Clarify (meaningless) statements like *foo++; with parentheses. |
224 | | //--------------------------------------------------------------------------- |
225 | | void CheckOther::clarifyStatement() |
226 | 1.36k | { |
227 | 1.36k | if (!mSettings->severity.isEnabled(Severity::warning)) |
228 | 0 | return; |
229 | | |
230 | 1.36k | logChecker("CheckOther::clarifyStatement"); // warning |
231 | | |
232 | 1.36k | const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); |
233 | 1.73k | for (const Scope * scope : symbolDatabase->functionScopes) { |
234 | 37.6k | for (const Token* tok = scope->bodyStart; tok && tok != scope->bodyEnd; tok = tok->next()) { |
235 | 35.9k | if (tok->astOperand1() && Token::Match(tok, "* %name%")) { |
236 | 153 | const Token *tok2 = tok->previous(); |
237 | | |
238 | 153 | while (tok2 && tok2->str() == "*") |
239 | 0 | tok2 = tok2->previous(); |
240 | | |
241 | 153 | if (tok2 && !tok2->astParent() && Token::Match(tok2, "[{};]")) { |
242 | 0 | tok2 = tok->astOperand1(); |
243 | 0 | if (Token::Match(tok2, "++|-- [;,]")) |
244 | 0 | clarifyStatementError(tok2); |
245 | 0 | } |
246 | 153 | } |
247 | 35.9k | } |
248 | 1.73k | } |
249 | 1.36k | } |
250 | | |
251 | | void CheckOther::clarifyStatementError(const Token *tok) |
252 | 0 | { |
253 | 0 | reportError(tok, Severity::warning, "clarifyStatement", "In expression like '*A++' the result of '*' is unused. Did you intend to write '(*A)++;'?\n" |
254 | 0 | "A statement like '*A++;' might not do what you intended. Postfix 'operator++' is executed before 'operator*'. " |
255 | 0 | "Thus, the dereference is meaningless. Did you intend to write '(*A)++;'?", CWE783, Certainty::normal); |
256 | 0 | } |
257 | | |
258 | | //--------------------------------------------------------------------------- |
259 | | // Check for suspicious occurrences of 'if(); {}'. |
260 | | //--------------------------------------------------------------------------- |
261 | | void CheckOther::checkSuspiciousSemicolon() |
262 | 1.36k | { |
263 | 1.36k | if (!mSettings->certainty.isEnabled(Certainty::inconclusive) || !mSettings->severity.isEnabled(Severity::warning)) |
264 | 0 | return; |
265 | | |
266 | 1.36k | const SymbolDatabase* const symbolDatabase = mTokenizer->getSymbolDatabase(); |
267 | | |
268 | 1.36k | logChecker("CheckOther::checkSuspiciousSemicolon"); // warning,inconclusive |
269 | | |
270 | | // Look for "if(); {}", "for(); {}" or "while(); {}" |
271 | 4.94k | for (const Scope &scope : symbolDatabase->scopeList) { |
272 | 4.94k | if (scope.type == Scope::eIf || scope.type == Scope::eElse || scope.type == Scope::eWhile || scope.type == Scope::eFor) { |
273 | | // Ensure the semicolon is at the same line number as the if/for/while statement |
274 | | // and the {..} block follows it without an extra empty line. |
275 | 1.84k | if (Token::simpleMatch(scope.bodyStart, "{ ; } {") && |
276 | 1.84k | scope.bodyStart->previous()->linenr() == scope.bodyStart->tokAt(2)->linenr() && |
277 | 1.84k | scope.bodyStart->linenr()+1 >= scope.bodyStart->tokAt(3)->linenr() && |
278 | 1.84k | !scope.bodyStart->tokAt(3)->isExpandedMacro()) { |
279 | 0 | suspiciousSemicolonError(scope.classDef); |
280 | 0 | } |
281 | 1.84k | } |
282 | 4.94k | } |
283 | 1.36k | } |
284 | | |
285 | | void CheckOther::suspiciousSemicolonError(const Token* tok) |
286 | 0 | { |
287 | 0 | reportError(tok, Severity::warning, "suspiciousSemicolon", |
288 | 0 | "Suspicious use of ; at the end of '" + (tok ? tok->str() : std::string()) + "' statement.", CWE398, Certainty::normal); |
289 | 0 | } |
290 | | |
291 | | |
292 | | //--------------------------------------------------------------------------- |
293 | | // For C++ code, warn if C-style casts are used on pointer types |
294 | | //--------------------------------------------------------------------------- |
295 | | void CheckOther::warningOldStylePointerCast() |
296 | 1.36k | { |
297 | | // Only valid on C++ code |
298 | 1.36k | if (!mSettings->severity.isEnabled(Severity::style) || !mTokenizer->isCPP()) |
299 | 0 | return; |
300 | | |
301 | 1.36k | logChecker("CheckOther::warningOldStylePointerCast"); // style,c++ |
302 | | |
303 | 1.36k | const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); |
304 | 1.73k | for (const Scope * scope : symbolDatabase->functionScopes) { |
305 | 1.73k | const Token* tok; |
306 | 1.73k | if (scope->function && scope->function->isConstructor()) |
307 | 0 | tok = scope->classDef; |
308 | 1.73k | else |
309 | 1.73k | tok = scope->bodyStart; |
310 | 37.6k | for (; tok && tok != scope->bodyEnd; tok = tok->next()) { |
311 | | // Old style pointer casting.. |
312 | 35.9k | if (!Token::Match(tok, "( const|volatile| const|volatile|class|struct| %type% * *| *| const|&| ) (| %name%|%num%|%bool%|%char%|%str%|&")) |
313 | 35.9k | continue; |
314 | 0 | if (Token::Match(tok->previous(), "%type%")) |
315 | 0 | continue; |
316 | | |
317 | | // skip first "const" in "const Type* const" |
318 | 0 | while (Token::Match(tok->next(), "const|volatile|class|struct")) |
319 | 0 | tok = tok->next(); |
320 | 0 | const Token* typeTok = tok->next(); |
321 | | // skip second "const" in "const Type* const" |
322 | 0 | if (tok->strAt(3) == "const") |
323 | 0 | tok = tok->next(); |
324 | |
|
325 | 0 | const Token *p = tok->tokAt(4); |
326 | 0 | if (p->hasKnownIntValue() && p->values().front().intvalue==0) // Casting nullpointers is safe |
327 | 0 | continue; |
328 | | |
329 | 0 | if (typeTok->tokType() == Token::eType || typeTok->tokType() == Token::eName) |
330 | 0 | cstyleCastError(tok); |
331 | 0 | } |
332 | 1.73k | } |
333 | 1.36k | } |
334 | | |
335 | | void CheckOther::cstyleCastError(const Token *tok) |
336 | 0 | { |
337 | 0 | reportError(tok, Severity::style, "cstyleCast", |
338 | 0 | "C-style pointer casting\n" |
339 | 0 | "C-style pointer casting detected. C++ offers four different kinds of casts as replacements: " |
340 | 0 | "static_cast, const_cast, dynamic_cast and reinterpret_cast. A C-style cast could evaluate to " |
341 | 0 | "any of those automatically, thus it is considered safer if the programmer explicitly states " |
342 | 0 | "which kind of cast is expected.", CWE398, Certainty::normal); |
343 | 0 | } |
344 | | |
345 | | //--------------------------------------------------------------------------- |
346 | | // float* f; double* d = (double*)f; <-- Pointer cast to a type with an incompatible binary data representation |
347 | | //--------------------------------------------------------------------------- |
348 | | |
349 | | void CheckOther::invalidPointerCast() |
350 | 1.36k | { |
351 | 1.36k | if (!mSettings->severity.isEnabled(Severity::portability)) |
352 | 0 | return; |
353 | | |
354 | 1.36k | logChecker("CheckOther::invalidPointerCast"); // portability |
355 | | |
356 | 1.36k | const bool printInconclusive = mSettings->certainty.isEnabled(Certainty::inconclusive); |
357 | 1.36k | const SymbolDatabase* const symbolDatabase = mTokenizer->getSymbolDatabase(); |
358 | 1.73k | for (const Scope * scope : symbolDatabase->functionScopes) { |
359 | 35.9k | for (const Token* tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) { |
360 | 34.1k | const Token* toTok = nullptr; |
361 | 34.1k | const Token* fromTok = nullptr; |
362 | | // Find cast |
363 | 34.1k | if (Token::Match(tok, "( const|volatile| const|volatile| %type% %type%| const| * )")) { |
364 | 0 | toTok = tok; |
365 | 0 | fromTok = tok->astOperand1(); |
366 | 34.1k | } else if (Token::simpleMatch(tok, "reinterpret_cast <") && tok->linkAt(1)) { |
367 | 0 | toTok = tok->linkAt(1)->next(); |
368 | 0 | fromTok = toTok->astOperand2(); |
369 | 0 | } |
370 | 34.1k | if (!fromTok) |
371 | 34.1k | continue; |
372 | | |
373 | 0 | const ValueType* fromType = fromTok->valueType(); |
374 | 0 | const ValueType* toType = toTok->valueType(); |
375 | 0 | if (!fromType || !toType || !fromType->pointer || !toType->pointer) |
376 | 0 | continue; |
377 | | |
378 | 0 | if (fromType->type != toType->type && fromType->type >= ValueType::Type::BOOL && toType->type >= ValueType::Type::BOOL && (toType->type != ValueType::Type::CHAR || printInconclusive)) { |
379 | 0 | if (toType->isIntegral() && fromType->isIntegral()) |
380 | 0 | continue; |
381 | | |
382 | 0 | invalidPointerCastError(tok, fromType->str(), toType->str(), toType->type == ValueType::Type::CHAR, toType->isIntegral()); |
383 | 0 | } |
384 | 0 | } |
385 | 1.73k | } |
386 | 1.36k | } |
387 | | |
388 | | |
389 | | void CheckOther::invalidPointerCastError(const Token* tok, const std::string& from, const std::string& to, bool inconclusive, bool toIsInt) |
390 | 0 | { |
391 | 0 | if (toIsInt) { // If we cast something to int*, this can be useful to play with its binary data representation |
392 | 0 | reportError(tok, Severity::portability, "invalidPointerCast", "Casting from " + from + " to " + to + " is not portable due to different binary data representations on different platforms.", CWE704, inconclusive ? Certainty::inconclusive : Certainty::normal); |
393 | 0 | } else |
394 | 0 | reportError(tok, Severity::portability, "invalidPointerCast", "Casting between " + from + " and " + to + " which have an incompatible binary data representation.", CWE704, Certainty::normal); |
395 | 0 | } |
396 | | |
397 | | |
398 | | //--------------------------------------------------------------------------- |
399 | | // Detect redundant assignments: x = 0; x = 4; |
400 | | //--------------------------------------------------------------------------- |
401 | | |
402 | | void CheckOther::checkRedundantAssignment() |
403 | 1.36k | { |
404 | 1.36k | if (!mSettings->severity.isEnabled(Severity::style)) |
405 | 0 | return; |
406 | | |
407 | 1.36k | logChecker("CheckOther::checkRedundantAssignment"); // style |
408 | | |
409 | 1.36k | const SymbolDatabase* symbolDatabase = mTokenizer->getSymbolDatabase(); |
410 | 1.73k | for (const Scope *scope : symbolDatabase->functionScopes) { |
411 | 1.73k | if (!scope->bodyStart) |
412 | 0 | continue; |
413 | 35.9k | for (const Token* tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) { |
414 | 34.1k | if (Token::simpleMatch(tok, "] (")) |
415 | | // todo: handle lambdas |
416 | 0 | break; |
417 | 34.1k | if (Token::simpleMatch(tok, "try {")) |
418 | | // todo: check try blocks |
419 | 0 | tok = tok->linkAt(1); |
420 | 34.1k | if ((tok->isAssignmentOp() || tok->tokType() == Token::eIncDecOp) && tok->astOperand1()) { |
421 | 3.34k | if (tok->astParent()) |
422 | 1.80k | continue; |
423 | | |
424 | | // Do not warn about redundant initialization when rhs is trivial |
425 | | // TODO : do not simplify the variable declarations |
426 | 1.53k | bool isInitialization = false; |
427 | 1.53k | if (Token::Match(tok->tokAt(-2), "; %var% =") && tok->tokAt(-2)->isSplittedVarDeclEq()) { |
428 | 0 | isInitialization = true; |
429 | 0 | bool trivial = true; |
430 | 0 | visitAstNodes(tok->astOperand2(), |
431 | 0 | [&](const Token *rhs) { |
432 | 0 | if (Token::simpleMatch(rhs, "{ 0 }")) |
433 | 0 | return ChildrenToVisit::none; |
434 | 0 | if (Token::Match(rhs, "%str%|%num%|%name%") && !rhs->varId()) |
435 | 0 | return ChildrenToVisit::none; |
436 | 0 | if (Token::Match(rhs, ":: %name%") && rhs->hasKnownIntValue()) |
437 | 0 | return ChildrenToVisit::none; |
438 | 0 | if (rhs->isCast()) |
439 | 0 | return ChildrenToVisit::op2; |
440 | 0 | trivial = false; |
441 | 0 | return ChildrenToVisit::done; |
442 | 0 | }); |
443 | 0 | if (trivial) |
444 | 0 | continue; |
445 | 0 | } |
446 | | |
447 | 1.53k | const Token* rhs = tok->astOperand2(); |
448 | | // Do not warn about assignment with 0 / NULL |
449 | 1.53k | if ((rhs && MathLib::isNullValue(rhs->str())) || isNullOperand(rhs)) |
450 | 4 | continue; |
451 | | |
452 | 1.53k | if (tok->astOperand1()->variable() && tok->astOperand1()->variable()->isReference()) |
453 | | // todo: check references |
454 | 0 | continue; |
455 | | |
456 | 1.53k | if (tok->astOperand1()->variable() && tok->astOperand1()->variable()->isStatic()) |
457 | | // todo: check static variables |
458 | 0 | continue; |
459 | | |
460 | 1.53k | bool inconclusive = false; |
461 | 1.53k | if (mTokenizer->isCPP() && tok->astOperand1()->valueType()) { |
462 | | // If there is a custom assignment operator => this is inconclusive |
463 | 1.14k | if (tok->astOperand1()->valueType()->typeScope) { |
464 | 0 | const std::string op = "operator" + tok->str(); |
465 | 0 | const std::list<Function>& fList = tok->astOperand1()->valueType()->typeScope->functionList; |
466 | 0 | inconclusive = std::any_of(fList.cbegin(), fList.cend(), [&](const Function& f) { |
467 | 0 | return f.name() == op; |
468 | 0 | }); |
469 | 0 | } |
470 | | // assigning a smart pointer has side effects |
471 | 1.14k | if (tok->astOperand1()->valueType()->type == ValueType::SMART_POINTER) |
472 | 0 | break; |
473 | 1.14k | } |
474 | 1.53k | if (inconclusive && !mSettings->certainty.isEnabled(Certainty::inconclusive)) |
475 | 0 | continue; |
476 | | |
477 | 1.53k | FwdAnalysis fwdAnalysis(mTokenizer->isCPP(), mSettings->library); |
478 | 1.53k | if (fwdAnalysis.hasOperand(tok->astOperand2(), tok->astOperand1())) |
479 | 207 | continue; |
480 | | |
481 | | // Is there a redundant assignment? |
482 | 1.32k | const Token *start; |
483 | 1.32k | if (tok->isAssignmentOp()) |
484 | 1.32k | start = tok->next(); |
485 | 0 | else |
486 | 0 | start = tok->findExpressionStartEndTokens().second->next(); |
487 | | |
488 | | // Get next assignment.. |
489 | 1.32k | const Token *nextAssign = fwdAnalysis.reassign(tok->astOperand1(), start, scope->bodyEnd); |
490 | | |
491 | 1.32k | if (!nextAssign) |
492 | 1.26k | continue; |
493 | | |
494 | | // there is redundant assignment. Is there a case between the assignments? |
495 | 62 | bool hasCase = false; |
496 | 468 | for (const Token *tok2 = tok; tok2 != nextAssign; tok2 = tok2->next()) { |
497 | 407 | if (tok2->str() == "break" || tok2->str() == "return") |
498 | 1 | break; |
499 | 406 | if (tok2->str() == "case") { |
500 | 0 | hasCase = true; |
501 | 0 | break; |
502 | 0 | } |
503 | 406 | } |
504 | | |
505 | | // warn |
506 | 62 | if (hasCase) |
507 | 0 | redundantAssignmentInSwitchError(tok, nextAssign, tok->astOperand1()->expressionString()); |
508 | 62 | else if (isInitialization) |
509 | 0 | redundantInitializationError(tok, nextAssign, tok->astOperand1()->expressionString(), inconclusive); |
510 | 62 | else |
511 | 62 | redundantAssignmentError(tok, nextAssign, tok->astOperand1()->expressionString(), inconclusive); |
512 | 62 | } |
513 | 34.1k | } |
514 | 1.73k | } |
515 | 1.36k | } |
516 | | |
517 | | void CheckOther::redundantCopyError(const Token *tok1, const Token* tok2, const std::string& var) |
518 | 0 | { |
519 | 0 | const std::list<const Token *> callstack = { tok1, tok2 }; |
520 | 0 | reportError(callstack, Severity::performance, "redundantCopy", |
521 | 0 | "$symbol:" + var + "\n" |
522 | 0 | "Buffer '$symbol' is being written before its old content has been used.", CWE563, Certainty::normal); |
523 | 0 | } |
524 | | |
525 | | void CheckOther::redundantAssignmentError(const Token *tok1, const Token* tok2, const std::string& var, bool inconclusive) |
526 | 62 | { |
527 | 62 | const ErrorPath errorPath = { ErrorPathItem(tok1, var + " is assigned"), ErrorPathItem(tok2, var + " is overwritten") }; |
528 | 62 | if (inconclusive) |
529 | 0 | reportError(errorPath, Severity::style, "redundantAssignment", |
530 | 0 | "$symbol:" + var + "\n" |
531 | 0 | "Variable '$symbol' is reassigned a value before the old one has been used if variable is no semaphore variable.\n" |
532 | 0 | "Variable '$symbol' is reassigned a value before the old one has been used. Make sure that this variable is not used like a semaphore in a threading environment before simplifying this code.", CWE563, Certainty::inconclusive); |
533 | 62 | else |
534 | 62 | reportError(errorPath, Severity::style, "redundantAssignment", |
535 | 62 | "$symbol:" + var + "\n" |
536 | 62 | "Variable '$symbol' is reassigned a value before the old one has been used.", CWE563, Certainty::normal); |
537 | 62 | } |
538 | | |
539 | | void CheckOther::redundantInitializationError(const Token *tok1, const Token* tok2, const std::string& var, bool inconclusive) |
540 | 0 | { |
541 | 0 | const ErrorPath errorPath = { ErrorPathItem(tok1, var + " is initialized"), ErrorPathItem(tok2, var + " is overwritten") }; |
542 | 0 | reportError(errorPath, Severity::style, "redundantInitialization", |
543 | 0 | "$symbol:" + var + "\nRedundant initialization for '$symbol'. The initialized value is overwritten before it is read.", |
544 | 0 | CWE563, |
545 | 0 | inconclusive ? Certainty::inconclusive : Certainty::normal); |
546 | 0 | } |
547 | | |
548 | | void CheckOther::redundantAssignmentInSwitchError(const Token *tok1, const Token* tok2, const std::string &var) |
549 | 0 | { |
550 | 0 | const ErrorPath errorPath = { ErrorPathItem(tok1, "$symbol is assigned"), ErrorPathItem(tok2, "$symbol is overwritten") }; |
551 | 0 | reportError(errorPath, Severity::style, "redundantAssignInSwitch", |
552 | 0 | "$symbol:" + var + "\n" |
553 | 0 | "Variable '$symbol' is reassigned a value before the old one has been used. 'break;' missing?", CWE563, Certainty::normal); |
554 | 0 | } |
555 | | |
556 | | |
557 | | //--------------------------------------------------------------------------- |
558 | | // switch (x) |
559 | | // { |
560 | | // case 2: |
561 | | // y = a; // <- this assignment is redundant |
562 | | // case 3: |
563 | | // y = b; // <- case 2 falls through and sets y twice |
564 | | // } |
565 | | //--------------------------------------------------------------------------- |
566 | | static inline bool isFunctionOrBreakPattern(const Token *tok) |
567 | 0 | { |
568 | 0 | return Token::Match(tok, "%name% (") || Token::Match(tok, "break|continue|return|exit|goto|throw"); |
569 | 0 | } |
570 | | |
571 | | void CheckOther::redundantBitwiseOperationInSwitchError() |
572 | 1.36k | { |
573 | 1.36k | if (!mSettings->severity.isEnabled(Severity::warning)) |
574 | 0 | return; |
575 | | |
576 | 1.36k | logChecker("CheckOther::redundantBitwiseOperationInSwitch"); // warning |
577 | | |
578 | 1.36k | const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); |
579 | | |
580 | | // Find the beginning of a switch. E.g.: |
581 | | // switch (var) { ... |
582 | 4.94k | for (const Scope &switchScope : symbolDatabase->scopeList) { |
583 | 4.94k | if (switchScope.type != Scope::eSwitch || !switchScope.bodyStart) |
584 | 4.94k | continue; |
585 | | |
586 | | // Check the contents of the switch statement |
587 | 0 | std::map<int, const Token*> varsWithBitsSet; |
588 | 0 | std::map<int, std::string> bitOperations; |
589 | |
|
590 | 0 | for (const Token *tok2 = switchScope.bodyStart->next(); tok2 != switchScope.bodyEnd; tok2 = tok2->next()) { |
591 | 0 | if (tok2->str() == "{") { |
592 | | // Inside a conditional or loop. Don't mark variable accesses as being redundant. E.g.: |
593 | | // case 3: b = 1; |
594 | | // case 4: if (a) { b = 2; } // Doesn't make the b=1 redundant because it's conditional |
595 | 0 | if (Token::Match(tok2->previous(), ")|else {") && tok2->link()) { |
596 | 0 | const Token* endOfConditional = tok2->link(); |
597 | 0 | for (const Token* tok3 = tok2; tok3 != endOfConditional; tok3 = tok3->next()) { |
598 | 0 | if (tok3->varId() != 0) { |
599 | 0 | varsWithBitsSet.erase(tok3->varId()); |
600 | 0 | bitOperations.erase(tok3->varId()); |
601 | 0 | } else if (isFunctionOrBreakPattern(tok3)) { |
602 | 0 | varsWithBitsSet.clear(); |
603 | 0 | bitOperations.clear(); |
604 | 0 | } |
605 | 0 | } |
606 | 0 | tok2 = endOfConditional; |
607 | 0 | } |
608 | 0 | } |
609 | | |
610 | | // Variable assignment. Report an error if it's assigned to twice before a break. E.g.: |
611 | | // case 3: b = 1; // <== redundant |
612 | | // case 4: b = 2; |
613 | |
|
614 | 0 | if (Token::Match(tok2->previous(), ";|{|}|: %var% = %any% ;")) { |
615 | 0 | varsWithBitsSet.erase(tok2->varId()); |
616 | 0 | bitOperations.erase(tok2->varId()); |
617 | 0 | } |
618 | | |
619 | | // Bitwise operation. Report an error if it's performed twice before a break. E.g.: |
620 | | // case 3: b |= 1; // <== redundant |
621 | | // case 4: b |= 1; |
622 | 0 | else if (Token::Match(tok2->previous(), ";|{|}|: %var% %assign% %num% ;") && |
623 | 0 | (tok2->strAt(1) == "|=" || tok2->strAt(1) == "&=") && |
624 | 0 | Token::Match(tok2->next()->astOperand2(), "%num%")) { |
625 | 0 | const std::string bitOp = tok2->strAt(1)[0] + tok2->strAt(2); |
626 | 0 | const std::map<int, const Token*>::const_iterator i2 = varsWithBitsSet.find(tok2->varId()); |
627 | | |
628 | | // This variable has not had a bit operation performed on it yet, so just make a note of it |
629 | 0 | if (i2 == varsWithBitsSet.end()) { |
630 | 0 | varsWithBitsSet[tok2->varId()] = tok2; |
631 | 0 | bitOperations[tok2->varId()] = bitOp; |
632 | 0 | } |
633 | | |
634 | | // The same bit operation has been performed on the same variable twice, so report an error |
635 | 0 | else if (bitOperations[tok2->varId()] == bitOp) |
636 | 0 | redundantBitwiseOperationInSwitchError(i2->second, i2->second->str()); |
637 | | |
638 | | // A different bit operation was performed on the variable, so clear it |
639 | 0 | else { |
640 | 0 | varsWithBitsSet.erase(tok2->varId()); |
641 | 0 | bitOperations.erase(tok2->varId()); |
642 | 0 | } |
643 | 0 | } |
644 | | |
645 | | // Bitwise operation. Report an error if it's performed twice before a break. E.g.: |
646 | | // case 3: b = b | 1; // <== redundant |
647 | | // case 4: b = b | 1; |
648 | 0 | else if (Token::Match(tok2->previous(), ";|{|}|: %var% = %name% %or%|& %num% ;") && |
649 | 0 | tok2->varId() == tok2->tokAt(2)->varId()) { |
650 | 0 | const std::string bitOp = tok2->strAt(3) + tok2->strAt(4); |
651 | 0 | const std::map<int, const Token*>::const_iterator i2 = varsWithBitsSet.find(tok2->varId()); |
652 | | |
653 | | // This variable has not had a bit operation performed on it yet, so just make a note of it |
654 | 0 | if (i2 == varsWithBitsSet.end()) { |
655 | 0 | varsWithBitsSet[tok2->varId()] = tok2; |
656 | 0 | bitOperations[tok2->varId()] = bitOp; |
657 | 0 | } |
658 | | |
659 | | // The same bit operation has been performed on the same variable twice, so report an error |
660 | 0 | else if (bitOperations[tok2->varId()] == bitOp) |
661 | 0 | redundantBitwiseOperationInSwitchError(i2->second, i2->second->str()); |
662 | | |
663 | | // A different bit operation was performed on the variable, so clear it |
664 | 0 | else { |
665 | 0 | varsWithBitsSet.erase(tok2->varId()); |
666 | 0 | bitOperations.erase(tok2->varId()); |
667 | 0 | } |
668 | 0 | } |
669 | | |
670 | | // Not a simple assignment so there may be good reason if this variable is assigned to twice. E.g.: |
671 | | // case 3: b = 1; |
672 | | // case 4: b++; |
673 | 0 | else if (tok2->varId() != 0 && tok2->strAt(1) != "|" && tok2->strAt(1) != "&") { |
674 | 0 | varsWithBitsSet.erase(tok2->varId()); |
675 | 0 | bitOperations.erase(tok2->varId()); |
676 | 0 | } |
677 | | |
678 | | // Reset our record of assignments if there is a break or function call. E.g.: |
679 | | // case 3: b = 1; break; |
680 | 0 | if (isFunctionOrBreakPattern(tok2)) { |
681 | 0 | varsWithBitsSet.clear(); |
682 | 0 | bitOperations.clear(); |
683 | 0 | } |
684 | 0 | } |
685 | 0 | } |
686 | 1.36k | } |
687 | | |
688 | | void CheckOther::redundantBitwiseOperationInSwitchError(const Token *tok, const std::string &varname) |
689 | 0 | { |
690 | 0 | reportError(tok, Severity::style, |
691 | 0 | "redundantBitwiseOperationInSwitch", |
692 | 0 | "$symbol:" + varname + "\n" |
693 | 0 | "Redundant bitwise operation on '$symbol' in 'switch' statement. 'break;' missing?"); |
694 | 0 | } |
695 | | |
696 | | |
697 | | //--------------------------------------------------------------------------- |
698 | | // Check for statements like case A||B: in switch() |
699 | | //--------------------------------------------------------------------------- |
700 | | void CheckOther::checkSuspiciousCaseInSwitch() |
701 | 1.36k | { |
702 | 1.36k | if (!mSettings->certainty.isEnabled(Certainty::inconclusive) || !mSettings->severity.isEnabled(Severity::warning)) |
703 | 0 | return; |
704 | | |
705 | 1.36k | logChecker("CheckOther::checkSuspiciousCaseInSwitch"); // warning,inconclusive |
706 | | |
707 | 1.36k | const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); |
708 | | |
709 | 4.94k | for (const Scope & scope : symbolDatabase->scopeList) { |
710 | 4.94k | if (scope.type != Scope::eSwitch) |
711 | 4.94k | continue; |
712 | | |
713 | 0 | for (const Token* tok = scope.bodyStart->next(); tok != scope.bodyEnd; tok = tok->next()) { |
714 | 0 | if (tok->str() == "case") { |
715 | 0 | const Token* finding = nullptr; |
716 | 0 | for (const Token* tok2 = tok->next(); tok2; tok2 = tok2->next()) { |
717 | 0 | if (tok2->str() == ":") |
718 | 0 | break; |
719 | 0 | if (Token::Match(tok2, "[;}{]")) |
720 | 0 | break; |
721 | | |
722 | 0 | if (tok2->str() == "?") |
723 | 0 | finding = nullptr; |
724 | 0 | else if (Token::Match(tok2, "&&|%oror%")) |
725 | 0 | finding = tok2; |
726 | 0 | } |
727 | 0 | if (finding) |
728 | 0 | suspiciousCaseInSwitchError(finding, finding->str()); |
729 | 0 | } |
730 | 0 | } |
731 | 0 | } |
732 | 1.36k | } |
733 | | |
734 | | void CheckOther::suspiciousCaseInSwitchError(const Token* tok, const std::string& operatorString) |
735 | 0 | { |
736 | 0 | reportError(tok, Severity::warning, "suspiciousCase", |
737 | 0 | "Found suspicious case label in switch(). Operator '" + operatorString + "' probably doesn't work as intended.\n" |
738 | 0 | "Using an operator like '" + operatorString + "' in a case label is suspicious. Did you intend to use a bitwise operator, multiple case labels or if/else instead?", CWE398, Certainty::inconclusive); |
739 | 0 | } |
740 | | |
741 | | //--------------------------------------------------------------------------- |
742 | | // Find consecutive return, break, continue, goto or throw statements. e.g.: |
743 | | // break; break; |
744 | | // Detect dead code, that follows such a statement. e.g.: |
745 | | // return(0); foo(); |
746 | | //--------------------------------------------------------------------------- |
747 | | void CheckOther::checkUnreachableCode() |
748 | 1.36k | { |
749 | 1.36k | if (!mSettings->severity.isEnabled(Severity::style)) |
750 | 0 | return; |
751 | | |
752 | 1.36k | logChecker("CheckOther::checkUnreachableCode"); // style |
753 | | |
754 | 1.36k | const bool printInconclusive = mSettings->certainty.isEnabled(Certainty::inconclusive); |
755 | 1.36k | const SymbolDatabase* symbolDatabase = mTokenizer->getSymbolDatabase(); |
756 | 1.73k | for (const Scope * scope : symbolDatabase->functionScopes) { |
757 | 19.0k | for (const Token* tok = scope->bodyStart; tok && tok != scope->bodyEnd; tok = tok->next()) { |
758 | 17.3k | const Token* secondBreak = nullptr; |
759 | 17.3k | const Token* labelName = nullptr; |
760 | 17.3k | if (tok->link() && Token::Match(tok, "(|[|<")) |
761 | 1.55k | tok = tok->link(); |
762 | 15.7k | else if (Token::Match(tok, "break|continue ;")) |
763 | 0 | secondBreak = tok->tokAt(2); |
764 | 15.7k | else if (Token::Match(tok, "[;{}:] return|throw") && tok->next()->isKeyword()) { |
765 | 1.91k | if (Token::simpleMatch(tok->astParent(), "?")) |
766 | 0 | continue; |
767 | 1.91k | tok = tok->next(); // tok should point to return or throw |
768 | 4.81k | for (const Token *tok2 = tok->next(); tok2; tok2 = tok2->next()) { |
769 | 4.81k | if (tok2->str() == "(" || tok2->str() == "{") |
770 | 113 | tok2 = tok2->link(); |
771 | 4.81k | if (tok2->str() == ";") { |
772 | 1.91k | secondBreak = tok2->next(); |
773 | 1.91k | break; |
774 | 1.91k | } |
775 | 4.81k | } |
776 | 13.8k | } else if (Token::Match(tok, "goto %any% ;")) { |
777 | 0 | secondBreak = tok->tokAt(3); |
778 | 0 | labelName = tok->next(); |
779 | 13.8k | } else if (Token::Match(tok, "%name% (") && mSettings->library.isnoreturn(tok) && !Token::Match(tok->next()->astParent(), "?|:")) { |
780 | 0 | if ((!tok->function() || (tok->function()->token != tok && tok->function()->tokenDef != tok)) && tok->linkAt(1)->strAt(1) != "{") |
781 | 0 | secondBreak = tok->linkAt(1)->tokAt(2); |
782 | 0 | if (Token::simpleMatch(secondBreak, "return")) { |
783 | | // clarification for tools that function returns |
784 | 0 | continue; |
785 | 0 | } |
786 | 0 | } |
787 | | |
788 | | // Statements follow directly, no line between them. (#3383) |
789 | | // TODO: Try to find a better way to avoid false positives due to preprocessor configurations. |
790 | 17.3k | const bool inconclusive = secondBreak && (secondBreak->linenr() - 1 > secondBreak->previous()->linenr()); |
791 | | |
792 | 17.3k | if (secondBreak && (printInconclusive || !inconclusive)) { |
793 | 1.91k | if (Token::Match(secondBreak, "continue|goto|throw|return") && secondBreak->isKeyword()) { |
794 | 0 | duplicateBreakError(secondBreak, inconclusive); |
795 | 0 | tok = Token::findmatch(secondBreak, "[}:]"); |
796 | 1.91k | } else if (secondBreak->str() == "break") { // break inside switch as second break statement should not issue a warning |
797 | 0 | if (tok->str() == "break") // If the previous was a break, too: Issue warning |
798 | 0 | duplicateBreakError(secondBreak, inconclusive); |
799 | 0 | else { |
800 | 0 | if (tok->scope()->type != Scope::eSwitch) // Check, if the enclosing scope is a switch |
801 | 0 | duplicateBreakError(secondBreak, inconclusive); |
802 | 0 | } |
803 | 0 | tok = Token::findmatch(secondBreak, "[}:]"); |
804 | 1.91k | } else if (!Token::Match(secondBreak, "return|}|case|default") && secondBreak->strAt(1) != ":") { // TODO: No bailout for unconditional scopes |
805 | | // If the goto label is followed by a loop construct in which the label is defined it's quite likely |
806 | | // that the goto jump was intended to skip some code on the first loop iteration. |
807 | 0 | bool labelInFollowingLoop = false; |
808 | 0 | if (labelName && Token::Match(secondBreak, "while|do|for")) { |
809 | 0 | const Token *scope2 = Token::findsimplematch(secondBreak, "{"); |
810 | 0 | if (scope2) { |
811 | 0 | for (const Token *tokIter = scope2; tokIter != scope2->link() && tokIter; tokIter = tokIter->next()) { |
812 | 0 | if (Token::Match(tokIter, "[;{}] %any% :") && labelName->str() == tokIter->strAt(1)) { |
813 | 0 | labelInFollowingLoop = true; |
814 | 0 | break; |
815 | 0 | } |
816 | 0 | } |
817 | 0 | } |
818 | 0 | } |
819 | | |
820 | | // hide FP for statements that just hide compiler warnings about unused function arguments |
821 | 0 | bool silencedCompilerWarningOnly = false; |
822 | 0 | const Token *silencedWarning = secondBreak; |
823 | 0 | for (;;) { |
824 | 0 | if (Token::Match(silencedWarning, "( void ) %name% ;")) { |
825 | 0 | silencedWarning = silencedWarning->tokAt(5); |
826 | 0 | continue; |
827 | 0 | } |
828 | 0 | if (silencedWarning && silencedWarning == scope->bodyEnd) |
829 | 0 | silencedCompilerWarningOnly = true; |
830 | 0 | break; |
831 | 0 | } |
832 | 0 | if (silencedWarning) |
833 | 0 | secondBreak = silencedWarning; |
834 | |
|
835 | 0 | if (!labelInFollowingLoop && !silencedCompilerWarningOnly) |
836 | 0 | unreachableCodeError(secondBreak, tok, inconclusive); |
837 | 0 | tok = Token::findmatch(secondBreak, "[}:]"); |
838 | 1.91k | } else if (secondBreak->scope() && secondBreak->scope()->isLoopScope() && secondBreak->str() == "}" && tok->str() == "continue") { |
839 | 0 | redundantContinueError(tok); |
840 | 0 | tok = secondBreak; |
841 | 0 | } else |
842 | 1.91k | tok = secondBreak; |
843 | | |
844 | 1.91k | if (!tok) |
845 | 0 | break; |
846 | 1.91k | tok = tok->previous(); // Will be advanced again by for loop |
847 | 1.91k | } |
848 | 17.3k | } |
849 | 1.73k | } |
850 | 1.36k | } |
851 | | |
852 | | void CheckOther::duplicateBreakError(const Token *tok, bool inconclusive) |
853 | 0 | { |
854 | 0 | reportError(tok, Severity::style, "duplicateBreak", |
855 | 0 | "Consecutive return, break, continue, goto or throw statements are unnecessary.\n" |
856 | 0 | "Consecutive return, break, continue, goto or throw statements are unnecessary. " |
857 | 0 | "The second statement can never be executed, and so should be removed.", CWE561, inconclusive ? Certainty::inconclusive : Certainty::normal); |
858 | 0 | } |
859 | | |
860 | | void CheckOther::unreachableCodeError(const Token *tok, const Token* noreturn, bool inconclusive) |
861 | 0 | { |
862 | 0 | std::string msg = "Statements following "; |
863 | 0 | if (noreturn && (noreturn->function() || mSettings->library.isnoreturn(noreturn))) |
864 | 0 | msg += "noreturn function '" + noreturn->str() + "()'"; |
865 | 0 | else if (noreturn && noreturn->isKeyword()) |
866 | 0 | msg += "'" + noreturn->str() + "'"; |
867 | 0 | else |
868 | 0 | msg += "return, break, continue, goto or throw"; |
869 | 0 | msg += " will never be executed."; |
870 | 0 | reportError(tok, Severity::style, "unreachableCode", |
871 | 0 | msg, CWE561, inconclusive ? Certainty::inconclusive : Certainty::normal); |
872 | 0 | } |
873 | | |
874 | | void CheckOther::redundantContinueError(const Token *tok) |
875 | 0 | { |
876 | 0 | reportError(tok, Severity::style, "redundantContinue", |
877 | 0 | "'continue' is redundant since it is the last statement in a loop.", CWE561, Certainty::normal); |
878 | 0 | } |
879 | | |
880 | 0 | static bool isSimpleExpr(const Token* tok, const Variable* var, const Settings* settings) { |
881 | 0 | if (!tok) |
882 | 0 | return false; |
883 | 0 | if (tok->isNumber() || tok->tokType() == Token::eString || tok->tokType() == Token::eChar || tok->isBoolean()) |
884 | 0 | return true; |
885 | 0 | bool needsCheck = tok->varId() > 0; |
886 | 0 | if (!needsCheck) { |
887 | 0 | const Token* ftok = tok->previous(); |
888 | 0 | if (Token::Match(ftok, "%name% (") && |
889 | 0 | ((ftok->function() && ftok->function()->isConst()) || settings->library.isFunctionConst(ftok->str(), /*pure*/ true))) |
890 | 0 | needsCheck = true; |
891 | 0 | } |
892 | 0 | return (needsCheck && !isExpressionChanged(tok, tok->astParent(), var->scope()->bodyEnd, settings, true)); |
893 | 0 | } |
894 | | |
895 | | //--------------------------------------------------------------------------- |
896 | | // Check scope of variables.. |
897 | | //--------------------------------------------------------------------------- |
898 | | void CheckOther::checkVariableScope() |
899 | 1.36k | { |
900 | 1.36k | if (mSettings->clang) |
901 | 0 | return; |
902 | | |
903 | 1.36k | if (!mSettings->severity.isEnabled(Severity::style)) |
904 | 0 | return; |
905 | | |
906 | 1.36k | const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); |
907 | | |
908 | | // In C it is common practice to declare local variables at the |
909 | | // start of functions. |
910 | 1.36k | if (mSettings->daca && mTokenizer->isC()) |
911 | 0 | return; |
912 | | |
913 | 1.36k | logChecker("CheckOther::checkVariableScope"); // style,notclang |
914 | | |
915 | 8.16k | for (const Variable* var : symbolDatabase->variableList()) { |
916 | 8.16k | if (!var || !var->isLocal() || var->isConst()) |
917 | 8.16k | continue; |
918 | | |
919 | 0 | const bool isPtrOrRef = var->isPointer() || var->isReference(); |
920 | 0 | const bool isSimpleType = var->typeStartToken()->isStandardType() || var->typeStartToken()->isEnumType() || (mTokenizer->isC() && var->type() && var->type()->isStructType()); |
921 | 0 | if (!isPtrOrRef && !isSimpleType && !astIsContainer(var->nameToken())) |
922 | 0 | continue; |
923 | | |
924 | 0 | if (mTokenizer->hasIfdef(var->nameToken(), var->scope()->bodyEnd)) |
925 | 0 | continue; |
926 | | |
927 | | // reference of range for loop variable.. |
928 | 0 | if (Token::Match(var->nameToken()->previous(), "& %var% = %var% .")) { |
929 | 0 | const Token *otherVarToken = var->nameToken()->tokAt(2); |
930 | 0 | const Variable *otherVar = otherVarToken->variable(); |
931 | 0 | if (otherVar && Token::Match(otherVar->nameToken(), "%var% :") && |
932 | 0 | otherVar->nameToken()->next()->astParent() && |
933 | 0 | Token::simpleMatch(otherVar->nameToken()->next()->astParent()->previous(), "for (")) |
934 | 0 | continue; |
935 | 0 | } |
936 | | |
937 | 0 | bool forHead = false; // Don't check variables declared in header of a for loop |
938 | 0 | for (const Token* tok = var->typeStartToken(); tok; tok = tok->previous()) { |
939 | 0 | if (tok->str() == "(") { |
940 | 0 | forHead = true; |
941 | 0 | break; |
942 | 0 | } |
943 | 0 | if (Token::Match(tok, "[;{}]")) |
944 | 0 | break; |
945 | 0 | } |
946 | 0 | if (forHead) |
947 | 0 | continue; |
948 | | |
949 | 0 | const Token* tok = var->nameToken()->next(); |
950 | 0 | bool isConstructor = false; |
951 | 0 | if (Token::Match(tok, "; %varid% =", var->declarationId())) { // bailout for assignment |
952 | 0 | tok = tok->tokAt(2)->astOperand2(); |
953 | 0 | if (!isSimpleExpr(tok, var, mSettings)) |
954 | 0 | continue; |
955 | 0 | } |
956 | 0 | else if (Token::Match(tok, "{|(")) { // bailout for constructor |
957 | 0 | isConstructor = true; |
958 | 0 | const Token* argTok = tok->astOperand2(); |
959 | 0 | bool bail = false; |
960 | 0 | while (argTok) { |
961 | 0 | if (Token::simpleMatch(argTok, ",")) { |
962 | 0 | if (!isSimpleExpr(argTok->astOperand2(), var, mSettings)) { |
963 | 0 | bail = true; |
964 | 0 | break; |
965 | 0 | } |
966 | 0 | } else if (argTok->str() != "." && !isSimpleExpr(argTok, var, mSettings)) { |
967 | 0 | bail = true; |
968 | 0 | break; |
969 | 0 | } |
970 | 0 | argTok = argTok->astOperand1(); |
971 | 0 | } |
972 | 0 | if (bail) |
973 | 0 | continue; |
974 | 0 | } |
975 | | // bailout if initialized with function call that has possible side effects |
976 | 0 | if (!isConstructor && Token::Match(tok, "[(=]") && Token::simpleMatch(tok->astOperand2(), "(")) |
977 | 0 | continue; |
978 | 0 | bool reduce = true; |
979 | 0 | bool used = false; // Don't warn about unused variables |
980 | 0 | for (; tok && tok != var->scope()->bodyEnd; tok = tok->next()) { |
981 | 0 | if (tok->str() == "{" && tok->scope() != tok->previous()->scope() && !tok->isExpandedMacro() && !isWithinScope(tok, var, Scope::ScopeType::eLambda)) { |
982 | 0 | if (used) { |
983 | 0 | bool used2 = false; |
984 | 0 | if (!checkInnerScope(tok, var, used2) || used2) { |
985 | 0 | reduce = false; |
986 | 0 | break; |
987 | 0 | } |
988 | 0 | } else if (!checkInnerScope(tok, var, used)) { |
989 | 0 | reduce = false; |
990 | 0 | break; |
991 | 0 | } |
992 | | |
993 | 0 | tok = tok->link(); |
994 | | |
995 | | // parse else if blocks.. |
996 | 0 | } else if (Token::simpleMatch(tok, "else { if (") && Token::simpleMatch(tok->linkAt(3), ") {")) { |
997 | 0 | const Token *endif = tok->linkAt(3)->linkAt(1); |
998 | 0 | bool elseif = false; |
999 | 0 | if (Token::simpleMatch(endif, "} }")) |
1000 | 0 | elseif = true; |
1001 | 0 | else if (Token::simpleMatch(endif, "} else {") && Token::simpleMatch(endif->linkAt(2),"} }")) |
1002 | 0 | elseif = true; |
1003 | 0 | if (elseif && Token::findmatch(tok->next(), "%varid%", tok->linkAt(1), var->declarationId())) { |
1004 | 0 | reduce = false; |
1005 | 0 | break; |
1006 | 0 | } |
1007 | 0 | } else if (tok->varId() == var->declarationId() || tok->str() == "goto") { |
1008 | 0 | reduce = false; |
1009 | 0 | break; |
1010 | 0 | } |
1011 | 0 | } |
1012 | |
|
1013 | 0 | if (reduce && used) |
1014 | 0 | variableScopeError(var->nameToken(), var->name()); |
1015 | 0 | } |
1016 | 1.36k | } |
1017 | | |
1018 | | bool CheckOther::checkInnerScope(const Token *tok, const Variable* var, bool& used) const |
1019 | 0 | { |
1020 | 0 | const Scope* scope = tok->next()->scope(); |
1021 | 0 | bool loopVariable = scope->isLoopScope(); |
1022 | 0 | bool noContinue = true; |
1023 | 0 | const Token* forHeadEnd = nullptr; |
1024 | 0 | const Token* end = tok->link(); |
1025 | 0 | if (scope->type == Scope::eUnconditional && (tok->strAt(-1) == ")" || tok->previous()->isName())) // Might be an unknown macro like BOOST_FOREACH |
1026 | 0 | loopVariable = true; |
1027 | |
|
1028 | 0 | if (scope->type == Scope::eDo) { |
1029 | 0 | end = end->linkAt(2); |
1030 | 0 | } else if (loopVariable && tok->strAt(-1) == ")") { |
1031 | 0 | tok = tok->linkAt(-1); // Jump to opening ( of for/while statement |
1032 | 0 | } else if (scope->type == Scope::eSwitch) { |
1033 | 0 | for (const Scope* innerScope : scope->nestedList) { |
1034 | 0 | if (used) { |
1035 | 0 | bool used2 = false; |
1036 | 0 | if (!checkInnerScope(innerScope->bodyStart, var, used2) || used2) { |
1037 | 0 | return false; |
1038 | 0 | } |
1039 | 0 | } else if (!checkInnerScope(innerScope->bodyStart, var, used)) { |
1040 | 0 | return false; |
1041 | 0 | } |
1042 | 0 | } |
1043 | 0 | } |
1044 | | |
1045 | 0 | bool bFirstAssignment=false; |
1046 | 0 | for (; tok && tok != end; tok = tok->next()) { |
1047 | 0 | if (tok->str() == "goto") |
1048 | 0 | return false; |
1049 | 0 | if (tok->str() == "continue") |
1050 | 0 | noContinue = false; |
1051 | |
|
1052 | 0 | if (Token::simpleMatch(tok, "for (")) |
1053 | 0 | forHeadEnd = tok->linkAt(1); |
1054 | 0 | if (tok == forHeadEnd) |
1055 | 0 | forHeadEnd = nullptr; |
1056 | |
|
1057 | 0 | if (loopVariable && noContinue && tok->scope() == scope && !forHeadEnd && scope->type != Scope::eSwitch && Token::Match(tok, "%varid% =", var->declarationId())) { // Assigned in outer scope. |
1058 | 0 | loopVariable = false; |
1059 | 0 | std::pair<const Token*, const Token*> range = tok->next()->findExpressionStartEndTokens(); |
1060 | 0 | if (range.first) |
1061 | 0 | range.first = range.first->next(); |
1062 | 0 | const Token* exprTok = findExpression(var->nameToken()->exprId(), range.first, range.second, [&](const Token* tok2) { |
1063 | 0 | return tok2->varId() == var->declarationId(); |
1064 | 0 | }); |
1065 | 0 | if (exprTok) { |
1066 | 0 | tok = exprTok; |
1067 | 0 | loopVariable = true; |
1068 | 0 | } |
1069 | 0 | } |
1070 | |
|
1071 | 0 | if (loopVariable && Token::Match(tok, "%varid% !!=", var->declarationId())) // Variable used in loop |
1072 | 0 | return false; |
1073 | | |
1074 | 0 | if (Token::Match(tok, "& %varid%", var->declarationId())) // Taking address of variable |
1075 | 0 | return false; |
1076 | | |
1077 | 0 | if (Token::Match(tok, "%varid% =", var->declarationId())) { |
1078 | 0 | if (!bFirstAssignment && var->isInit() && Token::findmatch(tok->tokAt(2), "%varid%", Token::findsimplematch(tok->tokAt(3), ";"), var->declarationId())) |
1079 | 0 | return false; |
1080 | 0 | bFirstAssignment = true; |
1081 | 0 | } |
1082 | | |
1083 | 0 | if (!bFirstAssignment && Token::Match(tok, "* %varid%", var->declarationId())) // dereferencing means access to previous content |
1084 | 0 | return false; |
1085 | | |
1086 | 0 | if (Token::Match(tok, "= %varid%", var->declarationId()) && (var->isArray() || var->isPointer() || (var->valueType() && var->valueType()->container))) // Create a copy of array/pointer. Bailout, because the memory it points to might be necessary in outer scope |
1087 | 0 | return false; |
1088 | | |
1089 | 0 | if (tok->varId() == var->declarationId()) { |
1090 | 0 | used = true; |
1091 | 0 | if (scope == tok->scope()) { |
1092 | 0 | if (scope->type == Scope::eSwitch) |
1093 | 0 | return false; // Used in outer switch scope - unsafe or impossible to reduce scope |
1094 | | |
1095 | 0 | if (scope->bodyStart && scope->bodyStart->isSimplifiedScope()) |
1096 | 0 | return false; // simplified if/for/switch init statement |
1097 | 0 | } |
1098 | 0 | if (var->isArrayOrPointer()) { |
1099 | 0 | int argn{}; |
1100 | 0 | if (const Token* ftok = getTokenArgumentFunction(tok, argn)) { // var passed to function? |
1101 | 0 | if (ftok->next()->astParent()) { // return value used? |
1102 | 0 | if (ftok->function() && Function::returnsPointer(ftok->function())) |
1103 | 0 | return false; |
1104 | 0 | const std::string ret = mSettings->library.returnValueType(ftok); // assume that var is returned |
1105 | 0 | if (!ret.empty() && ret.back() == '*') |
1106 | 0 | return false; |
1107 | 0 | } |
1108 | 0 | } |
1109 | 0 | } |
1110 | 0 | } |
1111 | 0 | } |
1112 | | |
1113 | 0 | return true; |
1114 | 0 | } |
1115 | | |
1116 | | void CheckOther::variableScopeError(const Token *tok, const std::string &varname) |
1117 | 0 | { |
1118 | 0 | reportError(tok, |
1119 | 0 | Severity::style, |
1120 | 0 | "variableScope", |
1121 | 0 | "$symbol:" + varname + "\n" |
1122 | 0 | "The scope of the variable '$symbol' can be reduced.\n" |
1123 | 0 | "The scope of the variable '$symbol' can be reduced. Warning: Be careful " |
1124 | 0 | "when fixing this message, especially when there are inner loops. Here is an " |
1125 | 0 | "example where cppcheck will write that the scope for 'i' can be reduced:\n" |
1126 | 0 | "void f(int x)\n" |
1127 | 0 | "{\n" |
1128 | 0 | " int i = 0;\n" |
1129 | 0 | " if (x) {\n" |
1130 | 0 | " // it's safe to move 'int i = 0;' here\n" |
1131 | 0 | " for (int n = 0; n < 10; ++n) {\n" |
1132 | 0 | " // it is possible but not safe to move 'int i = 0;' here\n" |
1133 | 0 | " do_something(&i);\n" |
1134 | 0 | " }\n" |
1135 | 0 | " }\n" |
1136 | 0 | "}\n" |
1137 | 0 | "When you see this message it is always safe to reduce the variable scope 1 level.", CWE398, Certainty::normal); |
1138 | 0 | } |
1139 | | |
1140 | | //--------------------------------------------------------------------------- |
1141 | | // Comma in return statement: return a+1, b++;. (experimental) |
1142 | | //--------------------------------------------------------------------------- |
1143 | | void CheckOther::checkCommaSeparatedReturn() |
1144 | 1.36k | { |
1145 | | // This is experimental for now. See #5076 |
1146 | 1.36k | if ((true) || !mSettings->severity.isEnabled(Severity::style)) // NOLINT(readability-simplify-boolean-expr) |
1147 | 1.36k | return; |
1148 | | |
1149 | | // logChecker |
1150 | | |
1151 | 0 | for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) { |
1152 | 0 | if (tok->str() == "return") { |
1153 | 0 | tok = tok->next(); |
1154 | 0 | while (tok && tok->str() != ";") { |
1155 | 0 | if (tok->link() && Token::Match(tok, "[([{<]")) |
1156 | 0 | tok = tok->link(); |
1157 | |
|
1158 | 0 | if (!tok->isExpandedMacro() && tok->str() == "," && tok->linenr() != tok->next()->linenr()) |
1159 | 0 | commaSeparatedReturnError(tok); |
1160 | |
|
1161 | 0 | tok = tok->next(); |
1162 | 0 | } |
1163 | | // bailout: missing semicolon (invalid code / bad tokenizer) |
1164 | 0 | if (!tok) |
1165 | 0 | break; |
1166 | 0 | } |
1167 | 0 | } |
1168 | 0 | } |
1169 | | |
1170 | | void CheckOther::commaSeparatedReturnError(const Token *tok) |
1171 | 0 | { |
1172 | 0 | reportError(tok, |
1173 | 0 | Severity::style, |
1174 | 0 | "commaSeparatedReturn", |
1175 | 0 | "Comma is used in return statement. The comma can easily be misread as a ';'.\n" |
1176 | 0 | "Comma is used in return statement. When comma is used in a return statement it can " |
1177 | 0 | "easily be misread as a semicolon. For example in the code below the value " |
1178 | 0 | "of 'b' is returned if the condition is true, but it is easy to think that 'a+1' is " |
1179 | 0 | "returned:\n" |
1180 | 0 | " if (x)\n" |
1181 | 0 | " return a + 1,\n" |
1182 | 0 | " b++;\n" |
1183 | 0 | "However it can be useful to use comma in macros. Cppcheck does not warn when such a " |
1184 | 0 | "macro is then used in a return statement, it is less likely such code is misunderstood.", CWE398, Certainty::normal); |
1185 | 0 | } |
1186 | | |
1187 | | //--------------------------------------------------------------------------- |
1188 | | // Check for function parameters that should be passed by const reference |
1189 | | //--------------------------------------------------------------------------- |
1190 | | static int estimateSize(const Type* type, const Settings* settings, const SymbolDatabase* symbolDatabase, int recursionDepth = 0) |
1191 | 0 | { |
1192 | 0 | if (recursionDepth > 20) |
1193 | 0 | return 0; |
1194 | | |
1195 | 0 | int cumulatedSize = 0; |
1196 | 0 | const bool isUnion = type->classScope->type == Scope::ScopeType::eUnion; |
1197 | 0 | const auto accumulateSize = [](int& cumulatedSize, int size, bool isUnion) -> void { |
1198 | 0 | if (isUnion) |
1199 | 0 | cumulatedSize = std::max(cumulatedSize, size); |
1200 | 0 | else |
1201 | 0 | cumulatedSize += size; |
1202 | 0 | }; |
1203 | 0 | for (const Variable&var : type->classScope->varlist) { |
1204 | 0 | int size = 0; |
1205 | 0 | if (var.isStatic()) |
1206 | 0 | continue; |
1207 | 0 | if (var.isPointer() || var.isReference()) |
1208 | 0 | size = settings->platform.sizeof_pointer; |
1209 | 0 | else if (var.type() && var.type()->classScope) |
1210 | 0 | size = estimateSize(var.type(), settings, symbolDatabase, recursionDepth+1); |
1211 | 0 | else if (var.valueType() && var.valueType()->type == ValueType::Type::CONTAINER) |
1212 | 0 | size = 3 * settings->platform.sizeof_pointer; // Just guess |
1213 | 0 | else |
1214 | 0 | size = symbolDatabase->sizeOfType(var.typeStartToken()); |
1215 | |
|
1216 | 0 | if (var.isArray()) |
1217 | 0 | size *= std::accumulate(var.dimensions().cbegin(), var.dimensions().cend(), 1, [](int v, const Dimension& d) { |
1218 | 0 | return v *= d.num; |
1219 | 0 | }); |
1220 | |
|
1221 | 0 | accumulateSize(cumulatedSize, size, isUnion); |
1222 | 0 | } |
1223 | 0 | return std::accumulate(type->derivedFrom.cbegin(), type->derivedFrom.cend(), cumulatedSize, [&](int v, const Type::BaseInfo& baseInfo) { |
1224 | 0 | if (baseInfo.type && baseInfo.type->classScope) |
1225 | 0 | v += estimateSize(baseInfo.type, settings, symbolDatabase, recursionDepth + 1); |
1226 | 0 | return v; |
1227 | 0 | }); |
1228 | 0 | } |
1229 | | |
1230 | 0 | static bool isConstRangeBasedFor(const Token* tok) { |
1231 | 0 | if (astIsRangeBasedForDecl(tok)) { |
1232 | 0 | const Variable* loopVar = tok->astParent()->astOperand1()->variable(); |
1233 | 0 | return loopVar && (!loopVar->isReference() || loopVar->isConst()); |
1234 | 0 | } |
1235 | 0 | return false; |
1236 | 0 | } |
1237 | | |
1238 | | static bool canBeConst(const Variable *var, const Settings* settings) |
1239 | 6.80k | { |
1240 | 6.80k | if (!var->scope()) |
1241 | 0 | return false; |
1242 | 6.80k | { |
1243 | | // check initializer list. If variable is moved from it can't be const. |
1244 | 6.80k | const Function* func_scope = var->scope()->function; |
1245 | 6.80k | if (func_scope && func_scope->type == Function::Type::eConstructor) { |
1246 | | //could be initialized in initializer list |
1247 | 0 | const Token* init = func_scope->arg->link()->next(); |
1248 | 0 | if (init->str() == "noexcept") { |
1249 | 0 | init = init->next(); |
1250 | 0 | if (init->link()) |
1251 | 0 | init = init->link()->next(); |
1252 | 0 | } |
1253 | 0 | if (init->str() == ":") { |
1254 | 0 | for (const Token* tok2 = func_scope->arg->link()->next()->next(); tok2 != var->scope()->bodyStart; tok2 = tok2->next()) { |
1255 | 0 | if (tok2->varId() != var->declarationId()) |
1256 | 0 | continue; |
1257 | 0 | const Token* parent = tok2->astParent(); |
1258 | 0 | if (parent && Token::simpleMatch(parent->previous(), "move (")) |
1259 | 0 | return false; |
1260 | 0 | } |
1261 | 0 | } |
1262 | 0 | } |
1263 | 6.80k | } |
1264 | 6.80k | for (const Token* tok2 = var->scope()->bodyStart; tok2 != var->scope()->bodyEnd; tok2 = tok2->next()) { |
1265 | 0 | if (tok2->varId() != var->declarationId()) |
1266 | 0 | continue; |
1267 | | |
1268 | 0 | const Token* parent = tok2->astParent(); |
1269 | 0 | while (Token::simpleMatch(parent, "[")) |
1270 | 0 | parent = parent->astParent(); |
1271 | 0 | if (!parent) |
1272 | 0 | continue; |
1273 | 0 | if (Token::simpleMatch(tok2->next(), ";") && tok2->next()->isSplittedVarDeclEq()) { |
1274 | 0 | tok2 = tok2->tokAt(2); |
1275 | 0 | tok2 = Token::findsimplematch(tok2, ";"); |
1276 | 0 | continue; |
1277 | 0 | } |
1278 | 0 | if (parent->str() == "<<" || isLikelyStreamRead(true, parent)) { |
1279 | 0 | if (parent->str() == "<<" && parent->astOperand1() == tok2) |
1280 | 0 | return false; |
1281 | 0 | if (parent->str() == ">>" && parent->astOperand2() == tok2) |
1282 | 0 | return false; |
1283 | 0 | } else if (parent->str() == "," || parent->str() == "(") { // function argument |
1284 | 0 | const Token* tok3 = tok2->previous(); |
1285 | 0 | int argNr = 0; |
1286 | 0 | while (tok3 && tok3->str() != "(") { |
1287 | 0 | if (tok3->link() && Token::Match(tok3, ")|]|}|>")) |
1288 | 0 | tok3 = tok3->link(); |
1289 | 0 | else if (tok3->link()) |
1290 | 0 | break; |
1291 | 0 | else if (tok3->str() == ";") |
1292 | 0 | break; |
1293 | 0 | else if (tok3->str() == ",") |
1294 | 0 | argNr++; |
1295 | 0 | tok3 = tok3->previous(); |
1296 | 0 | } |
1297 | 0 | if (!tok3 || tok3->str() != "(") |
1298 | 0 | return false; |
1299 | 0 | const Token* functionTok = tok3->astOperand1(); |
1300 | 0 | if (!functionTok) |
1301 | 0 | return false; |
1302 | 0 | const Function* tokFunction = functionTok->function(); |
1303 | 0 | if (!tokFunction && functionTok->str() == "." && (functionTok = functionTok->astOperand2())) |
1304 | 0 | tokFunction = functionTok->function(); |
1305 | 0 | if (tokFunction) { |
1306 | 0 | const Variable* argVar = tokFunction->getArgumentVar(argNr); |
1307 | 0 | if (!argVar || (!argVar->isConst() && argVar->isReference())) |
1308 | 0 | return false; |
1309 | 0 | } |
1310 | 0 | else if (!settings->library.isFunctionConst(functionTok)) |
1311 | 0 | return false; |
1312 | 0 | } else if (parent->isUnaryOp("&")) { |
1313 | | // TODO: check how pointer is used |
1314 | 0 | return false; |
1315 | 0 | } else if (parent->isConstOp() || |
1316 | 0 | (parent->astOperand2() && settings->library.isFunctionConst(parent->astOperand2()))) |
1317 | 0 | continue; |
1318 | 0 | else if (parent->isAssignmentOp()) { |
1319 | 0 | const Token* assignee = parent->astOperand1(); |
1320 | 0 | while (Token::simpleMatch(assignee, "[")) |
1321 | 0 | assignee = assignee->astOperand1(); |
1322 | 0 | if (assignee == tok2) |
1323 | 0 | return false; |
1324 | 0 | const Variable* assignedVar = assignee ? assignee->variable() : nullptr; |
1325 | 0 | if (assignedVar && |
1326 | 0 | !assignedVar->isConst() && |
1327 | 0 | assignedVar->isReference() && |
1328 | 0 | assignedVar->nameToken() == parent->astOperand1()) |
1329 | 0 | return false; |
1330 | 0 | } else if (Token::Match(tok2, "%var% . %name% (")) { |
1331 | 0 | const Function* func = tok2->tokAt(2)->function(); |
1332 | 0 | if (func && (func->isConst() || func->isStatic())) |
1333 | 0 | continue; |
1334 | 0 | return false; |
1335 | 0 | } else if (isConstRangeBasedFor(tok2)) |
1336 | 0 | continue; |
1337 | 0 | else |
1338 | 0 | return false; |
1339 | 0 | } |
1340 | | |
1341 | 6.80k | return true; |
1342 | 6.80k | } |
1343 | | |
1344 | | void CheckOther::checkPassByReference() |
1345 | 1.36k | { |
1346 | 1.36k | if (!mSettings->severity.isEnabled(Severity::performance) || mTokenizer->isC()) |
1347 | 0 | return; |
1348 | | |
1349 | 1.36k | logChecker("CheckOther::checkPassByReference"); // performance,c++ |
1350 | | |
1351 | 1.36k | const SymbolDatabase * const symbolDatabase = mTokenizer->getSymbolDatabase(); |
1352 | | |
1353 | 8.16k | for (const Variable* var : symbolDatabase->variableList()) { |
1354 | 8.16k | if (!var || !var->isArgument() || !var->isClass() || var->isPointer() || var->isArray() || var->isReference() || var->isEnumType()) |
1355 | 8.16k | continue; |
1356 | | |
1357 | 0 | if (var->scope() && var->scope()->function->arg->link()->strAt(-1) == "...") |
1358 | 0 | continue; // references could not be used as va_start parameters (#5824) |
1359 | | |
1360 | 0 | const Token * const varDeclEndToken = var->declEndToken(); |
1361 | 0 | if ((varDeclEndToken && varDeclEndToken->isExternC()) || |
1362 | 0 | (var->scope() && var->scope()->function && var->scope()->function->tokenDef && var->scope()->function->tokenDef->isExternC())) |
1363 | 0 | continue; // references cannot be used in functions in extern "C" blocks |
1364 | | |
1365 | 0 | bool inconclusive = false; |
1366 | |
|
1367 | 0 | const bool isContainer = var->valueType() && var->valueType()->type == ValueType::Type::CONTAINER && var->valueType()->container && !var->valueType()->container->view; |
1368 | 0 | if (!isContainer) { |
1369 | 0 | if (var->type() && !var->type()->isEnumType()) { // Check if type is a struct or class. |
1370 | | // Ensure that it is a large object. |
1371 | 0 | if (!var->type()->classScope) |
1372 | 0 | inconclusive = true; |
1373 | 0 | else if (estimateSize(var->type(), mSettings, symbolDatabase) <= 2 * mSettings->platform.sizeof_pointer) |
1374 | 0 | continue; |
1375 | 0 | } |
1376 | 0 | else |
1377 | 0 | continue; |
1378 | 0 | } |
1379 | | |
1380 | 0 | if (inconclusive && !mSettings->certainty.isEnabled(Certainty::inconclusive)) |
1381 | 0 | continue; |
1382 | | |
1383 | 0 | const bool isConst = var->isConst(); |
1384 | 0 | if (isConst) { |
1385 | 0 | passedByValueError(var->nameToken(), var->name(), inconclusive); |
1386 | 0 | continue; |
1387 | 0 | } |
1388 | | |
1389 | | // Check if variable could be const |
1390 | 0 | if (!var->scope() || var->scope()->function->isImplicitlyVirtual()) |
1391 | 0 | continue; |
1392 | | |
1393 | 0 | if (canBeConst(var, mSettings)) { |
1394 | 0 | passedByValueError(var->nameToken(), var->name(), inconclusive); |
1395 | 0 | } |
1396 | 0 | } |
1397 | 1.36k | } |
1398 | | |
1399 | | void CheckOther::passedByValueError(const Token *tok, const std::string &parname, bool inconclusive) |
1400 | 0 | { |
1401 | 0 | reportError(tok, Severity::performance, "passedByValue", |
1402 | 0 | "$symbol:" + parname + "\n" |
1403 | 0 | "Function parameter '$symbol' should be passed by const reference.\n" |
1404 | 0 | "Parameter '$symbol' is passed by value. It could be passed " |
1405 | 0 | "as a const reference which is usually faster and recommended in C++.", CWE398, inconclusive ? Certainty::inconclusive : Certainty::normal); |
1406 | 0 | } |
1407 | | |
1408 | | static bool isUnusedVariable(const Variable *var) |
1409 | 0 | { |
1410 | 0 | if (!var) |
1411 | 0 | return false; |
1412 | 0 | if (!var->scope()) |
1413 | 0 | return false; |
1414 | 0 | const Token *start = var->declEndToken(); |
1415 | 0 | if (!start) |
1416 | 0 | return false; |
1417 | 0 | if (Token::Match(start, "; %varid% =", var->declarationId())) |
1418 | 0 | start = start->tokAt(2); |
1419 | 0 | return !Token::findmatch(start->next(), "%varid%", var->scope()->bodyEnd, var->declarationId()); |
1420 | 0 | } |
1421 | | |
1422 | | static bool isVariableMutableInInitializer(const Token* start, const Token * end, nonneg int varid) |
1423 | 0 | { |
1424 | 0 | if (!start) |
1425 | 0 | return false; |
1426 | 0 | if (!end) |
1427 | 0 | return false; |
1428 | 0 | for (const Token *tok = start; tok != end; tok = tok->next()) { |
1429 | 0 | if (tok->varId() != varid) |
1430 | 0 | continue; |
1431 | 0 | if (tok->astParent()) { |
1432 | 0 | const Token * memberTok = tok->astParent()->previous(); |
1433 | 0 | if (Token::Match(memberTok, "%var% (") && memberTok->variable()) { |
1434 | 0 | const Variable * memberVar = memberTok->variable(); |
1435 | 0 | if (memberVar->isClass()) |
1436 | | //TODO: check if the called constructor could live with a const variable |
1437 | | // pending that, assume the worst (that it can't) |
1438 | 0 | return true; |
1439 | 0 | if (!memberVar->isReference()) |
1440 | 0 | continue; |
1441 | 0 | if (memberVar->isConst()) |
1442 | 0 | continue; |
1443 | 0 | } |
1444 | 0 | } |
1445 | 0 | return true; |
1446 | 0 | } |
1447 | 0 | return false; |
1448 | 0 | } |
1449 | | |
1450 | | void CheckOther::checkConstVariable() |
1451 | 1.36k | { |
1452 | 1.36k | if (!mSettings->severity.isEnabled(Severity::style) || mTokenizer->isC()) |
1453 | 0 | return; |
1454 | | |
1455 | 1.36k | const SymbolDatabase *const symbolDatabase = mTokenizer->getSymbolDatabase(); |
1456 | | |
1457 | 8.16k | for (const Variable *var : symbolDatabase->variableList()) { |
1458 | 8.16k | if (!var) |
1459 | 1.36k | continue; |
1460 | 6.80k | if (!var->isReference()) |
1461 | 6.80k | continue; |
1462 | 0 | if (var->isRValueReference()) |
1463 | 0 | continue; |
1464 | 0 | if (var->isPointer()) |
1465 | 0 | continue; |
1466 | 0 | if (var->isConst()) |
1467 | 0 | continue; |
1468 | 0 | const Scope* scope = var->scope(); |
1469 | 0 | if (!scope) |
1470 | 0 | continue; |
1471 | 0 | const Function* function = scope->function; |
1472 | 0 | if (!function && !scope->isLocal()) |
1473 | 0 | continue; |
1474 | 0 | if (function && var->isArgument()) { |
1475 | 0 | if (function->isImplicitlyVirtual() || function->templateDef) |
1476 | 0 | continue; |
1477 | 0 | if (isUnusedVariable(var)) |
1478 | 0 | continue; |
1479 | 0 | if (function->isConstructor() && isVariableMutableInInitializer(function->constructorMemberInitialization(), scope->bodyStart, var->declarationId())) |
1480 | 0 | continue; |
1481 | 0 | } |
1482 | 0 | if (var->isGlobal()) |
1483 | 0 | continue; |
1484 | 0 | if (var->isStatic()) |
1485 | 0 | continue; |
1486 | 0 | if (var->isArray() && !var->isStlType()) |
1487 | 0 | continue; |
1488 | 0 | if (var->isEnumType()) |
1489 | 0 | continue; |
1490 | 0 | if (var->isVolatile()) |
1491 | 0 | continue; |
1492 | 0 | if (isStructuredBindingVariable(var)) // TODO: check all bound variables |
1493 | 0 | continue; |
1494 | 0 | if (isVariableChanged(var, mSettings, mTokenizer->isCPP())) |
1495 | 0 | continue; |
1496 | 0 | const bool hasFunction = function != nullptr; |
1497 | 0 | if (!hasFunction) { |
1498 | 0 | const Scope* functionScope = scope; |
1499 | 0 | do { |
1500 | 0 | functionScope = functionScope->nestedIn; |
1501 | 0 | } while (functionScope && !(function = functionScope->function)); |
1502 | 0 | } |
1503 | 0 | if (function && (Function::returnsReference(function) || Function::returnsPointer(function)) && !Function::returnsConst(function)) { |
1504 | 0 | std::vector<const Token*> returns = Function::findReturns(function); |
1505 | 0 | if (std::any_of(returns.cbegin(), returns.cend(), [&](const Token* retTok) { |
1506 | 0 | if (retTok->varId() == var->declarationId()) |
1507 | 0 | return true; |
1508 | 0 | while (retTok && retTok->isCast()) |
1509 | 0 | retTok = retTok->astOperand2(); |
1510 | 0 | while (Token::simpleMatch(retTok, ".")) |
1511 | 0 | retTok = retTok->astOperand2(); |
1512 | 0 | if (Token::simpleMatch(retTok, "&")) |
1513 | 0 | retTok = retTok->astOperand1(); |
1514 | 0 | return ValueFlow::hasLifetimeToken(getParentLifetime(retTok), var->nameToken()); |
1515 | 0 | })) |
1516 | 0 | continue; |
1517 | 0 | } |
1518 | | // Skip if another non-const variable is initialized with this variable |
1519 | 0 | { |
1520 | | //Is it the right side of an initialization of a non-const reference |
1521 | 0 | bool usedInAssignment = false; |
1522 | 0 | for (const Token* tok = var->nameToken(); tok != scope->bodyEnd && tok != nullptr; tok = tok->next()) { |
1523 | 0 | if (Token::Match(tok, "& %var% = %varid%", var->declarationId())) { |
1524 | 0 | const Variable* refvar = tok->next()->variable(); |
1525 | 0 | if (refvar && !refvar->isConst() && refvar->nameToken() == tok->next()) { |
1526 | 0 | usedInAssignment = true; |
1527 | 0 | break; |
1528 | 0 | } |
1529 | 0 | } |
1530 | 0 | if (tok->isUnaryOp("&") && Token::Match(tok, "& %varid%", var->declarationId())) { |
1531 | 0 | const Token* opTok = tok->astParent(); |
1532 | 0 | int argn = -1; |
1533 | 0 | if (opTok && opTok->isUnaryOp("!")) |
1534 | 0 | continue; |
1535 | 0 | if (opTok && (opTok->isComparisonOp() || opTok->isAssignmentOp() || opTok->isCalculation())) { |
1536 | 0 | if (opTok->isComparisonOp() || opTok->isCalculation()) { |
1537 | 0 | if (opTok->astOperand1() != tok) |
1538 | 0 | opTok = opTok->astOperand1(); |
1539 | 0 | else |
1540 | 0 | opTok = opTok->astOperand2(); |
1541 | 0 | } |
1542 | 0 | if (opTok && opTok->valueType() && var->valueType() && opTok->valueType()->isConst(var->valueType()->pointer)) |
1543 | 0 | continue; |
1544 | 0 | } else if (const Token* ftok = getTokenArgumentFunction(tok, argn)) { |
1545 | 0 | bool inconclusive{}; |
1546 | 0 | if (var->valueType() && !isVariableChangedByFunctionCall(ftok, var->valueType()->pointer, var->declarationId(), mSettings, &inconclusive) && !inconclusive) |
1547 | 0 | continue; |
1548 | 0 | } |
1549 | 0 | usedInAssignment = true; |
1550 | 0 | break; |
1551 | 0 | } |
1552 | 0 | if (astIsRangeBasedForDecl(tok) && Token::Match(tok->astParent()->astOperand2(), "%varid%", var->declarationId())) { |
1553 | 0 | const Variable* refvar = tok->astParent()->astOperand1()->variable(); |
1554 | 0 | if (refvar && refvar->isReference() && !refvar->isConst()) { |
1555 | 0 | usedInAssignment = true; |
1556 | 0 | break; |
1557 | 0 | } |
1558 | 0 | } |
1559 | 0 | } |
1560 | 0 | if (usedInAssignment) |
1561 | 0 | continue; |
1562 | 0 | } |
1563 | | // Skip if we ever cast this variable to a pointer/reference to a non-const type |
1564 | 0 | { |
1565 | 0 | bool castToNonConst = false; |
1566 | 0 | for (const Token* tok = var->nameToken(); tok != scope->bodyEnd && tok != nullptr; tok = tok->next()) { |
1567 | 0 | if (tok->isCast()) { |
1568 | 0 | if (!tok->valueType()) { |
1569 | 0 | castToNonConst = true; // safe guess |
1570 | 0 | break; |
1571 | 0 | } |
1572 | 0 | const bool isConst = tok->valueType()->isConst(tok->valueType()->pointer); |
1573 | 0 | if (!isConst) { |
1574 | 0 | castToNonConst = true; |
1575 | 0 | break; |
1576 | 0 | } |
1577 | 0 | } |
1578 | 0 | } |
1579 | 0 | if (castToNonConst) |
1580 | 0 | continue; |
1581 | 0 | } |
1582 | | |
1583 | 0 | constVariableError(var, hasFunction ? function : nullptr); |
1584 | 0 | } |
1585 | 1.36k | } |
1586 | | |
1587 | | static const Token* getVariableChangedStart(const Variable* p) |
1588 | 0 | { |
1589 | 0 | if (p->isArgument()) |
1590 | 0 | return p->scope()->bodyStart; |
1591 | 0 | const Token* start = p->nameToken()->next(); |
1592 | 0 | if (start->isSplittedVarDeclEq()) |
1593 | 0 | start = start->tokAt(3); |
1594 | 0 | return start; |
1595 | 0 | } |
1596 | | |
1597 | | void CheckOther::checkConstPointer() |
1598 | 1.36k | { |
1599 | 1.36k | if (!mSettings->severity.isEnabled(Severity::style)) |
1600 | 0 | return; |
1601 | | |
1602 | 1.36k | logChecker("CheckOther::checkConstPointer"); // style |
1603 | | |
1604 | 1.36k | std::vector<const Variable*> pointers, nonConstPointers; |
1605 | 93.5k | for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) { |
1606 | 92.2k | const Variable* const var = tok->variable(); |
1607 | 92.2k | if (!var) |
1608 | 72.4k | continue; |
1609 | 19.8k | if (!var->isLocal() && !var->isArgument()) |
1610 | 19.8k | continue; |
1611 | 0 | const Token* const nameTok = var->nameToken(); |
1612 | | // declarations of (static) pointers are (not) split up, array declarations are never split up |
1613 | 0 | if (tok == nameTok && (!var->isStatic() || Token::simpleMatch(nameTok->next(), "[")) && |
1614 | 0 | !astIsRangeBasedForDecl(nameTok)) |
1615 | 0 | continue; |
1616 | 0 | const ValueType* const vt = tok->valueType(); |
1617 | 0 | if (!vt) |
1618 | 0 | continue; |
1619 | 0 | if ((vt->pointer != 1 && !(vt->pointer == 2 && var->isArray())) || (vt->constness & 1)) |
1620 | 0 | continue; |
1621 | 0 | if (var->typeStartToken()->isTemplateArg()) |
1622 | 0 | continue; |
1623 | 0 | if (std::find(nonConstPointers.cbegin(), nonConstPointers.cend(), var) != nonConstPointers.cend()) |
1624 | 0 | continue; |
1625 | 0 | pointers.emplace_back(var); |
1626 | 0 | const Token* parent = tok->astParent(); |
1627 | 0 | enum Deref { NONE, DEREF, MEMBER } deref = NONE; |
1628 | 0 | bool hasIncDec = false; |
1629 | 0 | if (parent && (parent->isUnaryOp("*") || (hasIncDec = parent->isIncDecOp() && parent->astParent() && parent->astParent()->isUnaryOp("*")))) |
1630 | 0 | deref = DEREF; |
1631 | 0 | else if (Token::simpleMatch(parent, "[") && parent->astOperand1() == tok && tok != nameTok) |
1632 | 0 | deref = DEREF; |
1633 | 0 | else if (Token::Match(parent, "%op%") && Token::simpleMatch(parent->astParent(), ".")) |
1634 | 0 | deref = DEREF; |
1635 | 0 | else if (Token::simpleMatch(parent, ".")) |
1636 | 0 | deref = MEMBER; |
1637 | 0 | else if (astIsRangeBasedForDecl(tok)) |
1638 | 0 | continue; |
1639 | 0 | if (deref != NONE) { |
1640 | 0 | const Token* gparent = parent->astParent(); |
1641 | 0 | if (deref == MEMBER) { |
1642 | 0 | if (!gparent) |
1643 | 0 | continue; |
1644 | 0 | if (parent->astOperand2()) { |
1645 | 0 | if (parent->astOperand2()->function() && parent->astOperand2()->function()->isConst()) |
1646 | 0 | continue; |
1647 | 0 | if (mSettings->library.isFunctionConst(parent->astOperand2())) |
1648 | 0 | continue; |
1649 | 0 | } |
1650 | 0 | } |
1651 | 0 | if (Token::Match(gparent, "%cop%") && !gparent->isUnaryOp("&") && !gparent->isUnaryOp("*")) |
1652 | 0 | continue; |
1653 | 0 | if (hasIncDec) { |
1654 | 0 | parent = gparent; |
1655 | 0 | gparent = gparent ? gparent->astParent() : nullptr; |
1656 | 0 | } |
1657 | 0 | int argn = -1; |
1658 | 0 | if (Token::simpleMatch(gparent, "return")) { |
1659 | 0 | const Function* function = gparent->scope()->function; |
1660 | 0 | if (function && (!Function::returnsReference(function) || Function::returnsConst(function))) |
1661 | 0 | continue; |
1662 | 0 | } |
1663 | 0 | else if (Token::Match(gparent, "%assign%") && parent == gparent->astOperand2()) { |
1664 | 0 | bool takingRef = false, nonConstPtrAssignment = false; |
1665 | 0 | const Token *lhs = gparent->astOperand1(); |
1666 | 0 | if (lhs && lhs->variable() && lhs->variable()->isReference() && lhs->variable()->nameToken() == lhs) |
1667 | 0 | takingRef = true; |
1668 | 0 | if (lhs && lhs->valueType() && lhs->valueType()->pointer && (lhs->valueType()->constness & 1) == 0 && |
1669 | 0 | parent->valueType() && parent->valueType()->pointer) |
1670 | 0 | nonConstPtrAssignment = true; |
1671 | 0 | if (!takingRef && !nonConstPtrAssignment) |
1672 | 0 | continue; |
1673 | 0 | } else if (Token::simpleMatch(gparent, "[") && gparent->astOperand2() == parent) |
1674 | 0 | continue; |
1675 | 0 | else if (const Token* ftok = getTokenArgumentFunction(parent, argn)) { |
1676 | 0 | bool inconclusive{}; |
1677 | 0 | if (!isVariableChangedByFunctionCall(ftok->next(), vt->pointer, var->declarationId(), mSettings, &inconclusive) && !inconclusive) |
1678 | 0 | continue; |
1679 | 0 | } |
1680 | 0 | } else { |
1681 | 0 | int argn = -1; |
1682 | 0 | if (Token::Match(parent, "%oror%|%comp%|&&|?|!|-")) |
1683 | 0 | continue; |
1684 | 0 | if (Token::simpleMatch(parent, "(") && Token::Match(parent->astOperand1(), "if|while")) |
1685 | 0 | continue; |
1686 | 0 | if (Token::simpleMatch(parent, "=") && parent->astOperand1() == tok) |
1687 | 0 | continue; |
1688 | 0 | if (const Token* ftok = getTokenArgumentFunction(tok, argn)) { |
1689 | 0 | if (ftok->function()) { |
1690 | 0 | const bool isCastArg = parent->isCast() && !ftok->function()->getOverloadedFunctions().empty(); // assume that cast changes the called function |
1691 | 0 | if (!isCastArg) { |
1692 | 0 | const Variable* argVar = ftok->function()->getArgumentVar(argn); |
1693 | 0 | if (argVar && argVar->valueType() && argVar->valueType()->isConst(vt->pointer)) { |
1694 | 0 | bool inconclusive{}; |
1695 | 0 | if (!isVariableChangedByFunctionCall(ftok, vt->pointer, var->declarationId(), mSettings, &inconclusive) && !inconclusive) |
1696 | 0 | continue; |
1697 | 0 | } |
1698 | 0 | } |
1699 | 0 | } else { |
1700 | 0 | const auto dir = mSettings->library.getArgDirection(ftok, argn + 1); |
1701 | 0 | if (dir == Library::ArgumentChecks::Direction::DIR_IN) |
1702 | 0 | continue; |
1703 | 0 | } |
1704 | 0 | } |
1705 | 0 | else if (Token::simpleMatch(parent, "(")) { |
1706 | 0 | if (parent->isCast() && parent->valueType() && var->valueType() && parent->valueType()->isConst(var->valueType()->pointer)) |
1707 | 0 | continue; |
1708 | 0 | } |
1709 | 0 | } |
1710 | 0 | nonConstPointers.emplace_back(var); |
1711 | 0 | } |
1712 | 1.36k | for (const Variable *p: pointers) { |
1713 | 0 | if (p->isArgument()) { |
1714 | 0 | if (!p->scope() || !p->scope()->function || p->scope()->function->isImplicitlyVirtual(true) || p->scope()->function->hasVirtualSpecifier()) |
1715 | 0 | continue; |
1716 | 0 | } |
1717 | 0 | if (std::find(nonConstPointers.cbegin(), nonConstPointers.cend(), p) == nonConstPointers.cend()) { |
1718 | 0 | const Token *start = getVariableChangedStart(p); |
1719 | 0 | const int indirect = p->isArray() ? p->dimensions().size() : 1; |
1720 | 0 | if (isVariableChanged(start, p->scope()->bodyEnd, indirect, p->declarationId(), false, mSettings, mTokenizer->isCPP())) |
1721 | 0 | continue; |
1722 | 0 | if (p->isArgument() && p->typeStartToken() && p->typeStartToken()->isSimplifiedTypedef() && !(Token::simpleMatch(p->typeEndToken(), "*") && !p->typeEndToken()->isSimplifiedTypedef())) |
1723 | 0 | continue; |
1724 | 0 | constVariableError(p, p->isArgument() ? p->scope()->function : nullptr); |
1725 | 0 | } |
1726 | 0 | } |
1727 | 1.36k | } |
1728 | | void CheckOther::constVariableError(const Variable *var, const Function *function) |
1729 | 0 | { |
1730 | 0 | if (!var) { |
1731 | 0 | reportError(nullptr, Severity::style, "constParameter", "Parameter 'x' can be declared with const"); |
1732 | 0 | reportError(nullptr, Severity::style, "constVariable", "Variable 'x' can be declared with const"); |
1733 | 0 | reportError(nullptr, Severity::style, "constParameterReference", "Parameter 'x' can be declared with const"); |
1734 | 0 | reportError(nullptr, Severity::style, "constVariableReference", "Variable 'x' can be declared with const"); |
1735 | 0 | reportError(nullptr, Severity::style, "constParameterPointer", "Parameter 'x' can be declared with const"); |
1736 | 0 | reportError(nullptr, Severity::style, "constVariablePointer", "Variable 'x' can be declared with const"); |
1737 | 0 | reportError(nullptr, Severity::style, "constParameterCallback", "Parameter 'x' can be declared with const, however it seems that 'f' is a callback function."); |
1738 | 0 | return; |
1739 | 0 | } |
1740 | | |
1741 | 0 | const std::string vartype(var->isArgument() ? "Parameter" : "Variable"); |
1742 | 0 | const std::string varname(var->name()); |
1743 | 0 | const std::string ptrRefArray = var->isArray() ? "const array" : (var->isPointer() ? "pointer to const" : "reference to const"); |
1744 | |
|
1745 | 0 | ErrorPath errorPath; |
1746 | 0 | std::string id = "const" + vartype; |
1747 | 0 | std::string message = "$symbol:" + varname + "\n" + vartype + " '$symbol' can be declared as " + ptrRefArray; |
1748 | 0 | errorPath.emplace_back(var->nameToken(), message); |
1749 | 0 | if (var->isArgument() && function && function->functionPointerUsage) { |
1750 | 0 | errorPath.emplace_front(function->functionPointerUsage, "You might need to cast the function pointer here"); |
1751 | 0 | id += "Callback"; |
1752 | 0 | message += ". However it seems that '" + function->name() + "' is a callback function, if '$symbol' is declared with const you might also need to cast function pointer(s)."; |
1753 | 0 | } else if (var->isReference()) { |
1754 | 0 | id += "Reference"; |
1755 | 0 | } else if (var->isPointer() && !var->isArray()) { |
1756 | 0 | id += "Pointer"; |
1757 | 0 | } |
1758 | |
|
1759 | 0 | reportError(errorPath, Severity::style, id.c_str(), message, CWE398, Certainty::normal); |
1760 | 0 | } |
1761 | | |
1762 | | //--------------------------------------------------------------------------- |
1763 | | // Check usage of char variables.. |
1764 | | //--------------------------------------------------------------------------- |
1765 | | |
1766 | | void CheckOther::checkCharVariable() |
1767 | 1.36k | { |
1768 | 1.36k | const bool warning = mSettings->severity.isEnabled(Severity::warning); |
1769 | 1.36k | const bool portability = mSettings->severity.isEnabled(Severity::portability); |
1770 | 1.36k | if (!warning && !portability) |
1771 | 0 | return; |
1772 | | |
1773 | 1.36k | logChecker("CheckOther::checkCharVariable"); // warning,portability |
1774 | | |
1775 | 1.36k | const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); |
1776 | 1.73k | for (const Scope * scope : symbolDatabase->functionScopes) { |
1777 | 37.6k | for (const Token* tok = scope->bodyStart; tok != scope->bodyEnd; tok = tok->next()) { |
1778 | 35.9k | if (Token::Match(tok, "%var% [")) { |
1779 | 0 | if (!tok->variable()) |
1780 | 0 | continue; |
1781 | 0 | if (!tok->variable()->isArray() && !tok->variable()->isPointer()) |
1782 | 0 | continue; |
1783 | 0 | const Token *index = tok->next()->astOperand2(); |
1784 | 0 | if (warning && tok->variable()->isArray() && astIsSignedChar(index) && index->getValueGE(0x80, mSettings)) |
1785 | 0 | signedCharArrayIndexError(tok); |
1786 | 0 | if (portability && astIsUnknownSignChar(index) && index->getValueGE(0x80, mSettings)) |
1787 | 0 | unknownSignCharArrayIndexError(tok); |
1788 | 35.9k | } else if (warning && Token::Match(tok, "[&|^]") && tok->isBinaryOp()) { |
1789 | 781 | bool warn = false; |
1790 | 781 | if (astIsSignedChar(tok->astOperand1())) { |
1791 | 0 | const ValueFlow::Value *v1 = tok->astOperand1()->getValueLE(-1, mSettings); |
1792 | 0 | const ValueFlow::Value *v2 = tok->astOperand2()->getMaxValue(false); |
1793 | 0 | if (!v1) |
1794 | 0 | v1 = tok->astOperand1()->getValueGE(0x80, mSettings); |
1795 | 0 | if (v1 && !(tok->str() == "&" && v2 && v2->isKnown() && v2->intvalue >= 0 && v2->intvalue < 0x100)) |
1796 | 0 | warn = true; |
1797 | 781 | } else if (astIsSignedChar(tok->astOperand2())) { |
1798 | 0 | const ValueFlow::Value *v1 = tok->astOperand2()->getValueLE(-1, mSettings); |
1799 | 0 | const ValueFlow::Value *v2 = tok->astOperand1()->getMaxValue(false); |
1800 | 0 | if (!v1) |
1801 | 0 | v1 = tok->astOperand2()->getValueGE(0x80, mSettings); |
1802 | 0 | if (v1 && !(tok->str() == "&" && v2 && v2->isKnown() && v2->intvalue >= 0 && v2->intvalue < 0x100)) |
1803 | 0 | warn = true; |
1804 | 0 | } |
1805 | | |
1806 | | // is the result stored in a short|int|long? |
1807 | 781 | if (warn && Token::simpleMatch(tok->astParent(), "=")) { |
1808 | 0 | const Token *lhs = tok->astParent()->astOperand1(); |
1809 | 0 | if (lhs && lhs->valueType() && lhs->valueType()->type >= ValueType::Type::SHORT) |
1810 | 0 | charBitOpError(tok); // This is an error.. |
1811 | 0 | } |
1812 | 781 | } |
1813 | 35.9k | } |
1814 | 1.73k | } |
1815 | 1.36k | } |
1816 | | |
1817 | | void CheckOther::signedCharArrayIndexError(const Token *tok) |
1818 | 0 | { |
1819 | 0 | reportError(tok, |
1820 | 0 | Severity::warning, |
1821 | 0 | "signedCharArrayIndex", |
1822 | 0 | "Signed 'char' type used as array index.\n" |
1823 | 0 | "Signed 'char' type used as array index. If the value " |
1824 | 0 | "can be greater than 127 there will be a buffer underflow " |
1825 | 0 | "because of sign extension.", CWE128, Certainty::normal); |
1826 | 0 | } |
1827 | | |
1828 | | void CheckOther::unknownSignCharArrayIndexError(const Token *tok) |
1829 | 0 | { |
1830 | 0 | reportError(tok, |
1831 | 0 | Severity::portability, |
1832 | 0 | "unknownSignCharArrayIndex", |
1833 | 0 | "'char' type used as array index.\n" |
1834 | 0 | "'char' type used as array index. Values greater than 127 will be " |
1835 | 0 | "treated depending on whether 'char' is signed or unsigned on target platform.", CWE758, Certainty::normal); |
1836 | 0 | } |
1837 | | |
1838 | | void CheckOther::charBitOpError(const Token *tok) |
1839 | 0 | { |
1840 | 0 | reportError(tok, |
1841 | 0 | Severity::warning, |
1842 | 0 | "charBitOp", |
1843 | 0 | "When using 'char' variables in bit operations, sign extension can generate unexpected results.\n" |
1844 | 0 | "When using 'char' variables in bit operations, sign extension can generate unexpected results. For example:\n" |
1845 | 0 | " char c = 0x80;\n" |
1846 | 0 | " int i = 0 | c;\n" |
1847 | 0 | " if (i & 0x8000)\n" |
1848 | 0 | " printf(\"not expected\");\n" |
1849 | 0 | "The \"not expected\" will be printed on the screen.", CWE398, Certainty::normal); |
1850 | 0 | } |
1851 | | |
1852 | | //--------------------------------------------------------------------------- |
1853 | | // Incomplete statement.. |
1854 | | //--------------------------------------------------------------------------- |
1855 | | |
1856 | | static bool isType(const Token * tok, bool unknown) |
1857 | 0 | { |
1858 | 0 | if (tok && (tok->isStandardType() || (!tok->isKeyword() && Token::Match(tok, "%type%")) || tok->str() == "auto")) |
1859 | 0 | return true; |
1860 | 0 | if (Token::simpleMatch(tok, "::")) |
1861 | 0 | return isType(tok->astOperand2(), unknown); |
1862 | 0 | if (Token::simpleMatch(tok, "<") && tok->link()) |
1863 | 0 | return true; |
1864 | 0 | if (unknown && Token::Match(tok, "%name% !!(")) |
1865 | 0 | return true; |
1866 | 0 | return false; |
1867 | 0 | } |
1868 | | |
1869 | | static bool isVarDeclOp(const Token* tok) |
1870 | 0 | { |
1871 | 0 | if (!tok) |
1872 | 0 | return false; |
1873 | 0 | const Token * vartok = tok->astOperand2(); |
1874 | 0 | if (vartok && vartok->variable() && vartok->variable()->nameToken() == vartok) |
1875 | 0 | return true; |
1876 | 0 | const Token * typetok = tok->astOperand1(); |
1877 | 0 | return isType(typetok, vartok && vartok->varId() != 0); |
1878 | 0 | } |
1879 | | |
1880 | | static bool isBracketAccess(const Token* tok) |
1881 | 3.45k | { |
1882 | 3.45k | if (!Token::simpleMatch(tok, "[") || !tok->astOperand1()) |
1883 | 3.45k | return false; |
1884 | 0 | tok = tok->astOperand1(); |
1885 | 0 | if (tok->str() == ".") |
1886 | 0 | tok = tok->astOperand2(); |
1887 | 0 | while (Token::simpleMatch(tok, "[")) |
1888 | 0 | tok = tok->astOperand1(); |
1889 | 0 | if (!tok || !tok->variable()) |
1890 | 0 | return false; |
1891 | 0 | return tok->variable()->nameToken() != tok; |
1892 | 0 | } |
1893 | | |
1894 | 3.45k | static bool isConstant(const Token* tok) { |
1895 | 3.45k | return Token::Match(tok, "%bool%|%num%|%str%|%char%|nullptr|NULL"); |
1896 | 3.45k | } |
1897 | | |
1898 | | static bool isConstStatement(const Token *tok, bool cpp) |
1899 | 3.45k | { |
1900 | 3.45k | if (!tok) |
1901 | 0 | return false; |
1902 | 3.45k | if (tok->isExpandedMacro()) |
1903 | 0 | return false; |
1904 | 3.45k | if (tok->varId() != 0) |
1905 | 0 | return true; |
1906 | 3.45k | if (isConstant(tok)) |
1907 | 0 | return true; |
1908 | 3.45k | if (Token::Match(tok, "*|&|&&") && |
1909 | 3.45k | (Token::Match(tok->previous(), "::|.|const|volatile|restrict") || isVarDeclOp(tok))) |
1910 | 0 | return false; |
1911 | 3.45k | if (Token::Match(tok, "<<|>>") && !astIsIntegral(tok, false)) |
1912 | 0 | return false; |
1913 | 3.45k | if (tok->astTop() && Token::simpleMatch(tok->astTop()->astOperand1(), "delete")) |
1914 | 0 | return false; |
1915 | 3.45k | if (Token::Match(tok, "&&|%oror%")) |
1916 | 0 | return isConstStatement(tok->astOperand1(), cpp) && isConstStatement(tok->astOperand2(), cpp); |
1917 | 3.45k | if (Token::Match(tok, "!|~|%cop%") && (tok->astOperand1() || tok->astOperand2())) |
1918 | 0 | return true; |
1919 | 3.45k | if (Token::simpleMatch(tok->previous(), "sizeof (")) |
1920 | 0 | return true; |
1921 | 3.45k | if (isCPPCast(tok)) { |
1922 | 0 | if (Token::simpleMatch(tok->astOperand1(), "dynamic_cast") && Token::simpleMatch(tok->astOperand1()->linkAt(1)->previous(), "& >")) |
1923 | 0 | return false; |
1924 | 0 | return isWithoutSideEffects(cpp, tok) && isConstStatement(tok->astOperand2(), cpp); |
1925 | 0 | } |
1926 | 3.45k | if (tok->isCast() && tok->next() && tok->next()->isStandardType()) |
1927 | 0 | return isWithoutSideEffects(cpp, tok->astOperand1()) && isConstStatement(tok->astOperand1(), cpp); |
1928 | 3.45k | if (Token::simpleMatch(tok, ".")) |
1929 | 0 | return isConstStatement(tok->astOperand2(), cpp); |
1930 | 3.45k | if (Token::simpleMatch(tok, ",")) { |
1931 | 0 | if (tok->astParent()) // warn about const statement on rhs at the top level |
1932 | 0 | return isConstStatement(tok->astOperand1(), cpp) && isConstStatement(tok->astOperand2(), cpp); |
1933 | | |
1934 | 0 | const Token* lml = previousBeforeAstLeftmostLeaf(tok); // don't warn about matrix/vector assignment (e.g. Eigen) |
1935 | 0 | if (lml) |
1936 | 0 | lml = lml->next(); |
1937 | 0 | const Token* stream = lml; |
1938 | 0 | while (stream && Token::Match(stream->astParent(), ".|[|(|*")) |
1939 | 0 | stream = stream->astParent(); |
1940 | 0 | return (!stream || !isLikelyStream(cpp, stream)) && isConstStatement(tok->astOperand2(), cpp); |
1941 | 0 | } |
1942 | 3.45k | if (Token::simpleMatch(tok, "?") && Token::simpleMatch(tok->astOperand2(), ":")) // ternary operator |
1943 | 0 | return isConstStatement(tok->astOperand1(), cpp) && isConstStatement(tok->astOperand2()->astOperand1(), cpp) && isConstStatement(tok->astOperand2()->astOperand2(), cpp); |
1944 | 3.45k | if (isBracketAccess(tok) && isWithoutSideEffects(cpp, tok->astOperand1(), /*checkArrayAccess*/ true, /*checkReference*/ false)) { |
1945 | 0 | if (Token::simpleMatch(tok->astParent(), "[")) |
1946 | 0 | return isConstStatement(tok->astOperand2(), cpp) && isConstStatement(tok->astParent(), cpp); |
1947 | 0 | return isConstStatement(tok->astOperand2(), cpp); |
1948 | 0 | } |
1949 | 3.45k | return false; |
1950 | 3.45k | } |
1951 | | |
1952 | | static bool isVoidStmt(const Token *tok) |
1953 | 0 | { |
1954 | 0 | if (Token::simpleMatch(tok, "( void")) |
1955 | 0 | return true; |
1956 | 0 | if (isCPPCast(tok) && tok->astOperand1() && Token::Match(tok->astOperand1()->next(), "< void *| >")) |
1957 | 0 | return true; |
1958 | 0 | const Token *tok2 = tok; |
1959 | 0 | while (tok2->astOperand1()) |
1960 | 0 | tok2 = tok2->astOperand1(); |
1961 | 0 | if (Token::simpleMatch(tok2->previous(), ")") && Token::simpleMatch(tok2->previous()->link(), "( void")) |
1962 | 0 | return true; |
1963 | 0 | if (Token::simpleMatch(tok2, "( void")) |
1964 | 0 | return true; |
1965 | 0 | return Token::Match(tok2->previous(), "delete|throw|return"); |
1966 | 0 | } |
1967 | | |
1968 | | static bool isConstTop(const Token *tok) |
1969 | 37.6k | { |
1970 | 37.6k | if (!tok) |
1971 | 0 | return false; |
1972 | 37.6k | if (!tok->astParent()) |
1973 | 18.7k | return true; |
1974 | 18.9k | if (Token::simpleMatch(tok->astParent(), ";") && |
1975 | 18.9k | Token::Match(tok->astTop()->previous(), "for|if (") && Token::simpleMatch(tok->astTop()->astOperand2(), ";")) { |
1976 | 0 | if (Token::simpleMatch(tok->astParent()->astParent(), ";")) |
1977 | 0 | return tok->astParent()->astOperand2() == tok; |
1978 | 0 | return tok->astParent()->astOperand1() == tok; |
1979 | 0 | } |
1980 | 18.9k | if (Token::simpleMatch(tok, "[")) { |
1981 | 0 | const Token* bracTok = tok; |
1982 | 0 | while (Token::simpleMatch(bracTok->astParent(), "[")) |
1983 | 0 | bracTok = bracTok->astParent(); |
1984 | 0 | if (!bracTok->astParent()) |
1985 | 0 | return true; |
1986 | 0 | } |
1987 | 18.9k | if (tok->str() == "," && tok->astParent()->isAssignmentOp()) |
1988 | 0 | return true; |
1989 | 18.9k | return false; |
1990 | 18.9k | } |
1991 | | |
1992 | | void CheckOther::checkIncompleteStatement() |
1993 | 1.36k | { |
1994 | 1.36k | if (!mSettings->severity.isEnabled(Severity::warning)) |
1995 | 0 | return; |
1996 | | |
1997 | 1.36k | logChecker("CheckOther::checkIncompleteStatement"); // warning |
1998 | | |
1999 | 93.5k | for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) { |
2000 | 92.2k | const Scope *scope = tok->scope(); |
2001 | 92.2k | if (scope && !scope->isExecutable()) |
2002 | 54.5k | continue; |
2003 | 37.6k | if (!isConstTop(tok)) |
2004 | 18.9k | continue; |
2005 | 18.7k | if (tok->str() == "," && Token::simpleMatch(tok->astTop()->previous(), "for (")) |
2006 | 0 | continue; |
2007 | | |
2008 | | // Do not warn for statement when both lhs and rhs has side effects: |
2009 | | // dostuff() || x=213; |
2010 | 18.7k | if (Token::Match(tok, "%oror%|&&")) { |
2011 | 0 | bool warn = false; |
2012 | 0 | visitAstNodes(tok, [&warn](const Token *child) { |
2013 | 0 | if (Token::Match(child, "%oror%|&&")) |
2014 | 0 | return ChildrenToVisit::op1_and_op2; |
2015 | 0 | if (child->isAssignmentOp()) |
2016 | 0 | return ChildrenToVisit::none; |
2017 | 0 | if (child->tokType() == Token::Type::eIncDecOp) |
2018 | 0 | return ChildrenToVisit::none; |
2019 | 0 | if (Token::Match(child->previous(), "%name% (")) |
2020 | 0 | return ChildrenToVisit::none; |
2021 | 0 | warn = true; |
2022 | 0 | return ChildrenToVisit::done; |
2023 | 0 | }); |
2024 | 0 | if (!warn) |
2025 | 0 | continue; |
2026 | 0 | } |
2027 | | |
2028 | 18.7k | const Token *rtok = nextAfterAstRightmostLeaf(tok); |
2029 | 18.7k | if (!Token::simpleMatch(tok->astParent(), ";") && !Token::simpleMatch(rtok, ";") && |
2030 | 18.7k | !Token::Match(tok->previous(), ";|}|{ %any% ;") && |
2031 | 18.7k | !(mTokenizer->isCPP() && tok->isCast() && !tok->astParent()) && |
2032 | 18.7k | !Token::simpleMatch(tok->tokAt(-2), "for (") && |
2033 | 18.7k | !Token::Match(tok->tokAt(-1), "%var% [") && |
2034 | 18.7k | !(tok->str() == "," && tok->astParent() && tok->astParent()->isAssignmentOp())) |
2035 | 15.2k | continue; |
2036 | | // Skip statement expressions |
2037 | 3.45k | if (Token::simpleMatch(rtok, "; } )")) |
2038 | 0 | continue; |
2039 | 3.45k | if (!isConstStatement(tok, mTokenizer->isCPP())) |
2040 | 3.45k | continue; |
2041 | 0 | if (isVoidStmt(tok)) |
2042 | 0 | continue; |
2043 | 0 | if (mTokenizer->isCPP() && tok->str() == "&" && !(tok->astOperand1()->valueType() && tok->astOperand1()->valueType()->isIntegral())) |
2044 | | // Possible archive |
2045 | 0 | continue; |
2046 | 0 | const bool inconclusive = tok->isConstOp(); |
2047 | 0 | if (mSettings->certainty.isEnabled(Certainty::inconclusive) || !inconclusive) |
2048 | 0 | constStatementError(tok, tok->isNumber() ? "numeric" : "string", inconclusive); |
2049 | 0 | } |
2050 | 1.36k | } |
2051 | | |
2052 | | void CheckOther::constStatementError(const Token *tok, const std::string &type, bool inconclusive) |
2053 | 0 | { |
2054 | 0 | const Token *valueTok = tok; |
2055 | 0 | while (valueTok && valueTok->isCast()) |
2056 | 0 | valueTok = valueTok->astOperand2() ? valueTok->astOperand2() : valueTok->astOperand1(); |
2057 | |
|
2058 | 0 | std::string msg; |
2059 | 0 | if (Token::simpleMatch(tok, "==")) |
2060 | 0 | msg = "Found suspicious equality comparison. Did you intend to assign a value instead?"; |
2061 | 0 | else if (Token::Match(tok, ",|!|~|%cop%")) |
2062 | 0 | msg = "Found suspicious operator '" + tok->str() + "', result is not used."; |
2063 | 0 | else if (Token::Match(tok, "%var%")) |
2064 | 0 | msg = "Unused variable value '" + tok->str() + "'"; |
2065 | 0 | else if (isConstant(valueTok)) { |
2066 | 0 | std::string typeStr("string"); |
2067 | 0 | if (valueTok->isNumber()) |
2068 | 0 | typeStr = "numeric"; |
2069 | 0 | else if (valueTok->isBoolean()) |
2070 | 0 | typeStr = "bool"; |
2071 | 0 | else if (valueTok->tokType() == Token::eChar) |
2072 | 0 | typeStr = "character"; |
2073 | 0 | else if (isNullOperand(valueTok)) |
2074 | 0 | typeStr = "NULL"; |
2075 | 0 | msg = "Redundant code: Found a statement that begins with " + typeStr + " constant."; |
2076 | 0 | } |
2077 | 0 | else if (!tok) |
2078 | 0 | msg = "Redundant code: Found a statement that begins with " + type + " constant."; |
2079 | 0 | else if (tok->isCast() && tok->tokType() == Token::Type::eExtendedOp) { |
2080 | 0 | msg = "Redundant code: Found unused cast "; |
2081 | 0 | msg += valueTok ? "of expression '" + valueTok->expressionString() + "'." : "expression."; |
2082 | 0 | } |
2083 | 0 | else if (tok->str() == "?" && tok->tokType() == Token::Type::eExtendedOp) |
2084 | 0 | msg = "Redundant code: Found unused result of ternary operator."; |
2085 | 0 | else if (tok->str() == "." && tok->tokType() == Token::Type::eOther) |
2086 | 0 | msg = "Redundant code: Found unused member access."; |
2087 | 0 | else if (tok->str() == "[" && tok->tokType() == Token::Type::eExtendedOp) |
2088 | 0 | msg = "Redundant code: Found unused array access."; |
2089 | 0 | else if (mSettings->debugwarnings) { |
2090 | 0 | reportError(tok, Severity::debug, "debug", "constStatementError not handled."); |
2091 | 0 | return; |
2092 | 0 | } |
2093 | 0 | reportError(tok, Severity::warning, "constStatement", msg, CWE398, inconclusive ? Certainty::inconclusive : Certainty::normal); |
2094 | 0 | } |
2095 | | |
2096 | | //--------------------------------------------------------------------------- |
2097 | | // Detect division by zero. |
2098 | | //--------------------------------------------------------------------------- |
2099 | | void CheckOther::checkZeroDivision() |
2100 | 1.36k | { |
2101 | 1.36k | logChecker("CheckOther::checkZeroDivision"); |
2102 | | |
2103 | 93.5k | for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) { |
2104 | 92.2k | if (!tok->astOperand2() || !tok->astOperand1()) |
2105 | 78.0k | continue; |
2106 | 14.2k | if (tok->str() != "%" && tok->str() != "/" && tok->str() != "%=" && tok->str() != "/=") |
2107 | 13.6k | continue; |
2108 | 557 | if (!tok->valueType() || !tok->valueType()->isIntegral()) |
2109 | 237 | continue; |
2110 | 320 | if (tok->scope() && tok->scope()->type == Scope::eEnum) // don't warn for compile-time error |
2111 | 0 | continue; |
2112 | | |
2113 | | // Value flow.. |
2114 | 320 | const ValueFlow::Value *value = tok->astOperand2()->getValue(0LL); |
2115 | 320 | if (value && mSettings->isEnabled(value, false)) |
2116 | 4 | zerodivError(tok, value); |
2117 | 320 | } |
2118 | 1.36k | } |
2119 | | |
2120 | | void CheckOther::zerodivError(const Token *tok, const ValueFlow::Value *value) |
2121 | 4 | { |
2122 | 4 | if (!tok && !value) { |
2123 | 0 | reportError(tok, Severity::error, "zerodiv", "Division by zero.", CWE369, Certainty::normal); |
2124 | 0 | reportError(tok, Severity::warning, "zerodivcond", ValueFlow::eitherTheConditionIsRedundant(nullptr) + " or there is division by zero.", CWE369, Certainty::normal); |
2125 | 0 | return; |
2126 | 0 | } |
2127 | | |
2128 | 4 | const ErrorPath errorPath = getErrorPath(tok, value, "Division by zero"); |
2129 | | |
2130 | 4 | std::ostringstream errmsg; |
2131 | 4 | if (value->condition) { |
2132 | 0 | const int line = tok ? tok->linenr() : 0; |
2133 | 0 | errmsg << ValueFlow::eitherTheConditionIsRedundant(value->condition) |
2134 | 0 | << " or there is division by zero at line " << line << "."; |
2135 | 0 | } else |
2136 | 4 | errmsg << "Division by zero."; |
2137 | | |
2138 | 4 | reportError(errorPath, |
2139 | 4 | value->errorSeverity() ? Severity::error : Severity::warning, |
2140 | 4 | value->condition ? "zerodivcond" : "zerodiv", |
2141 | 4 | errmsg.str(), CWE369, value->isInconclusive() ? Certainty::inconclusive : Certainty::normal); |
2142 | 4 | } |
2143 | | |
2144 | | //--------------------------------------------------------------------------- |
2145 | | // Check for NaN (not-a-number) in an arithmetic expression, e.g. |
2146 | | // double d = 1.0 / 0.0 + 100.0; |
2147 | | //--------------------------------------------------------------------------- |
2148 | | |
2149 | | void CheckOther::checkNanInArithmeticExpression() |
2150 | 1.36k | { |
2151 | 1.36k | if (!mSettings->severity.isEnabled(Severity::style)) |
2152 | 0 | return; |
2153 | 1.36k | logChecker("CheckOther::checkNanInArithmeticExpression"); // style |
2154 | 93.5k | for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) { |
2155 | 92.2k | if (tok->str() != "/") |
2156 | 91.9k | continue; |
2157 | 269 | if (!Token::Match(tok->astParent(), "[+-]")) |
2158 | 226 | continue; |
2159 | 43 | if (Token::simpleMatch(tok->astOperand2(), "0.0")) |
2160 | 0 | nanInArithmeticExpressionError(tok); |
2161 | 43 | } |
2162 | 1.36k | } |
2163 | | |
2164 | | void CheckOther::nanInArithmeticExpressionError(const Token *tok) |
2165 | 0 | { |
2166 | 0 | reportError(tok, Severity::style, "nanInArithmeticExpression", |
2167 | 0 | "Using NaN/Inf in a computation.\n" |
2168 | 0 | "Using NaN/Inf in a computation. " |
2169 | 0 | "Although nothing bad really happens, it is suspicious.", CWE369, Certainty::normal); |
2170 | 0 | } |
2171 | | |
2172 | | //--------------------------------------------------------------------------- |
2173 | | // Creating instance of classes which are destroyed immediately |
2174 | | //--------------------------------------------------------------------------- |
2175 | | void CheckOther::checkMisusedScopedObject() |
2176 | 1.36k | { |
2177 | | // Skip this check for .c files |
2178 | 1.36k | if (mTokenizer->isC()) |
2179 | 0 | return; |
2180 | | |
2181 | 1.36k | if (!mSettings->severity.isEnabled(Severity::style)) |
2182 | 0 | return; |
2183 | | |
2184 | 1.36k | logChecker("CheckOther::checkMisusedScopedObject"); // style,c++ |
2185 | | |
2186 | 35.9k | auto getConstructorTok = [](const Token* tok, std::string& typeStr) -> const Token* { |
2187 | 35.9k | if (!Token::Match(tok, "[;{}] %name%") || tok->next()->isKeyword()) |
2188 | 34.3k | return nullptr; |
2189 | 1.53k | tok = tok->next(); |
2190 | 1.53k | typeStr.clear(); |
2191 | 1.53k | while (Token::Match(tok, "%name% ::")) { |
2192 | 0 | typeStr += tok->str(); |
2193 | 0 | typeStr += "::"; |
2194 | 0 | tok = tok->tokAt(2); |
2195 | 0 | } |
2196 | 1.53k | typeStr += tok->str(); |
2197 | 1.53k | const Token* endTok = tok; |
2198 | 1.53k | if (Token::Match(endTok, "%name% <")) |
2199 | 0 | endTok = endTok->linkAt(1); |
2200 | 1.53k | if (Token::Match(endTok, "%name%|> (|{") && Token::Match(endTok->linkAt(1), ")|} ;") && |
2201 | 1.53k | !Token::simpleMatch(endTok->next()->astParent(), ";")) { // for loop condition |
2202 | 0 | return tok; |
2203 | 0 | } |
2204 | 1.53k | return nullptr; |
2205 | 1.53k | }; |
2206 | | |
2207 | 1.36k | auto isLibraryConstructor = [&](const Token* tok, const std::string& typeStr) -> bool { |
2208 | 0 | const Library::TypeCheck typeCheck = mSettings->library.getTypeCheck("unusedvar", typeStr); |
2209 | 0 | if (typeCheck == Library::TypeCheck::check || typeCheck == Library::TypeCheck::checkFiniteLifetime) |
2210 | 0 | return true; |
2211 | 0 | return mSettings->library.detectContainerOrIterator(tok); |
2212 | 0 | }; |
2213 | | |
2214 | 1.36k | const SymbolDatabase* const symbolDatabase = mTokenizer->getSymbolDatabase(); |
2215 | 1.36k | std::string typeStr; |
2216 | 1.73k | for (const Scope * scope : symbolDatabase->functionScopes) { |
2217 | 37.6k | for (const Token *tok = scope->bodyStart; tok && tok != scope->bodyEnd; tok = tok->next()) { |
2218 | 35.9k | const Token* ctorTok = getConstructorTok(tok, typeStr); |
2219 | 35.9k | if (ctorTok && (((ctorTok->type() || ctorTok->isStandardType() || (ctorTok->function() && ctorTok->function()->isConstructor())) // TODO: The rhs of || should be removed; It is a workaround for a symboldatabase bug |
2220 | 0 | && (!ctorTok->function() || ctorTok->function()->isConstructor()) // // is not a function on this scope or is function in this scope and it's a ctor |
2221 | 0 | && ctorTok->str() != "void") || isLibraryConstructor(tok->next(), typeStr))) { |
2222 | 0 | const Token* parTok = ctorTok->next(); |
2223 | 0 | if (Token::simpleMatch(parTok, "<") && parTok->link()) |
2224 | 0 | parTok = parTok->link()->next(); |
2225 | 0 | if (const Token* arg = parTok->astOperand2()) { |
2226 | 0 | if (!isConstStatement(arg, mTokenizer->isCPP())) |
2227 | 0 | continue; |
2228 | 0 | if (parTok->str() == "(") { |
2229 | 0 | if (arg->varId() && !(arg->variable() && arg->variable()->nameToken() != arg)) |
2230 | 0 | continue; |
2231 | 0 | const Token* rml = nextAfterAstRightmostLeaf(arg); |
2232 | 0 | if (rml && rml->previous() && rml->previous()->varId()) |
2233 | 0 | continue; |
2234 | 0 | } |
2235 | 0 | } |
2236 | 0 | tok = tok->next(); |
2237 | 0 | misusedScopeObjectError(ctorTok, typeStr); |
2238 | 0 | tok = tok->next(); |
2239 | 0 | } |
2240 | 35.9k | if (tok->isAssignmentOp() && Token::simpleMatch(tok->astOperand1(), "(") && tok->astOperand1()->astOperand1()) { |
2241 | 0 | if (const Function* ftok = tok->astOperand1()->astOperand1()->function()) { |
2242 | 0 | if (ftok->retType && Token::Match(ftok->retType->classDef, "class|struct|union") && !Function::returnsReference(ftok, /*unknown*/ false, /*includeRValueRef*/ true)) |
2243 | 0 | misusedScopeObjectError(tok->next(), ftok->retType->name(), /*isAssignment*/ true); |
2244 | 0 | } |
2245 | 0 | } |
2246 | 35.9k | } |
2247 | 1.73k | } |
2248 | 1.36k | } |
2249 | | |
2250 | | void CheckOther::misusedScopeObjectError(const Token *tok, const std::string& varname, bool isAssignment) |
2251 | 0 | { |
2252 | 0 | std::string msg = "Instance of '$symbol' object is destroyed immediately"; |
2253 | 0 | msg += isAssignment ? ", assignment has no effect." : "."; |
2254 | 0 | reportError(tok, Severity::style, |
2255 | 0 | "unusedScopedObject", |
2256 | 0 | "$symbol:" + varname + "\n" + |
2257 | 0 | msg, CWE563, Certainty::normal); |
2258 | 0 | } |
2259 | | |
2260 | | static const Token * getSingleExpressionInBlock(const Token * tok) |
2261 | 314 | { |
2262 | 314 | if (!tok) |
2263 | 0 | return nullptr; |
2264 | 314 | const Token * top = tok->astTop(); |
2265 | 314 | if (!top) |
2266 | 0 | return nullptr; |
2267 | 314 | const Token * nextExpression = nextAfterAstRightmostLeaf(top); |
2268 | 314 | if (!Token::simpleMatch(nextExpression, "; }")) |
2269 | 258 | return nullptr; |
2270 | 56 | return top; |
2271 | 314 | } |
2272 | | |
2273 | | //----------------------------------------------------------------------------- |
2274 | | // check for duplicate code in if and else branches |
2275 | | // if (a) { b = true; } else { b = true; } |
2276 | | //----------------------------------------------------------------------------- |
2277 | | void CheckOther::checkDuplicateBranch() |
2278 | 1.36k | { |
2279 | | // This is inconclusive since in practice most warnings are noise: |
2280 | | // * There can be unfixed low-priority todos. The code is fine as it |
2281 | | // is but it could be possible to enhance it. Writing a warning |
2282 | | // here is noise since the code is fine (see cppcheck, abiword, ..) |
2283 | | // * There can be overspecified code so some conditions can't be true |
2284 | | // and their conditional code is a duplicate of the condition that |
2285 | | // is always true just in case it would be false. See for instance |
2286 | | // abiword. |
2287 | 1.36k | if (!mSettings->severity.isEnabled(Severity::style) || !mSettings->certainty.isEnabled(Certainty::inconclusive)) |
2288 | 0 | return; |
2289 | | |
2290 | 1.36k | logChecker("CheckOther::checkDuplicateBranch"); // style,inconclusive |
2291 | | |
2292 | 1.36k | const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); |
2293 | | |
2294 | 4.94k | for (const Scope & scope : symbolDatabase->scopeList) { |
2295 | 4.94k | if (scope.type != Scope::eIf) |
2296 | 4.02k | continue; |
2297 | | |
2298 | | // check all the code in the function for if (..) else |
2299 | 919 | if (Token::simpleMatch(scope.bodyEnd, "} else {")) { |
2300 | | // Make sure there are no macros (different macros might be expanded |
2301 | | // to the same code) |
2302 | 457 | bool macro = false; |
2303 | 7.75k | for (const Token *tok = scope.bodyStart; tok != scope.bodyEnd->linkAt(2); tok = tok->next()) { |
2304 | 7.30k | if (tok->isExpandedMacro()) { |
2305 | 0 | macro = true; |
2306 | 0 | break; |
2307 | 0 | } |
2308 | 7.30k | } |
2309 | 457 | if (macro) |
2310 | 0 | continue; |
2311 | | |
2312 | 457 | const Token * const tokIf = scope.bodyStart->next(); |
2313 | 457 | const Token * const tokElse = scope.bodyEnd->tokAt(3); |
2314 | | |
2315 | | // compare first tok before stringifying the whole blocks |
2316 | 457 | const std::string tokIfStr = tokIf->stringify(false, true, false); |
2317 | 457 | if (tokIfStr.empty()) |
2318 | 0 | continue; |
2319 | | |
2320 | 457 | const std::string tokElseStr = tokElse->stringify(false, true, false); |
2321 | | |
2322 | 457 | if (tokIfStr == tokElseStr) { |
2323 | | // save if branch code |
2324 | 199 | const std::string branch1 = tokIf->stringifyList(scope.bodyEnd); |
2325 | | |
2326 | 199 | if (branch1.empty()) |
2327 | 195 | continue; |
2328 | | |
2329 | | // save else branch code |
2330 | 4 | const std::string branch2 = tokElse->stringifyList(scope.bodyEnd->linkAt(2)); |
2331 | | |
2332 | | // check for duplicates |
2333 | 4 | if (branch1 == branch2) { |
2334 | 0 | duplicateBranchError(scope.classDef, scope.bodyEnd->next(), ErrorPath{}); |
2335 | 0 | continue; |
2336 | 0 | } |
2337 | 4 | } |
2338 | | |
2339 | | // check for duplicates using isSameExpression |
2340 | 262 | const Token * branchTop1 = getSingleExpressionInBlock(tokIf); |
2341 | 262 | if (!branchTop1) |
2342 | 210 | continue; |
2343 | 52 | const Token * branchTop2 = getSingleExpressionInBlock(tokElse); |
2344 | 52 | if (!branchTop2) |
2345 | 48 | continue; |
2346 | 4 | if (branchTop1->str() != branchTop2->str()) |
2347 | 2 | continue; |
2348 | 2 | ErrorPath errorPath; |
2349 | 2 | if (isSameExpression(mTokenizer->isCPP(), false, branchTop1->astOperand1(), branchTop2->astOperand1(), mSettings->library, true, true, &errorPath) && |
2350 | 2 | isSameExpression(mTokenizer->isCPP(), false, branchTop1->astOperand2(), branchTop2->astOperand2(), mSettings->library, true, true, &errorPath)) |
2351 | 0 | duplicateBranchError(scope.classDef, scope.bodyEnd->next(), errorPath); |
2352 | 2 | } |
2353 | 919 | } |
2354 | 1.36k | } |
2355 | | |
2356 | | void CheckOther::duplicateBranchError(const Token *tok1, const Token *tok2, ErrorPath errors) |
2357 | 0 | { |
2358 | 0 | errors.emplace_back(tok2, ""); |
2359 | 0 | errors.emplace_back(tok1, ""); |
2360 | |
|
2361 | 0 | reportError(errors, Severity::style, "duplicateBranch", "Found duplicate branches for 'if' and 'else'.\n" |
2362 | 0 | "Finding the same code in an 'if' and related 'else' branch is suspicious and " |
2363 | 0 | "might indicate a cut and paste or logic error. Please examine this code " |
2364 | 0 | "carefully to determine if it is correct.", CWE398, Certainty::inconclusive); |
2365 | 0 | } |
2366 | | |
2367 | | |
2368 | | //----------------------------------------------------------------------------- |
2369 | | // Check for a free() of an invalid address |
2370 | | // char* p = malloc(100); |
2371 | | // free(p + 10); |
2372 | | //----------------------------------------------------------------------------- |
2373 | | void CheckOther::checkInvalidFree() |
2374 | 1.36k | { |
2375 | 1.36k | std::map<int, bool> inconclusive; |
2376 | 1.36k | std::map<int, std::string> allocation; |
2377 | | |
2378 | 1.36k | logChecker("CheckOther::checkInvalidFree"); |
2379 | | |
2380 | 1.36k | const bool printInconclusive = mSettings->certainty.isEnabled(Certainty::inconclusive); |
2381 | 1.36k | const SymbolDatabase* symbolDatabase = mTokenizer->getSymbolDatabase(); |
2382 | 1.73k | for (const Scope * scope : symbolDatabase->functionScopes) { |
2383 | 35.9k | for (const Token* tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) { |
2384 | | |
2385 | | // Keep track of which variables were assigned addresses to newly-allocated memory |
2386 | 34.1k | if ((mTokenizer->isCPP() && Token::Match(tok, "%var% = new")) || |
2387 | 34.1k | (Token::Match(tok, "%var% = %name% (") && mSettings->library.getAllocFuncInfo(tok->tokAt(2)))) { |
2388 | 0 | allocation.insert(std::make_pair(tok->varId(), tok->strAt(2))); |
2389 | 0 | inconclusive.insert(std::make_pair(tok->varId(), false)); |
2390 | 0 | } |
2391 | | |
2392 | | // If a previously-allocated pointer is incremented or decremented, any subsequent |
2393 | | // free involving pointer arithmetic may or may not be invalid, so we should only |
2394 | | // report an inconclusive result. |
2395 | 34.1k | else if (Token::Match(tok, "%var% = %name% +|-") && |
2396 | 34.1k | tok->varId() == tok->tokAt(2)->varId() && |
2397 | 34.1k | allocation.find(tok->varId()) != allocation.end()) { |
2398 | 0 | if (printInconclusive) |
2399 | 0 | inconclusive[tok->varId()] = true; |
2400 | 0 | else { |
2401 | 0 | allocation.erase(tok->varId()); |
2402 | 0 | inconclusive.erase(tok->varId()); |
2403 | 0 | } |
2404 | 0 | } |
2405 | | |
2406 | | // If a previously-allocated pointer is assigned a completely new value, |
2407 | | // we can't know if any subsequent free() on that pointer is valid or not. |
2408 | 34.1k | else if (Token::Match(tok, "%var% =")) { |
2409 | 1.51k | allocation.erase(tok->varId()); |
2410 | 1.51k | inconclusive.erase(tok->varId()); |
2411 | 1.51k | } |
2412 | | |
2413 | | // If a variable that was previously assigned a newly-allocated memory location is |
2414 | | // added or subtracted from when used to free the memory, report an error. |
2415 | 32.6k | else if ((Token::Match(tok, "%name% ( %any% +|-") && mSettings->library.getDeallocFuncInfo(tok)) || |
2416 | 32.6k | Token::Match(tok, "delete [ ] ( %any% +|-") || |
2417 | 32.6k | Token::Match(tok, "delete %any% +|- %any%")) { |
2418 | |
|
2419 | 0 | const int varIndex = tok->strAt(1) == "(" ? 2 : |
2420 | 0 | tok->strAt(3) == "(" ? 4 : 1; |
2421 | 0 | const int var1 = tok->tokAt(varIndex)->varId(); |
2422 | 0 | const int var2 = tok->tokAt(varIndex + 2)->varId(); |
2423 | 0 | const std::map<int, bool>::const_iterator alloc1 = inconclusive.find(var1); |
2424 | 0 | const std::map<int, bool>::const_iterator alloc2 = inconclusive.find(var2); |
2425 | 0 | if (alloc1 != inconclusive.end()) { |
2426 | 0 | invalidFreeError(tok, allocation[var1], alloc1->second); |
2427 | 0 | } else if (alloc2 != inconclusive.end()) { |
2428 | 0 | invalidFreeError(tok, allocation[var2], alloc2->second); |
2429 | 0 | } |
2430 | 0 | } |
2431 | | |
2432 | | // If the previously-allocated variable is passed in to another function |
2433 | | // as a parameter, it might be modified, so we shouldn't report an error |
2434 | | // if it is later used to free memory |
2435 | 32.6k | else if (Token::Match(tok, "%name% (") && !mSettings->library.isFunctionConst(tok->str(), true)) { |
2436 | 1.46k | const Token* tok2 = Token::findmatch(tok->next(), "%var%", tok->linkAt(1)); |
2437 | 4.48k | while (tok2 != nullptr) { |
2438 | 3.02k | allocation.erase(tok->varId()); |
2439 | 3.02k | inconclusive.erase(tok2->varId()); |
2440 | 3.02k | tok2 = Token::findmatch(tok2->next(), "%var%", tok->linkAt(1)); |
2441 | 3.02k | } |
2442 | 1.46k | } |
2443 | 34.1k | } |
2444 | 1.73k | } |
2445 | 1.36k | } |
2446 | | |
2447 | | void CheckOther::invalidFreeError(const Token *tok, const std::string &allocation, bool inconclusive) |
2448 | 0 | { |
2449 | 0 | std::string alloc = allocation; |
2450 | 0 | if (alloc != "new") |
2451 | 0 | alloc += "()"; |
2452 | 0 | std::string deallocated = (alloc == "new") ? "deleted" : "freed"; |
2453 | 0 | reportError(tok, Severity::error, "invalidFree", "Mismatching address is " + deallocated + ". The address you get from " + alloc + " must be " + deallocated + " without offset.", CWE(0U), inconclusive ? Certainty::inconclusive : Certainty::normal); |
2454 | 0 | } |
2455 | | |
2456 | | |
2457 | | //--------------------------------------------------------------------------- |
2458 | | // check for the same expression on both sides of an operator |
2459 | | // (x == x), (x && x), (x || x) |
2460 | | // (x.y == x.y), (x.y && x.y), (x.y || x.y) |
2461 | | //--------------------------------------------------------------------------- |
2462 | | |
2463 | | namespace { |
2464 | | bool notconst(const Function* func) |
2465 | 1.73k | { |
2466 | 1.73k | return !func->isConst(); |
2467 | 1.73k | } |
2468 | | |
2469 | | void getConstFunctions(const SymbolDatabase *symbolDatabase, std::list<const Function*> &constFunctions) |
2470 | 1.36k | { |
2471 | 4.94k | for (const Scope &scope : symbolDatabase->scopeList) { |
2472 | | // only add const functions that do not have a non-const overloaded version |
2473 | | // since it is pretty much impossible to tell which is being called. |
2474 | 4.94k | using StringFunctionMap = std::map<std::string, std::list<const Function*>>; |
2475 | 4.94k | StringFunctionMap functionsByName; |
2476 | 4.94k | for (const Function &func : scope.functionList) { |
2477 | 1.73k | functionsByName[func.tokenDef->str()].push_back(&func); |
2478 | 1.73k | } |
2479 | 4.94k | for (std::pair<const std::string, std::list<const Function*>>& it : functionsByName) { |
2480 | 1.73k | const std::list<const Function*>::const_iterator nc = std::find_if(it.second.cbegin(), it.second.cend(), notconst); |
2481 | 1.73k | if (nc == it.second.cend()) { |
2482 | | // ok to add all of them |
2483 | 0 | constFunctions.splice(constFunctions.end(), it.second); |
2484 | 0 | } |
2485 | 1.73k | } |
2486 | 4.94k | } |
2487 | 1.36k | } |
2488 | | } |
2489 | | |
2490 | | void CheckOther::checkDuplicateExpression() |
2491 | 1.36k | { |
2492 | 1.36k | const bool styleEnabled = mSettings->severity.isEnabled(Severity::style); |
2493 | 1.36k | const bool warningEnabled = mSettings->severity.isEnabled(Severity::warning); |
2494 | 1.36k | if (!styleEnabled && !warningEnabled) |
2495 | 0 | return; |
2496 | | |
2497 | 1.36k | logChecker("CheckOther::checkDuplicateExpression"); // style,warning |
2498 | | |
2499 | | // Parse all executing scopes.. |
2500 | 1.36k | const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); |
2501 | | |
2502 | 1.36k | std::list<const Function*> constFunctions; |
2503 | 1.36k | getConstFunctions(symbolDatabase, constFunctions); |
2504 | | |
2505 | 1.36k | const bool cpp = mTokenizer->isCPP(); |
2506 | | |
2507 | 1.73k | for (const Scope *scope : symbolDatabase->functionScopes) { |
2508 | 37.6k | for (const Token *tok = scope->bodyStart; tok != scope->bodyEnd; tok = tok->next()) { |
2509 | 35.9k | if (tok->str() == "=" && Token::Match(tok->astOperand1(), "%var%")) { |
2510 | 1.46k | const Token * endStatement = Token::findsimplematch(tok, ";"); |
2511 | 1.46k | if (Token::Match(endStatement, "; %type% %var% ;")) { |
2512 | 25 | endStatement = endStatement->tokAt(4); |
2513 | 25 | } |
2514 | 1.46k | if (Token::Match(endStatement, "%var% %assign%")) { |
2515 | 0 | const Token * nextAssign = endStatement->tokAt(1); |
2516 | 0 | const Token * var1 = tok->astOperand1(); |
2517 | 0 | const Token * var2 = nextAssign->astOperand1(); |
2518 | 0 | if (var1 && var2 && |
2519 | 0 | Token::Match(var1->previous(), ";|{|} %var%") && |
2520 | 0 | Token::Match(var2->previous(), ";|{|} %var%") && |
2521 | 0 | var2->valueType() && var1->valueType() && |
2522 | 0 | var2->valueType()->originalTypeName == var1->valueType()->originalTypeName && |
2523 | 0 | var2->valueType()->pointer == var1->valueType()->pointer && |
2524 | 0 | var2->valueType()->constness == var1->valueType()->constness && |
2525 | 0 | var2->varId() != var1->varId() && ( |
2526 | 0 | tok->astOperand2()->isArithmeticalOp() || |
2527 | 0 | tok->astOperand2()->str() == "." || |
2528 | 0 | Token::Match(tok->astOperand2()->previous(), "%name% (") |
2529 | 0 | ) && |
2530 | 0 | tok->next()->tokType() != Token::eType && |
2531 | 0 | isSameExpression(cpp, true, tok->next(), nextAssign->next(), mSettings->library, true, false) && |
2532 | 0 | isSameExpression(cpp, true, tok->astOperand2(), nextAssign->astOperand2(), mSettings->library, true, false) && |
2533 | 0 | tok->astOperand2()->expressionString() == nextAssign->astOperand2()->expressionString()) { |
2534 | 0 | bool differentDomain = false; |
2535 | 0 | const Scope * varScope = var1->scope() ? var1->scope() : scope; |
2536 | 0 | for (const Token *assignTok = Token::findsimplematch(var2, ";"); assignTok && assignTok != varScope->bodyEnd; assignTok = assignTok->next()) { |
2537 | 0 | if (!Token::Match(assignTok, "%assign%|%comp%")) |
2538 | 0 | continue; |
2539 | 0 | if (!assignTok->astOperand1()) |
2540 | 0 | continue; |
2541 | 0 | if (!assignTok->astOperand2()) |
2542 | 0 | continue; |
2543 | | |
2544 | 0 | if (assignTok->astOperand1()->varId() != var1->varId() && |
2545 | 0 | assignTok->astOperand1()->varId() != var2->varId() && |
2546 | 0 | !isSameExpression(cpp, |
2547 | 0 | true, |
2548 | 0 | tok->astOperand2(), |
2549 | 0 | assignTok->astOperand1(), |
2550 | 0 | mSettings->library, |
2551 | 0 | true, |
2552 | 0 | true)) |
2553 | 0 | continue; |
2554 | 0 | if (assignTok->astOperand2()->varId() != var1->varId() && |
2555 | 0 | assignTok->astOperand2()->varId() != var2->varId() && |
2556 | 0 | !isSameExpression(cpp, |
2557 | 0 | true, |
2558 | 0 | tok->astOperand2(), |
2559 | 0 | assignTok->astOperand2(), |
2560 | 0 | mSettings->library, |
2561 | 0 | true, |
2562 | 0 | true)) |
2563 | 0 | continue; |
2564 | 0 | differentDomain = true; |
2565 | 0 | break; |
2566 | 0 | } |
2567 | 0 | if (!differentDomain && !isUniqueExpression(tok->astOperand2())) |
2568 | 0 | duplicateAssignExpressionError(var1, var2, false); |
2569 | 0 | else if (mSettings->certainty.isEnabled(Certainty::inconclusive)) |
2570 | 0 | duplicateAssignExpressionError(var1, var2, true); |
2571 | 0 | } |
2572 | 0 | } |
2573 | 1.46k | } |
2574 | 35.9k | auto isInsideLambdaCaptureList = [](const Token* tok) { |
2575 | 7.69k | const Token* parent = tok->astParent(); |
2576 | 7.69k | while (Token::simpleMatch(parent, ",")) |
2577 | 0 | parent = parent->astParent(); |
2578 | 7.69k | return isLambdaCaptureList(parent); |
2579 | 7.69k | }; |
2580 | 35.9k | ErrorPath errorPath; |
2581 | 35.9k | if (tok->isOp() && tok->astOperand1() && !Token::Match(tok, "+|*|<<|>>|+=|*=|<<=|>>=") && !isInsideLambdaCaptureList(tok)) { |
2582 | 7.69k | if (Token::Match(tok, "==|!=|-") && astIsFloat(tok->astOperand1(), true)) |
2583 | 144 | continue; |
2584 | 7.54k | const bool pointerDereference = (tok->astOperand1() && tok->astOperand1()->isUnaryOp("*")) || |
2585 | 7.54k | (tok->astOperand2() && tok->astOperand2()->isUnaryOp("*")); |
2586 | 7.54k | const bool followVar = (!isConstVarExpression(tok) || Token::Match(tok, "%comp%|%oror%|&&")) && !pointerDereference; |
2587 | 7.54k | if (isSameExpression(cpp, |
2588 | 7.54k | true, |
2589 | 7.54k | tok->astOperand1(), |
2590 | 7.54k | tok->astOperand2(), |
2591 | 7.54k | mSettings->library, |
2592 | 7.54k | true, |
2593 | 7.54k | followVar, |
2594 | 7.54k | &errorPath)) { |
2595 | 548 | if (isWithoutSideEffects(cpp, tok->astOperand1())) { |
2596 | 548 | const Token* loopTok = isInLoopCondition(tok); |
2597 | 548 | if (!loopTok || !isExpressionChanged(tok, tok, loopTok->link()->next()->link(), mSettings, cpp)) { |
2598 | 547 | const bool assignment = tok->str() == "="; |
2599 | 547 | if (assignment && warningEnabled) |
2600 | 163 | selfAssignmentError(tok, tok->astOperand1()->expressionString()); |
2601 | 384 | else if (styleEnabled) { |
2602 | 384 | if (cpp && mSettings->standards.cpp >= Standards::CPP11 && tok->str() == "==") { |
2603 | 78 | const Token* parent = tok->astParent(); |
2604 | 91 | while (parent && parent->astParent()) { |
2605 | 13 | parent = parent->astParent(); |
2606 | 13 | } |
2607 | 78 | if (parent && parent->previous() && parent->previous()->str() == "static_assert") { |
2608 | 0 | continue; |
2609 | 0 | } |
2610 | 78 | } |
2611 | 384 | duplicateExpressionError(tok->astOperand1(), tok->astOperand2(), tok, errorPath); |
2612 | 384 | } |
2613 | 547 | } |
2614 | 548 | } |
2615 | 7.00k | } else if (tok->str() == "=" && Token::simpleMatch(tok->astOperand2(), "=") && |
2616 | 7.00k | isSameExpression(cpp, |
2617 | 56 | false, |
2618 | 56 | tok->astOperand1(), |
2619 | 56 | tok->astOperand2()->astOperand1(), |
2620 | 56 | mSettings->library, |
2621 | 56 | true, |
2622 | 56 | false)) { |
2623 | 15 | if (warningEnabled && isWithoutSideEffects(cpp, tok->astOperand1())) { |
2624 | 15 | selfAssignmentError(tok, tok->astOperand1()->expressionString()); |
2625 | 15 | } |
2626 | 6.98k | } else if (styleEnabled && |
2627 | 6.98k | isOppositeExpression(cpp, |
2628 | 6.98k | tok->astOperand1(), |
2629 | 6.98k | tok->astOperand2(), |
2630 | 6.98k | mSettings->library, |
2631 | 6.98k | false, |
2632 | 6.98k | true, |
2633 | 6.98k | &errorPath) && |
2634 | 6.98k | !Token::Match(tok, "=|-|-=|/|/=") && |
2635 | 6.98k | isWithoutSideEffects(cpp, tok->astOperand1())) { |
2636 | 0 | oppositeExpressionError(tok, errorPath); |
2637 | 6.98k | } else if (!Token::Match(tok, "[-/%]")) { // These operators are not associative |
2638 | 6.32k | if (styleEnabled && tok->astOperand2() && tok->str() == tok->astOperand1()->str() && |
2639 | 6.32k | isSameExpression(cpp, |
2640 | 124 | true, |
2641 | 124 | tok->astOperand2(), |
2642 | 124 | tok->astOperand1()->astOperand2(), |
2643 | 124 | mSettings->library, |
2644 | 124 | true, |
2645 | 124 | followVar, |
2646 | 124 | &errorPath) && |
2647 | 6.32k | isWithoutSideEffects(cpp, tok->astOperand2())) |
2648 | 11 | duplicateExpressionError(tok->astOperand2(), tok->astOperand1()->astOperand2(), tok, errorPath); |
2649 | 6.31k | else if (tok->astOperand2() && isConstExpression(tok->astOperand1(), mSettings->library, cpp)) { |
2650 | 3.30k | auto checkDuplicate = [&](const Token* exp1, const Token* exp2, const Token* ast1) { |
2651 | 176 | if (isSameExpression(cpp, true, exp1, exp2, mSettings->library, true, true, &errorPath) && |
2652 | 176 | isWithoutSideEffects(cpp, exp1) && |
2653 | 176 | isWithoutSideEffects(cpp, ast1->astOperand2())) |
2654 | 5 | duplicateExpressionError(exp1, exp2, tok, errorPath, /*hasMultipleExpr*/ true); |
2655 | 176 | }; |
2656 | 3.30k | const Token *ast1 = tok->astOperand1(); |
2657 | 3.40k | while (ast1 && tok->str() == ast1->str()) { // chain of identical operators |
2658 | 93 | checkDuplicate(ast1->astOperand2(), tok->astOperand2(), ast1); |
2659 | 93 | if (ast1->astOperand1() && ast1->astOperand1()->str() != tok->str()) // check first condition in the chain |
2660 | 83 | checkDuplicate(ast1->astOperand1(), tok->astOperand2(), ast1); |
2661 | 93 | ast1 = ast1->astOperand1(); |
2662 | 93 | } |
2663 | 3.30k | } |
2664 | 6.32k | } |
2665 | 28.2k | } else if (styleEnabled && tok->astOperand1() && tok->astOperand2() && tok->str() == ":" && tok->astParent() && tok->astParent()->str() == "?") { |
2666 | 0 | if (!tok->astOperand1()->values().empty() && !tok->astOperand2()->values().empty() && isEqualKnownValue(tok->astOperand1(), tok->astOperand2()) && |
2667 | 0 | !isVariableChanged(tok->astParent(), /*indirect*/ 0, mSettings, cpp) && |
2668 | 0 | isConstStatement(tok->astOperand1(), cpp) && isConstStatement(tok->astOperand2(), cpp)) |
2669 | 0 | duplicateValueTernaryError(tok); |
2670 | 0 | else if (isSameExpression(cpp, true, tok->astOperand1(), tok->astOperand2(), mSettings->library, false, true, &errorPath)) |
2671 | 0 | duplicateExpressionTernaryError(tok, errorPath); |
2672 | 0 | } |
2673 | 35.9k | } |
2674 | 1.73k | } |
2675 | 1.36k | } |
2676 | | |
2677 | | void CheckOther::oppositeExpressionError(const Token *opTok, ErrorPath errors) |
2678 | 0 | { |
2679 | 0 | errors.emplace_back(opTok, ""); |
2680 | |
|
2681 | 0 | const std::string& op = opTok ? opTok->str() : "&&"; |
2682 | |
|
2683 | 0 | reportError(errors, Severity::style, "oppositeExpression", "Opposite expression on both sides of \'" + op + "\'.\n" |
2684 | 0 | "Finding the opposite expression on both sides of an operator is suspicious and might " |
2685 | 0 | "indicate a cut and paste or logic error. Please examine this code carefully to " |
2686 | 0 | "determine if it is correct.", CWE398, Certainty::normal); |
2687 | 0 | } |
2688 | | |
2689 | | void CheckOther::duplicateExpressionError(const Token *tok1, const Token *tok2, const Token *opTok, ErrorPath errors, bool hasMultipleExpr) |
2690 | 400 | { |
2691 | 400 | errors.emplace_back(opTok, ""); |
2692 | | |
2693 | 400 | const std::string& expr1 = tok1 ? tok1->expressionString() : "x"; |
2694 | 400 | const std::string& expr2 = tok2 ? tok2->expressionString() : "x"; |
2695 | | |
2696 | 400 | const std::string& op = opTok ? opTok->str() : "&&"; |
2697 | 400 | std::string msg = "Same expression " + (hasMultipleExpr ? "\'" + expr1 + "\'" + " found multiple times in chain of \'" + op + "\' operators" : "on both sides of \'" + op + "\'"); |
2698 | 400 | const char *id = "duplicateExpression"; |
2699 | 400 | if (expr1 != expr2 && (!opTok || !opTok->isArithmeticalOp())) { |
2700 | 0 | id = "knownConditionTrueFalse"; |
2701 | 0 | std::string exprMsg = "The comparison \'" + expr1 + " " + op + " " + expr2 + "\' is always "; |
2702 | 0 | if (Token::Match(opTok, "==|>=|<=")) |
2703 | 0 | msg = exprMsg + "true"; |
2704 | 0 | else if (Token::Match(opTok, "!=|>|<")) |
2705 | 0 | msg = exprMsg + "false"; |
2706 | 0 | if (!Token::Match(tok1, "%num%|NULL|nullptr") && !Token::Match(tok2, "%num%|NULL|nullptr")) |
2707 | 0 | msg += " because '" + expr1 + "' and '" + expr2 + "' represent the same value"; |
2708 | 0 | } |
2709 | | |
2710 | 400 | reportError(errors, Severity::style, id, msg + |
2711 | 400 | (std::string(".\nFinding the same expression ") + (hasMultipleExpr ? "more than once in a condition" : "on both sides of an operator")) + |
2712 | 400 | " is suspicious and might indicate a cut and paste or logic error. Please examine this code carefully to " |
2713 | 400 | "determine if it is correct.", CWE398, Certainty::normal); |
2714 | 400 | } |
2715 | | |
2716 | | void CheckOther::duplicateAssignExpressionError(const Token *tok1, const Token *tok2, bool inconclusive) |
2717 | 0 | { |
2718 | 0 | const std::list<const Token *> toks = { tok2, tok1 }; |
2719 | |
|
2720 | 0 | const std::string& var1 = tok1 ? tok1->str() : "x"; |
2721 | 0 | const std::string& var2 = tok2 ? tok2->str() : "x"; |
2722 | |
|
2723 | 0 | reportError(toks, Severity::style, "duplicateAssignExpression", |
2724 | 0 | "Same expression used in consecutive assignments of '" + var1 + "' and '" + var2 + "'.\n" |
2725 | 0 | "Finding variables '" + var1 + "' and '" + var2 + "' that are assigned the same expression " |
2726 | 0 | "is suspicious and might indicate a cut and paste or logic error. Please examine this code carefully to " |
2727 | 0 | "determine if it is correct.", CWE398, inconclusive ? Certainty::inconclusive : Certainty::normal); |
2728 | 0 | } |
2729 | | |
2730 | | void CheckOther::duplicateExpressionTernaryError(const Token *tok, ErrorPath errors) |
2731 | 0 | { |
2732 | 0 | errors.emplace_back(tok, ""); |
2733 | 0 | reportError(errors, Severity::style, "duplicateExpressionTernary", "Same expression in both branches of ternary operator.\n" |
2734 | 0 | "Finding the same expression in both branches of ternary operator is suspicious as " |
2735 | 0 | "the same code is executed regardless of the condition.", CWE398, Certainty::normal); |
2736 | 0 | } |
2737 | | |
2738 | | void CheckOther::duplicateValueTernaryError(const Token *tok) |
2739 | 0 | { |
2740 | 0 | reportError(tok, Severity::style, "duplicateValueTernary", "Same value in both branches of ternary operator.\n" |
2741 | 0 | "Finding the same value in both branches of ternary operator is suspicious as " |
2742 | 0 | "the same code is executed regardless of the condition.", CWE398, Certainty::normal); |
2743 | 0 | } |
2744 | | |
2745 | | void CheckOther::selfAssignmentError(const Token *tok, const std::string &varname) |
2746 | 178 | { |
2747 | 178 | reportError(tok, Severity::warning, |
2748 | 178 | "selfAssignment", |
2749 | 178 | "$symbol:" + varname + "\n" |
2750 | 178 | "Redundant assignment of '$symbol' to itself.", CWE398, Certainty::normal); |
2751 | 178 | } |
2752 | | |
2753 | | //----------------------------------------------------------------------------- |
2754 | | // Check is a comparison of two variables leads to condition, which is |
2755 | | // always true or false. |
2756 | | // For instance: int a = 1; if(isless(a,a)){...} |
2757 | | // In this case isless(a,a) always evaluates to false. |
2758 | | // |
2759 | | // Reference: |
2760 | | // - http://www.cplusplus.com/reference/cmath/ |
2761 | | //----------------------------------------------------------------------------- |
2762 | | void CheckOther::checkComparisonFunctionIsAlwaysTrueOrFalse() |
2763 | 1.36k | { |
2764 | 1.36k | if (!mSettings->severity.isEnabled(Severity::warning)) |
2765 | 0 | return; |
2766 | | |
2767 | 1.36k | logChecker("CheckOther::checkComparisonFunctionIsAlwaysTrueOrFalse"); // warning |
2768 | | |
2769 | 1.36k | const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); |
2770 | 1.73k | for (const Scope * scope : symbolDatabase->functionScopes) { |
2771 | 35.9k | for (const Token* tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) { |
2772 | 34.1k | if (tok->isName() && Token::Match(tok, "isgreater|isless|islessgreater|isgreaterequal|islessequal ( %var% , %var% )")) { |
2773 | 0 | const int varidLeft = tok->tokAt(2)->varId();// get the left varid |
2774 | 0 | const int varidRight = tok->tokAt(4)->varId();// get the right varid |
2775 | | // compare varids: if they are not zero but equal |
2776 | | // --> the comparison function is called with the same variables |
2777 | 0 | if (varidLeft == varidRight) { |
2778 | 0 | const std::string& functionName = tok->str(); // store function name |
2779 | 0 | const std::string& varNameLeft = tok->strAt(2); // get the left variable name |
2780 | 0 | if (functionName == "isgreater" || functionName == "isless" || functionName == "islessgreater") { |
2781 | | // e.g.: isgreater(x,x) --> (x)>(x) --> false |
2782 | 0 | checkComparisonFunctionIsAlwaysTrueOrFalseError(tok, functionName, varNameLeft, false); |
2783 | 0 | } else { // functionName == "isgreaterequal" || functionName == "islessequal" |
2784 | | // e.g.: isgreaterequal(x,x) --> (x)>=(x) --> true |
2785 | 0 | checkComparisonFunctionIsAlwaysTrueOrFalseError(tok, functionName, varNameLeft, true); |
2786 | 0 | } |
2787 | 0 | } |
2788 | 0 | } |
2789 | 34.1k | } |
2790 | 1.73k | } |
2791 | 1.36k | } |
2792 | | void CheckOther::checkComparisonFunctionIsAlwaysTrueOrFalseError(const Token* tok, const std::string &functionName, const std::string &varName, const bool result) |
2793 | 0 | { |
2794 | 0 | const std::string strResult = bool_to_string(result); |
2795 | 0 | const struct CWE cweResult = result ? CWE571 : CWE570; |
2796 | |
|
2797 | 0 | reportError(tok, Severity::warning, "comparisonFunctionIsAlwaysTrueOrFalse", |
2798 | 0 | "$symbol:" + functionName + "\n" |
2799 | 0 | "Comparison of two identical variables with $symbol(" + varName + "," + varName + ") always evaluates to " + strResult + ".\n" |
2800 | 0 | "The function $symbol is designed to compare two variables. Calling this function with one variable (" + varName + ") " |
2801 | 0 | "for both parameters leads to a statement which is always " + strResult + ".", cweResult, Certainty::normal); |
2802 | 0 | } |
2803 | | |
2804 | | //--------------------------------------------------------------------------- |
2805 | | // Check testing sign of unsigned variables and pointers. |
2806 | | //--------------------------------------------------------------------------- |
2807 | | void CheckOther::checkSignOfUnsignedVariable() |
2808 | 1.36k | { |
2809 | 1.36k | if (!mSettings->severity.isEnabled(Severity::style)) |
2810 | 0 | return; |
2811 | | |
2812 | 1.36k | logChecker("CheckOther::checkSignOfUnsignedVariable"); // style |
2813 | | |
2814 | 1.36k | const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); |
2815 | | |
2816 | 1.73k | for (const Scope * scope : symbolDatabase->functionScopes) { |
2817 | | // check all the code in the function |
2818 | 35.9k | for (const Token *tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) { |
2819 | 34.1k | const ValueFlow::Value *zeroValue = nullptr; |
2820 | 34.1k | const Token *nonZeroExpr = nullptr; |
2821 | 34.1k | if (comparisonNonZeroExpressionLessThanZero(tok, &zeroValue, &nonZeroExpr)) { |
2822 | 0 | const ValueType* vt = nonZeroExpr->valueType(); |
2823 | 0 | if (vt->pointer) |
2824 | 0 | pointerLessThanZeroError(tok, zeroValue); |
2825 | 0 | else |
2826 | 0 | unsignedLessThanZeroError(tok, zeroValue, nonZeroExpr->expressionString()); |
2827 | 34.1k | } else if (testIfNonZeroExpressionIsPositive(tok, &zeroValue, &nonZeroExpr)) { |
2828 | 0 | const ValueType* vt = nonZeroExpr->valueType(); |
2829 | 0 | if (vt->pointer) |
2830 | 0 | pointerPositiveError(tok, zeroValue); |
2831 | 0 | else |
2832 | 0 | unsignedPositiveError(tok, zeroValue, nonZeroExpr->expressionString()); |
2833 | 0 | } |
2834 | 34.1k | } |
2835 | 1.73k | } |
2836 | 1.36k | } |
2837 | | |
2838 | | bool CheckOther::comparisonNonZeroExpressionLessThanZero(const Token *tok, const ValueFlow::Value **zeroValue, const Token **nonZeroExpr) |
2839 | 34.2k | { |
2840 | 34.2k | if (!tok->isComparisonOp() || !tok->astOperand1() || !tok->astOperand2()) |
2841 | 32.2k | return false; |
2842 | | |
2843 | 2.05k | const ValueFlow::Value *v1 = tok->astOperand1()->getValue(0); |
2844 | 2.05k | const ValueFlow::Value *v2 = tok->astOperand2()->getValue(0); |
2845 | | |
2846 | 2.05k | if (Token::Match(tok, "<|<=") && v2 && v2->isKnown()) { |
2847 | 14 | *zeroValue = v2; |
2848 | 14 | *nonZeroExpr = tok->astOperand1(); |
2849 | 2.03k | } else if (Token::Match(tok, ">|>=") && v1 && v1->isKnown()) { |
2850 | 22 | *zeroValue = v1; |
2851 | 22 | *nonZeroExpr = tok->astOperand2(); |
2852 | 2.01k | } else { |
2853 | 2.01k | return false; |
2854 | 2.01k | } |
2855 | | |
2856 | 36 | const ValueType* vt = (*nonZeroExpr)->valueType(); |
2857 | 36 | return vt && (vt->pointer || vt->sign == ValueType::UNSIGNED); |
2858 | 2.05k | } |
2859 | | |
2860 | | bool CheckOther::testIfNonZeroExpressionIsPositive(const Token *tok, const ValueFlow::Value **zeroValue, const Token **nonZeroExpr) |
2861 | 34.2k | { |
2862 | 34.2k | if (!tok->isComparisonOp() || !tok->astOperand1() || !tok->astOperand2()) |
2863 | 32.2k | return false; |
2864 | | |
2865 | 2.05k | const ValueFlow::Value *v1 = tok->astOperand1()->getValue(0); |
2866 | 2.05k | const ValueFlow::Value *v2 = tok->astOperand2()->getValue(0); |
2867 | | |
2868 | 2.05k | if (Token::simpleMatch(tok, ">=") && v2 && v2->isKnown()) { |
2869 | 1 | *zeroValue = v2; |
2870 | 1 | *nonZeroExpr = tok->astOperand1(); |
2871 | 2.04k | } else if (Token::simpleMatch(tok, "<=") && v1 && v1->isKnown()) { |
2872 | 7 | *zeroValue = v1; |
2873 | 7 | *nonZeroExpr = tok->astOperand2(); |
2874 | 2.04k | } else { |
2875 | 2.04k | return false; |
2876 | 2.04k | } |
2877 | | |
2878 | 8 | const ValueType* vt = (*nonZeroExpr)->valueType(); |
2879 | 8 | return vt && (vt->pointer || vt->sign == ValueType::UNSIGNED); |
2880 | 2.05k | } |
2881 | | |
2882 | | void CheckOther::unsignedLessThanZeroError(const Token *tok, const ValueFlow::Value * v, const std::string &varname) |
2883 | 0 | { |
2884 | 0 | reportError(getErrorPath(tok, v, "Unsigned less than zero"), Severity::style, "unsignedLessThanZero", |
2885 | 0 | "$symbol:" + varname + "\n" |
2886 | 0 | "Checking if unsigned expression '$symbol' is less than zero.\n" |
2887 | 0 | "The unsigned expression '$symbol' will never be negative so it " |
2888 | 0 | "is either pointless or an error to check if it is.", CWE570, Certainty::normal); |
2889 | 0 | } |
2890 | | |
2891 | | void CheckOther::pointerLessThanZeroError(const Token *tok, const ValueFlow::Value *v) |
2892 | 0 | { |
2893 | 0 | reportError(getErrorPath(tok, v, "Pointer less than zero"), Severity::style, "pointerLessThanZero", |
2894 | 0 | "A pointer can not be negative so it is either pointless or an error to check if it is.", CWE570, Certainty::normal); |
2895 | 0 | } |
2896 | | |
2897 | | void CheckOther::unsignedPositiveError(const Token *tok, const ValueFlow::Value * v, const std::string &varname) |
2898 | 0 | { |
2899 | 0 | reportError(getErrorPath(tok, v, "Unsigned positive"), Severity::style, "unsignedPositive", |
2900 | 0 | "$symbol:" + varname + "\n" |
2901 | 0 | "Unsigned expression '$symbol' can't be negative so it is unnecessary to test it.", CWE570, Certainty::normal); |
2902 | 0 | } |
2903 | | |
2904 | | void CheckOther::pointerPositiveError(const Token *tok, const ValueFlow::Value * v) |
2905 | 0 | { |
2906 | 0 | reportError(getErrorPath(tok, v, "Pointer positive"), Severity::style, "pointerPositive", |
2907 | 0 | "A pointer can not be negative so it is either pointless or an error to check if it is not.", CWE570, Certainty::normal); |
2908 | 0 | } |
2909 | | |
2910 | | /* check if a constructor in given class scope takes a reference */ |
2911 | | static bool constructorTakesReference(const Scope * const classScope) |
2912 | 0 | { |
2913 | 0 | return std::any_of(classScope->functionList.begin(), classScope->functionList.end(), [&](const Function& constructor) { |
2914 | 0 | if (constructor.isConstructor()) { |
2915 | 0 | for (int argnr = 0U; argnr < constructor.argCount(); argnr++) { |
2916 | 0 | const Variable * const argVar = constructor.getArgumentVar(argnr); |
2917 | 0 | if (argVar && argVar->isReference()) { |
2918 | 0 | return true; |
2919 | 0 | } |
2920 | 0 | } |
2921 | 0 | } |
2922 | 0 | return false; |
2923 | 0 | }); |
2924 | 0 | } |
2925 | | |
2926 | | //--------------------------------------------------------------------------- |
2927 | | // This check rule works for checking the "const A a = getA()" usage when getA() returns "const A &" or "A &". |
2928 | | // In most scenarios, "const A & a = getA()" will be more efficient. |
2929 | | //--------------------------------------------------------------------------- |
2930 | | void CheckOther::checkRedundantCopy() |
2931 | 1.36k | { |
2932 | 1.36k | if (!mSettings->severity.isEnabled(Severity::performance) || mTokenizer->isC() || !mSettings->certainty.isEnabled(Certainty::inconclusive)) |
2933 | 0 | return; |
2934 | | |
2935 | 1.36k | logChecker("CheckOther::checkRedundantCopy"); // c++,performance,inconclusive |
2936 | | |
2937 | 1.36k | const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); |
2938 | | |
2939 | 8.16k | for (const Variable* var : symbolDatabase->variableList()) { |
2940 | 8.16k | if (!var || var->isReference() || (!var->isConst() && !canBeConst(var, mSettings)) || var->isPointer() || (!var->type() && !var->isStlType())) // bailout if var is of standard type, if it is a pointer or non-const |
2941 | 8.16k | continue; |
2942 | | |
2943 | 0 | const Token* startTok = var->nameToken(); |
2944 | 0 | if (startTok->strAt(1) == "=") // %type% %name% = ... ; |
2945 | 0 | ; |
2946 | 0 | else if (Token::Match(startTok->next(), "(|{") && var->isClass() && var->typeScope()) { |
2947 | | // Object is instantiated. Warn if constructor takes arguments by value. |
2948 | 0 | if (constructorTakesReference(var->typeScope())) |
2949 | 0 | continue; |
2950 | 0 | } else if (Token::simpleMatch(startTok->next(), ";") && startTok->next()->isSplittedVarDeclEq()) { |
2951 | 0 | startTok = startTok->tokAt(2); |
2952 | 0 | } else |
2953 | 0 | continue; |
2954 | | |
2955 | 0 | const Token* tok = startTok->next()->astOperand2(); |
2956 | 0 | if (!tok) |
2957 | 0 | continue; |
2958 | 0 | if (!Token::Match(tok->previous(), "%name% (")) |
2959 | 0 | continue; |
2960 | 0 | if (!Token::Match(tok->link(), ") )| ;")) // bailout for usage like "const A a = getA()+3" |
2961 | 0 | continue; |
2962 | | |
2963 | 0 | const Token* dot = tok->astOperand1(); |
2964 | 0 | if (Token::simpleMatch(dot, ".")) { |
2965 | 0 | if (dot->astOperand1() && isVariableChanged(dot->astOperand1()->variable(), mSettings, mTokenizer->isCPP())) |
2966 | 0 | continue; |
2967 | 0 | if (isTemporary(/*cpp*/ true, dot, &mSettings->library, /*unknown*/ true)) |
2968 | 0 | continue; |
2969 | 0 | } |
2970 | 0 | if (exprDependsOnThis(tok->previous())) |
2971 | 0 | continue; |
2972 | | |
2973 | 0 | const Function* func = tok->previous()->function(); |
2974 | 0 | if (func && func->tokenDef->strAt(-1) == "&") { |
2975 | 0 | const Scope* fScope = func->functionScope; |
2976 | 0 | if (fScope && fScope->bodyEnd && Token::Match(fScope->bodyEnd->tokAt(-3), "return %var% ;")) { |
2977 | 0 | const Token* varTok = fScope->bodyEnd->tokAt(-2); |
2978 | 0 | if (varTok->variable() && !varTok->variable()->isGlobal()) |
2979 | 0 | redundantCopyError(startTok, startTok->str()); |
2980 | 0 | } |
2981 | 0 | } |
2982 | 0 | } |
2983 | 1.36k | } |
2984 | | |
2985 | | void CheckOther::redundantCopyError(const Token *tok,const std::string& varname) |
2986 | 0 | { |
2987 | 0 | reportError(tok, Severity::performance, "redundantCopyLocalConst", |
2988 | 0 | "$symbol:" + varname + "\n" |
2989 | 0 | "Use const reference for '$symbol' to avoid unnecessary data copying.\n" |
2990 | 0 | "The const variable '$symbol' is assigned a copy of the data. You can avoid " |
2991 | 0 | "the unnecessary data copying by converting '$symbol' to const reference.", |
2992 | 0 | CWE398, |
2993 | 0 | Certainty::inconclusive); // since #5618 that check became inconclusive |
2994 | 0 | } |
2995 | | |
2996 | | //--------------------------------------------------------------------------- |
2997 | | // Checking for shift by negative values |
2998 | | //--------------------------------------------------------------------------- |
2999 | | |
3000 | | static bool isNegative(const Token *tok, const Settings *settings) |
3001 | 0 | { |
3002 | 0 | return tok->valueType() && tok->valueType()->sign == ValueType::SIGNED && tok->getValueLE(-1LL, settings); |
3003 | 0 | } |
3004 | | |
3005 | | void CheckOther::checkNegativeBitwiseShift() |
3006 | 1.36k | { |
3007 | 1.36k | const bool portability = mSettings->severity.isEnabled(Severity::portability); |
3008 | | |
3009 | 1.36k | logChecker("CheckOther::checkNegativeBitwiseShift"); |
3010 | | |
3011 | 93.5k | for (const Token* tok = mTokenizer->tokens(); tok; tok = tok->next()) { |
3012 | 92.2k | if (!tok->astOperand1() || !tok->astOperand2()) |
3013 | 78.0k | continue; |
3014 | | |
3015 | 14.2k | if (!Token::Match(tok, "<<|>>|<<=|>>=")) |
3016 | 14.2k | continue; |
3017 | | |
3018 | | // don't warn if lhs is a class. this is an overloaded operator then |
3019 | 0 | if (mTokenizer->isCPP()) { |
3020 | 0 | const ValueType * lhsType = tok->astOperand1()->valueType(); |
3021 | 0 | if (!lhsType || !lhsType->isIntegral()) |
3022 | 0 | continue; |
3023 | 0 | } |
3024 | | |
3025 | | // bailout if operation is protected by ?: |
3026 | 0 | bool ternary = false; |
3027 | 0 | for (const Token *parent = tok; parent; parent = parent->astParent()) { |
3028 | 0 | if (Token::Match(parent, "?|:")) { |
3029 | 0 | ternary = true; |
3030 | 0 | break; |
3031 | 0 | } |
3032 | 0 | } |
3033 | 0 | if (ternary) |
3034 | 0 | continue; |
3035 | | |
3036 | | // Get negative rhs value. preferably a value which doesn't have 'condition'. |
3037 | 0 | if (portability && isNegative(tok->astOperand1(), mSettings)) |
3038 | 0 | negativeBitwiseShiftError(tok, 1); |
3039 | 0 | else if (isNegative(tok->astOperand2(), mSettings)) |
3040 | 0 | negativeBitwiseShiftError(tok, 2); |
3041 | 0 | } |
3042 | 1.36k | } |
3043 | | |
3044 | | |
3045 | | void CheckOther::negativeBitwiseShiftError(const Token *tok, int op) |
3046 | 0 | { |
3047 | 0 | if (op == 1) |
3048 | | // LHS - this is used by intention in various software, if it |
3049 | | // is used often in a project and works as expected then this is |
3050 | | // a portability issue |
3051 | 0 | reportError(tok, Severity::portability, "shiftNegativeLHS", "Shifting a negative value is technically undefined behaviour", CWE758, Certainty::normal); |
3052 | 0 | else // RHS |
3053 | 0 | reportError(tok, Severity::error, "shiftNegative", "Shifting by a negative value is undefined behaviour", CWE758, Certainty::normal); |
3054 | 0 | } |
3055 | | |
3056 | | //--------------------------------------------------------------------------- |
3057 | | // Check for incompletely filled buffers. |
3058 | | //--------------------------------------------------------------------------- |
3059 | | void CheckOther::checkIncompleteArrayFill() |
3060 | 1.36k | { |
3061 | 1.36k | if (!mSettings->certainty.isEnabled(Certainty::inconclusive)) |
3062 | 0 | return; |
3063 | 1.36k | const bool printWarning = mSettings->severity.isEnabled(Severity::warning); |
3064 | 1.36k | const bool printPortability = mSettings->severity.isEnabled(Severity::portability); |
3065 | 1.36k | if (!printPortability && !printWarning) |
3066 | 0 | return; |
3067 | | |
3068 | 1.36k | logChecker("CheckOther::checkIncompleteArrayFill"); // warning,portability,inconclusive |
3069 | | |
3070 | 1.36k | const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); |
3071 | | |
3072 | 1.73k | for (const Scope * scope : symbolDatabase->functionScopes) { |
3073 | 35.9k | for (const Token* tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) { |
3074 | 34.1k | if (Token::Match(tok, "memset|memcpy|memmove (") && Token::Match(tok->linkAt(1)->tokAt(-2), ", %num% )")) { |
3075 | 0 | const Token* tok2 = tok->tokAt(2); |
3076 | 0 | if (tok2->str() == "::") |
3077 | 0 | tok2 = tok2->next(); |
3078 | 0 | while (Token::Match(tok2, "%name% ::|.")) |
3079 | 0 | tok2 = tok2->tokAt(2); |
3080 | 0 | if (!Token::Match(tok2, "%var% ,")) |
3081 | 0 | continue; |
3082 | | |
3083 | 0 | const Variable *var = tok2->variable(); |
3084 | 0 | if (!var || !var->isArray() || var->dimensions().empty() || !var->dimension(0)) |
3085 | 0 | continue; |
3086 | | |
3087 | 0 | if (MathLib::toLongNumber(tok->linkAt(1)->strAt(-1)) == var->dimension(0)) { |
3088 | 0 | int size = mTokenizer->sizeOfType(var->typeStartToken()); |
3089 | 0 | if (size == 0 && var->valueType()->pointer) |
3090 | 0 | size = mSettings->platform.sizeof_pointer; |
3091 | 0 | else if (size == 0 && var->type()) |
3092 | 0 | size = estimateSize(var->type(), mSettings, symbolDatabase); |
3093 | 0 | const Token* tok3 = tok->next()->astOperand2()->astOperand1()->astOperand1(); |
3094 | 0 | if ((size != 1 && size != 100 && size != 0) || var->isPointer()) { |
3095 | 0 | if (printWarning) |
3096 | 0 | incompleteArrayFillError(tok, tok3->expressionString(), tok->str(), false); |
3097 | 0 | } else if (var->valueType()->type == ValueType::Type::BOOL && printPortability) // sizeof(bool) is not 1 on all platforms |
3098 | 0 | incompleteArrayFillError(tok, tok3->expressionString(), tok->str(), true); |
3099 | 0 | } |
3100 | 0 | } |
3101 | 34.1k | } |
3102 | 1.73k | } |
3103 | 1.36k | } |
3104 | | |
3105 | | void CheckOther::incompleteArrayFillError(const Token* tok, const std::string& buffer, const std::string& function, bool boolean) |
3106 | 0 | { |
3107 | 0 | if (boolean) |
3108 | 0 | reportError(tok, Severity::portability, "incompleteArrayFill", |
3109 | 0 | "$symbol:" + buffer + "\n" |
3110 | 0 | "$symbol:" + function + "\n" |
3111 | 0 | "Array '" + buffer + "' might be filled incompletely. Did you forget to multiply the size given to '" + function + "()' with 'sizeof(*" + buffer + ")'?\n" |
3112 | 0 | "The array '" + buffer + "' is filled incompletely. The function '" + function + "()' needs the size given in bytes, but the type 'bool' is larger than 1 on some platforms. Did you forget to multiply the size with 'sizeof(*" + buffer + ")'?", CWE131, Certainty::inconclusive); |
3113 | 0 | else |
3114 | 0 | reportError(tok, Severity::warning, "incompleteArrayFill", |
3115 | 0 | "$symbol:" + buffer + "\n" |
3116 | 0 | "$symbol:" + function + "\n" |
3117 | 0 | "Array '" + buffer + "' is filled incompletely. Did you forget to multiply the size given to '" + function + "()' with 'sizeof(*" + buffer + ")'?\n" |
3118 | 0 | "The array '" + buffer + "' is filled incompletely. The function '" + function + "()' needs the size given in bytes, but an element of the given array is larger than one byte. Did you forget to multiply the size with 'sizeof(*" + buffer + ")'?", CWE131, Certainty::inconclusive); |
3119 | 0 | } |
3120 | | |
3121 | | //--------------------------------------------------------------------------- |
3122 | | // Detect NULL being passed to variadic function. |
3123 | | //--------------------------------------------------------------------------- |
3124 | | |
3125 | | void CheckOther::checkVarFuncNullUB() |
3126 | 1.36k | { |
3127 | 1.36k | if (!mSettings->severity.isEnabled(Severity::portability)) |
3128 | 0 | return; |
3129 | | |
3130 | 1.36k | logChecker("CheckOther::checkVarFuncNullUB"); // portability |
3131 | | |
3132 | 1.36k | const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); |
3133 | 1.73k | for (const Scope * scope : symbolDatabase->functionScopes) { |
3134 | 37.6k | for (const Token* tok = scope->bodyStart; tok != scope->bodyEnd; tok = tok->next()) { |
3135 | | // Is NULL passed to a function? |
3136 | 35.9k | if (Token::Match(tok,"[(,] NULL [,)]")) { |
3137 | | // Locate function name in this function call. |
3138 | 0 | const Token *ftok = tok; |
3139 | 0 | int argnr = 1; |
3140 | 0 | while (ftok && ftok->str() != "(") { |
3141 | 0 | if (ftok->str() == ")") |
3142 | 0 | ftok = ftok->link(); |
3143 | 0 | else if (ftok->str() == ",") |
3144 | 0 | ++argnr; |
3145 | 0 | ftok = ftok->previous(); |
3146 | 0 | } |
3147 | 0 | ftok = ftok ? ftok->previous() : nullptr; |
3148 | 0 | if (ftok && ftok->isName()) { |
3149 | | // If this is a variadic function then report error |
3150 | 0 | const Function *f = ftok->function(); |
3151 | 0 | if (f && f->argCount() <= argnr) { |
3152 | 0 | const Token *tok2 = f->argDef; |
3153 | 0 | tok2 = tok2 ? tok2->link() : nullptr; // goto ')' |
3154 | 0 | if (tok2 && Token::simpleMatch(tok2->tokAt(-1), "...")) |
3155 | 0 | varFuncNullUBError(tok); |
3156 | 0 | } |
3157 | 0 | } |
3158 | 0 | } |
3159 | 35.9k | } |
3160 | 1.73k | } |
3161 | 1.36k | } |
3162 | | |
3163 | | void CheckOther::varFuncNullUBError(const Token *tok) |
3164 | 0 | { |
3165 | 0 | reportError(tok, |
3166 | 0 | Severity::portability, |
3167 | 0 | "varFuncNullUB", |
3168 | 0 | "Passing NULL after the last typed argument to a variadic function leads to undefined behaviour.\n" |
3169 | 0 | "Passing NULL after the last typed argument to a variadic function leads to undefined behaviour.\n" |
3170 | 0 | "The C99 standard, in section 7.15.1.1, states that if the type used by va_arg() is not compatible with the type of the actual next argument (as promoted according to the default argument promotions), the behavior is undefined.\n" |
3171 | 0 | "The value of the NULL macro is an implementation-defined null pointer constant (7.17), which can be any integer constant expression with the value 0, or such an expression casted to (void*) (6.3.2.3). This includes values like 0, 0L, or even 0LL.\n" |
3172 | 0 | "In practice on common architectures, this will cause real crashes if sizeof(int) != sizeof(void*), and NULL is defined to 0 or any other null pointer constant that promotes to int.\n" |
3173 | 0 | "To reproduce you might be able to use this little code example on 64bit platforms. If the output includes \"ERROR\", the sentinel had only 4 out of 8 bytes initialized to zero and was not detected as the final argument to stop argument processing via va_arg(). Changing the 0 to (void*)0 or 0L will make the \"ERROR\" output go away.\n" |
3174 | 0 | "#include <stdarg.h>\n" |
3175 | 0 | "#include <stdio.h>\n" |
3176 | 0 | "\n" |
3177 | 0 | "void f(char *s, ...) {\n" |
3178 | 0 | " va_list ap;\n" |
3179 | 0 | " va_start(ap,s);\n" |
3180 | 0 | " for (;;) {\n" |
3181 | 0 | " char *p = va_arg(ap,char*);\n" |
3182 | 0 | " printf(\"%018p, %s\\n\", p, (long)p & 255 ? p : \"\");\n" |
3183 | 0 | " if(!p) break;\n" |
3184 | 0 | " }\n" |
3185 | 0 | " va_end(ap);\n" |
3186 | 0 | "}\n" |
3187 | 0 | "\n" |
3188 | 0 | "void g() {\n" |
3189 | 0 | " char *s2 = \"x\";\n" |
3190 | 0 | " char *s3 = \"ERROR\";\n" |
3191 | 0 | "\n" |
3192 | 0 | " // changing 0 to 0L for the 7th argument (which is intended to act as sentinel) makes the error go away on x86_64\n" |
3193 | 0 | " f(\"first\", s2, s2, s2, s2, s2, 0, s3, (char*)0);\n" |
3194 | 0 | "}\n" |
3195 | 0 | "\n" |
3196 | 0 | "void h() {\n" |
3197 | 0 | " int i;\n" |
3198 | 0 | " volatile unsigned char a[1000];\n" |
3199 | 0 | " for (i = 0; i<sizeof(a); i++)\n" |
3200 | 0 | " a[i] = -1;\n" |
3201 | 0 | "}\n" |
3202 | 0 | "\n" |
3203 | 0 | "int main() {\n" |
3204 | 0 | " h();\n" |
3205 | 0 | " g();\n" |
3206 | 0 | " return 0;\n" |
3207 | 0 | "}", CWE475, Certainty::normal); |
3208 | 0 | } |
3209 | | |
3210 | | void CheckOther::checkRedundantPointerOp() |
3211 | 1.36k | { |
3212 | 1.36k | if (!mSettings->severity.isEnabled(Severity::style)) |
3213 | 0 | return; |
3214 | | |
3215 | 1.36k | logChecker("CheckOther::checkRedundantPointerOp"); // style |
3216 | | |
3217 | 93.5k | for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) { |
3218 | 92.2k | if (tok->isExpandedMacro() && tok->str() == "(") |
3219 | 0 | tok = tok->link(); |
3220 | | |
3221 | 92.2k | bool addressOfDeref{}; |
3222 | 92.2k | if (tok->isUnaryOp("&") && tok->astOperand1()->isUnaryOp("*")) |
3223 | 0 | addressOfDeref = true; |
3224 | 92.2k | else if (tok->isUnaryOp("*") && tok->astOperand1()->isUnaryOp("&")) |
3225 | 0 | addressOfDeref = false; |
3226 | 92.2k | else |
3227 | 92.2k | continue; |
3228 | | |
3229 | | // variable |
3230 | 0 | const Token *varTok = tok->astOperand1()->astOperand1(); |
3231 | 0 | if (!varTok || varTok->isExpandedMacro()) |
3232 | 0 | continue; |
3233 | | |
3234 | 0 | if (!addressOfDeref) { // dereference of address |
3235 | 0 | if (tok->isExpandedMacro()) |
3236 | 0 | continue; |
3237 | 0 | if (varTok->valueType() && varTok->valueType()->pointer && varTok->valueType()->reference == Reference::LValue) |
3238 | 0 | continue; |
3239 | 0 | } |
3240 | | |
3241 | 0 | const Variable *var = varTok->variable(); |
3242 | 0 | if (!var || (addressOfDeref && !var->isPointer())) |
3243 | 0 | continue; |
3244 | | |
3245 | 0 | redundantPointerOpError(tok, var->name(), false, addressOfDeref); |
3246 | 0 | } |
3247 | 1.36k | } |
3248 | | |
3249 | | void CheckOther::redundantPointerOpError(const Token* tok, const std::string &varname, bool inconclusive, bool addressOfDeref) |
3250 | 0 | { |
3251 | 0 | std::string msg = "$symbol:" + varname + "\nRedundant pointer operation on '$symbol' - it's already a "; |
3252 | 0 | msg += addressOfDeref ? "pointer." : "variable."; |
3253 | 0 | reportError(tok, Severity::style, "redundantPointerOp", msg, CWE398, inconclusive ? Certainty::inconclusive : Certainty::normal); |
3254 | 0 | } |
3255 | | |
3256 | | void CheckOther::checkInterlockedDecrement() |
3257 | 1.36k | { |
3258 | 1.36k | if (!mSettings->platform.isWindows()) { |
3259 | 1.36k | return; |
3260 | 1.36k | } |
3261 | | |
3262 | 0 | logChecker("CheckOther::checkInterlockedDecrement"); // windows-platform |
3263 | |
|
3264 | 0 | for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) { |
3265 | 0 | if (tok->isName() && Token::Match(tok, "InterlockedDecrement ( & %name% ) ; if ( %name%|!|0")) { |
3266 | 0 | const Token* interlockedVarTok = tok->tokAt(3); |
3267 | 0 | const Token* checkStartTok = interlockedVarTok->tokAt(5); |
3268 | 0 | if ((Token::Match(checkStartTok, "0 %comp% %name% )") && checkStartTok->strAt(2) == interlockedVarTok->str()) || |
3269 | 0 | (Token::Match(checkStartTok, "! %name% )") && checkStartTok->strAt(1) == interlockedVarTok->str()) || |
3270 | 0 | (Token::Match(checkStartTok, "%name% )") && checkStartTok->str() == interlockedVarTok->str()) || |
3271 | 0 | (Token::Match(checkStartTok, "%name% %comp% 0 )") && checkStartTok->str() == interlockedVarTok->str())) { |
3272 | 0 | raceAfterInterlockedDecrementError(checkStartTok); |
3273 | 0 | } |
3274 | 0 | } else if (Token::Match(tok, "if ( ::| InterlockedDecrement ( & %name%")) { |
3275 | 0 | const Token* condEnd = tok->next()->link(); |
3276 | 0 | const Token* funcTok = tok->tokAt(2); |
3277 | 0 | const Token* firstAccessTok = funcTok->str() == "::" ? funcTok->tokAt(4) : funcTok->tokAt(3); |
3278 | 0 | if (condEnd && condEnd->next() && condEnd->next()->link()) { |
3279 | 0 | const Token* ifEndTok = condEnd->next()->link(); |
3280 | 0 | if (Token::Match(ifEndTok, "} return %name%")) { |
3281 | 0 | const Token* secondAccessTok = ifEndTok->tokAt(2); |
3282 | 0 | if (secondAccessTok->str() == firstAccessTok->str()) { |
3283 | 0 | raceAfterInterlockedDecrementError(secondAccessTok); |
3284 | 0 | } |
3285 | 0 | } else if (Token::Match(ifEndTok, "} else { return %name%")) { |
3286 | 0 | const Token* secondAccessTok = ifEndTok->tokAt(4); |
3287 | 0 | if (secondAccessTok->str() == firstAccessTok->str()) { |
3288 | 0 | raceAfterInterlockedDecrementError(secondAccessTok); |
3289 | 0 | } |
3290 | 0 | } |
3291 | 0 | } |
3292 | 0 | } |
3293 | 0 | } |
3294 | 0 | } |
3295 | | |
3296 | | void CheckOther::raceAfterInterlockedDecrementError(const Token* tok) |
3297 | 0 | { |
3298 | 0 | reportError(tok, Severity::error, "raceAfterInterlockedDecrement", |
3299 | 0 | "Race condition: non-interlocked access after InterlockedDecrement(). Use InterlockedDecrement() return value instead.", CWE362, Certainty::normal); |
3300 | 0 | } |
3301 | | |
3302 | | void CheckOther::checkUnusedLabel() |
3303 | 1.36k | { |
3304 | 1.36k | if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->severity.isEnabled(Severity::warning)) |
3305 | 0 | return; |
3306 | | |
3307 | 1.36k | logChecker("CheckOther::checkUnusedLabel"); // style,warning |
3308 | | |
3309 | 1.36k | const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); |
3310 | 1.73k | for (const Scope * scope : symbolDatabase->functionScopes) { |
3311 | 1.73k | const bool hasIfdef = mTokenizer->hasIfdef(scope->bodyStart, scope->bodyEnd); |
3312 | 37.6k | for (const Token* tok = scope->bodyStart; tok != scope->bodyEnd; tok = tok->next()) { |
3313 | 35.9k | if (!tok->scope()->isExecutable()) |
3314 | 0 | tok = tok->scope()->bodyEnd; |
3315 | | |
3316 | 35.9k | if (Token::Match(tok, "{|}|; %name% :") && !tok->tokAt(1)->isKeyword()) { |
3317 | 0 | const std::string tmp("goto " + tok->strAt(1)); |
3318 | 0 | if (!Token::findsimplematch(scope->bodyStart->next(), tmp.c_str(), tmp.size(), scope->bodyEnd->previous())) |
3319 | 0 | unusedLabelError(tok->next(), tok->next()->scope()->type == Scope::eSwitch, hasIfdef); |
3320 | 0 | } |
3321 | 35.9k | } |
3322 | 1.73k | } |
3323 | 1.36k | } |
3324 | | |
3325 | | void CheckOther::unusedLabelError(const Token* tok, bool inSwitch, bool hasIfdef) |
3326 | 0 | { |
3327 | 0 | if (tok && !mSettings->severity.isEnabled(inSwitch ? Severity::warning : Severity::style)) |
3328 | 0 | return; |
3329 | | |
3330 | 0 | std::string id = "unusedLabel"; |
3331 | 0 | if (inSwitch) |
3332 | 0 | id += "Switch"; |
3333 | 0 | if (hasIfdef) |
3334 | 0 | id += "Configuration"; |
3335 | |
|
3336 | 0 | std::string msg = "$symbol:" + (tok ? tok->str() : emptyString) + "\nLabel '$symbol' is not used."; |
3337 | 0 | if (hasIfdef) |
3338 | 0 | msg += " There is #if in function body so the label might be used in code that is removed by the preprocessor."; |
3339 | 0 | if (inSwitch) |
3340 | 0 | msg += " Should this be a 'case' of the enclosing switch()?"; |
3341 | |
|
3342 | 0 | reportError(tok, |
3343 | 0 | inSwitch ? Severity::warning : Severity::style, |
3344 | 0 | id, |
3345 | 0 | msg, |
3346 | 0 | CWE398, |
3347 | 0 | Certainty::normal); |
3348 | 0 | } |
3349 | | |
3350 | | |
3351 | | void CheckOther::checkEvaluationOrder() |
3352 | 1.36k | { |
3353 | | // This checker is not written according to C++11 sequencing rules |
3354 | 1.36k | if (mTokenizer->isCPP() && mSettings->standards.cpp >= Standards::CPP11) |
3355 | 1.36k | return; |
3356 | | |
3357 | 0 | logChecker("CheckOther::checkEvaluationOrder"); // C/C++03 |
3358 | |
|
3359 | 0 | const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); |
3360 | 0 | for (const Scope * functionScope : symbolDatabase->functionScopes) { |
3361 | 0 | for (const Token* tok = functionScope->bodyStart; tok != functionScope->bodyEnd; tok = tok->next()) { |
3362 | 0 | if (tok->tokType() != Token::eIncDecOp && !tok->isAssignmentOp()) |
3363 | 0 | continue; |
3364 | 0 | if (!tok->astOperand1()) |
3365 | 0 | continue; |
3366 | 0 | for (const Token *tok2 = tok;; tok2 = tok2->astParent()) { |
3367 | | // If ast parent is a sequence point then break |
3368 | 0 | const Token * const parent = tok2->astParent(); |
3369 | 0 | if (!parent) |
3370 | 0 | break; |
3371 | 0 | if (Token::Match(parent, "%oror%|&&|?|:|;")) |
3372 | 0 | break; |
3373 | 0 | if (parent->str() == ",") { |
3374 | 0 | const Token *par = parent; |
3375 | 0 | while (Token::simpleMatch(par,",")) |
3376 | 0 | par = par->astParent(); |
3377 | | // not function or in a while clause => break |
3378 | 0 | if (!(par && par->str() == "(" && par->astOperand2() && par->strAt(-1) != "while")) |
3379 | 0 | break; |
3380 | | // control flow (if|while|etc) => break |
3381 | 0 | if (Token::simpleMatch(par->link(),") {")) |
3382 | 0 | break; |
3383 | | // sequence point in function argument: dostuff((1,2),3) => break |
3384 | 0 | par = par->next(); |
3385 | 0 | while (par && (par->previous() != parent)) |
3386 | 0 | par = par->nextArgument(); |
3387 | 0 | if (!par) |
3388 | 0 | break; |
3389 | 0 | } |
3390 | 0 | if (parent->str() == "(" && parent->astOperand2()) |
3391 | 0 | break; |
3392 | | |
3393 | | // self assignment.. |
3394 | 0 | if (tok2 == tok && |
3395 | 0 | tok->str() == "=" && |
3396 | 0 | parent->str() == "=" && |
3397 | 0 | isSameExpression(mTokenizer->isCPP(), false, tok->astOperand1(), parent->astOperand1(), mSettings->library, true, false)) { |
3398 | 0 | if (mSettings->severity.isEnabled(Severity::warning) && |
3399 | 0 | isSameExpression(mTokenizer->isCPP(), true, tok->astOperand1(), parent->astOperand1(), mSettings->library, true, false)) |
3400 | 0 | selfAssignmentError(parent, tok->astOperand1()->expressionString()); |
3401 | 0 | break; |
3402 | 0 | } |
3403 | | |
3404 | | // Is expression used? |
3405 | 0 | bool foundError = false; |
3406 | 0 | visitAstNodes((parent->astOperand1() != tok2) ? parent->astOperand1() : parent->astOperand2(), |
3407 | 0 | [&](const Token *tok3) { |
3408 | 0 | if (tok3->str() == "&" && !tok3->astOperand2()) |
3409 | 0 | return ChildrenToVisit::none; // don't handle address-of for now |
3410 | 0 | if (tok3->str() == "(" && Token::simpleMatch(tok3->previous(), "sizeof")) |
3411 | 0 | return ChildrenToVisit::none; // don't care about sizeof usage |
3412 | 0 | if (isSameExpression(mTokenizer->isCPP(), false, tok->astOperand1(), tok3, mSettings->library, true, false)) |
3413 | 0 | foundError = true; |
3414 | 0 | return foundError ? ChildrenToVisit::done : ChildrenToVisit::op1_and_op2; |
3415 | 0 | }); |
3416 | |
|
3417 | 0 | if (foundError) { |
3418 | 0 | unknownEvaluationOrder(parent); |
3419 | 0 | break; |
3420 | 0 | } |
3421 | 0 | } |
3422 | 0 | } |
3423 | 0 | } |
3424 | 0 | } |
3425 | | |
3426 | | void CheckOther::unknownEvaluationOrder(const Token* tok) |
3427 | 0 | { |
3428 | 0 | reportError(tok, Severity::error, "unknownEvaluationOrder", |
3429 | 0 | "Expression '" + (tok ? tok->expressionString() : std::string("x = x++;")) + "' depends on order of evaluation of side effects", CWE768, Certainty::normal); |
3430 | 0 | } |
3431 | | |
3432 | | void CheckOther::checkAccessOfMovedVariable() |
3433 | 1.36k | { |
3434 | 1.36k | if (!mTokenizer->isCPP() || mSettings->standards.cpp < Standards::CPP11 || !mSettings->severity.isEnabled(Severity::warning)) |
3435 | 0 | return; |
3436 | 1.36k | logChecker("CheckOther::checkAccessOfMovedVariable"); // c++11,warning |
3437 | 1.36k | const bool reportInconclusive = mSettings->certainty.isEnabled(Certainty::inconclusive); |
3438 | 1.36k | const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); |
3439 | 1.73k | for (const Scope * scope : symbolDatabase->functionScopes) { |
3440 | 1.73k | const Token * scopeStart = scope->bodyStart; |
3441 | 1.73k | if (scope->function) { |
3442 | 1.73k | const Token * memberInitializationStart = scope->function->constructorMemberInitialization(); |
3443 | 1.73k | if (memberInitializationStart) |
3444 | 0 | scopeStart = memberInitializationStart; |
3445 | 1.73k | } |
3446 | 35.9k | for (const Token* tok = scopeStart->next(); tok != scope->bodyEnd; tok = tok->next()) { |
3447 | 34.1k | if (!tok->astParent()) |
3448 | 15.2k | continue; |
3449 | 18.9k | const ValueFlow::Value * movedValue = tok->getMovedValue(); |
3450 | 18.9k | if (!movedValue || movedValue->moveKind == ValueFlow::Value::MoveKind::NonMovedVariable) |
3451 | 18.9k | continue; |
3452 | 0 | if (movedValue->isInconclusive() && !reportInconclusive) |
3453 | 0 | continue; |
3454 | | |
3455 | 0 | bool inconclusive = false; |
3456 | 0 | bool accessOfMoved = false; |
3457 | 0 | if (tok->strAt(1) == ".") { |
3458 | 0 | if (tok->next()->originalName() == "->") |
3459 | 0 | accessOfMoved = true; |
3460 | 0 | else |
3461 | 0 | inconclusive = true; |
3462 | 0 | } else { |
3463 | 0 | const ExprUsage usage = getExprUsage(tok, 0, mSettings, mTokenizer->isCPP()); |
3464 | 0 | if (usage == ExprUsage::Used) |
3465 | 0 | accessOfMoved = true; |
3466 | 0 | if (usage == ExprUsage::PassedByReference) |
3467 | 0 | accessOfMoved = !isVariableChangedByFunctionCall(tok, 0, mSettings, &inconclusive); |
3468 | 0 | else if (usage == ExprUsage::Inconclusive) |
3469 | 0 | inconclusive = true; |
3470 | 0 | } |
3471 | 0 | if (accessOfMoved || (inconclusive && reportInconclusive)) |
3472 | 0 | accessMovedError(tok, tok->str(), movedValue, inconclusive || movedValue->isInconclusive()); |
3473 | 0 | } |
3474 | 1.73k | } |
3475 | 1.36k | } |
3476 | | |
3477 | | void CheckOther::accessMovedError(const Token *tok, const std::string &varname, const ValueFlow::Value *value, bool inconclusive) |
3478 | 0 | { |
3479 | 0 | if (!tok) { |
3480 | 0 | reportError(tok, Severity::warning, "accessMoved", "Access of moved variable 'v'.", CWE672, Certainty::normal); |
3481 | 0 | reportError(tok, Severity::warning, "accessForwarded", "Access of forwarded variable 'v'.", CWE672, Certainty::normal); |
3482 | 0 | return; |
3483 | 0 | } |
3484 | | |
3485 | 0 | const char * errorId = nullptr; |
3486 | 0 | std::string kindString; |
3487 | 0 | switch (value->moveKind) { |
3488 | 0 | case ValueFlow::Value::MoveKind::MovedVariable: |
3489 | 0 | errorId = "accessMoved"; |
3490 | 0 | kindString = "moved"; |
3491 | 0 | break; |
3492 | 0 | case ValueFlow::Value::MoveKind::ForwardedVariable: |
3493 | 0 | errorId = "accessForwarded"; |
3494 | 0 | kindString = "forwarded"; |
3495 | 0 | break; |
3496 | 0 | default: |
3497 | 0 | return; |
3498 | 0 | } |
3499 | 0 | const std::string errmsg("$symbol:" + varname + "\nAccess of " + kindString + " variable '$symbol'."); |
3500 | 0 | const ErrorPath errorPath = getErrorPath(tok, value, errmsg); |
3501 | 0 | reportError(errorPath, Severity::warning, errorId, errmsg, CWE672, inconclusive ? Certainty::inconclusive : Certainty::normal); |
3502 | 0 | } |
3503 | | |
3504 | | |
3505 | | |
3506 | | void CheckOther::checkFuncArgNamesDifferent() |
3507 | 1.36k | { |
3508 | 1.36k | const bool style = mSettings->severity.isEnabled(Severity::style); |
3509 | 1.36k | const bool inconclusive = mSettings->certainty.isEnabled(Certainty::inconclusive); |
3510 | 1.36k | const bool warning = mSettings->severity.isEnabled(Severity::warning); |
3511 | | |
3512 | 1.36k | if (!(warning || (style && inconclusive))) |
3513 | 0 | return; |
3514 | | |
3515 | 1.36k | logChecker("CheckOther::checkFuncArgNamesDifferent"); // style,warning,inconclusive |
3516 | | |
3517 | 1.36k | const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); |
3518 | | // check every function |
3519 | 1.73k | for (const Scope *scope : symbolDatabase->functionScopes) { |
3520 | 1.73k | const Function * function = scope->function; |
3521 | | // only check functions with arguments |
3522 | 1.73k | if (!function || function->argCount() == 0) |
3523 | 1.73k | continue; |
3524 | | |
3525 | | // only check functions with separate declarations and definitions |
3526 | 0 | if (function->argDef == function->arg) |
3527 | 0 | continue; |
3528 | | |
3529 | | // get the function argument name tokens |
3530 | 0 | std::vector<const Token *> declarations(function->argCount()); |
3531 | 0 | std::vector<const Token *> definitions(function->argCount()); |
3532 | 0 | const Token * decl = function->argDef->next(); |
3533 | 0 | for (int j = 0; j < function->argCount(); ++j) { |
3534 | 0 | declarations[j] = nullptr; |
3535 | 0 | definitions[j] = nullptr; |
3536 | | // get the definition |
3537 | 0 | const Variable * variable = function->getArgumentVar(j); |
3538 | 0 | if (variable) { |
3539 | 0 | definitions[j] = variable->nameToken(); |
3540 | 0 | } |
3541 | | // get the declaration (search for first token with varId) |
3542 | 0 | while (decl && !Token::Match(decl, ",|)|;")) { |
3543 | | // skip everything after the assignment because |
3544 | | // it could also have a varId or be the first |
3545 | | // token with a varId if there is no name token |
3546 | 0 | if (decl->str() == "=") { |
3547 | 0 | decl = decl->nextArgument(); |
3548 | 0 | break; |
3549 | 0 | } |
3550 | | // skip over template |
3551 | 0 | if (decl->link()) |
3552 | 0 | decl = decl->link(); |
3553 | 0 | else if (decl->varId()) |
3554 | 0 | declarations[j] = decl; |
3555 | 0 | decl = decl->next(); |
3556 | 0 | } |
3557 | 0 | if (Token::simpleMatch(decl, ",")) |
3558 | 0 | decl = decl->next(); |
3559 | 0 | } |
3560 | | // check for different argument order |
3561 | 0 | if (warning) { |
3562 | 0 | bool order_different = false; |
3563 | 0 | for (int j = 0; j < function->argCount(); ++j) { |
3564 | 0 | if (!declarations[j] || !definitions[j] || declarations[j]->str() == definitions[j]->str()) |
3565 | 0 | continue; |
3566 | | |
3567 | 0 | for (int k = 0; k < function->argCount(); ++k) { |
3568 | 0 | if (j != k && definitions[k] && declarations[j]->str() == definitions[k]->str()) { |
3569 | 0 | order_different = true; |
3570 | 0 | break; |
3571 | 0 | } |
3572 | 0 | } |
3573 | 0 | } |
3574 | 0 | if (order_different) { |
3575 | 0 | funcArgOrderDifferent(function->name(), function->argDef->next(), function->arg->next(), declarations, definitions); |
3576 | 0 | continue; |
3577 | 0 | } |
3578 | 0 | } |
3579 | | // check for different argument names |
3580 | 0 | if (style && inconclusive) { |
3581 | 0 | for (int j = 0; j < function->argCount(); ++j) { |
3582 | 0 | if (declarations[j] && definitions[j] && declarations[j]->str() != definitions[j]->str()) |
3583 | 0 | funcArgNamesDifferent(function->name(), j, declarations[j], definitions[j]); |
3584 | 0 | } |
3585 | 0 | } |
3586 | 0 | } |
3587 | 1.36k | } |
3588 | | |
3589 | | void CheckOther::funcArgNamesDifferent(const std::string & functionName, nonneg int index, |
3590 | | const Token* declaration, const Token* definition) |
3591 | 0 | { |
3592 | 0 | std::list<const Token *> tokens = { declaration,definition }; |
3593 | 0 | reportError(tokens, Severity::style, "funcArgNamesDifferent", |
3594 | 0 | "$symbol:" + functionName + "\n" |
3595 | 0 | "Function '$symbol' argument " + std::to_string(index + 1) + " names different: declaration '" + |
3596 | 0 | (declaration ? declaration->str() : std::string("A")) + "' definition '" + |
3597 | 0 | (definition ? definition->str() : std::string("B")) + "'.", CWE628, Certainty::inconclusive); |
3598 | 0 | } |
3599 | | |
3600 | | void CheckOther::funcArgOrderDifferent(const std::string & functionName, |
3601 | | const Token* declaration, const Token* definition, |
3602 | | const std::vector<const Token *> & declarations, |
3603 | | const std::vector<const Token *> & definitions) |
3604 | 0 | { |
3605 | 0 | std::list<const Token *> tokens = { |
3606 | 0 | !declarations.empty() ? declarations[0] ? declarations[0] : declaration : nullptr, |
3607 | 0 | !definitions.empty() ? definitions[0] ? definitions[0] : definition : nullptr |
3608 | 0 | }; |
3609 | 0 | std::string msg = "$symbol:" + functionName + "\nFunction '$symbol' argument order different: declaration '"; |
3610 | 0 | for (int i = 0; i < declarations.size(); ++i) { |
3611 | 0 | if (i != 0) |
3612 | 0 | msg += ", "; |
3613 | 0 | if (declarations[i]) |
3614 | 0 | msg += declarations[i]->str(); |
3615 | 0 | } |
3616 | 0 | msg += "' definition '"; |
3617 | 0 | for (int i = 0; i < definitions.size(); ++i) { |
3618 | 0 | if (i != 0) |
3619 | 0 | msg += ", "; |
3620 | 0 | if (definitions[i]) |
3621 | 0 | msg += definitions[i]->str(); |
3622 | 0 | } |
3623 | 0 | msg += "'"; |
3624 | 0 | reportError(tokens, Severity::warning, "funcArgOrderDifferent", msg, CWE683, Certainty::normal); |
3625 | 0 | } |
3626 | | |
3627 | | static const Token *findShadowed(const Scope *scope, const std::string &varname, int linenr) |
3628 | 0 | { |
3629 | 0 | if (!scope) |
3630 | 0 | return nullptr; |
3631 | 0 | for (const Variable &var : scope->varlist) { |
3632 | 0 | if (scope->isExecutable() && var.nameToken()->linenr() > linenr) |
3633 | 0 | continue; |
3634 | 0 | if (var.name() == varname) |
3635 | 0 | return var.nameToken(); |
3636 | 0 | } |
3637 | 0 | auto it = std::find_if(scope->functionList.cbegin(), scope->functionList.cend(), [&](const Function& f) { |
3638 | 0 | return f.type == Function::Type::eFunction && f.name() == varname; |
3639 | 0 | }); |
3640 | 0 | if (it != scope->functionList.end()) |
3641 | 0 | return it->tokenDef; |
3642 | | |
3643 | 0 | if (scope->type == Scope::eLambda) |
3644 | 0 | return nullptr; |
3645 | 0 | const Token* shadowed = findShadowed(scope->nestedIn, varname, linenr); |
3646 | 0 | if (!shadowed) |
3647 | 0 | shadowed = findShadowed(scope->functionOf, varname, linenr); |
3648 | 0 | return shadowed; |
3649 | 0 | } |
3650 | | |
3651 | | void CheckOther::checkShadowVariables() |
3652 | 1.36k | { |
3653 | 1.36k | if (!mSettings->severity.isEnabled(Severity::style)) |
3654 | 0 | return; |
3655 | 1.36k | logChecker("CheckOther::checkShadowVariables"); // style |
3656 | 1.36k | const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); |
3657 | 4.94k | for (const Scope & scope : symbolDatabase->scopeList) { |
3658 | 4.94k | if (!scope.isExecutable() || scope.type == Scope::eLambda) |
3659 | 1.36k | continue; |
3660 | 3.58k | const Scope *functionScope = &scope; |
3661 | 6.20k | while (functionScope && functionScope->type != Scope::ScopeType::eFunction && functionScope->type != Scope::ScopeType::eLambda) |
3662 | 2.62k | functionScope = functionScope->nestedIn; |
3663 | 3.58k | for (const Variable &var : scope.varlist) { |
3664 | 0 | if (var.nameToken() && var.nameToken()->isExpandedMacro()) // #8903 |
3665 | 0 | continue; |
3666 | | |
3667 | 0 | if (functionScope && functionScope->type == Scope::ScopeType::eFunction && functionScope->function) { |
3668 | 0 | const auto argList = functionScope->function->argumentList; |
3669 | 0 | auto it = std::find_if(argList.cbegin(), argList.cend(), [&](const Variable& arg) { |
3670 | 0 | return arg.nameToken() && var.name() == arg.name(); |
3671 | 0 | }); |
3672 | 0 | if (it != argList.end()) { |
3673 | 0 | shadowError(var.nameToken(), it->nameToken(), "argument"); |
3674 | 0 | continue; |
3675 | 0 | } |
3676 | 0 | } |
3677 | | |
3678 | 0 | const Token *shadowed = findShadowed(scope.nestedIn, var.name(), var.nameToken()->linenr()); |
3679 | 0 | if (!shadowed) |
3680 | 0 | shadowed = findShadowed(scope.functionOf, var.name(), var.nameToken()->linenr()); |
3681 | 0 | if (!shadowed) |
3682 | 0 | continue; |
3683 | 0 | if (scope.type == Scope::eFunction && scope.className == var.name()) |
3684 | 0 | continue; |
3685 | 0 | if (functionScope->functionOf && functionScope->functionOf->isClassOrStructOrUnion() && functionScope->function && functionScope->function->isStatic() && |
3686 | 0 | shadowed->variable() && !shadowed->variable()->isLocal()) |
3687 | 0 | continue; |
3688 | 0 | shadowError(var.nameToken(), shadowed, (shadowed->varId() != 0) ? "variable" : "function"); |
3689 | 0 | } |
3690 | 3.58k | } |
3691 | 1.36k | } |
3692 | | |
3693 | | void CheckOther::shadowError(const Token *var, const Token *shadowed, std::string type) |
3694 | 0 | { |
3695 | 0 | ErrorPath errorPath; |
3696 | 0 | errorPath.emplace_back(shadowed, "Shadowed declaration"); |
3697 | 0 | errorPath.emplace_back(var, "Shadow variable"); |
3698 | 0 | const std::string &varname = var ? var->str() : type; |
3699 | 0 | const std::string Type = char(std::toupper(type[0])) + type.substr(1); |
3700 | 0 | const std::string id = "shadow" + Type; |
3701 | 0 | const std::string message = "$symbol:" + varname + "\nLocal variable \'$symbol\' shadows outer " + type; |
3702 | 0 | reportError(errorPath, Severity::style, id.c_str(), message, CWE398, Certainty::normal); |
3703 | 0 | } |
3704 | | |
3705 | | static bool isVariableExpression(const Token* tok) |
3706 | 0 | { |
3707 | 0 | if (tok->varId() != 0) |
3708 | 0 | return true; |
3709 | 0 | if (Token::simpleMatch(tok, ".")) |
3710 | 0 | return isVariableExpression(tok->astOperand1()) && |
3711 | 0 | isVariableExpression(tok->astOperand2()); |
3712 | 0 | if (Token::simpleMatch(tok, "[")) |
3713 | 0 | return isVariableExpression(tok->astOperand1()); |
3714 | 0 | return false; |
3715 | 0 | } |
3716 | | |
3717 | | static bool isVariableExprHidden(const Token* tok) |
3718 | 0 | { |
3719 | 0 | if (!tok) |
3720 | 0 | return false; |
3721 | 0 | if (!tok->astParent()) |
3722 | 0 | return false; |
3723 | 0 | if (Token::simpleMatch(tok->astParent(), "*") && Token::simpleMatch(tok->astSibling(), "0")) |
3724 | 0 | return true; |
3725 | 0 | if (Token::simpleMatch(tok->astParent(), "&&") && Token::simpleMatch(tok->astSibling(), "false")) |
3726 | 0 | return true; |
3727 | 0 | if (Token::simpleMatch(tok->astParent(), "||") && Token::simpleMatch(tok->astSibling(), "true")) |
3728 | 0 | return true; |
3729 | 0 | return false; |
3730 | 0 | } |
3731 | | |
3732 | | void CheckOther::checkKnownArgument() |
3733 | 1.36k | { |
3734 | 1.36k | if (!mSettings->severity.isEnabled(Severity::style)) |
3735 | 0 | return; |
3736 | 1.36k | logChecker("CheckOther::checkKnownArgument"); // style |
3737 | 1.36k | const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); |
3738 | 1.73k | for (const Scope *functionScope : symbolDatabase->functionScopes) { |
3739 | 37.6k | for (const Token *tok = functionScope->bodyStart; tok != functionScope->bodyEnd; tok = tok->next()) { |
3740 | 35.9k | if (!tok->hasKnownIntValue()) |
3741 | 31.9k | continue; |
3742 | 3.95k | if (Token::Match(tok, "++|--|%assign%")) |
3743 | 354 | continue; |
3744 | 3.59k | if (!Token::Match(tok->astParent(), "(|{|,")) |
3745 | 3.43k | continue; |
3746 | 164 | if (tok->astParent()->isCast() || (tok->isCast() && Token::Match(tok->astOperand2(), "++|--|%assign%"))) |
3747 | 0 | continue; |
3748 | 164 | int argn = -1; |
3749 | 164 | const Token* ftok = getTokenArgumentFunction(tok, argn); |
3750 | 164 | if (!ftok) |
3751 | 0 | continue; |
3752 | 164 | if (ftok->isCast()) |
3753 | 0 | continue; |
3754 | 164 | if (Token::Match(ftok, "if|while|switch|sizeof")) |
3755 | 164 | continue; |
3756 | 0 | if (tok == tok->astParent()->previous()) |
3757 | 0 | continue; |
3758 | 0 | if (isConstVarExpression(tok)) |
3759 | 0 | continue; |
3760 | 0 | if (Token::Match(tok->astOperand1(), "%name% (")) |
3761 | 0 | continue; |
3762 | 0 | const Token * tok2 = tok; |
3763 | 0 | if (isCPPCast(tok2)) |
3764 | 0 | tok2 = tok2->astOperand2(); |
3765 | 0 | if (isVariableExpression(tok2)) |
3766 | 0 | continue; |
3767 | 0 | if (tok->isComparisonOp() && |
3768 | 0 | isSameExpression( |
3769 | 0 | mTokenizer->isCPP(), true, tok->astOperand1(), tok->astOperand2(), mSettings->library, true, true)) |
3770 | 0 | continue; |
3771 | | // ensure that there is a integer variable in expression with unknown value |
3772 | 0 | const Token* vartok = nullptr; |
3773 | 0 | visitAstNodes(tok, [&](const Token* child) { |
3774 | 0 | if (Token::Match(child, "%var%|.|[")) { |
3775 | 0 | if (child->hasKnownIntValue()) |
3776 | 0 | return ChildrenToVisit::none; |
3777 | 0 | if (astIsIntegral(child, false) && !astIsPointer(child) && child->values().empty()) { |
3778 | 0 | vartok = child; |
3779 | 0 | return ChildrenToVisit::done; |
3780 | 0 | } |
3781 | 0 | } |
3782 | 0 | return ChildrenToVisit::op1_and_op2; |
3783 | 0 | }); |
3784 | 0 | if (!vartok) |
3785 | 0 | continue; |
3786 | 0 | if (vartok->astSibling() && |
3787 | 0 | findAstNode(vartok->astSibling(), [](const Token* child) { |
3788 | 0 | return Token::simpleMatch(child, "sizeof"); |
3789 | 0 | })) |
3790 | 0 | continue; |
3791 | | // ensure that function name does not contain "assert" |
3792 | 0 | std::string funcname = ftok->str(); |
3793 | 0 | strTolower(funcname); |
3794 | 0 | if (funcname.find("assert") != std::string::npos) |
3795 | 0 | continue; |
3796 | 0 | knownArgumentError(tok, ftok, &tok->values().front(), vartok->expressionString(), isVariableExprHidden(vartok)); |
3797 | 0 | } |
3798 | 1.73k | } |
3799 | 1.36k | } |
3800 | | |
3801 | | void CheckOther::knownArgumentError(const Token *tok, const Token *ftok, const ValueFlow::Value *value, const std::string &varexpr, bool isVariableExpressionHidden) |
3802 | 0 | { |
3803 | 0 | if (!tok) { |
3804 | 0 | reportError(tok, Severity::style, "knownArgument", "Argument 'x-x' to function 'func' is always 0. It does not matter what value 'x' has."); |
3805 | 0 | reportError(tok, Severity::style, "knownArgumentHiddenVariableExpression", "Argument 'x*0' to function 'func' is always 0. Constant literal calculation disable/hide variable expression 'x'."); |
3806 | 0 | return; |
3807 | 0 | } |
3808 | | |
3809 | 0 | const MathLib::bigint intvalue = value->intvalue; |
3810 | 0 | const std::string &expr = tok->expressionString(); |
3811 | 0 | const std::string &fun = ftok->str(); |
3812 | |
|
3813 | 0 | std::string ftype = "function "; |
3814 | 0 | if (ftok->type()) |
3815 | 0 | ftype = "constructor "; |
3816 | 0 | else if (fun == "{") |
3817 | 0 | ftype = "init list "; |
3818 | |
|
3819 | 0 | const char *id; |
3820 | 0 | std::string errmsg = "Argument '" + expr + "' to " + ftype + fun + " is always " + std::to_string(intvalue) + ". "; |
3821 | 0 | if (!isVariableExpressionHidden) { |
3822 | 0 | id = "knownArgument"; |
3823 | 0 | errmsg += "It does not matter what value '" + varexpr + "' has."; |
3824 | 0 | } else { |
3825 | 0 | id = "knownArgumentHiddenVariableExpression"; |
3826 | 0 | errmsg += "Constant literal calculation disable/hide variable expression '" + varexpr + "'."; |
3827 | 0 | } |
3828 | |
|
3829 | 0 | const ErrorPath errorPath = getErrorPath(tok, value, errmsg); |
3830 | 0 | reportError(errorPath, Severity::style, id, errmsg, CWE570, Certainty::normal); |
3831 | 0 | } |
3832 | | |
3833 | | void CheckOther::checkKnownPointerToBool() |
3834 | 1.36k | { |
3835 | 1.36k | if (!mSettings->severity.isEnabled(Severity::style)) |
3836 | 0 | return; |
3837 | 1.36k | logChecker("CheckOther::checkKnownPointerToBool"); // style |
3838 | 1.36k | const SymbolDatabase* symbolDatabase = mTokenizer->getSymbolDatabase(); |
3839 | 1.73k | for (const Scope* functionScope : symbolDatabase->functionScopes) { |
3840 | 37.6k | for (const Token* tok = functionScope->bodyStart; tok != functionScope->bodyEnd; tok = tok->next()) { |
3841 | 35.9k | if (!tok->hasKnownIntValue()) |
3842 | 31.9k | continue; |
3843 | 3.95k | if (!astIsPointer(tok)) |
3844 | 3.95k | continue; |
3845 | 0 | if (Token::Match(tok->astParent(), "?|!|&&|%oror%|%comp%")) |
3846 | 0 | continue; |
3847 | 0 | if (tok->astParent() && Token::Match(tok->astParent()->previous(), "if|while|switch|sizeof (")) |
3848 | 0 | continue; |
3849 | 0 | if (tok->isExpandedMacro()) |
3850 | 0 | continue; |
3851 | 0 | if (findParent(tok, [](const Token* parent) { |
3852 | 0 | return parent->isExpandedMacro(); |
3853 | 0 | })) |
3854 | 0 | continue; |
3855 | 0 | if (!isUsedAsBool(tok, mSettings)) |
3856 | 0 | continue; |
3857 | 0 | const ValueFlow::Value& value = tok->values().front(); |
3858 | 0 | knownPointerToBoolError(tok, &value); |
3859 | 0 | } |
3860 | 1.73k | } |
3861 | 1.36k | } |
3862 | | |
3863 | | void CheckOther::knownPointerToBoolError(const Token* tok, const ValueFlow::Value* value) |
3864 | 0 | { |
3865 | 0 | if (!tok) { |
3866 | 0 | reportError(tok, Severity::style, "knownPointerToBool", "Pointer expression 'p' converted to bool is always true."); |
3867 | 0 | return; |
3868 | 0 | } |
3869 | 0 | std::string cond = bool_to_string(value->intvalue); |
3870 | 0 | const std::string& expr = tok->expressionString(); |
3871 | 0 | std::string errmsg = "Pointer expression '" + expr + "' converted to bool is always " + cond + "."; |
3872 | 0 | const ErrorPath errorPath = getErrorPath(tok, value, errmsg); |
3873 | 0 | reportError(errorPath, Severity::style, "knownPointerToBool", errmsg, CWE570, Certainty::normal); |
3874 | 0 | } |
3875 | | |
3876 | | void CheckOther::checkComparePointers() |
3877 | 1.36k | { |
3878 | 1.36k | logChecker("CheckOther::checkComparePointers"); |
3879 | 1.36k | const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); |
3880 | 1.73k | for (const Scope *functionScope : symbolDatabase->functionScopes) { |
3881 | 37.6k | for (const Token *tok = functionScope->bodyStart; tok != functionScope->bodyEnd; tok = tok->next()) { |
3882 | 35.9k | if (!Token::Match(tok, "<|>|<=|>=|-")) |
3883 | 34.1k | continue; |
3884 | 1.76k | const Token *tok1 = tok->astOperand1(); |
3885 | 1.76k | const Token *tok2 = tok->astOperand2(); |
3886 | 1.76k | if (!astIsPointer(tok1) || !astIsPointer(tok2)) |
3887 | 1.76k | continue; |
3888 | 0 | ValueFlow::Value v1 = ValueFlow::getLifetimeObjValue(tok1); |
3889 | 0 | ValueFlow::Value v2 = ValueFlow::getLifetimeObjValue(tok2); |
3890 | 0 | if (!v1.isLocalLifetimeValue() || !v2.isLocalLifetimeValue()) |
3891 | 0 | continue; |
3892 | 0 | const Variable *var1 = v1.tokvalue->variable(); |
3893 | 0 | const Variable *var2 = v2.tokvalue->variable(); |
3894 | 0 | if (!var1 || !var2) |
3895 | 0 | continue; |
3896 | 0 | if (v1.tokvalue->varId() == v2.tokvalue->varId()) |
3897 | 0 | continue; |
3898 | 0 | if (var1->isReference() || var2->isReference()) |
3899 | 0 | continue; |
3900 | 0 | if (var1->isRValueReference() || var2->isRValueReference()) |
3901 | 0 | continue; |
3902 | 0 | if (const Token* parent2 = getParentLifetime(mTokenizer->isCPP(), v2.tokvalue, &mSettings->library)) |
3903 | 0 | if (var1 == parent2->variable()) |
3904 | 0 | continue; |
3905 | 0 | if (const Token* parent1 = getParentLifetime(mTokenizer->isCPP(), v1.tokvalue, &mSettings->library)) |
3906 | 0 | if (var2 == parent1->variable()) |
3907 | 0 | continue; |
3908 | 0 | comparePointersError(tok, &v1, &v2); |
3909 | 0 | } |
3910 | 1.73k | } |
3911 | 1.36k | } |
3912 | | |
3913 | | void CheckOther::comparePointersError(const Token *tok, const ValueFlow::Value *v1, const ValueFlow::Value *v2) |
3914 | 0 | { |
3915 | 0 | ErrorPath errorPath; |
3916 | 0 | std::string verb = "Comparing"; |
3917 | 0 | if (Token::simpleMatch(tok, "-")) |
3918 | 0 | verb = "Subtracting"; |
3919 | 0 | if (v1) { |
3920 | 0 | errorPath.emplace_back(v1->tokvalue->variable()->nameToken(), "Variable declared here."); |
3921 | 0 | errorPath.insert(errorPath.end(), v1->errorPath.cbegin(), v1->errorPath.cend()); |
3922 | 0 | } |
3923 | 0 | if (v2) { |
3924 | 0 | errorPath.emplace_back(v2->tokvalue->variable()->nameToken(), "Variable declared here."); |
3925 | 0 | errorPath.insert(errorPath.end(), v2->errorPath.cbegin(), v2->errorPath.cend()); |
3926 | 0 | } |
3927 | 0 | errorPath.emplace_back(tok, ""); |
3928 | 0 | reportError( |
3929 | 0 | errorPath, Severity::error, "comparePointers", verb + " pointers that point to different objects", CWE570, Certainty::normal); |
3930 | 0 | } |
3931 | | |
3932 | | void CheckOther::checkModuloOfOne() |
3933 | 1.36k | { |
3934 | 1.36k | if (!mSettings->severity.isEnabled(Severity::style)) |
3935 | 0 | return; |
3936 | | |
3937 | 1.36k | logChecker("CheckOther::checkModuloOfOne"); // style |
3938 | | |
3939 | 93.5k | for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) { |
3940 | 92.2k | if (!tok->astOperand2() || !tok->astOperand1()) |
3941 | 78.0k | continue; |
3942 | 14.2k | if (tok->str() != "%") |
3943 | 13.9k | continue; |
3944 | 289 | if (!tok->valueType() || !tok->valueType()->isIntegral()) |
3945 | 127 | continue; |
3946 | | |
3947 | | // Value flow.. |
3948 | 162 | const ValueFlow::Value *value = tok->astOperand2()->getValue(1LL); |
3949 | 162 | if (value && value->isKnown()) |
3950 | 0 | checkModuloOfOneError(tok); |
3951 | 162 | } |
3952 | 1.36k | } |
3953 | | |
3954 | | void CheckOther::checkModuloOfOneError(const Token *tok) |
3955 | 0 | { |
3956 | 0 | reportError(tok, Severity::style, "moduloofone", "Modulo of one is always equal to zero"); |
3957 | 0 | } |
3958 | | |
3959 | | //----------------------------------------------------------------------------- |
3960 | | // Overlapping write (undefined behavior) |
3961 | | //----------------------------------------------------------------------------- |
3962 | | static bool getBufAndOffset(const Token *expr, const Token **buf, MathLib::bigint *offset) |
3963 | 0 | { |
3964 | 0 | if (!expr) |
3965 | 0 | return false; |
3966 | 0 | const Token *bufToken, *offsetToken; |
3967 | 0 | if (expr->isUnaryOp("&") && Token::simpleMatch(expr->astOperand1(), "[")) { |
3968 | 0 | bufToken = expr->astOperand1()->astOperand1(); |
3969 | 0 | offsetToken = expr->astOperand1()->astOperand2(); |
3970 | 0 | } else if (Token::Match(expr, "+|-") && expr->isBinaryOp()) { |
3971 | 0 | const bool pointer1 = (expr->astOperand1()->valueType() && expr->astOperand1()->valueType()->pointer > 0); |
3972 | 0 | const bool pointer2 = (expr->astOperand2()->valueType() && expr->astOperand2()->valueType()->pointer > 0); |
3973 | 0 | if (pointer1 && !pointer2) { |
3974 | 0 | bufToken = expr->astOperand1(); |
3975 | 0 | offsetToken = expr->astOperand2(); |
3976 | 0 | } else if (!pointer1 && pointer2) { |
3977 | 0 | bufToken = expr->astOperand2(); |
3978 | 0 | offsetToken = expr->astOperand1(); |
3979 | 0 | } else { |
3980 | 0 | return false; |
3981 | 0 | } |
3982 | 0 | } else if (expr->valueType() && expr->valueType()->pointer > 0) { |
3983 | 0 | *buf = expr; |
3984 | 0 | *offset = 0; |
3985 | 0 | return true; |
3986 | 0 | } else { |
3987 | 0 | return false; |
3988 | 0 | } |
3989 | 0 | if (!bufToken->valueType() || !bufToken->valueType()->pointer) |
3990 | 0 | return false; |
3991 | 0 | if (!offsetToken->hasKnownIntValue()) |
3992 | 0 | return false; |
3993 | 0 | *buf = bufToken; |
3994 | 0 | *offset = offsetToken->getKnownIntValue(); |
3995 | 0 | return true; |
3996 | 0 | } |
3997 | | |
3998 | | void CheckOther::checkOverlappingWrite() |
3999 | 1.36k | { |
4000 | 1.36k | logChecker("CheckOther::checkOverlappingWrite"); |
4001 | 1.36k | const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); |
4002 | 1.73k | for (const Scope *functionScope : symbolDatabase->functionScopes) { |
4003 | 37.6k | for (const Token *tok = functionScope->bodyStart; tok != functionScope->bodyEnd; tok = tok->next()) { |
4004 | 35.9k | if (tok->isAssignmentOp()) { |
4005 | | // check if LHS is a union member.. |
4006 | 1.90k | const Token * const lhs = tok->astOperand1(); |
4007 | 1.90k | if (!Token::simpleMatch(lhs, ".") || !lhs->isBinaryOp()) |
4008 | 1.90k | continue; |
4009 | 0 | const Variable * const lhsvar = lhs->astOperand1()->variable(); |
4010 | 0 | if (!lhsvar || !lhsvar->typeScope() || lhsvar->typeScope()->type != Scope::ScopeType::eUnion) |
4011 | 0 | continue; |
4012 | 0 | const Token* const lhsmember = lhs->astOperand2(); |
4013 | 0 | if (!lhsmember) |
4014 | 0 | continue; |
4015 | | |
4016 | | // Is other union member used in RHS? |
4017 | 0 | const Token *errorToken = nullptr; |
4018 | 0 | visitAstNodes(tok->astOperand2(), [lhsvar, lhsmember, &errorToken](const Token *rhs) { |
4019 | 0 | if (!Token::simpleMatch(rhs, ".")) |
4020 | 0 | return ChildrenToVisit::op1_and_op2; |
4021 | 0 | if (!rhs->isBinaryOp() || rhs->astOperand1()->variable() != lhsvar) |
4022 | 0 | return ChildrenToVisit::none; |
4023 | 0 | if (lhsmember->str() == rhs->astOperand2()->str()) |
4024 | 0 | return ChildrenToVisit::none; |
4025 | 0 | const Variable* rhsmembervar = rhs->astOperand2()->variable(); |
4026 | 0 | const Scope* varscope1 = lhsmember->variable() ? lhsmember->variable()->typeStartToken()->scope() : nullptr; |
4027 | 0 | const Scope* varscope2 = rhsmembervar ? rhsmembervar->typeStartToken()->scope() : nullptr; |
4028 | 0 | if (varscope1 && varscope1 == varscope2 && varscope1 != lhsvar->typeScope()) |
4029 | | // lhsmember and rhsmember are declared in same anonymous scope inside union |
4030 | 0 | return ChildrenToVisit::none; |
4031 | 0 | errorToken = rhs->astOperand2(); |
4032 | 0 | return ChildrenToVisit::done; |
4033 | 0 | }); |
4034 | 0 | if (errorToken) |
4035 | 0 | overlappingWriteUnion(tok); |
4036 | 34.0k | } else if (Token::Match(tok, "%name% (")) { |
4037 | 1.46k | const Library::NonOverlappingData *nonOverlappingData = mSettings->library.getNonOverlappingData(tok); |
4038 | 1.46k | if (!nonOverlappingData) |
4039 | 1.46k | continue; |
4040 | 0 | const std::vector<const Token *> args = getArguments(tok); |
4041 | 0 | if (nonOverlappingData->ptr1Arg <= 0 || nonOverlappingData->ptr1Arg > args.size()) |
4042 | 0 | continue; |
4043 | 0 | if (nonOverlappingData->ptr2Arg <= 0 || nonOverlappingData->ptr2Arg > args.size()) |
4044 | 0 | continue; |
4045 | | |
4046 | 0 | const Token *ptr1 = args[nonOverlappingData->ptr1Arg - 1]; |
4047 | 0 | if (ptr1->hasKnownIntValue() && ptr1->getKnownIntValue() == 0) |
4048 | 0 | continue; |
4049 | | |
4050 | 0 | const Token *ptr2 = args[nonOverlappingData->ptr2Arg - 1]; |
4051 | 0 | if (ptr2->hasKnownIntValue() && ptr2->getKnownIntValue() == 0) |
4052 | 0 | continue; |
4053 | | |
4054 | | // TODO: nonOverlappingData->strlenArg |
4055 | 0 | if (nonOverlappingData->sizeArg <= 0 || nonOverlappingData->sizeArg > args.size()) { |
4056 | 0 | if (nonOverlappingData->sizeArg == -1) { |
4057 | 0 | ErrorPath errorPath; |
4058 | 0 | const bool macro = true; |
4059 | 0 | const bool pure = true; |
4060 | 0 | const bool follow = true; |
4061 | 0 | if (!isSameExpression(mTokenizer->isCPP(), macro, ptr1, ptr2, mSettings->library, pure, follow, &errorPath)) |
4062 | 0 | continue; |
4063 | 0 | overlappingWriteFunction(tok); |
4064 | 0 | } |
4065 | 0 | continue; |
4066 | 0 | } |
4067 | 0 | if (!args[nonOverlappingData->sizeArg-1]->hasKnownIntValue()) |
4068 | 0 | continue; |
4069 | 0 | const MathLib::bigint sizeValue = args[nonOverlappingData->sizeArg-1]->getKnownIntValue(); |
4070 | 0 | const Token *buf1, *buf2; |
4071 | 0 | MathLib::bigint offset1, offset2; |
4072 | 0 | if (!getBufAndOffset(ptr1, &buf1, &offset1)) |
4073 | 0 | continue; |
4074 | 0 | if (!getBufAndOffset(ptr2, &buf2, &offset2)) |
4075 | 0 | continue; |
4076 | | |
4077 | 0 | if (offset1 < offset2 && offset1 + sizeValue <= offset2) |
4078 | 0 | continue; |
4079 | 0 | if (offset2 < offset1 && offset2 + sizeValue <= offset1) |
4080 | 0 | continue; |
4081 | | |
4082 | 0 | ErrorPath errorPath; |
4083 | 0 | const bool macro = true; |
4084 | 0 | const bool pure = true; |
4085 | 0 | const bool follow = true; |
4086 | 0 | if (!isSameExpression(mTokenizer->isCPP(), macro, buf1, buf2, mSettings->library, pure, follow, &errorPath)) |
4087 | 0 | continue; |
4088 | 0 | overlappingWriteFunction(tok); |
4089 | 0 | } |
4090 | 35.9k | } |
4091 | 1.73k | } |
4092 | 1.36k | } |
4093 | | |
4094 | | void CheckOther::overlappingWriteUnion(const Token *tok) |
4095 | 0 | { |
4096 | 0 | reportError(tok, Severity::error, "overlappingWriteUnion", "Overlapping read/write of union is undefined behavior"); |
4097 | 0 | } |
4098 | | |
4099 | | void CheckOther::overlappingWriteFunction(const Token *tok) |
4100 | 0 | { |
4101 | 0 | const std::string &funcname = tok ? tok->str() : emptyString; |
4102 | 0 | reportError(tok, Severity::error, "overlappingWriteFunction", "Overlapping read/write in " + funcname + "() is undefined behavior"); |
4103 | 0 | } |