1 /*
2 * Cppcheck - A tool for static C/C++ code analysis
3 * Copyright (C) 2007-2021 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 // You should not write statements with side effects in assert()
21 //---------------------------------------------------------------------------
22
23 #include "checkassert.h"
24
25 #include "settings.h"
26 #include "symboldatabase.h"
27 #include "token.h"
28 #include "tokenize.h"
29 #include "tokenlist.h"
30
31 //---------------------------------------------------------------------------
32
33 // CWE ids used
34 static const struct CWE CWE398(398U); // Indicator of Poor Code Quality
35
36 // Register this check class (by creating a static instance of it)
37 namespace {
38 CheckAssert instance;
39 }
40
assertWithSideEffects()41 void CheckAssert::assertWithSideEffects()
42 {
43 if (!mSettings->severity.isEnabled(Severity::warning))
44 return;
45
46 for (const Token* tok = mTokenizer->list.front(); tok; tok = tok->next()) {
47 if (!Token::simpleMatch(tok, "assert ("))
48 continue;
49
50 const Token *endTok = tok->next()->link();
51 for (const Token* tmp = tok->next(); tmp != endTok; tmp = tmp->next()) {
52 if (Token::simpleMatch(tmp, "sizeof ("))
53 tmp = tmp->linkAt(1);
54
55 checkVariableAssignment(tmp, tok->scope());
56
57 if (tmp->tokType() != Token::eFunction)
58 continue;
59
60 const Function* f = tmp->function();
61 if (f->nestedIn->isClassOrStruct() && !f->isStatic() && !f->isConst()) {
62 sideEffectInAssertError(tmp, f->name()); // Non-const member function called
63 continue;
64 }
65 const Scope* scope = f->functionScope;
66 if (!scope) continue;
67
68 for (const Token *tok2 = scope->bodyStart; tok2 != scope->bodyEnd; tok2 = tok2->next()) {
69 if (!tok2->isAssignmentOp() && tok2->tokType() != Token::eIncDecOp)
70 continue;
71
72 const Variable* var = tok2->previous()->variable();
73 if (!var || var->isLocal() || (var->isArgument() && !var->isReference() && !var->isPointer()))
74 continue; // See ticket #4937. Assigning function arguments not passed by reference is ok.
75 if (var->isArgument() && var->isPointer() && tok2->strAt(-2) != "*")
76 continue; // Pointers need to be dereferenced, otherwise there is no error
77
78 bool noReturnInScope = true;
79 for (const Token *rt = scope->bodyStart; rt != scope->bodyEnd; rt = rt->next()) {
80 if (rt->str() != "return") continue; // find all return statements
81 if (inSameScope(rt, tok2)) {
82 noReturnInScope = false;
83 break;
84 }
85 }
86 if (noReturnInScope) continue;
87
88 sideEffectInAssertError(tmp, f->name());
89 break;
90 }
91 }
92 tok = endTok;
93 }
94 }
95 //---------------------------------------------------------------------------
96
97
sideEffectInAssertError(const Token * tok,const std::string & functionName)98 void CheckAssert::sideEffectInAssertError(const Token *tok, const std::string& functionName)
99 {
100 reportError(tok, Severity::warning,
101 "assertWithSideEffect",
102 "$symbol:" + functionName + "\n"
103 "Assert statement calls a function which may have desired side effects: '$symbol'.\n"
104 "Non-pure function: '$symbol' is called inside assert statement. "
105 "Assert statements are removed from release builds so the code inside "
106 "assert statement is not executed. If the code is needed also in release "
107 "builds, this is a bug.", CWE398, Certainty::normal);
108 }
109
assignmentInAssertError(const Token * tok,const std::string & varname)110 void CheckAssert::assignmentInAssertError(const Token *tok, const std::string& varname)
111 {
112 reportError(tok, Severity::warning,
113 "assignmentInAssert",
114 "$symbol:" + varname + "\n"
115 "Assert statement modifies '$symbol'.\n"
116 "Variable '$symbol' is modified inside assert statement. "
117 "Assert statements are removed from release builds so the code inside "
118 "assert statement is not executed. If the code is needed also in release "
119 "builds, this is a bug.", CWE398, Certainty::normal);
120 }
121
122 // checks if side effects happen on the variable prior to tmp
checkVariableAssignment(const Token * assignTok,const Scope * assertionScope)123 void CheckAssert::checkVariableAssignment(const Token* assignTok, const Scope *assertionScope)
124 {
125 if (!assignTok->isAssignmentOp() && assignTok->tokType() != Token::eIncDecOp)
126 return;
127
128 const Variable* var = assignTok->astOperand1()->variable();
129 if (!var)
130 return;
131
132 // Variable declared in inner scope in assert => don't warn
133 if (assertionScope != var->scope()) {
134 const Scope *s = var->scope();
135 while (s && s != assertionScope)
136 s = s->nestedIn;
137 if (s == assertionScope)
138 return;
139 }
140
141 // assignment
142 if (assignTok->isAssignmentOp() || assignTok->tokType() == Token::eIncDecOp) {
143 if (var->isConst()) {
144 return;
145 }
146 assignmentInAssertError(assignTok, var->name());
147 }
148 // TODO: function calls on var
149 }
150
inSameScope(const Token * returnTok,const Token * assignTok)151 bool CheckAssert::inSameScope(const Token* returnTok, const Token* assignTok)
152 {
153 // TODO: even if a return is in the same scope, the assignment might not affect it.
154 return returnTok->scope() == assignTok->scope();
155 }
156