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