Cppcheck
checkassert.cpp
Go to the documentation of this file.
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 // You should not write statements with side effects in assert()
21 //---------------------------------------------------------------------------
22 
23 #include "checkassert.h"
24 
25 #include "astutils.h"
26 #include "errortypes.h"
27 #include "settings.h"
28 #include "symboldatabase.h"
29 #include "token.h"
30 #include "tokenize.h"
31 #include "tokenlist.h"
32 
33 //---------------------------------------------------------------------------
34 
35 // CWE ids used
36 static const CWE CWE398(398U); // Indicator of Poor Code Quality
37 
38 // Register this check class (by creating a static instance of it)
39 namespace {
40  CheckAssert instance;
41 }
42 
44 {
46  return;
47 
48  logChecker("CheckAssert::assertWithSideEffects"); // warning
49 
50  for (const Token* tok = mTokenizer->list.front(); tok; tok = tok->next()) {
51  if (!Token::simpleMatch(tok, "assert ("))
52  continue;
53 
54  const Token *endTok = tok->next()->link();
55  for (const Token* tmp = tok->next(); tmp != endTok; tmp = tmp->next()) {
56  if (Token::simpleMatch(tmp, "sizeof ("))
57  tmp = tmp->linkAt(1);
58 
59  checkVariableAssignment(tmp, tok->scope());
60 
61  if (tmp->tokType() != Token::eFunction) {
62  if (const Library::Function* f = mSettings->library.getFunction(tmp)) {
63  if (f->isconst || f->ispure)
64  continue;
65  if (Library::getContainerYield(tmp->next()) != Library::Container::Yield::NO_YIELD) // bailout, assume read access
66  continue;
67  if (std::any_of(f->argumentChecks.begin(), f->argumentChecks.end(), [](const std::pair<int, Library::ArgumentChecks>& ac) {
68  return ac.second.iteratorInfo.container > 0; // bailout, takes iterators -> assume read access
69  }))
70  continue;
71  if (tmp->str() == "get" && Token::simpleMatch(tmp->astParent(), ".") && astIsSmartPointer(tmp->astParent()->astOperand1()))
72  continue;
73  if (f->containerYield == Library::Container::Yield::START_ITERATOR || // bailout for std::begin/end/prev/next
74  f->containerYield == Library::Container::Yield::END_ITERATOR ||
75  f->containerYield == Library::Container::Yield::ITERATOR)
76  continue;
78  }
79  continue;
80  }
81 
82  const Function* f = tmp->function();
83  const Scope* scope = f->functionScope;
84  if (!scope) {
85  // guess that const method doesn't have side effects
86  if (f->nestedIn->isClassOrStruct() && !f->isConst() && !f->isStatic())
87  sideEffectInAssertError(tmp, f->name()); // Non-const member function called, assume it has side effects
88  continue;
89  }
90 
91  for (const Token *tok2 = scope->bodyStart; tok2 != scope->bodyEnd; tok2 = tok2->next()) {
92  if (!tok2->isAssignmentOp() && tok2->tokType() != Token::eIncDecOp)
93  continue;
94 
95  const Variable* var = tok2->previous()->variable();
96  if (!var || var->isLocal() || (var->isArgument() && !var->isReference() && !var->isPointer()))
97  continue; // See ticket #4937. Assigning function arguments not passed by reference is ok.
98  if (var->isArgument() && var->isPointer() && tok2->strAt(-2) != "*")
99  continue; // Pointers need to be dereferenced, otherwise there is no error
100 
101  bool noReturnInScope = true;
102  for (const Token *rt = scope->bodyStart; rt != scope->bodyEnd; rt = rt->next()) {
103  if (rt->str() != "return") continue; // find all return statements
104  if (inSameScope(rt, tok2)) {
105  noReturnInScope = false;
106  break;
107  }
108  }
109  if (noReturnInScope) continue;
110 
111  sideEffectInAssertError(tmp, f->name());
112  break;
113  }
114  }
115  tok = endTok;
116  }
117 }
118 //---------------------------------------------------------------------------
119 
120 
121 void CheckAssert::sideEffectInAssertError(const Token *tok, const std::string& functionName)
122 {
124  "assertWithSideEffect",
125  "$symbol:" + functionName + "\n"
126  "Assert statement calls a function which may have desired side effects: '$symbol'.\n"
127  "Non-pure function: '$symbol' is called inside assert statement. "
128  "Assert statements are removed from release builds so the code inside "
129  "assert statement is not executed. If the code is needed also in release "
130  "builds, this is a bug.", CWE398, Certainty::normal);
131 }
132 
133 void CheckAssert::assignmentInAssertError(const Token *tok, const std::string& varname)
134 {
136  "assignmentInAssert",
137  "$symbol:" + varname + "\n"
138  "Assert statement modifies '$symbol'.\n"
139  "Variable '$symbol' is modified inside assert statement. "
140  "Assert statements are removed from release builds so the code inside "
141  "assert statement is not executed. If the code is needed also in release "
142  "builds, this is a bug.", CWE398, Certainty::normal);
143 }
144 
145 // checks if side effects happen on the variable prior to tmp
146 void CheckAssert::checkVariableAssignment(const Token* assignTok, const Scope *assertionScope)
147 {
148  if (!assignTok->isAssignmentOp() && assignTok->tokType() != Token::eIncDecOp)
149  return;
150 
151  const Variable* var = assignTok->astOperand1()->variable();
152  if (!var)
153  return;
154 
155  // Variable declared in inner scope in assert => don't warn
156  if (assertionScope != var->scope()) {
157  const Scope *s = var->scope();
158  while (s && s != assertionScope)
159  s = s->nestedIn;
160  if (s == assertionScope)
161  return;
162  }
163 
164  // assignment
165  if (assignTok->isAssignmentOp() || assignTok->tokType() == Token::eIncDecOp) {
166  if (var->isConst()) {
167  return;
168  }
169  assignmentInAssertError(assignTok, var->name());
170  }
171  // TODO: function calls on var
172 }
173 
174 bool CheckAssert::inSameScope(const Token* returnTok, const Token* assignTok)
175 {
176  // TODO: even if a return is in the same scope, the assignment might not affect it.
177  return returnTok->scope() == assignTok->scope();
178 }
bool astIsSmartPointer(const Token *tok)
Definition: astutils.cpp:225
static const CWE CWE398(398U)
Checking for side effects in assert statements.
Definition: checkassert.h:43
void assertWithSideEffects()
Definition: checkassert.cpp:43
void assignmentInAssertError(const Token *tok, const std::string &varname)
void sideEffectInAssertError(const Token *tok, const std::string &functionName)
static bool inSameScope(const Token *returnTok, const Token *assignTok)
void checkVariableAssignment(const Token *assignTok, const Scope *assertionScope)
void reportError(const Token *tok, const Severity severity, const std::string &id, const std::string &msg)
report an error
Definition: check.h:138
const Settings *const mSettings
Definition: check.h:134
const Tokenizer *const mTokenizer
Definition: check.h:133
void logChecker(const char id[])
log checker
Definition: check.cpp:129
bool isStatic() const
const std::string & name() const
const Scope * functionScope
scope of function body
const Scope * nestedIn
Scope the function is declared in.
bool isConst() const
static Library::Container::Yield getContainerYield(const Token *const cond)
Definition: library.cpp:1269
const Function * getFunction(const Token *ftok) const
Definition: library.cpp:1474
std::string getFunctionName(const Token *ftok) const
Get function name for function call.
Definition: library.cpp:1016
const Scope * nestedIn
const Token * bodyStart
'{' token
const Token * bodyEnd
'}' token
bool isClassOrStruct() const
Library library
Library.
Definition: settings.h:237
SimpleEnableGroup< Severity > severity
Definition: settings.h:358
bool isEnabled(T flag) const
Definition: settings.h:66
const Token * front() const
get first token of list
Definition: tokenlist.h:119
The token list that the TokenList generates is a linked-list of this class.
Definition: token.h:150
void astOperand1(Token *tok)
Definition: token.cpp:1456
Token::Type tokType() const
Definition: token.h:343
void scope(const Scope *s)
Associate this token with given scope.
Definition: token.h:1042
void link(Token *linkToToken)
Create link to given token.
Definition: token.h:1015
@ eFunction
Definition: token.h:161
@ eIncDecOp
Definition: token.h:163
bool isAssignmentOp() const
Definition: token.h:401
Token * next()
Definition: token.h:830
static bool simpleMatch(const Token *tok, const char(&pattern)[count])
Match given token (or list of tokens) to a pattern list.
Definition: token.h:252
TokenList list
Token list: stores all tokens.
Definition: tokenize.h:590
Information about a member variable.
bool isArgument() const
Is variable a function argument.
bool isReference() const
Is reference variable.
bool isLocal() const
Is variable local.
const Scope * scope() const
Get Scope pointer of enclosing scope.
const std::string & name() const
Get name string.
bool isConst() const
Is variable const.
bool isPointer() const
Is pointer variable.
@ warning
Warning.