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 #include "checkio.h"
21
22 #include "library.h"
23 #include "mathlib.h"
24 #include "settings.h"
25 #include "symboldatabase.h"
26 #include "token.h"
27 #include "tokenize.h"
28 #include "utils.h"
29 #include "valueflow.h"
30
31 #include <cctype>
32 #include <cstdlib>
33 #include <list>
34 #include <map>
35 #include <set>
36 #include <utility>
37 #include <vector>
38
39 //---------------------------------------------------------------------------
40
41 // Register CheckIO..
42 namespace {
43 CheckIO instance;
44 }
45
46 // CVE ID used:
47 static const CWE CWE119(119U); // Improper Restriction of Operations within the Bounds of a Memory Buffer
48 static const CWE CWE398(398U); // Indicator of Poor Code Quality
49 static const CWE CWE664(664U); // Improper Control of a Resource Through its Lifetime
50 static const CWE CWE685(685U); // Function Call With Incorrect Number of Arguments
51 static const CWE CWE686(686U); // Function Call With Incorrect Argument Type
52 static const CWE CWE687(687U); // Function Call With Incorrectly Specified Argument Value
53 static const CWE CWE704(704U); // Incorrect Type Conversion or Cast
54 static const CWE CWE910(910U); // Use of Expired File Descriptor
55
56 //---------------------------------------------------------------------------
57 // std::cout << std::cout;
58 //---------------------------------------------------------------------------
checkCoutCerrMisusage()59 void CheckIO::checkCoutCerrMisusage()
60 {
61 if (mTokenizer->isC())
62 return;
63
64 const SymbolDatabase * const symbolDatabase = mTokenizer->getSymbolDatabase();
65 for (const Scope * scope : symbolDatabase->functionScopes) {
66 for (const Token *tok = scope->bodyStart; tok && tok != scope->bodyEnd; tok = tok->next()) {
67 if (Token::Match(tok, "std :: cout|cerr !!.") && tok->next()->astParent() && tok->next()->astParent()->astOperand1() == tok->next()) {
68 const Token* tok2 = tok->next();
69 while (tok2->astParent() && tok2->astParent()->str() == "<<") {
70 tok2 = tok2->astParent();
71 if (tok2->astOperand2() && Token::Match(tok2->astOperand2()->previous(), "std :: cout|cerr"))
72 coutCerrMisusageError(tok, tok2->astOperand2()->strAt(1));
73 }
74 }
75 }
76 }
77 }
78
coutCerrMisusageError(const Token * tok,const std::string & streamName)79 void CheckIO::coutCerrMisusageError(const Token* tok, const std::string& streamName)
80 {
81 reportError(tok, Severity::error, "coutCerrMisusage", "Invalid usage of output stream: '<< std::" + streamName + "'.", CWE398, Certainty::normal);
82 }
83
84 //---------------------------------------------------------------------------
85 // fflush(stdin) <- fflush only applies to output streams in ANSI C
86 // fread(); fwrite(); <- consecutive read/write statements require repositioning in between
87 // fopen("","r"); fwrite(); <- write to read-only file (or vice versa)
88 // fclose(); fread(); <- Use closed file
89 //---------------------------------------------------------------------------
90 enum class OpenMode { CLOSED, READ_MODE, WRITE_MODE, RW_MODE, UNKNOWN_OM };
getMode(const std::string & str)91 static OpenMode getMode(const std::string& str)
92 {
93 if (str.find('+', 1) != std::string::npos)
94 return OpenMode::RW_MODE;
95 else if (str.find('w') != std::string::npos || str.find('a') != std::string::npos)
96 return OpenMode::WRITE_MODE;
97 else if (str.find('r') != std::string::npos)
98 return OpenMode::READ_MODE;
99 return OpenMode::UNKNOWN_OM;
100 }
101
102 struct Filepointer {
103 OpenMode mode;
104 nonneg int mode_indent;
105 enum class Operation {NONE, UNIMPORTANT, READ, WRITE, POSITIONING, OPEN, CLOSE, UNKNOWN_OP} lastOperation;
106 nonneg int op_indent;
107 enum class AppendMode { UNKNOWN_AM, APPEND, APPEND_EX };
108 AppendMode append_mode;
109 std::string filename;
FilepointerFilepointer110 explicit Filepointer(OpenMode mode_ = OpenMode::UNKNOWN_OM)
111 : mode(mode_), mode_indent(0), lastOperation(Operation::NONE), op_indent(0), append_mode(AppendMode::UNKNOWN_AM) {}
112 };
113
114 namespace {
115 const std::unordered_set<std::string> whitelist = { "clearerr", "feof", "ferror", "fgetpos", "ftell", "setbuf", "setvbuf", "ungetc", "ungetwc" };
116 }
117
checkFileUsage()118 void CheckIO::checkFileUsage()
119 {
120 const bool windows = mSettings->isWindowsPlatform();
121 const bool printPortability = mSettings->severity.isEnabled(Severity::portability);
122 const bool printWarnings = mSettings->severity.isEnabled(Severity::warning);
123
124 std::map<int, Filepointer> filepointers;
125
126 const SymbolDatabase* symbolDatabase = mTokenizer->getSymbolDatabase();
127 for (const Variable* var : symbolDatabase->variableList()) {
128 if (!var || !var->declarationId() || var->isArray() || !Token::simpleMatch(var->typeStartToken(), "FILE *"))
129 continue;
130
131 if (var->isLocal()) {
132 if (var->nameToken()->strAt(1) == "(") // initialize by calling "ctor"
133 filepointers.insert(std::make_pair(var->declarationId(), Filepointer(OpenMode::UNKNOWN_OM)));
134 else
135 filepointers.insert(std::make_pair(var->declarationId(), Filepointer(OpenMode::CLOSED)));
136 } else {
137 filepointers.insert(std::make_pair(var->declarationId(), Filepointer(OpenMode::UNKNOWN_OM)));
138 // TODO: If all fopen calls we find open the file in the same type, we can set Filepointer::mode
139 }
140 }
141
142 for (const Scope * scope : symbolDatabase->functionScopes) {
143 int indent = 0;
144 for (const Token *tok = scope->bodyStart; tok != scope->bodyEnd; tok = tok->next()) {
145 if (tok->str() == "{")
146 indent++;
147 else if (tok->str() == "}") {
148 indent--;
149 for (std::pair<const int, Filepointer>& filepointer : filepointers) {
150 if (indent < filepointer.second.mode_indent) {
151 filepointer.second.mode_indent = 0;
152 filepointer.second.mode = OpenMode::UNKNOWN_OM;
153 }
154 if (indent < filepointer.second.op_indent) {
155 filepointer.second.op_indent = 0;
156 filepointer.second.lastOperation = Filepointer::Operation::UNKNOWN_OP;
157 }
158 }
159 } else if (tok->str() == "return" || tok->str() == "continue" || tok->str() == "break" || mSettings->library.isnoreturn(tok)) { // Reset upon return, continue or break
160 for (std::pair<const int, Filepointer>& filepointer : filepointers) {
161 filepointer.second.mode_indent = 0;
162 filepointer.second.mode = OpenMode::UNKNOWN_OM;
163 filepointer.second.op_indent = 0;
164 filepointer.second.lastOperation = Filepointer::Operation::UNKNOWN_OP;
165 }
166 } else if (Token::Match(tok, "%var% =") &&
167 (tok->strAt(2) != "fopen" && tok->strAt(2) != "freopen" && tok->strAt(2) != "tmpfile" &&
168 (windows ? (tok->str() != "_wfopen" && tok->str() != "_wfreopen") : true))) {
169 std::map<int, Filepointer>::iterator i = filepointers.find(tok->varId());
170 if (i != filepointers.end()) {
171 i->second.mode = OpenMode::UNKNOWN_OM;
172 i->second.lastOperation = Filepointer::Operation::UNKNOWN_OP;
173 }
174 } else if (Token::Match(tok, "%name% (") && tok->previous() && (!tok->previous()->isName() || Token::Match(tok->previous(), "return|throw"))) {
175 std::string mode;
176 const Token* fileTok = nullptr;
177 const Token* fileNameTok = nullptr;
178 Filepointer::Operation operation = Filepointer::Operation::NONE;
179
180 if ((tok->str() == "fopen" || tok->str() == "freopen" || tok->str() == "tmpfile" ||
181 (windows && (tok->str() == "_wfopen" || tok->str() == "_wfreopen"))) &&
182 tok->strAt(-1) == "=") {
183 if (tok->str() != "tmpfile") {
184 const Token* modeTok = tok->tokAt(2)->nextArgument();
185 if (modeTok && modeTok->tokType() == Token::eString)
186 mode = modeTok->strValue();
187 } else
188 mode = "wb+";
189 fileTok = tok->tokAt(-2);
190 operation = Filepointer::Operation::OPEN;
191 if (Token::Match(tok, "fopen ( %str% ,"))
192 fileNameTok = tok->tokAt(2);
193 } else if (windows && Token::Match(tok, "fopen_s|freopen_s|_wfopen_s|_wfreopen_s ( & %name%")) {
194 const Token* modeTok = tok->tokAt(2)->nextArgument()->nextArgument();
195 if (modeTok && modeTok->tokType() == Token::eString)
196 mode = modeTok->strValue();
197 fileTok = tok->tokAt(3);
198 operation = Filepointer::Operation::OPEN;
199 } else if ((tok->str() == "rewind" || tok->str() == "fseek" || tok->str() == "fsetpos" || tok->str() == "fflush") ||
200 (windows && tok->str() == "_fseeki64")) {
201 fileTok = tok->tokAt(2);
202 if (printPortability && fileTok && tok->str() == "fflush") {
203 if (fileTok->str() == "stdin")
204 fflushOnInputStreamError(tok, fileTok->str());
205 else {
206 const Filepointer& f = filepointers[fileTok->varId()];
207 if (f.mode == OpenMode::READ_MODE)
208 fflushOnInputStreamError(tok, fileTok->str());
209 }
210 }
211 operation = Filepointer::Operation::POSITIONING;
212 } else if (tok->str() == "fgetc" || tok->str() == "fgetwc" ||
213 tok->str() == "fgets" || tok->str() == "fgetws" || tok->str() == "fread" ||
214 tok->str() == "fscanf" || tok->str() == "fwscanf" || tok->str() == "getc" ||
215 (windows && (tok->str() == "fscanf_s" || tok->str() == "fwscanf_s"))) {
216 if (tok->str().find("scanf") != std::string::npos)
217 fileTok = tok->tokAt(2);
218 else
219 fileTok = tok->linkAt(1)->previous();
220 operation = Filepointer::Operation::READ;
221 } else if (tok->str() == "fputc" || tok->str() == "fputwc" ||
222 tok->str() == "fputs" || tok->str() == "fputws" || tok->str() == "fwrite" ||
223 tok->str() == "fprintf" || tok->str() == "fwprintf" || tok->str() == "putcc" ||
224 (windows && (tok->str() == "fprintf_s" || tok->str() == "fwprintf_s"))) {
225 if (tok->str().find("printf") != std::string::npos)
226 fileTok = tok->tokAt(2);
227 else
228 fileTok = tok->linkAt(1)->previous();
229 operation = Filepointer::Operation::WRITE;
230 } else if (tok->str() == "fclose") {
231 fileTok = tok->tokAt(2);
232 operation = Filepointer::Operation::CLOSE;
233 } else if (whitelist.find(tok->str()) != whitelist.end()) {
234 fileTok = tok->tokAt(2);
235 if ((tok->str() == "ungetc" || tok->str() == "ungetwc") && fileTok)
236 fileTok = fileTok->nextArgument();
237 operation = Filepointer::Operation::UNIMPORTANT;
238 } else if (!Token::Match(tok, "if|for|while|catch|switch") && !mSettings->library.isFunctionConst(tok->str(), true)) {
239 const Token* const end2 = tok->linkAt(1);
240 if (scope->functionOf && scope->functionOf->isClassOrStruct() && !scope->function->isStatic() && ((tok->strAt(-1) != "::" && tok->strAt(-1) != ".") || tok->strAt(-2) == "this")) {
241 if (!tok->function() || (tok->function()->nestedIn && tok->function()->nestedIn->isClassOrStruct())) {
242 for (std::pair<const int, Filepointer>& filepointer : filepointers) {
243 const Variable* var = symbolDatabase->getVariableFromVarId(filepointer.first);
244 if (!var || !(var->isLocal() || var->isGlobal() || var->isStatic())) {
245 filepointer.second.mode = OpenMode::UNKNOWN_OM;
246 filepointer.second.mode_indent = 0;
247 filepointer.second.op_indent = indent;
248 filepointer.second.lastOperation = Filepointer::Operation::UNKNOWN_OP;
249 }
250 }
251 continue;
252 }
253 }
254 for (const Token* tok2 = tok->tokAt(2); tok2 != end2; tok2 = tok2->next()) {
255 if (tok2->varId() && filepointers.find(tok2->varId()) != filepointers.end()) {
256 fileTok = tok2;
257 operation = Filepointer::Operation::UNKNOWN_OP; // Assume that repositioning was last operation and that the file is opened now
258 break;
259 }
260 }
261 }
262
263 while (Token::Match(fileTok, "%name% ."))
264 fileTok = fileTok->tokAt(2);
265
266 if (!fileTok || !fileTok->varId() || fileTok->strAt(1) == "[")
267 continue;
268
269 if (filepointers.find(fileTok->varId()) == filepointers.end()) { // function call indicates: Its a File
270 filepointers.insert(std::make_pair(fileTok->varId(), Filepointer(OpenMode::UNKNOWN_OM)));
271 }
272
273 Filepointer& f = filepointers[fileTok->varId()];
274
275 switch (operation) {
276 case Filepointer::Operation::OPEN:
277 if (fileNameTok) {
278 for (std::map<int, Filepointer>::const_iterator it = filepointers.cbegin(); it != filepointers.cend(); ++it) {
279 const Filepointer &fptr = it->second;
280 if (fptr.filename == fileNameTok->str() && (fptr.mode == OpenMode::RW_MODE || fptr.mode == OpenMode::WRITE_MODE))
281 incompatibleFileOpenError(tok, fileNameTok->str());
282 }
283
284 f.filename = fileNameTok->str();
285 }
286
287 f.mode = getMode(mode);
288 if (mode.find('a') != std::string::npos) {
289 if (f.mode == OpenMode::RW_MODE)
290 f.append_mode = Filepointer::AppendMode::APPEND_EX;
291 else
292 f.append_mode = Filepointer::AppendMode::APPEND;
293 } else
294 f.append_mode = Filepointer::AppendMode::UNKNOWN_AM;
295 f.mode_indent = indent;
296 break;
297 case Filepointer::Operation::POSITIONING:
298 if (f.mode == OpenMode::CLOSED)
299 useClosedFileError(tok);
300 else if (f.append_mode == Filepointer::AppendMode::APPEND && tok->str() != "fflush" && printWarnings)
301 seekOnAppendedFileError(tok);
302 break;
303 case Filepointer::Operation::READ:
304 if (f.mode == OpenMode::CLOSED)
305 useClosedFileError(tok);
306 else if (f.mode == OpenMode::WRITE_MODE)
307 readWriteOnlyFileError(tok);
308 else if (f.lastOperation == Filepointer::Operation::WRITE)
309 ioWithoutPositioningError(tok);
310 break;
311 case Filepointer::Operation::WRITE:
312 if (f.mode == OpenMode::CLOSED)
313 useClosedFileError(tok);
314 else if (f.mode == OpenMode::READ_MODE)
315 writeReadOnlyFileError(tok);
316 else if (f.lastOperation == Filepointer::Operation::READ)
317 ioWithoutPositioningError(tok);
318 break;
319 case Filepointer::Operation::CLOSE:
320 if (f.mode == OpenMode::CLOSED)
321 useClosedFileError(tok);
322 else
323 f.mode = OpenMode::CLOSED;
324 f.mode_indent = indent;
325 break;
326 case Filepointer::Operation::UNIMPORTANT:
327 if (f.mode == OpenMode::CLOSED)
328 useClosedFileError(tok);
329 break;
330 case Filepointer::Operation::UNKNOWN_OP:
331 f.mode = OpenMode::UNKNOWN_OM;
332 f.mode_indent = 0;
333 break;
334 default:
335 break;
336 }
337 if (operation != Filepointer::Operation::NONE && operation != Filepointer::Operation::UNIMPORTANT) {
338 f.op_indent = indent;
339 f.lastOperation = operation;
340 }
341 }
342 }
343 for (std::pair<const int, Filepointer>& filepointer : filepointers) {
344 filepointer.second.op_indent = 0;
345 filepointer.second.mode = OpenMode::UNKNOWN_OM;
346 filepointer.second.lastOperation = Filepointer::Operation::UNKNOWN_OP;
347 }
348 }
349 }
350
fflushOnInputStreamError(const Token * tok,const std::string & varname)351 void CheckIO::fflushOnInputStreamError(const Token *tok, const std::string &varname)
352 {
353 reportError(tok, Severity::portability,
354 "fflushOnInputStream", "fflush() called on input stream '" + varname + "' may result in undefined behaviour on non-linux systems.", CWE398, Certainty::normal);
355 }
356
ioWithoutPositioningError(const Token * tok)357 void CheckIO::ioWithoutPositioningError(const Token *tok)
358 {
359 reportError(tok, Severity::error,
360 "IOWithoutPositioning", "Read and write operations without a call to a positioning function (fseek, fsetpos or rewind) or fflush in between result in undefined behaviour.", CWE664, Certainty::normal);
361 }
362
readWriteOnlyFileError(const Token * tok)363 void CheckIO::readWriteOnlyFileError(const Token *tok)
364 {
365 reportError(tok, Severity::error,
366 "readWriteOnlyFile", "Read operation on a file that was opened only for writing.", CWE664, Certainty::normal);
367 }
368
writeReadOnlyFileError(const Token * tok)369 void CheckIO::writeReadOnlyFileError(const Token *tok)
370 {
371 reportError(tok, Severity::error,
372 "writeReadOnlyFile", "Write operation on a file that was opened only for reading.", CWE664, Certainty::normal);
373 }
374
useClosedFileError(const Token * tok)375 void CheckIO::useClosedFileError(const Token *tok)
376 {
377 reportError(tok, Severity::error,
378 "useClosedFile", "Used file that is not opened.", CWE910, Certainty::normal);
379 }
380
seekOnAppendedFileError(const Token * tok)381 void CheckIO::seekOnAppendedFileError(const Token *tok)
382 {
383 reportError(tok, Severity::warning,
384 "seekOnAppendedFile", "Repositioning operation performed on a file opened in append mode has no effect.", CWE398, Certainty::normal);
385 }
386
incompatibleFileOpenError(const Token * tok,const std::string & filename)387 void CheckIO::incompatibleFileOpenError(const Token *tok, const std::string &filename)
388 {
389 reportError(tok, Severity::warning,
390 "incompatibleFileOpen", "The file '" + filename + "' is opened for read and write access at the same time on different streams", CWE664, Certainty::normal);
391 }
392
393
394 //---------------------------------------------------------------------------
395 // scanf without field width limits can crash with huge input data
396 //---------------------------------------------------------------------------
invalidScanf()397 void CheckIO::invalidScanf()
398 {
399 if (!mSettings->severity.isEnabled(Severity::warning))
400 return;
401
402 const SymbolDatabase * const symbolDatabase = mTokenizer->getSymbolDatabase();
403 for (const Scope * scope : symbolDatabase->functionScopes) {
404 for (const Token *tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) {
405 const Token *formatToken = nullptr;
406 if (Token::Match(tok, "scanf|vscanf ( %str% ,"))
407 formatToken = tok->tokAt(2);
408 else if (Token::Match(tok, "sscanf|vsscanf|fscanf|vfscanf (")) {
409 const Token* nextArg = tok->tokAt(2)->nextArgument();
410 if (nextArg && nextArg->tokType() == Token::eString)
411 formatToken = nextArg;
412 else
413 continue;
414 } else
415 continue;
416
417 bool format = false;
418
419 // scan the string backwards, so we do not need to keep states
420 const std::string &formatstr(formatToken->str());
421 for (std::size_t i = 1; i < formatstr.length(); i++) {
422 if (formatstr[i] == '%')
423 format = !format;
424
425 else if (!format)
426 continue;
427
428 else if (std::isdigit(formatstr[i]) || formatstr[i] == '*') {
429 format = false;
430 }
431
432 else if (std::isalpha((unsigned char)formatstr[i]) || formatstr[i] == '[') {
433 if (formatstr[i] == 's' || formatstr[i] == '[' || formatstr[i] == 'S' || (formatstr[i] == 'l' && formatstr[i+1] == 's')) // #3490 - field width limits are only necessary for string input
434 invalidScanfError(tok);
435 format = false;
436 }
437 }
438 }
439 }
440 }
441
invalidScanfError(const Token * tok)442 void CheckIO::invalidScanfError(const Token *tok)
443 {
444 const std::string fname = (tok ? tok->str() : std::string("scanf"));
445 reportError(tok, Severity::warning,
446 "invalidscanf", fname + "() without field width limits can crash with huge input data.\n" +
447 fname + "() without field width limits can crash with huge input data. Add a field width "
448 "specifier to fix this problem.\n"
449 "\n"
450 "Sample program that can crash:\n"
451 "\n"
452 "#include <stdio.h>\n"
453 "int main()\n"
454 "{\n"
455 " char c[5];\n"
456 " scanf(\"%s\", c);\n"
457 " return 0;\n"
458 "}\n"
459 "\n"
460 "Typing in 5 or more characters may make the program crash. The correct usage "
461 "here is 'scanf(\"%4s\", c);', as the maximum field width does not include the "
462 "terminating null byte.\n"
463 "Source: http://linux.die.net/man/3/scanf\n"
464 "Source: http://www.opensource.apple.com/source/xnu/xnu-1456.1.26/libkern/stdio/scanf.c",
465 CWE119, Certainty::normal);
466 }
467
468 //---------------------------------------------------------------------------
469 // printf("%u", "xyz"); // Wrong argument type
470 // printf("%u%s", 1); // Too few arguments
471 // printf("", 1); // Too much arguments
472 //---------------------------------------------------------------------------
473
findFormat(nonneg int arg,const Token * firstArg,const Token ** formatStringTok,const Token ** formatArgTok)474 static bool findFormat(nonneg int arg, const Token *firstArg,
475 const Token **formatStringTok, const Token **formatArgTok)
476 {
477 const Token* argTok = firstArg;
478
479 for (int i = 0; i < arg && argTok; ++i)
480 argTok = argTok->nextArgument();
481
482 if (Token::Match(argTok, "%str% [,)]")) {
483 *formatArgTok = argTok->nextArgument();
484 *formatStringTok = argTok;
485 return true;
486 } else if (Token::Match(argTok, "%var% [,)]") &&
487 argTok->variable() &&
488 Token::Match(argTok->variable()->typeStartToken(), "char|wchar_t") &&
489 (argTok->variable()->isPointer() ||
490 (argTok->variable()->dimensions().size() == 1 &&
491 argTok->variable()->dimensionKnown(0) &&
492 argTok->variable()->dimension(0) != 0))) {
493 *formatArgTok = argTok->nextArgument();
494 if (!argTok->values().empty()) {
495 std::list<ValueFlow::Value>::const_iterator value = std::find_if(
496 argTok->values().begin(), argTok->values().end(), std::mem_fn(&ValueFlow::Value::isTokValue));
497 if (value != argTok->values().end() && value->isTokValue() && value->tokvalue &&
498 value->tokvalue->tokType() == Token::eString) {
499 *formatStringTok = value->tokvalue;
500 }
501 }
502 return true;
503 }
504 return false;
505 }
506
507 // Utility function returning whether iToTest equals iTypename or iOptionalPrefix+iTypename
typesMatch(const std::string & iToTest,const std::string & iTypename,const std::string & iOptionalPrefix="std::")508 static inline bool typesMatch(const std::string& iToTest, const std::string& iTypename, const std::string& iOptionalPrefix = "std::")
509 {
510 return (iToTest == iTypename) || (iToTest == iOptionalPrefix + iTypename);
511 }
512
checkWrongPrintfScanfArguments()513 void CheckIO::checkWrongPrintfScanfArguments()
514 {
515 const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
516 const bool isWindows = mSettings->isWindowsPlatform();
517
518 for (const Scope * scope : symbolDatabase->functionScopes) {
519 for (const Token *tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) {
520 if (!tok->isName()) continue;
521
522 const Token* argListTok = nullptr; // Points to first va_list argument
523 const Token* formatStringTok = nullptr; // Points to format string token
524
525 bool scan = false;
526 bool scanf_s = false;
527 int formatStringArgNo = -1;
528
529 if (tok->strAt(1) == "(" && mSettings->library.formatstr_function(tok)) {
530 formatStringArgNo = mSettings->library.formatstr_argno(tok);
531 scan = mSettings->library.formatstr_scan(tok);
532 scanf_s = mSettings->library.formatstr_secure(tok);
533 }
534
535 if (formatStringArgNo >= 0) {
536 // formatstring found in library. Find format string and first argument belonging to format string.
537 if (!findFormat(formatStringArgNo, tok->tokAt(2), &formatStringTok, &argListTok))
538 continue;
539 } else if (Token::simpleMatch(tok, "swprintf (")) {
540 if (Token::Match(tok->tokAt(2)->nextArgument(), "%str%")) {
541 // Find third parameter and format string
542 if (!findFormat(1, tok->tokAt(2), &formatStringTok, &argListTok))
543 continue;
544 } else {
545 // Find fourth parameter and format string
546 if (!findFormat(2, tok->tokAt(2), &formatStringTok, &argListTok))
547 continue;
548 }
549 } else if (isWindows && Token::Match(tok, "sprintf_s|swprintf_s (")) {
550 // template <size_t size> int sprintf_s(char (&buffer)[size], const char *format, ...);
551 if (findFormat(1, tok->tokAt(2), &formatStringTok, &argListTok)) {
552 if (!formatStringTok)
553 continue;
554 }
555 // int sprintf_s(char *buffer, size_t sizeOfBuffer, const char *format, ...);
556 else if (findFormat(2, tok->tokAt(2), &formatStringTok, &argListTok)) {
557 if (!formatStringTok)
558 continue;
559 }
560 } else if (isWindows && Token::Match(tok, "_snprintf_s|_snwprintf_s (")) {
561 // template <size_t size> int _snprintf_s(char (&buffer)[size], size_t count, const char *format, ...);
562 if (findFormat(2, tok->tokAt(2), &formatStringTok, &argListTok)) {
563 if (!formatStringTok)
564 continue;
565 }
566 // int _snprintf_s(char *buffer, size_t sizeOfBuffer, size_t count, const char *format, ...);
567 else if (findFormat(3, tok->tokAt(2), &formatStringTok, &argListTok)) {
568 if (!formatStringTok)
569 continue;
570 }
571 } else {
572 continue;
573 }
574
575 if (!formatStringTok)
576 continue;
577
578 checkFormatString(tok, formatStringTok, argListTok, scan, scanf_s);
579 }
580 }
581 }
582
checkFormatString(const Token * const tok,const Token * const formatStringTok,const Token * argListTok,const bool scan,const bool scanf_s)583 void CheckIO::checkFormatString(const Token * const tok,
584 const Token * const formatStringTok,
585 const Token * argListTok,
586 const bool scan,
587 const bool scanf_s)
588 {
589 const bool isWindows = mSettings->isWindowsPlatform();
590 const bool printWarning = mSettings->severity.isEnabled(Severity::warning);
591 const std::string &formatString = formatStringTok->str();
592
593 // Count format string parameters..
594 int numFormat = 0;
595 int numSecure = 0;
596 bool percent = false;
597 const Token* argListTok2 = argListTok;
598 std::set<int> parameterPositionsUsed;
599 for (std::string::const_iterator i = formatString.begin(); i != formatString.end(); ++i) {
600 if (*i == '%') {
601 percent = !percent;
602 } else if (percent && *i == '[') {
603 while (i != formatString.end()) {
604 if (*i == ']') {
605 numFormat++;
606 if (argListTok)
607 argListTok = argListTok->nextArgument();
608 percent = false;
609 break;
610 }
611 ++i;
612 }
613 if (scanf_s) {
614 numSecure++;
615 if (argListTok) {
616 argListTok = argListTok->nextArgument();
617 }
618 }
619 if (i == formatString.end())
620 break;
621 } else if (percent) {
622 percent = false;
623
624 bool _continue = false;
625 bool skip = false;
626 std::string width;
627 int parameterPosition = 0;
628 bool hasParameterPosition = false;
629 while (i != formatString.end() && *i != '[' && !std::isalpha((unsigned char)*i)) {
630 if (*i == '*') {
631 skip = true;
632 if (scan)
633 _continue = true;
634 else {
635 numFormat++;
636 if (argListTok)
637 argListTok = argListTok->nextArgument();
638 }
639 } else if (std::isdigit(*i)) {
640 width += *i;
641 } else if (*i == '$') {
642 parameterPosition = std::atoi(width.c_str());
643 hasParameterPosition = true;
644 width.clear();
645 }
646 ++i;
647 }
648 if (i != formatString.end() && *i == '[') {
649 while (i != formatString.end()) {
650 if (*i == ']') {
651 if (!skip) {
652 numFormat++;
653 if (argListTok)
654 argListTok = argListTok->nextArgument();
655 }
656 break;
657 }
658 ++i;
659 }
660 if (scanf_s && !skip) {
661 numSecure++;
662 if (argListTok) {
663 argListTok = argListTok->nextArgument();
664 }
665 }
666 _continue = true;
667 }
668 if (i == formatString.end())
669 break;
670 if (_continue)
671 continue;
672
673 if (scan || *i != 'm') { // %m is a non-standard extension that requires no parameter on print functions.
674 ++numFormat;
675
676 // Handle parameter positions (POSIX extension) - Ticket #4900
677 if (hasParameterPosition) {
678 if (parameterPositionsUsed.find(parameterPosition) == parameterPositionsUsed.end())
679 parameterPositionsUsed.insert(parameterPosition);
680 else // Parameter already referenced, hence don't consider it a new format
681 --numFormat;
682 }
683
684 // Perform type checks
685 ArgumentInfo argInfo(argListTok, mSettings, mTokenizer->isCPP());
686
687 if (argInfo.typeToken && !argInfo.isLibraryType(mSettings)) {
688 if (scan) {
689 std::string specifier;
690 bool done = false;
691 while (!done) {
692 switch (*i) {
693 case 's':
694 specifier += *i;
695 if (argInfo.variableInfo && argInfo.isKnownType() && argInfo.variableInfo->isArray() && (argInfo.variableInfo->dimensions().size() == 1) && argInfo.variableInfo->dimensions()[0].known) {
696 if (!width.empty()) {
697 const int numWidth = std::atoi(width.c_str());
698 if (numWidth != (argInfo.variableInfo->dimension(0) - 1))
699 invalidScanfFormatWidthError(tok, numFormat, numWidth, argInfo.variableInfo, 's');
700 }
701 }
702 if (argListTok && argListTok->tokType() != Token::eString &&
703 argInfo.isKnownType() && argInfo.isArrayOrPointer() &&
704 (!Token::Match(argInfo.typeToken, "char|wchar_t") ||
705 argInfo.typeToken->strAt(-1) == "const")) {
706 if (!(argInfo.isArrayOrPointer() && argInfo.element && !argInfo.typeToken->isStandardType()))
707 invalidScanfArgTypeError_s(tok, numFormat, specifier, &argInfo);
708 }
709 if (scanf_s) {
710 numSecure++;
711 if (argListTok) {
712 argListTok = argListTok->nextArgument();
713 }
714 }
715 done = true;
716 break;
717 case 'c':
718 if (argInfo.variableInfo && argInfo.isKnownType() && argInfo.variableInfo->isArray() && (argInfo.variableInfo->dimensions().size() == 1) && argInfo.variableInfo->dimensions()[0].known) {
719 if (!width.empty()) {
720 const int numWidth = std::atoi(width.c_str());
721 if (numWidth > argInfo.variableInfo->dimension(0))
722 invalidScanfFormatWidthError(tok, numFormat, numWidth, argInfo.variableInfo, 'c');
723 }
724 }
725 if (scanf_s) {
726 numSecure++;
727 if (argListTok) {
728 argListTok = argListTok->nextArgument();
729 }
730 }
731 done = true;
732 break;
733 case 'x':
734 case 'X':
735 case 'u':
736 case 'o':
737 specifier += *i;
738 if (argInfo.typeToken->tokType() == Token::eString)
739 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true);
740 else if (argInfo.isKnownType()) {
741 if (!Token::Match(argInfo.typeToken, "char|short|int|long")) {
742 if (argInfo.typeToken->isStandardType() || !argInfo.element)
743 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true);
744 } else if (!argInfo.typeToken->isUnsigned() ||
745 !argInfo.isArrayOrPointer() ||
746 argInfo.typeToken->strAt(-1) == "const") {
747 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true);
748 } else {
749 switch (specifier[0]) {
750 case 'h':
751 if (specifier[1] == 'h') {
752 if (argInfo.typeToken->str() != "char")
753 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true);
754 } else if (argInfo.typeToken->str() != "short")
755 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true);
756 break;
757 case 'l':
758 if (specifier[1] == 'l') {
759 if (argInfo.typeToken->str() != "long" || !argInfo.typeToken->isLong())
760 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true);
761 else if (typesMatch(argInfo.typeToken->originalName(), "size_t") ||
762 typesMatch(argInfo.typeToken->originalName(), "ptrdiff_t") ||
763 typesMatch(argInfo.typeToken->originalName(), "uintmax_t"))
764 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true);
765 } else if (argInfo.typeToken->str() != "long" || argInfo.typeToken->isLong())
766 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true);
767 else if (typesMatch(argInfo.typeToken->originalName(), "size_t") ||
768 typesMatch(argInfo.typeToken->originalName(), "ptrdiff_t") ||
769 typesMatch(argInfo.typeToken->originalName(), "uintmax_t"))
770 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true);
771 break;
772 case 'I':
773 if (specifier.find("I64") != std::string::npos) {
774 if (argInfo.typeToken->str() != "long" || !argInfo.typeToken->isLong())
775 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true);
776 } else if (specifier.find("I32") != std::string::npos) {
777 if (argInfo.typeToken->str() != "int" || argInfo.typeToken->isLong())
778 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true);
779 } else if (!typesMatch(argInfo.typeToken->originalName(), "size_t"))
780 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true);
781 break;
782 case 'j':
783 if (!typesMatch(argInfo.typeToken->originalName(), "uintmax_t"))
784 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true);
785 break;
786 case 'z':
787 if (!typesMatch(argInfo.typeToken->originalName(), "size_t"))
788 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true);
789 break;
790 case 't':
791 if (!typesMatch(argInfo.typeToken->originalName(), "ptrdiff_t"))
792 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true);
793 break;
794 case 'L':
795 if (argInfo.typeToken->str() != "long" || !argInfo.typeToken->isLong())
796 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true);
797 else if (typesMatch(argInfo.typeToken->originalName(), "size_t") ||
798 typesMatch(argInfo.typeToken->originalName(), "uintmax_t"))
799 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true);
800 break;
801 default:
802 if (argInfo.typeToken->str() != "int")
803 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true);
804 else if (typesMatch(argInfo.typeToken->originalName(), "size_t") ||
805 typesMatch(argInfo.typeToken->originalName(), "ptrdiff_t") ||
806 typesMatch(argInfo.typeToken->originalName(), "uintmax_t"))
807 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true);
808 break;
809 }
810 }
811 }
812 done = true;
813 break;
814 case 'n':
815 case 'd':
816 case 'i':
817 specifier += *i;
818 if (argInfo.typeToken->tokType() == Token::eString)
819 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false);
820 else if (argInfo.isKnownType()) {
821 if (!Token::Match(argInfo.typeToken, "char|short|int|long")) {
822 if (argInfo.typeToken->isStandardType() || !argInfo.element)
823 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false);
824 } else if (argInfo.typeToken->isUnsigned() ||
825 !argInfo.isArrayOrPointer() ||
826 argInfo.typeToken->strAt(-1) == "const") {
827 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false);
828 } else {
829 switch (specifier[0]) {
830 case 'h':
831 if (specifier[1] == 'h') {
832 if (argInfo.typeToken->str() != "char")
833 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false);
834 } else if (argInfo.typeToken->str() != "short")
835 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false);
836 break;
837 case 'l':
838 if (specifier[1] == 'l') {
839 if (argInfo.typeToken->str() != "long" || !argInfo.typeToken->isLong())
840 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false);
841 else if (typesMatch(argInfo.typeToken->originalName(), "ptrdiff_t") ||
842 typesMatch(argInfo.typeToken->originalName(), "intmax_t"))
843 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false);
844 } else if (argInfo.typeToken->str() != "long" || argInfo.typeToken->isLong())
845 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false);
846 else if (typesMatch(argInfo.typeToken->originalName(), "ptrdiff_t") ||
847 typesMatch(argInfo.typeToken->originalName(), "intmax_t"))
848 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false);
849 break;
850 case 'I':
851 if (specifier.find("I64") != std::string::npos) {
852 if (argInfo.typeToken->str() != "long" || !argInfo.typeToken->isLong())
853 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false);
854 } else if (specifier.find("I32") != std::string::npos) {
855 if (argInfo.typeToken->str() != "int" || argInfo.typeToken->isLong())
856 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false);
857 } else if (!typesMatch(argInfo.typeToken->originalName(), "ptrdiff_t"))
858 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false);
859 break;
860 case 'j':
861 if (!typesMatch(argInfo.typeToken->originalName(), "intmax_t"))
862 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false);
863 break;
864 case 'z':
865 if (!(typesMatch(argInfo.typeToken->originalName(), "ssize_t") ||
866 (isWindows && typesMatch(argInfo.typeToken->originalName(), "SSIZE_T"))))
867 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false);
868 break;
869 case 't':
870 if (!typesMatch(argInfo.typeToken->originalName(), "ptrdiff_t"))
871 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false);
872 break;
873 case 'L':
874 if (argInfo.typeToken->str() != "long" || !argInfo.typeToken->isLong())
875 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false);
876 break;
877 default:
878 if (argInfo.typeToken->str() != "int")
879 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false);
880 else if (typesMatch(argInfo.typeToken->originalName(), "ptrdiff_t") ||
881 argInfo.typeToken->originalName() == "intmax_t")
882 invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false);
883 break;
884 }
885 }
886 }
887 done = true;
888 break;
889 case 'e':
890 case 'E':
891 case 'f':
892 case 'g':
893 case 'G':
894 case 'a':
895 specifier += *i;
896 if (argInfo.typeToken->tokType() == Token::eString)
897 invalidScanfArgTypeError_float(tok, numFormat, specifier, &argInfo);
898 else if (argInfo.isKnownType()) {
899 if (!Token::Match(argInfo.typeToken, "float|double")) {
900 if (argInfo.typeToken->isStandardType())
901 invalidScanfArgTypeError_float(tok, numFormat, specifier, &argInfo);
902 } else if (!argInfo.isArrayOrPointer() ||
903 argInfo.typeToken->strAt(-1) == "const") {
904 invalidScanfArgTypeError_float(tok, numFormat, specifier, &argInfo);
905 } else {
906 switch (specifier[0]) {
907 case 'l':
908 if (argInfo.typeToken->str() != "double" || argInfo.typeToken->isLong())
909 invalidScanfArgTypeError_float(tok, numFormat, specifier, &argInfo);
910 break;
911 case 'L':
912 if (argInfo.typeToken->str() != "double" || !argInfo.typeToken->isLong())
913 invalidScanfArgTypeError_float(tok, numFormat, specifier, &argInfo);
914 break;
915 default:
916 if (argInfo.typeToken->str() != "float")
917 invalidScanfArgTypeError_float(tok, numFormat, specifier, &argInfo);
918 break;
919 }
920 }
921 }
922 done = true;
923 break;
924 case 'I':
925 if ((i+1 != formatString.end() && *(i+1) == '6' &&
926 i+2 != formatString.end() && *(i+2) == '4') ||
927 (i+1 != formatString.end() && *(i+1) == '3' &&
928 i+2 != formatString.end() && *(i+2) == '2')) {
929 specifier += *i++;
930 specifier += *i++;
931 if ((i+1) != formatString.end() && !isalpha(*(i+1))) {
932 specifier += *i;
933 invalidLengthModifierError(tok, numFormat, specifier);
934 done = true;
935 } else {
936 specifier += *i++;
937 }
938 } else {
939 if ((i+1) != formatString.end() && !isalpha(*(i+1))) {
940 specifier += *i;
941 invalidLengthModifierError(tok, numFormat, specifier);
942 done = true;
943 } else {
944 specifier += *i++;
945 }
946 }
947 break;
948 case 'h':
949 case 'l':
950 if (i+1 != formatString.end() && *(i+1) == *i)
951 specifier += *i++;
952 FALLTHROUGH;
953 case 'j':
954 case 'q':
955 case 't':
956 case 'z':
957 case 'L':
958 // Expect an alphabetical character after these specifiers
959 if ((i + 1) != formatString.end() && !isalpha(*(i+1))) {
960 specifier += *i;
961 invalidLengthModifierError(tok, numFormat, specifier);
962 done = true;
963 } else {
964 specifier += *i++;
965 }
966 break;
967 default:
968 done = true;
969 break;
970 }
971 }
972 } else if (printWarning) {
973 std::string specifier;
974 bool done = false;
975 while (!done) {
976 switch (*i) {
977 case 's':
978 if (argListTok->tokType() != Token::eString &&
979 argInfo.isKnownType() && !argInfo.isArrayOrPointer()) {
980 if (!Token::Match(argInfo.typeToken, "char|wchar_t")) {
981 if (!argInfo.element)
982 invalidPrintfArgTypeError_s(tok, numFormat, &argInfo);
983 }
984 }
985 done = true;
986 break;
987 case 'n':
988 if ((argInfo.isKnownType() && (!argInfo.isArrayOrPointer() || argInfo.typeToken->strAt(-1) == "const")) || argListTok->tokType() == Token::eString)
989 invalidPrintfArgTypeError_n(tok, numFormat, &argInfo);
990 done = true;
991 break;
992 case 'c':
993 case 'x':
994 case 'X':
995 case 'o':
996 specifier += *i;
997 if (argInfo.typeToken->tokType() == Token::eString)
998 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
999 else if (argInfo.isArrayOrPointer() && !argInfo.element) {
1000 // use %p on pointers and arrays
1001 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1002 } else if (argInfo.isKnownType()) {
1003 if (!Token::Match(argInfo.typeToken, "bool|short|long|int|char|wchar_t")) {
1004 if (!(!argInfo.isArrayOrPointer() && argInfo.element))
1005 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1006 } else {
1007 switch (specifier[0]) {
1008 case 'h':
1009 if (specifier[1] == 'h') {
1010 if (!(argInfo.typeToken->str() == "char" && argInfo.typeToken->isUnsigned()))
1011 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1012 } else if (!(argInfo.typeToken->str() == "short" && argInfo.typeToken->isUnsigned()))
1013 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1014 break;
1015 case 'l':
1016 if (specifier[1] == 'l') {
1017 if (argInfo.typeToken->str() != "long" || !argInfo.typeToken->isLong())
1018 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1019 else if (typesMatch(argInfo.typeToken->originalName(), "size_t") ||
1020 argInfo.typeToken->originalName() == "uintmax_t")
1021 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1022 } else if (argInfo.typeToken->str() != "long" || argInfo.typeToken->isLong())
1023 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1024 else if (typesMatch(argInfo.typeToken->originalName(), "size_t") ||
1025 argInfo.typeToken->originalName() == "uintmax_t")
1026 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1027 break;
1028 case 'j':
1029 if (argInfo.typeToken->originalName() != "uintmax_t")
1030 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1031 break;
1032 case 'z':
1033 if (!typesMatch(argInfo.typeToken->originalName(), "size_t"))
1034 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1035 break;
1036 case 't':
1037 if (!typesMatch(argInfo.typeToken->originalName(), "ptrdiff_t"))
1038 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1039 break;
1040 case 'I':
1041 if (specifier.find("I64") != std::string::npos) {
1042 if (argInfo.typeToken->str() != "long" || !argInfo.typeToken->isLong())
1043 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1044 } else if (specifier.find("I32") != std::string::npos) {
1045 if (argInfo.typeToken->str() != "int" || argInfo.typeToken->isLong())
1046 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1047 } else if (!(typesMatch(argInfo.typeToken->originalName(), "size_t") ||
1048 argInfo.typeToken->originalName() == "WPARAM" ||
1049 argInfo.typeToken->originalName() == "UINT_PTR" ||
1050 argInfo.typeToken->originalName() == "LONG_PTR" ||
1051 argInfo.typeToken->originalName() == "LPARAM" ||
1052 argInfo.typeToken->originalName() == "LRESULT"))
1053 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1054 break;
1055 default:
1056 if (!Token::Match(argInfo.typeToken, "bool|char|short|wchar_t|int"))
1057 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1058 break;
1059 }
1060 }
1061 }
1062 done = true;
1063 break;
1064 case 'd':
1065 case 'i':
1066 specifier += *i;
1067 if (argInfo.typeToken->tokType() == Token::eString) {
1068 invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo);
1069 } else if (argInfo.isArrayOrPointer() && !argInfo.element) {
1070 // use %p on pointers and arrays
1071 invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo);
1072 } else if (argInfo.isKnownType()) {
1073 if (argInfo.typeToken->isUnsigned() && !Token::Match(argInfo.typeToken, "char|short")) {
1074 if (!(!argInfo.isArrayOrPointer() && argInfo.element))
1075 invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo);
1076 } else if (!Token::Match(argInfo.typeToken, "bool|char|short|int|long")) {
1077 if (!(!argInfo.isArrayOrPointer() && argInfo.element))
1078 invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo);
1079 } else {
1080 switch (specifier[0]) {
1081 case 'h':
1082 if (specifier[1] == 'h') {
1083 if (!(argInfo.typeToken->str() == "char" && !argInfo.typeToken->isUnsigned()))
1084 invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo);
1085 } else if (!(argInfo.typeToken->str() == "short" && !argInfo.typeToken->isUnsigned()))
1086 invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo);
1087 break;
1088 case 'l':
1089 if (specifier[1] == 'l') {
1090 if (argInfo.typeToken->str() != "long" || !argInfo.typeToken->isLong())
1091 invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo);
1092 else if (typesMatch(argInfo.typeToken->originalName(), "ptrdiff_t") ||
1093 argInfo.typeToken->originalName() == "intmax_t")
1094 invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo);
1095 } else if (argInfo.typeToken->str() != "long" || argInfo.typeToken->isLong())
1096 invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo);
1097 else if (typesMatch(argInfo.typeToken->originalName(), "ptrdiff_t") ||
1098 argInfo.typeToken->originalName() == "intmax_t")
1099 invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo);
1100 break;
1101 case 'j':
1102 if (argInfo.typeToken->originalName() != "intmax_t")
1103 invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo);
1104 break;
1105 case 't':
1106 if (!typesMatch(argInfo.typeToken->originalName(), "ptrdiff_t"))
1107 invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo);
1108 break;
1109 case 'I':
1110 if (specifier.find("I64") != std::string::npos) {
1111 if (argInfo.typeToken->str() != "long" || !argInfo.typeToken->isLong())
1112 invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo);
1113 } else if (specifier.find("I32") != std::string::npos) {
1114 if (argInfo.typeToken->str() != "int" || argInfo.typeToken->isLong())
1115 invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo);
1116 } else if (!typesMatch(argInfo.typeToken->originalName(), "ptrdiff_t"))
1117 invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo);
1118 break;
1119 case 'z':
1120 if (!(typesMatch(argInfo.typeToken->originalName(), "ssize_t") ||
1121 (isWindows && typesMatch(argInfo.typeToken->originalName(), "SSIZE_T"))))
1122 invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo);
1123 break;
1124 case 'L':
1125 if (argInfo.typeToken->str() != "long" || !argInfo.typeToken->isLong())
1126 invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo);
1127 break;
1128 default:
1129 if (!Token::Match(argInfo.typeToken, "bool|char|short|int"))
1130 invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo);
1131 else if (typesMatch(argInfo.typeToken->originalName(), "ptrdiff_t") ||
1132 argInfo.typeToken->originalName() == "intmax_t")
1133 invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo);
1134 break;
1135 }
1136 }
1137 }
1138 done = true;
1139 break;
1140 case 'u':
1141 specifier += *i;
1142 if (argInfo.typeToken->tokType() == Token::eString) {
1143 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1144 } else if (argInfo.isArrayOrPointer() && !argInfo.element) {
1145 // use %p on pointers and arrays
1146 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1147 } else if (argInfo.isKnownType()) {
1148 if (!argInfo.typeToken->isUnsigned() && !Token::Match(argInfo.typeToken, "bool|_Bool")) {
1149 if (!(!argInfo.isArrayOrPointer() && argInfo.element))
1150 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1151 } else if (!Token::Match(argInfo.typeToken, "bool|char|short|long|int")) {
1152 if (!(!argInfo.isArrayOrPointer() && argInfo.element))
1153 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1154 } else {
1155 switch (specifier[0]) {
1156 case 'h':
1157 if (specifier[1] == 'h') {
1158 if (!(argInfo.typeToken->str() == "char" && argInfo.typeToken->isUnsigned()))
1159 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1160 } else if (!(argInfo.typeToken->str() == "short" && argInfo.typeToken->isUnsigned()))
1161 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1162 break;
1163 case 'l':
1164 if (specifier[1] == 'l') {
1165 if (argInfo.typeToken->str() != "long" || !argInfo.typeToken->isLong())
1166 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1167 else if (typesMatch(argInfo.typeToken->originalName(), "size_t") ||
1168 argInfo.typeToken->originalName() == "uintmax_t")
1169 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1170 } else if (argInfo.typeToken->str() != "long" || argInfo.typeToken->isLong())
1171 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1172 else if (typesMatch(argInfo.typeToken->originalName(), "size_t") ||
1173 argInfo.typeToken->originalName() == "uintmax_t")
1174 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1175 break;
1176 case 'j':
1177 if (argInfo.typeToken->originalName() != "uintmax_t")
1178 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1179 break;
1180 case 'z':
1181 if (!typesMatch(argInfo.typeToken->originalName(), "size_t"))
1182 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1183 break;
1184 case 't':
1185 if (!typesMatch(argInfo.typeToken->originalName(), "ptrdiff_t"))
1186 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1187 break;
1188 case 'I':
1189 if (specifier.find("I64") != std::string::npos) {
1190 if (argInfo.typeToken->str() != "long" || !argInfo.typeToken->isLong())
1191 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1192 } else if (specifier.find("I32") != std::string::npos) {
1193 if (argInfo.typeToken->str() != "int" || argInfo.typeToken->isLong())
1194 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1195 } else if (!typesMatch(argInfo.typeToken->originalName(), "size_t"))
1196 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1197 break;
1198 case 'L':
1199 if (argInfo.typeToken->str() != "long" || !argInfo.typeToken->isLong())
1200 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1201 break;
1202 default:
1203 if (!Token::Match(argInfo.typeToken, "bool|char|short|int"))
1204 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1205 else if (typesMatch(argInfo.typeToken->originalName(), "size_t") ||
1206 argInfo.typeToken->originalName() == "intmax_t")
1207 invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
1208 break;
1209 }
1210 }
1211 }
1212 done = true;
1213 break;
1214 case 'p':
1215 if (argInfo.typeToken->tokType() == Token::eString)
1216 ; // string literals are passed as pointers to literal start, okay
1217 else if (argInfo.isKnownType() && !argInfo.isArrayOrPointer())
1218 invalidPrintfArgTypeError_p(tok, numFormat, &argInfo);
1219 done = true;
1220 break;
1221 case 'e':
1222 case 'E':
1223 case 'f':
1224 case 'g':
1225 case 'G':
1226 specifier += *i;
1227 if (argInfo.typeToken->tokType() == Token::eString)
1228 invalidPrintfArgTypeError_float(tok, numFormat, specifier, &argInfo);
1229 else if (argInfo.isArrayOrPointer() && !argInfo.element) {
1230 // use %p on pointers and arrays
1231 invalidPrintfArgTypeError_float(tok, numFormat, specifier, &argInfo);
1232 } else if (argInfo.isKnownType()) {
1233 if (!Token::Match(argInfo.typeToken, "float|double")) {
1234 if (!(!argInfo.isArrayOrPointer() && argInfo.element))
1235 invalidPrintfArgTypeError_float(tok, numFormat, specifier, &argInfo);
1236 } else if ((specifier[0] == 'L' && (!argInfo.typeToken->isLong() || argInfo.typeToken->str() != "double")) ||
1237 (specifier[0] != 'L' && argInfo.typeToken->isLong()))
1238 invalidPrintfArgTypeError_float(tok, numFormat, specifier, &argInfo);
1239 }
1240 done = true;
1241 break;
1242 case 'h': // Can be 'hh' (signed char or unsigned char) or 'h' (short int or unsigned short int)
1243 case 'l': { // Can be 'll' (long long int or unsigned long long int) or 'l' (long int or unsigned long int)
1244 // If the next character is the same (which makes 'hh' or 'll') then expect another alphabetical character
1245 if (i != formatString.end() && (i + 1) != formatString.end() && *(i + 1) == *i) {
1246 if ((i + 2) != formatString.end() && !isalpha(*(i + 2))) {
1247 std::string modifier;
1248 modifier += *i;
1249 modifier += *(i + 1);
1250 invalidLengthModifierError(tok, numFormat, modifier);
1251 done = true;
1252 } else {
1253 specifier = *i++;
1254 specifier += *i++;
1255 }
1256 } else {
1257 if (i != formatString.end()) {
1258 if ((i + 1) != formatString.end() && !isalpha(*(i + 1))) {
1259 std::string modifier;
1260 modifier += *i;
1261 invalidLengthModifierError(tok, numFormat, modifier);
1262 done = true;
1263 } else {
1264 specifier = *i++;
1265 }
1266 } else {
1267 done = true;
1268 }
1269 }
1270 }
1271 break;
1272 case 'I': // Microsoft extension: I for size_t and ptrdiff_t, I32 for __int32, and I64 for __int64
1273 if ((*(i+1) == '3' && *(i+2) == '2') ||
1274 (*(i+1) == '6' && *(i+2) == '4')) {
1275 specifier += *i++;
1276 specifier += *i++;
1277 }
1278 FALLTHROUGH;
1279 case 'j': // intmax_t or uintmax_t
1280 case 'z': // size_t
1281 case 't': // ptrdiff_t
1282 case 'L': // long double
1283 // Expect an alphabetical character after these specifiers
1284 if ((i + 1) != formatString.end() && !isalpha(*(i+1))) {
1285 specifier += *i;
1286 invalidLengthModifierError(tok, numFormat, specifier);
1287 done = true;
1288 } else {
1289 specifier += *i++;
1290 }
1291 break;
1292 default:
1293 done = true;
1294 break;
1295 }
1296 }
1297 }
1298 }
1299
1300 if (argListTok)
1301 argListTok = argListTok->nextArgument(); // Find next argument
1302 }
1303 }
1304 }
1305
1306 // Count printf/scanf parameters..
1307 int numFunction = 0;
1308 while (argListTok2) {
1309 numFunction++;
1310 argListTok2 = argListTok2->nextArgument(); // Find next argument
1311 }
1312
1313 if (printWarning) {
1314 // Check that all parameter positions reference an actual parameter
1315 for (int i : parameterPositionsUsed) {
1316 if ((i == 0) || (i > numFormat))
1317 wrongPrintfScanfPosixParameterPositionError(tok, tok->str(), i, numFormat);
1318 }
1319 }
1320
1321 // Mismatching number of parameters => warning
1322 if ((numFormat + numSecure) != numFunction)
1323 wrongPrintfScanfArgumentsError(tok, tok->originalName().empty() ? tok->str() : tok->originalName(), numFormat + numSecure, numFunction);
1324 }
1325
1326 // We currently only support string literals, variables, and functions.
1327 /// @todo add non-string literals, and generic expressions
1328
ArgumentInfo(const Token * arg,const Settings * settings,bool _isCPP)1329 CheckIO::ArgumentInfo::ArgumentInfo(const Token * arg, const Settings *settings, bool _isCPP)
1330 : variableInfo(nullptr)
1331 , typeToken(nullptr)
1332 , functionInfo(nullptr)
1333 , tempToken(nullptr)
1334 , element(false)
1335 , _template(false)
1336 , address(false)
1337 , isCPP(_isCPP)
1338 {
1339 if (!arg)
1340 return;
1341
1342 // Use AST type info
1343 // TODO: This is a bailout so that old code is used in simple cases. Remove the old code and always use the AST type.
1344 if (!Token::Match(arg, "%str% ,|)") && !(Token::Match(arg,"%var%") && arg->variable() && arg->variable()->isArray())) {
1345 const Token *top = arg;
1346 while (top->str() == "(" && !top->isCast())
1347 top = top->next();
1348 while (top->astParent() && top->astParent()->str() != "," && top->astParent() != arg->previous())
1349 top = top->astParent();
1350 const ValueType *valuetype = top->argumentType();
1351 if (valuetype && valuetype->type >= ValueType::Type::BOOL) {
1352 typeToken = tempToken = new Token();
1353 if (valuetype->pointer && valuetype->constness & 1) {
1354 tempToken->str("const");
1355 tempToken->insertToken("a");
1356 tempToken = tempToken->next();
1357 }
1358 if (valuetype->type == ValueType::BOOL)
1359 tempToken->str("bool");
1360 else if (valuetype->type == ValueType::CHAR)
1361 tempToken->str("char");
1362 else if (valuetype->type == ValueType::SHORT)
1363 tempToken->str("short");
1364 else if (valuetype->type == ValueType::WCHAR_T)
1365 tempToken->str("wchar_t");
1366 else if (valuetype->type == ValueType::INT)
1367 tempToken->str("int");
1368 else if (valuetype->type == ValueType::LONG)
1369 tempToken->str("long");
1370 else if (valuetype->type == ValueType::LONGLONG) {
1371 tempToken->str("long");
1372 tempToken->isLong(true);
1373 } else if (valuetype->type == ValueType::FLOAT)
1374 tempToken->str("float");
1375 else if (valuetype->type == ValueType::DOUBLE)
1376 tempToken->str("double");
1377 else if (valuetype->type == ValueType::LONGDOUBLE) {
1378 tempToken->str("double");
1379 tempToken->isLong(true);
1380 }
1381 if (valuetype->isIntegral()) {
1382 if (valuetype->sign == ValueType::Sign::UNSIGNED)
1383 tempToken->isUnsigned(true);
1384 else if (valuetype->sign == ValueType::Sign::SIGNED)
1385 tempToken->isSigned(true);
1386 }
1387 if (!valuetype->originalTypeName.empty())
1388 tempToken->originalName(valuetype->originalTypeName);
1389 for (int p = 0; p < valuetype->pointer; p++)
1390 tempToken->insertToken("*");
1391 tempToken = const_cast<Token*>(typeToken);
1392 return;
1393 }
1394 }
1395
1396
1397 if (arg->tokType() == Token::eString) {
1398 typeToken = arg;
1399 return;
1400 } else if (arg->str() == "&" || arg->tokType() == Token::eVariable ||
1401 arg->tokType() == Token::eFunction || Token::Match(arg, "%type% ::") ||
1402 (Token::Match(arg, "static_cast|reinterpret_cast|const_cast <") &&
1403 Token::simpleMatch(arg->linkAt(1), "> (") &&
1404 Token::Match(arg->linkAt(1)->linkAt(1), ") ,|)"))) {
1405 if (Token::Match(arg, "static_cast|reinterpret_cast|const_cast")) {
1406 typeToken = arg->tokAt(2);
1407 while (typeToken->str() == "const" || typeToken->str() == "extern")
1408 typeToken = typeToken->next();
1409 return;
1410 }
1411 if (arg->str() == "&") {
1412 address = true;
1413 arg = arg->next();
1414 }
1415 while (Token::Match(arg, "%type% ::"))
1416 arg = arg->tokAt(2);
1417 if (!arg || !(arg->tokType() == Token::eVariable || arg->tokType() == Token::eFunction))
1418 return;
1419 const Token *varTok = nullptr;
1420 const Token *tok1 = arg->next();
1421 for (; tok1; tok1 = tok1->next()) {
1422 if (tok1->str() == "," || tok1->str() == ")") {
1423 if (tok1->previous()->str() == "]") {
1424 varTok = tok1->linkAt(-1)->previous();
1425 if (varTok->str() == ")" && varTok->link()->previous()->tokType() == Token::eFunction) {
1426 const Function * function = varTok->link()->previous()->function();
1427 if (function && function->retType && function->retType->isEnumType()) {
1428 if (function->retType->classScope->enumType)
1429 typeToken = function->retType->classScope->enumType;
1430 else {
1431 tempToken = new Token();
1432 tempToken->fileIndex(tok1->fileIndex());
1433 tempToken->linenr(tok1->linenr());
1434 tempToken->str("int");
1435 typeToken = tempToken;
1436 }
1437 } else if (function && function->retDef) {
1438 typeToken = function->retDef;
1439 while (typeToken->str() == "const" || typeToken->str() == "extern")
1440 typeToken = typeToken->next();
1441 functionInfo = function;
1442 element = true;
1443 }
1444 return;
1445 }
1446 } else if (tok1->previous()->str() == ")" && tok1->linkAt(-1)->previous()->tokType() == Token::eFunction) {
1447 const Function * function = tok1->linkAt(-1)->previous()->function();
1448 if (function && function->retType && function->retType->isEnumType()) {
1449 if (function->retType->classScope->enumType)
1450 typeToken = function->retType->classScope->enumType;
1451 else {
1452 tempToken = new Token();
1453 tempToken->fileIndex(tok1->fileIndex());
1454 tempToken->linenr(tok1->linenr());
1455 tempToken->str("int");
1456 typeToken = tempToken;
1457 }
1458 } else if (function && function->retDef) {
1459 typeToken = function->retDef;
1460 while (typeToken->str() == "const" || typeToken->str() == "extern")
1461 typeToken = typeToken->next();
1462 functionInfo = function;
1463 element = false;
1464 }
1465 return;
1466 } else
1467 varTok = tok1->previous();
1468 break;
1469 } else if (tok1->str() == "(" || tok1->str() == "{" || tok1->str() == "[")
1470 tok1 = tok1->link();
1471 else if (tok1->link() && tok1->str() == "<")
1472 tok1 = tok1->link();
1473
1474 // check for some common well known functions
1475 else if (isCPP && ((Token::Match(tok1->previous(), "%var% . size|empty|c_str ( ) [,)]") && isStdContainer(tok1->previous())) ||
1476 (Token::Match(tok1->previous(), "] . size|empty|c_str ( ) [,)]") && isStdContainer(tok1->previous()->link()->previous())))) {
1477 tempToken = new Token();
1478 tempToken->fileIndex(tok1->fileIndex());
1479 tempToken->linenr(tok1->linenr());
1480 if (tok1->next()->str() == "size") {
1481 // size_t is platform dependent
1482 if (settings->sizeof_size_t == 8) {
1483 tempToken->str("long");
1484 if (settings->sizeof_long != 8)
1485 tempToken->isLong(true);
1486 } else if (settings->sizeof_size_t == 4) {
1487 if (settings->sizeof_long == 4) {
1488 tempToken->str("long");
1489 } else {
1490 tempToken->str("int");
1491 }
1492 }
1493
1494 tempToken->originalName("size_t");
1495 tempToken->isUnsigned(true);
1496 } else if (tok1->next()->str() == "empty") {
1497 tempToken->str("bool");
1498 } else if (tok1->next()->str() == "c_str") {
1499 tempToken->str("const");
1500 tempToken->insertToken("*");
1501 if (typeToken->strAt(2) == "string")
1502 tempToken->insertToken("char");
1503 else
1504 tempToken->insertToken("wchar_t");
1505 }
1506 typeToken = tempToken;
1507 return;
1508 }
1509
1510 // check for std::vector::at() and std::string::at()
1511 else if (Token::Match(tok1->previous(), "%var% . at (") &&
1512 Token::Match(tok1->linkAt(2), ") [,)]")) {
1513 varTok = tok1->previous();
1514 variableInfo = varTok->variable();
1515
1516 if (!variableInfo || !isStdVectorOrString()) {
1517 variableInfo = nullptr;
1518 typeToken = nullptr;
1519 }
1520
1521 return;
1522 } else if (!(tok1->str() == "." || tok1->tokType() == Token::eVariable || tok1->tokType() == Token::eFunction))
1523 return;
1524 }
1525
1526 if (varTok) {
1527 variableInfo = varTok->variable();
1528 element = tok1->previous()->str() == "]";
1529
1530 // look for std::vector operator [] and use template type as return type
1531 if (variableInfo) {
1532 if (element && isStdVectorOrString()) { // isStdVectorOrString sets type token if true
1533 element = false; // not really an array element
1534 } else if (variableInfo->isEnumType()) {
1535 if (variableInfo->type() && variableInfo->type()->classScope && variableInfo->type()->classScope->enumType)
1536 typeToken = variableInfo->type()->classScope->enumType;
1537 else {
1538 tempToken = new Token();
1539 tempToken->fileIndex(tok1->fileIndex());
1540 tempToken->linenr(tok1->linenr());
1541 tempToken->str("int");
1542 typeToken = tempToken;
1543 }
1544 } else
1545 typeToken = variableInfo->typeStartToken();
1546 }
1547
1548 return;
1549 }
1550 }
1551 }
1552
~ArgumentInfo()1553 CheckIO::ArgumentInfo::~ArgumentInfo()
1554 {
1555 if (tempToken) {
1556 while (tempToken->next())
1557 tempToken->deleteNext();
1558
1559 delete tempToken;
1560 }
1561 }
1562
1563 namespace {
1564 const std::set<std::string> stl_vector = { "array", "vector" };
1565 const std::set<std::string> stl_string = { "string", "u16string", "u32string", "wstring" };
1566 }
1567
isStdVectorOrString()1568 bool CheckIO::ArgumentInfo::isStdVectorOrString()
1569 {
1570 if (!isCPP)
1571 return false;
1572 if (variableInfo->isStlType(stl_vector)) {
1573 typeToken = variableInfo->typeStartToken()->tokAt(4);
1574 _template = true;
1575 return true;
1576 } else if (variableInfo->isStlType(stl_string)) {
1577 tempToken = new Token();
1578 tempToken->fileIndex(variableInfo->typeStartToken()->fileIndex());
1579 tempToken->linenr(variableInfo->typeStartToken()->linenr());
1580 if (variableInfo->typeStartToken()->strAt(2) == "string")
1581 tempToken->str("char");
1582 else
1583 tempToken->str("wchar_t");
1584 typeToken = tempToken;
1585 return true;
1586 } else if (variableInfo->type() && !variableInfo->type()->derivedFrom.empty()) {
1587 const std::vector<Type::BaseInfo>& derivedFrom = variableInfo->type()->derivedFrom;
1588 for (const Type::BaseInfo & i : derivedFrom) {
1589 const Token* nameTok = i.nameTok;
1590 if (Token::Match(nameTok, "std :: vector|array <")) {
1591 typeToken = nameTok->tokAt(4);
1592 _template = true;
1593 return true;
1594 } else if (Token::Match(nameTok, "std :: string|wstring")) {
1595 tempToken = new Token();
1596 tempToken->fileIndex(variableInfo->typeStartToken()->fileIndex());
1597 tempToken->linenr(variableInfo->typeStartToken()->linenr());
1598 if (nameTok->strAt(2) == "string")
1599 tempToken->str("char");
1600 else
1601 tempToken->str("wchar_t");
1602 typeToken = tempToken;
1603 return true;
1604 }
1605 }
1606 } else if (variableInfo->type()) {
1607 const Scope * classScope = variableInfo->type()->classScope;
1608 if (classScope) {
1609 for (const Function &func : classScope->functionList) {
1610 if (func.name() == "operator[]") {
1611 if (Token::Match(func.retDef, "%type% &")) {
1612 typeToken = func.retDef;
1613 return true;
1614 }
1615 }
1616 }
1617 }
1618 }
1619
1620 return false;
1621 }
1622
1623 static const std::set<std::string> stl_container = {
1624 "array", "bitset", "deque", "forward_list",
1625 "hash_map", "hash_multimap", "hash_set",
1626 "list", "map", "multimap", "multiset",
1627 "priority_queue", "queue", "set", "stack",
1628 "unordered_map", "unordered_multimap", "unordered_multiset", "unordered_set", "vector"
1629 };
1630
isStdContainer(const Token * tok)1631 bool CheckIO::ArgumentInfo::isStdContainer(const Token *tok)
1632 {
1633 if (!isCPP)
1634 return false;
1635 if (tok && tok->variable()) {
1636 const Variable* variable = tok->variable();
1637 if (variable->isStlType(stl_container)) {
1638 typeToken = variable->typeStartToken()->tokAt(4);
1639 return true;
1640 } else if (variable->isStlType(stl_string)) {
1641 typeToken = variable->typeStartToken();
1642 return true;
1643 } else if (variable->type() && !variable->type()->derivedFrom.empty()) {
1644 for (const Type::BaseInfo &baseInfo : variable->type()->derivedFrom) {
1645 const Token* nameTok = baseInfo.nameTok;
1646 if (Token::Match(nameTok, "std :: vector|array|bitset|deque|list|forward_list|map|multimap|multiset|priority_queue|queue|set|stack|hash_map|hash_multimap|hash_set|unordered_map|unordered_multimap|unordered_set|unordered_multiset <")) {
1647 typeToken = nameTok->tokAt(4);
1648 return true;
1649 } else if (Token::Match(nameTok, "std :: string|wstring")) {
1650 typeToken = nameTok;
1651 return true;
1652 }
1653 }
1654 }
1655 }
1656
1657 return false;
1658 }
1659
isArrayOrPointer() const1660 bool CheckIO::ArgumentInfo::isArrayOrPointer() const
1661 {
1662 if (address)
1663 return true;
1664 else if (variableInfo && !_template) {
1665 return variableInfo->isArrayOrPointer();
1666 } else {
1667 const Token *tok = typeToken;
1668 while (Token::Match(tok, "const|struct"))
1669 tok = tok->next();
1670 if (tok && tok->strAt(1) == "*")
1671 return true;
1672 }
1673 return false;
1674 }
1675
isComplexType() const1676 bool CheckIO::ArgumentInfo::isComplexType() const
1677 {
1678 if (variableInfo->type())
1679 return (true);
1680
1681 const Token* varTypeTok = typeToken;
1682 if (varTypeTok->str() == "std")
1683 varTypeTok = varTypeTok->tokAt(2);
1684
1685 return ((variableInfo->isStlStringType() || (varTypeTok->strAt(1) == "<" && varTypeTok->linkAt(1) && varTypeTok->linkAt(1)->strAt(1) != "::")) && !variableInfo->isArrayOrPointer());
1686 }
1687
isKnownType() const1688 bool CheckIO::ArgumentInfo::isKnownType() const
1689 {
1690 if (variableInfo)
1691 return (typeToken->isStandardType() || typeToken->next()->isStandardType() || isComplexType());
1692 else if (functionInfo)
1693 return (typeToken->isStandardType() || functionInfo->retType || Token::Match(typeToken, "std :: string|wstring"));
1694
1695 return typeToken->isStandardType() || Token::Match(typeToken, "std :: string|wstring");
1696 }
1697
isLibraryType(const Settings * settings) const1698 bool CheckIO::ArgumentInfo::isLibraryType(const Settings *settings) const
1699 {
1700 return typeToken && typeToken->isStandardType() && settings->library.podtype(typeToken->str());
1701 }
1702
wrongPrintfScanfArgumentsError(const Token * tok,const std::string & functionName,nonneg int numFormat,nonneg int numFunction)1703 void CheckIO::wrongPrintfScanfArgumentsError(const Token* tok,
1704 const std::string &functionName,
1705 nonneg int numFormat,
1706 nonneg int numFunction)
1707 {
1708 const Severity::SeverityType severity = numFormat > numFunction ? Severity::error : Severity::warning;
1709 if (severity != Severity::error && !mSettings->severity.isEnabled(Severity::warning))
1710 return;
1711
1712 std::ostringstream errmsg;
1713 errmsg << functionName
1714 << " format string requires "
1715 << numFormat
1716 << " parameter" << (numFormat != 1 ? "s" : "") << " but "
1717 << (numFormat > numFunction ? "only " : "")
1718 << numFunction
1719 << (numFunction != 1 ? " are" : " is")
1720 << " given.";
1721
1722 reportError(tok, severity, "wrongPrintfScanfArgNum", errmsg.str(), CWE685, Certainty::normal);
1723 }
1724
wrongPrintfScanfPosixParameterPositionError(const Token * tok,const std::string & functionName,nonneg int index,nonneg int numFunction)1725 void CheckIO::wrongPrintfScanfPosixParameterPositionError(const Token* tok, const std::string& functionName,
1726 nonneg int index, nonneg int numFunction)
1727 {
1728 if (!mSettings->severity.isEnabled(Severity::warning))
1729 return;
1730 std::ostringstream errmsg;
1731 errmsg << functionName << ": ";
1732 if (index == 0) {
1733 errmsg << "parameter positions start at 1, not 0";
1734 } else {
1735 errmsg << "referencing parameter " << index << " while " << numFunction << " arguments given";
1736 }
1737 reportError(tok, Severity::warning, "wrongPrintfScanfParameterPositionError", errmsg.str(), CWE685, Certainty::normal);
1738 }
1739
invalidScanfArgTypeError_s(const Token * tok,nonneg int numFormat,const std::string & specifier,const ArgumentInfo * argInfo)1740 void CheckIO::invalidScanfArgTypeError_s(const Token* tok, nonneg int numFormat, const std::string& specifier, const ArgumentInfo* argInfo)
1741 {
1742 const Severity::SeverityType severity = getSeverity(argInfo);
1743 if (!mSettings->severity.isEnabled(severity))
1744 return;
1745 std::ostringstream errmsg;
1746 errmsg << "%" << specifier << " in format string (no. " << numFormat << ") requires a \'";
1747 if (specifier[0] == 's')
1748 errmsg << "char";
1749 else if (specifier[0] == 'S')
1750 errmsg << "wchar_t";
1751 errmsg << " *\' but the argument type is ";
1752 argumentType(errmsg, argInfo);
1753 errmsg << ".";
1754 reportError(tok, severity, "invalidScanfArgType_s", errmsg.str(), CWE686, Certainty::normal);
1755 }
invalidScanfArgTypeError_int(const Token * tok,nonneg int numFormat,const std::string & specifier,const ArgumentInfo * argInfo,bool isUnsigned)1756 void CheckIO::invalidScanfArgTypeError_int(const Token* tok, nonneg int numFormat, const std::string& specifier, const ArgumentInfo* argInfo, bool isUnsigned)
1757 {
1758 const Severity::SeverityType severity = getSeverity(argInfo);
1759 if (!mSettings->severity.isEnabled(severity))
1760 return;
1761 std::ostringstream errmsg;
1762 errmsg << "%" << specifier << " in format string (no. " << numFormat << ") requires \'";
1763 if (specifier[0] == 'h') {
1764 if (specifier[1] == 'h')
1765 errmsg << (isUnsigned ? "unsigned " : "") << "char";
1766 else
1767 errmsg << (isUnsigned ? "unsigned " : "") << "short";
1768 } else if (specifier[0] == 'l') {
1769 if (specifier[1] == 'l')
1770 errmsg << (isUnsigned ? "unsigned " : "") << "long long";
1771 else
1772 errmsg << (isUnsigned ? "unsigned " : "") << "long";
1773 } else if (specifier.find("I32") != std::string::npos) {
1774 errmsg << (isUnsigned ? "unsigned " : "") << "__int32";
1775 } else if (specifier.find("I64") != std::string::npos) {
1776 errmsg << (isUnsigned ? "unsigned " : "") << "__int64";
1777 } else if (specifier[0] == 'I') {
1778 errmsg << (isUnsigned ? "size_t" : "ptrdiff_t");
1779 } else if (specifier[0] == 'j') {
1780 if (isUnsigned)
1781 errmsg << "uintmax_t";
1782 else
1783 errmsg << "intmax_t";
1784 } else if (specifier[0] == 'z') {
1785 if (specifier[1] == 'd' || specifier[1] == 'i')
1786 errmsg << "ssize_t";
1787 else
1788 errmsg << "size_t";
1789 } else if (specifier[0] == 't') {
1790 errmsg << (isUnsigned ? "unsigned " : "") << "ptrdiff_t";
1791 } else if (specifier[0] == 'L') {
1792 errmsg << (isUnsigned ? "unsigned " : "") << "long long";
1793 } else {
1794 errmsg << (isUnsigned ? "unsigned " : "") << "int";
1795 }
1796 errmsg << " *\' but the argument type is ";
1797 argumentType(errmsg, argInfo);
1798 errmsg << ".";
1799 reportError(tok, severity, "invalidScanfArgType_int", errmsg.str(), CWE686, Certainty::normal);
1800 }
invalidScanfArgTypeError_float(const Token * tok,nonneg int numFormat,const std::string & specifier,const ArgumentInfo * argInfo)1801 void CheckIO::invalidScanfArgTypeError_float(const Token* tok, nonneg int numFormat, const std::string& specifier, const ArgumentInfo* argInfo)
1802 {
1803 const Severity::SeverityType severity = getSeverity(argInfo);
1804 if (!mSettings->severity.isEnabled(severity))
1805 return;
1806 std::ostringstream errmsg;
1807 errmsg << "%" << specifier << " in format string (no. " << numFormat << ") requires \'";
1808 if (specifier[0] == 'l' && specifier[1] != 'l')
1809 errmsg << "double";
1810 else if (specifier[0] == 'L')
1811 errmsg << "long double";
1812 else
1813 errmsg << "float";
1814 errmsg << " *\' but the argument type is ";
1815 argumentType(errmsg, argInfo);
1816 errmsg << ".";
1817 reportError(tok, severity, "invalidScanfArgType_float", errmsg.str(), CWE686, Certainty::normal);
1818 }
1819
invalidPrintfArgTypeError_s(const Token * tok,nonneg int numFormat,const ArgumentInfo * argInfo)1820 void CheckIO::invalidPrintfArgTypeError_s(const Token* tok, nonneg int numFormat, const ArgumentInfo* argInfo)
1821 {
1822 const Severity::SeverityType severity = getSeverity(argInfo);
1823 if (!mSettings->severity.isEnabled(severity))
1824 return;
1825 std::ostringstream errmsg;
1826 errmsg << "%s in format string (no. " << numFormat << ") requires \'char *\' but the argument type is ";
1827 argumentType(errmsg, argInfo);
1828 errmsg << ".";
1829 reportError(tok, severity, "invalidPrintfArgType_s", errmsg.str(), CWE686, Certainty::normal);
1830 }
invalidPrintfArgTypeError_n(const Token * tok,nonneg int numFormat,const ArgumentInfo * argInfo)1831 void CheckIO::invalidPrintfArgTypeError_n(const Token* tok, nonneg int numFormat, const ArgumentInfo* argInfo)
1832 {
1833 const Severity::SeverityType severity = getSeverity(argInfo);
1834 if (!mSettings->severity.isEnabled(severity))
1835 return;
1836 std::ostringstream errmsg;
1837 errmsg << "%n in format string (no. " << numFormat << ") requires \'int *\' but the argument type is ";
1838 argumentType(errmsg, argInfo);
1839 errmsg << ".";
1840 reportError(tok, severity, "invalidPrintfArgType_n", errmsg.str(), CWE686, Certainty::normal);
1841 }
invalidPrintfArgTypeError_p(const Token * tok,nonneg int numFormat,const ArgumentInfo * argInfo)1842 void CheckIO::invalidPrintfArgTypeError_p(const Token* tok, nonneg int numFormat, const ArgumentInfo* argInfo)
1843 {
1844 const Severity::SeverityType severity = getSeverity(argInfo);
1845 if (!mSettings->severity.isEnabled(severity))
1846 return;
1847 std::ostringstream errmsg;
1848 errmsg << "%p in format string (no. " << numFormat << ") requires an address but the argument type is ";
1849 argumentType(errmsg, argInfo);
1850 errmsg << ".";
1851 reportError(tok, severity, "invalidPrintfArgType_p", errmsg.str(), CWE686, Certainty::normal);
1852 }
printfFormatType(std::ostream & os,const std::string & specifier,bool isUnsigned)1853 static void printfFormatType(std::ostream& os, const std::string& specifier, bool isUnsigned)
1854 {
1855 os << "\'";
1856 if (specifier[0] == 'l') {
1857 if (specifier[1] == 'l')
1858 os << (isUnsigned ? "unsigned " : "") << "long long";
1859 else
1860 os << (isUnsigned ? "unsigned " : "") << "long";
1861 } else if (specifier[0] == 'h') {
1862 if (specifier[1] == 'h')
1863 os << (isUnsigned ? "unsigned " : "") << "char";
1864 else
1865 os << (isUnsigned ? "unsigned " : "") << "short";
1866 } else if (specifier.find("I32") != std::string::npos) {
1867 os << (isUnsigned ? "unsigned " : "") << "__int32";
1868 } else if (specifier.find("I64") != std::string::npos) {
1869 os << (isUnsigned ? "unsigned " : "") << "__int64";
1870 } else if (specifier[0] == 'I') {
1871 os << (isUnsigned ? "size_t" : "ptrdiff_t");
1872 } else if (specifier[0] == 'j') {
1873 if (isUnsigned)
1874 os << "uintmax_t";
1875 else
1876 os << "intmax_t";
1877 } else if (specifier[0] == 'z') {
1878 if (specifier[1] == 'd' || specifier[1] == 'i')
1879 os << "ssize_t";
1880 else
1881 os << "size_t";
1882 } else if (specifier[0] == 't') {
1883 os << (isUnsigned ? "unsigned " : "") << "ptrdiff_t";
1884 } else if (specifier[0] == 'L') {
1885 os << (isUnsigned ? "unsigned " : "") << "long long";
1886 } else {
1887 os << (isUnsigned ? "unsigned " : "") << "int";
1888 }
1889 os << "\'";
1890 }
1891
invalidPrintfArgTypeError_uint(const Token * tok,nonneg int numFormat,const std::string & specifier,const ArgumentInfo * argInfo)1892 void CheckIO::invalidPrintfArgTypeError_uint(const Token* tok, nonneg int numFormat, const std::string& specifier, const ArgumentInfo* argInfo)
1893 {
1894 const Severity::SeverityType severity = getSeverity(argInfo);
1895 if (!mSettings->severity.isEnabled(severity))
1896 return;
1897 std::ostringstream errmsg;
1898 errmsg << "%" << specifier << " in format string (no. " << numFormat << ") requires ";
1899 printfFormatType(errmsg, specifier, true);
1900 errmsg << " but the argument type is ";
1901 argumentType(errmsg, argInfo);
1902 errmsg << ".";
1903 reportError(tok, severity, "invalidPrintfArgType_uint", errmsg.str(), CWE686, Certainty::normal);
1904 }
1905
invalidPrintfArgTypeError_sint(const Token * tok,nonneg int numFormat,const std::string & specifier,const ArgumentInfo * argInfo)1906 void CheckIO::invalidPrintfArgTypeError_sint(const Token* tok, nonneg int numFormat, const std::string& specifier, const ArgumentInfo* argInfo)
1907 {
1908 const Severity::SeverityType severity = getSeverity(argInfo);
1909 if (!mSettings->severity.isEnabled(severity))
1910 return;
1911 std::ostringstream errmsg;
1912 errmsg << "%" << specifier << " in format string (no. " << numFormat << ") requires ";
1913 printfFormatType(errmsg, specifier, false);
1914 errmsg << " but the argument type is ";
1915 argumentType(errmsg, argInfo);
1916 errmsg << ".";
1917 reportError(tok, severity, "invalidPrintfArgType_sint", errmsg.str(), CWE686, Certainty::normal);
1918 }
invalidPrintfArgTypeError_float(const Token * tok,nonneg int numFormat,const std::string & specifier,const ArgumentInfo * argInfo)1919 void CheckIO::invalidPrintfArgTypeError_float(const Token* tok, nonneg int numFormat, const std::string& specifier, const ArgumentInfo* argInfo)
1920 {
1921 const Severity::SeverityType severity = getSeverity(argInfo);
1922 if (!mSettings->severity.isEnabled(severity))
1923 return;
1924 std::ostringstream errmsg;
1925 errmsg << "%" << specifier << " in format string (no. " << numFormat << ") requires \'";
1926 if (specifier[0] == 'L')
1927 errmsg << "long ";
1928 errmsg << "double\' but the argument type is ";
1929 argumentType(errmsg, argInfo);
1930 errmsg << ".";
1931 reportError(tok, severity, "invalidPrintfArgType_float", errmsg.str(), CWE686, Certainty::normal);
1932 }
1933
getSeverity(const CheckIO::ArgumentInfo * argInfo)1934 Severity::SeverityType CheckIO::getSeverity(const CheckIO::ArgumentInfo *argInfo)
1935 {
1936 return (argInfo && argInfo->typeToken && !argInfo->typeToken->originalName().empty()) ? Severity::portability : Severity::warning;
1937 }
1938
argumentType(std::ostream & os,const ArgumentInfo * argInfo)1939 void CheckIO::argumentType(std::ostream& os, const ArgumentInfo * argInfo)
1940 {
1941 if (argInfo) {
1942 os << "\'";
1943 const Token *type = argInfo->typeToken;
1944 if (type->tokType() == Token::eString) {
1945 if (type->isLong())
1946 os << "const wchar_t *";
1947 else
1948 os << "const char *";
1949 } else {
1950 if (type->originalName().empty()) {
1951 if (type->strAt(-1) == "const")
1952 os << "const ";
1953 while (Token::Match(type, "const|struct")) {
1954 os << type->str() << " ";
1955 type = type->next();
1956 }
1957 while (Token::Match(type, "%any% ::")) {
1958 os << type->str() << "::";
1959 type = type->tokAt(2);
1960 }
1961 os << type->stringify(false, true, false);
1962 if (type->strAt(1) == "*" && !argInfo->element)
1963 os << " *";
1964 else if (argInfo->variableInfo && !argInfo->element && argInfo->variableInfo->isArray())
1965 os << " *";
1966 else if (type->strAt(1) == "*" && argInfo->variableInfo && argInfo->element && argInfo->variableInfo->isArray())
1967 os << " *";
1968 if (argInfo->address)
1969 os << " *";
1970 } else {
1971 if (type->isUnsigned()) {
1972 if (type->originalName() == "__int64" || type->originalName() == "__int32" || type->originalName() == "ptrdiff_t")
1973 os << "unsigned ";
1974 }
1975 os << type->originalName();
1976 if (type->strAt(1) == "*" || argInfo->address)
1977 os << " *";
1978 os << " {aka " << type->stringify(false, true, false);
1979 if (type->strAt(1) == "*" || argInfo->address)
1980 os << " *";
1981 os << "}";
1982 }
1983 }
1984 os << "\'";
1985 } else
1986 os << "Unknown";
1987 }
1988
invalidLengthModifierError(const Token * tok,nonneg int numFormat,const std::string & modifier)1989 void CheckIO::invalidLengthModifierError(const Token* tok, nonneg int numFormat, const std::string& modifier)
1990 {
1991 if (!mSettings->severity.isEnabled(Severity::warning))
1992 return;
1993 std::ostringstream errmsg;
1994 errmsg << "'" << modifier << "' in format string (no. " << numFormat << ") is a length modifier and cannot be used without a conversion specifier.";
1995 reportError(tok, Severity::warning, "invalidLengthModifierError", errmsg.str(), CWE704, Certainty::normal);
1996 }
1997
invalidScanfFormatWidthError(const Token * tok,nonneg int numFormat,int width,const Variable * var,char c)1998 void CheckIO::invalidScanfFormatWidthError(const Token* tok, nonneg int numFormat, int width, const Variable *var, char c)
1999 {
2000 MathLib::bigint arrlen = 0;
2001 std::string varname;
2002
2003 if (var) {
2004 arrlen = var->dimension(0);
2005 varname = var->name();
2006 }
2007
2008 std::ostringstream errmsg;
2009 if (arrlen > width) {
2010 if (tok != nullptr && (!mSettings->certainty.isEnabled(Certainty::inconclusive) || !mSettings->severity.isEnabled(Severity::warning)))
2011 return;
2012 errmsg << "Width " << width << " given in format string (no. " << numFormat << ") is smaller than destination buffer"
2013 << " '" << varname << "[" << arrlen << "]'.";
2014 reportError(tok, Severity::warning, "invalidScanfFormatWidth_smaller", errmsg.str(), CWE(0U), Certainty::inconclusive);
2015 } else {
2016 errmsg << "Width " << width << " given in format string (no. " << numFormat << ") is larger than destination buffer '"
2017 << varname << "[" << arrlen << "]', use %" << (c == 'c' ? arrlen : (arrlen - 1)) << c << " to prevent overflowing it.";
2018 reportError(tok, Severity::error, "invalidScanfFormatWidth", errmsg.str(), CWE687, Certainty::normal);
2019 }
2020 }
2021