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