1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
|
<?xml version="1.0" encoding="UTF-8"?>
<section id="writing-rules-3">
<title>Part 3 - Introduction to writing rules with C++</title>
<section>
<title>Introduction</title>
<para>The goal for this article is to introduce how
Cppcheck rules are written with C++. With C++ it is
possible to write more complex rules than is possible with regular
expressions.</para>
</section>
<section>
<title>Basics</title>
<para>A C++ rule is written in a C++ function.</para>
<para>Rules are organized into Check classes. For instance there is a
class with the name <literal>CheckStl</literal> that contains various stl
rules. The <literal>CheckOther</literal> can always be used if no other
class suits you.</para>
<para>When you have added your rule you must recompile Cppcheck before you
can test it.</para>
</section>
<section>
<title>Division by zero</title>
<para>This simple regular expression will check for division by
zero:</para>
<programlisting>cppcheck --rule="/ 0"</programlisting>
<para>Here is the corresponding C++ check:</para>
<programlisting>// Detect division by zero
void CheckOther::divisionByZero()
{
// Loop through all tokens
for (const Token *tok = _tokenizer->tokens(); tok; tok = tok->next())
{
// check if there is a division by zero
if (Token::Match(tok, "/ 0"))
{
// report error
divisionByZeroError(tok);
}
}
}
// Report error
void CheckOther::divisionByZeroError()
{
reportError(tok, // location
Severity::error, // severity
"divisionByZero", // id
"Division by zero"); // message
}</programlisting>
<para>The <literal>Token::Match</literal> matches tokens against
expressions. A few rules about Token::Match expressions are:</para>
<itemizedlist>
<listitem>
<para>tokens are either completely matched or not matched at all. The
token "abc" is not matched by "ab".</para>
</listitem>
<listitem>
<para>Spaces are used as separators.</para>
</listitem>
<listitem>
<para>With normal regular expressions there are special meanings for +
* ? ( ). These are just normal characters in
<literal>Token::Match</literal> patterns.</para>
</listitem>
</itemizedlist>
</section>
<section>
<title>Condition before deallocation</title>
<para>In the first <link linkend="writing-rules-1">Writing rules</link> part
I described a rule that looks for redundant conditions. Here is the regular
expression that was shown:</para>
<programlisting>if \( p \) { free \( p \) ; }</programlisting>
<para>The corresponding <literal>Token::Match</literal> expression
is:</para>
<programlisting>if ( %var% ) { free ( %var% ) ; }</programlisting>
<para>The <literal>%var%</literal> pattern match any variable name. Here
is a C++ function:</para>
<programlisting>// Find redundant condition before deallocation
void CheckOther::dealloc()
{
// Loop through all tokens
for (const Token *tok = _tokenizer->tokens(); tok; tok = tok->next())
{
// Is there a condition and a deallocation?
if (Token::Match(tok, "if ( %var% ) { free ( %var% ) ; }"))
{
// Get variable name used in condition:
const std::string varname1 = tok->strAt(2);
// Get variable name used in deallocation:
const std::string varname2 = tok->strAt(7);
// Is the same variable used?
if (varname1 == varname2)
{
// report warning
deallocWarning(tok);
}
}
}
}
// Report warning
void CheckOther::deallocWarning()
{
reportError(tok, // location
Severity::warning, // severity
"dealloc", // id
"Redundant condition"); // message
}</programlisting>
<para>The strAt function is used to fetch strings from the token list. The
parameter specifies the token offset. The result for "tok->tokAt(1)" is
the same as for "tok->next()".</para>
</section>
<section>
<title>Validate function parameters</title>
<para>Sometimes it is known that a function can't handle certain
parameters. Here is an example rule that checks that the parameters for
strtol or strtoul are valid:</para>
<programlisting>//---------------------------------------------------------------------------
// strtol(str, 0, radix) <- radix must be 0 or 2-36
//---------------------------------------------------------------------------
void CheckOther::invalidFunctionUsage()
{
// Loop through all tokens
for (const Token *tok = _tokenizer->tokens(); tok; tok = tok->next())
{
// Is there a function call for strtol or strtoul?
if (!Token::Match(tok, "strtol|strtoul ("))
continue;
// Locate the third parameter of the function call..
// Counter that counts the parameters.
int param = 1;
// Scan the function call tokens. The "tok->tokAt(2)" returns
// the token after the "("
for (const Token *tok2 = tok->tokAt(2); tok2; tok2 = tok2->next())
{
// If a "(" is found then jump to the corresponding ")"
if (tok2->str() == "(")
tok2 = tok2->link();
// End of function call.
else if (tok2->str() == ")")
break;
// Found a ",". increment param counter
else if (tok2->str() == ",")
{
++param;
// If the param is 3 then check if the parameter is valid
if (param == 3)
{
if (Token::Match(tok2, ", %num% )"))
{
// convert next token into a number
MathLib::bigint radix;
radix = MathLib::toBigNumber(tok2->strAt(1));
// invalid radix?
if (!(radix == 0 || (radix >= 2 && radix <= 36)))
{
dangerousUsageStrtolError(tok2);
}
}
break;
}
}
}
}
}
void CheckOther::dangerousUsageStrtolError(const Token *tok)
{
reportError(tok, // location
Severity::error, // severity
"dangerousUsageStrtol", // id
"Invalid radix"); // message
}</programlisting>
<para>The link() member function is used to find the corresponding ( ) [ ]
or { } token.</para>
<para>The inner loop is not necessary if you just want to get the last
parameter. This code will check if the last parameter is
numerical..</para>
<programlisting>..
// Is there a function call?
if (!Token::Match(tok, "do_something ("))
continue;
if (Token::Match(tok->next()->link()->tokAt(-2), "(|, %num% )"))
...</programlisting>
<para>The pattern <literal>(|,</literal> can also be written as
<literal>[(,]</literal>.</para>
</section>
</section>
|