/* * Cppcheck - A tool for static C/C++ code analysis * Copyright (C) 2007-2023 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ //--------------------------------------------------------------------------- #include "checkio.h" #include "errortypes.h" #include "library.h" #include "mathlib.h" #include "platform.h" #include "settings.h" #include "symboldatabase.h" #include "token.h" #include "tokenize.h" #include "utils.h" #include "vfvalue.h" #include #include #include #include #include #include #include #include // IWYU pragma: keep #include #include #include //--------------------------------------------------------------------------- // Register CheckIO.. namespace { CheckIO instance; } // CVE ID used: static const CWE CWE119(119U); // Improper Restriction of Operations within the Bounds of a Memory Buffer static const CWE CWE398(398U); // Indicator of Poor Code Quality static const CWE CWE664(664U); // Improper Control of a Resource Through its Lifetime static const CWE CWE685(685U); // Function Call With Incorrect Number of Arguments static const CWE CWE686(686U); // Function Call With Incorrect Argument Type static const CWE CWE687(687U); // Function Call With Incorrectly Specified Argument Value static const CWE CWE704(704U); // Incorrect Type Conversion or Cast static const CWE CWE910(910U); // Use of Expired File Descriptor //--------------------------------------------------------------------------- // std::cout << std::cout; //--------------------------------------------------------------------------- void CheckIO::checkCoutCerrMisusage() { if (mTokenizer->isC()) return; logChecker("CheckIO::checkCoutCerrMisusage"); // c const SymbolDatabase * const symbolDatabase = mTokenizer->getSymbolDatabase(); for (const Scope * scope : symbolDatabase->functionScopes) { for (const Token *tok = scope->bodyStart; tok && tok != scope->bodyEnd; tok = tok->next()) { if (Token::Match(tok, "std :: cout|cerr !!.") && tok->next()->astParent() && tok->next()->astParent()->astOperand1() == tok->next()) { const Token* tok2 = tok->next(); while (tok2->astParent() && tok2->astParent()->str() == "<<") { tok2 = tok2->astParent(); if (tok2->astOperand2() && Token::Match(tok2->astOperand2()->previous(), "std :: cout|cerr")) coutCerrMisusageError(tok, tok2->astOperand2()->strAt(1)); } } } } } void CheckIO::coutCerrMisusageError(const Token* tok, const std::string& streamName) { reportError(tok, Severity::error, "coutCerrMisusage", "Invalid usage of output stream: '<< std::" + streamName + "'.", CWE398, Certainty::normal); } //--------------------------------------------------------------------------- // fflush(stdin) <- fflush only applies to output streams in ANSI C // fread(); fwrite(); <- consecutive read/write statements require repositioning in between // fopen("","r"); fwrite(); <- write to read-only file (or vice versa) // fclose(); fread(); <- Use closed file //--------------------------------------------------------------------------- enum class OpenMode { CLOSED, READ_MODE, WRITE_MODE, RW_MODE, UNKNOWN_OM }; static OpenMode getMode(const std::string& str) { if (str.find('+', 1) != std::string::npos) return OpenMode::RW_MODE; if (str.find('w') != std::string::npos || str.find('a') != std::string::npos) return OpenMode::WRITE_MODE; if (str.find('r') != std::string::npos) return OpenMode::READ_MODE; return OpenMode::UNKNOWN_OM; } namespace { struct Filepointer { OpenMode mode; nonneg int mode_indent{}; enum class Operation {NONE, UNIMPORTANT, READ, WRITE, POSITIONING, OPEN, CLOSE, UNKNOWN_OP} lastOperation = Operation::NONE; nonneg int op_indent{}; enum class AppendMode { UNKNOWN_AM, APPEND, APPEND_EX }; AppendMode append_mode = AppendMode::UNKNOWN_AM; std::string filename; explicit Filepointer(OpenMode mode_ = OpenMode::UNKNOWN_OM) : mode(mode_) {} }; const std::unordered_set whitelist = { "clearerr", "feof", "ferror", "fgetpos", "ftell", "setbuf", "setvbuf", "ungetc", "ungetwc" }; } void CheckIO::checkFileUsage() { const bool windows = mSettings->platform.isWindows(); const bool printPortability = mSettings->severity.isEnabled(Severity::portability); const bool printWarnings = mSettings->severity.isEnabled(Severity::warning); std::map filepointers; logChecker("CheckIO::checkFileUsage"); const SymbolDatabase* symbolDatabase = mTokenizer->getSymbolDatabase(); for (const Variable* var : symbolDatabase->variableList()) { if (!var || !var->declarationId() || var->isArray() || !Token::simpleMatch(var->typeStartToken(), "FILE *")) continue; if (var->isLocal()) { if (var->nameToken()->strAt(1) == "(") // initialize by calling "ctor" filepointers.insert(std::make_pair(var->declarationId(), Filepointer(OpenMode::UNKNOWN_OM))); else filepointers.insert(std::make_pair(var->declarationId(), Filepointer(OpenMode::CLOSED))); } else { filepointers.insert(std::make_pair(var->declarationId(), Filepointer(OpenMode::UNKNOWN_OM))); // TODO: If all fopen calls we find open the file in the same type, we can set Filepointer::mode } } for (const Scope * scope : symbolDatabase->functionScopes) { int indent = 0; for (const Token *tok = scope->bodyStart; tok != scope->bodyEnd; tok = tok->next()) { if (tok->str() == "{") indent++; else if (tok->str() == "}") { indent--; for (std::pair& filepointer : filepointers) { if (indent < filepointer.second.mode_indent) { filepointer.second.mode_indent = 0; filepointer.second.mode = OpenMode::UNKNOWN_OM; } if (indent < filepointer.second.op_indent) { filepointer.second.op_indent = 0; filepointer.second.lastOperation = Filepointer::Operation::UNKNOWN_OP; } } } else if (tok->str() == "return" || tok->str() == "continue" || tok->str() == "break" || mSettings->library.isnoreturn(tok)) { // Reset upon return, continue or break for (std::pair& filepointer : filepointers) { filepointer.second.mode_indent = 0; filepointer.second.mode = OpenMode::UNKNOWN_OM; filepointer.second.op_indent = 0; filepointer.second.lastOperation = Filepointer::Operation::UNKNOWN_OP; } } else if (Token::Match(tok, "%var% =") && (tok->strAt(2) != "fopen" && tok->strAt(2) != "freopen" && tok->strAt(2) != "tmpfile" && (windows ? (tok->str() != "_wfopen" && tok->str() != "_wfreopen") : true))) { const std::map::iterator i = filepointers.find(tok->varId()); if (i != filepointers.end()) { i->second.mode = OpenMode::UNKNOWN_OM; i->second.lastOperation = Filepointer::Operation::UNKNOWN_OP; } } else if (Token::Match(tok, "%name% (") && tok->previous() && (!tok->previous()->isName() || Token::Match(tok->previous(), "return|throw"))) { std::string mode; const Token* fileTok = nullptr; const Token* fileNameTok = nullptr; Filepointer::Operation operation = Filepointer::Operation::NONE; if ((tok->str() == "fopen" || tok->str() == "freopen" || tok->str() == "tmpfile" || (windows && (tok->str() == "_wfopen" || tok->str() == "_wfreopen"))) && tok->strAt(-1) == "=") { if (tok->str() != "tmpfile") { const Token* modeTok = tok->tokAt(2)->nextArgument(); if (modeTok && modeTok->tokType() == Token::eString) mode = modeTok->strValue(); } else mode = "wb+"; fileTok = tok->tokAt(-2); operation = Filepointer::Operation::OPEN; if (Token::Match(tok, "fopen ( %str% ,")) fileNameTok = tok->tokAt(2); } else if (windows && Token::Match(tok, "fopen_s|freopen_s|_wfopen_s|_wfreopen_s ( & %name%")) { const Token* modeTok = tok->tokAt(2)->nextArgument()->nextArgument(); if (modeTok && modeTok->tokType() == Token::eString) mode = modeTok->strValue(); fileTok = tok->tokAt(3); operation = Filepointer::Operation::OPEN; } else if ((tok->str() == "rewind" || tok->str() == "fseek" || tok->str() == "fsetpos" || tok->str() == "fflush") || (windows && tok->str() == "_fseeki64")) { fileTok = tok->tokAt(2); if (printPortability && fileTok && tok->str() == "fflush") { if (fileTok->str() == "stdin") fflushOnInputStreamError(tok, fileTok->str()); else { const Filepointer& f = filepointers[fileTok->varId()]; if (f.mode == OpenMode::READ_MODE) fflushOnInputStreamError(tok, fileTok->str()); } } operation = Filepointer::Operation::POSITIONING; } else if (tok->str() == "fgetc" || tok->str() == "fgetwc" || tok->str() == "fgets" || tok->str() == "fgetws" || tok->str() == "fread" || tok->str() == "fscanf" || tok->str() == "fwscanf" || tok->str() == "getc" || (windows && (tok->str() == "fscanf_s" || tok->str() == "fwscanf_s"))) { if (tok->str().find("scanf") != std::string::npos) fileTok = tok->tokAt(2); else fileTok = tok->linkAt(1)->previous(); operation = Filepointer::Operation::READ; } else if (tok->str() == "fputc" || tok->str() == "fputwc" || tok->str() == "fputs" || tok->str() == "fputws" || tok->str() == "fwrite" || tok->str() == "fprintf" || tok->str() == "fwprintf" || tok->str() == "putcc" || (windows && (tok->str() == "fprintf_s" || tok->str() == "fwprintf_s"))) { if (tok->str().find("printf") != std::string::npos) fileTok = tok->tokAt(2); else fileTok = tok->linkAt(1)->previous(); operation = Filepointer::Operation::WRITE; } else if (tok->str() == "fclose") { fileTok = tok->tokAt(2); operation = Filepointer::Operation::CLOSE; } else if (whitelist.find(tok->str()) != whitelist.end()) { fileTok = tok->tokAt(2); if ((tok->str() == "ungetc" || tok->str() == "ungetwc") && fileTok) fileTok = fileTok->nextArgument(); operation = Filepointer::Operation::UNIMPORTANT; } else if (!Token::Match(tok, "if|for|while|catch|switch") && !mSettings->library.isFunctionConst(tok->str(), true)) { const Token* const end2 = tok->linkAt(1); if (scope->functionOf && scope->functionOf->isClassOrStruct() && !scope->function->isStatic() && ((tok->strAt(-1) != "::" && tok->strAt(-1) != ".") || tok->strAt(-2) == "this")) { if (!tok->function() || (tok->function()->nestedIn && tok->function()->nestedIn->isClassOrStruct())) { for (std::pair& filepointer : filepointers) { const Variable* var = symbolDatabase->getVariableFromVarId(filepointer.first); if (!var || !(var->isLocal() || var->isGlobal() || var->isStatic())) { filepointer.second.mode = OpenMode::UNKNOWN_OM; filepointer.second.mode_indent = 0; filepointer.second.op_indent = indent; filepointer.second.lastOperation = Filepointer::Operation::UNKNOWN_OP; } } continue; } } for (const Token* tok2 = tok->tokAt(2); tok2 != end2; tok2 = tok2->next()) { if (tok2->varId() && filepointers.find(tok2->varId()) != filepointers.end()) { fileTok = tok2; operation = Filepointer::Operation::UNKNOWN_OP; // Assume that repositioning was last operation and that the file is opened now break; } } } while (Token::Match(fileTok, "%name% .")) fileTok = fileTok->tokAt(2); if (!fileTok || !fileTok->varId() || fileTok->strAt(1) == "[") continue; if (filepointers.find(fileTok->varId()) == filepointers.end()) { // function call indicates: Its a File filepointers.insert(std::make_pair(fileTok->varId(), Filepointer(OpenMode::UNKNOWN_OM))); } Filepointer& f = filepointers[fileTok->varId()]; switch (operation) { case Filepointer::Operation::OPEN: if (fileNameTok) { for (std::map::const_iterator it = filepointers.cbegin(); it != filepointers.cend(); ++it) { const Filepointer &fptr = it->second; if (fptr.filename == fileNameTok->str() && (fptr.mode == OpenMode::RW_MODE || fptr.mode == OpenMode::WRITE_MODE)) incompatibleFileOpenError(tok, fileNameTok->str()); } f.filename = fileNameTok->str(); } f.mode = getMode(mode); if (mode.find('a') != std::string::npos) { if (f.mode == OpenMode::RW_MODE) f.append_mode = Filepointer::AppendMode::APPEND_EX; else f.append_mode = Filepointer::AppendMode::APPEND; } else f.append_mode = Filepointer::AppendMode::UNKNOWN_AM; f.mode_indent = indent; break; case Filepointer::Operation::POSITIONING: if (f.mode == OpenMode::CLOSED) useClosedFileError(tok); else if (f.append_mode == Filepointer::AppendMode::APPEND && tok->str() != "fflush" && printWarnings) seekOnAppendedFileError(tok); break; case Filepointer::Operation::READ: if (f.mode == OpenMode::CLOSED) useClosedFileError(tok); else if (f.mode == OpenMode::WRITE_MODE) readWriteOnlyFileError(tok); else if (f.lastOperation == Filepointer::Operation::WRITE) ioWithoutPositioningError(tok); break; case Filepointer::Operation::WRITE: if (f.mode == OpenMode::CLOSED) useClosedFileError(tok); else if (f.mode == OpenMode::READ_MODE) writeReadOnlyFileError(tok); else if (f.lastOperation == Filepointer::Operation::READ) ioWithoutPositioningError(tok); break; case Filepointer::Operation::CLOSE: if (f.mode == OpenMode::CLOSED) useClosedFileError(tok); else f.mode = OpenMode::CLOSED; f.mode_indent = indent; break; case Filepointer::Operation::UNIMPORTANT: if (f.mode == OpenMode::CLOSED) useClosedFileError(tok); break; case Filepointer::Operation::UNKNOWN_OP: f.mode = OpenMode::UNKNOWN_OM; f.mode_indent = 0; break; default: break; } if (operation != Filepointer::Operation::NONE && operation != Filepointer::Operation::UNIMPORTANT) { f.op_indent = indent; f.lastOperation = operation; } } } for (std::pair& filepointer : filepointers) { filepointer.second.op_indent = 0; filepointer.second.mode = OpenMode::UNKNOWN_OM; filepointer.second.lastOperation = Filepointer::Operation::UNKNOWN_OP; } } } void CheckIO::fflushOnInputStreamError(const Token *tok, const std::string &varname) { reportError(tok, Severity::portability, "fflushOnInputStream", "fflush() called on input stream '" + varname + "' may result in undefined behaviour on non-linux systems.", CWE398, Certainty::normal); } void CheckIO::ioWithoutPositioningError(const Token *tok) { reportError(tok, Severity::error, "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); } void CheckIO::readWriteOnlyFileError(const Token *tok) { reportError(tok, Severity::error, "readWriteOnlyFile", "Read operation on a file that was opened only for writing.", CWE664, Certainty::normal); } void CheckIO::writeReadOnlyFileError(const Token *tok) { reportError(tok, Severity::error, "writeReadOnlyFile", "Write operation on a file that was opened only for reading.", CWE664, Certainty::normal); } void CheckIO::useClosedFileError(const Token *tok) { reportError(tok, Severity::error, "useClosedFile", "Used file that is not opened.", CWE910, Certainty::normal); } void CheckIO::seekOnAppendedFileError(const Token *tok) { reportError(tok, Severity::warning, "seekOnAppendedFile", "Repositioning operation performed on a file opened in append mode has no effect.", CWE398, Certainty::normal); } void CheckIO::incompatibleFileOpenError(const Token *tok, const std::string &filename) { reportError(tok, Severity::warning, "incompatibleFileOpen", "The file '" + filename + "' is opened for read and write access at the same time on different streams", CWE664, Certainty::normal); } //--------------------------------------------------------------------------- // scanf without field width limits can crash with huge input data //--------------------------------------------------------------------------- void CheckIO::invalidScanf() { if (!mSettings->severity.isEnabled(Severity::warning)) return; const SymbolDatabase * const symbolDatabase = mTokenizer->getSymbolDatabase(); for (const Scope * scope : symbolDatabase->functionScopes) { for (const Token *tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) { const Token *formatToken = nullptr; if (Token::Match(tok, "scanf|vscanf ( %str% ,")) formatToken = tok->tokAt(2); else if (Token::Match(tok, "sscanf|vsscanf|fscanf|vfscanf (")) { const Token* nextArg = tok->tokAt(2)->nextArgument(); if (nextArg && nextArg->tokType() == Token::eString) formatToken = nextArg; else continue; } else continue; bool format = false; // scan the string backwards, so we do not need to keep states const std::string &formatstr(formatToken->str()); for (std::size_t i = 1; i < formatstr.length(); i++) { if (formatstr[i] == '%') format = !format; else if (!format) continue; else if (std::isdigit(formatstr[i]) || formatstr[i] == '*') { format = false; } else if (std::isalpha((unsigned char)formatstr[i]) || formatstr[i] == '[') { 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 invalidScanfError(tok); format = false; } } } } } void CheckIO::invalidScanfError(const Token *tok) { const std::string fname = (tok ? tok->str() : std::string("scanf")); reportError(tok, Severity::warning, "invalidscanf", fname + "() without field width limits can crash with huge input data.\n" + fname + "() without field width limits can crash with huge input data. Add a field width " "specifier to fix this problem.\n" "\n" "Sample program that can crash:\n" "\n" "#include \n" "int main()\n" "{\n" " char c[5];\n" " scanf(\"%s\", c);\n" " return 0;\n" "}\n" "\n" "Typing in 5 or more characters may make the program crash. The correct usage " "here is 'scanf(\"%4s\", c);', as the maximum field width does not include the " "terminating null byte.\n" "Source: http://linux.die.net/man/3/scanf\n" "Source: http://www.opensource.apple.com/source/xnu/xnu-1456.1.26/libkern/stdio/scanf.c", CWE119, Certainty::normal); } //--------------------------------------------------------------------------- // printf("%u", "xyz"); // Wrong argument type // printf("%u%s", 1); // Too few arguments // printf("", 1); // Too much arguments //--------------------------------------------------------------------------- static bool findFormat(nonneg int arg, const Token *firstArg, const Token **formatStringTok, const Token **formatArgTok) { const Token* argTok = firstArg; for (int i = 0; i < arg && argTok; ++i) argTok = argTok->nextArgument(); if (Token::Match(argTok, "%str% [,)]")) { *formatArgTok = argTok->nextArgument(); *formatStringTok = argTok; return true; } if (Token::Match(argTok, "%var% [,)]") && argTok->variable() && Token::Match(argTok->variable()->typeStartToken(), "char|wchar_t") && (argTok->variable()->isPointer() || (argTok->variable()->dimensions().size() == 1 && argTok->variable()->dimensionKnown(0) && argTok->variable()->dimension(0) != 0))) { *formatArgTok = argTok->nextArgument(); if (!argTok->values().empty()) { const std::list::const_iterator value = std::find_if( argTok->values().cbegin(), argTok->values().cend(), std::mem_fn(&ValueFlow::Value::isTokValue)); if (value != argTok->values().cend() && value->isTokValue() && value->tokvalue && value->tokvalue->tokType() == Token::eString) { *formatStringTok = value->tokvalue; } } return true; } return false; } // Utility function returning whether iToTest equals iTypename or iOptionalPrefix+iTypename static inline bool typesMatch(const std::string& iToTest, const std::string& iTypename, const std::string& iOptionalPrefix = "std::") { return (iToTest == iTypename) || (iToTest == iOptionalPrefix + iTypename); } void CheckIO::checkWrongPrintfScanfArguments() { const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); const bool isWindows = mSettings->platform.isWindows(); logChecker("CheckIO::checkWrongPrintfScanfArguments"); for (const Scope * scope : symbolDatabase->functionScopes) { for (const Token *tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) { if (!tok->isName()) continue; const Token* argListTok = nullptr; // Points to first va_list argument const Token* formatStringTok = nullptr; // Points to format string token bool scan = false; bool scanf_s = false; int formatStringArgNo = -1; if (tok->strAt(1) == "(" && mSettings->library.formatstr_function(tok)) { formatStringArgNo = mSettings->library.formatstr_argno(tok); scan = mSettings->library.formatstr_scan(tok); scanf_s = mSettings->library.formatstr_secure(tok); } if (formatStringArgNo >= 0) { // formatstring found in library. Find format string and first argument belonging to format string. if (!findFormat(formatStringArgNo, tok->tokAt(2), &formatStringTok, &argListTok)) continue; } else if (Token::simpleMatch(tok, "swprintf (")) { if (Token::Match(tok->tokAt(2)->nextArgument(), "%str%")) { // Find third parameter and format string if (!findFormat(1, tok->tokAt(2), &formatStringTok, &argListTok)) continue; } else { // Find fourth parameter and format string if (!findFormat(2, tok->tokAt(2), &formatStringTok, &argListTok)) continue; } } else if (isWindows && Token::Match(tok, "sprintf_s|swprintf_s (")) { // template int sprintf_s(char (&buffer)[size], const char *format, ...); if (findFormat(1, tok->tokAt(2), &formatStringTok, &argListTok)) { if (!formatStringTok) continue; } // int sprintf_s(char *buffer, size_t sizeOfBuffer, const char *format, ...); else if (findFormat(2, tok->tokAt(2), &formatStringTok, &argListTok)) { if (!formatStringTok) continue; } } else if (isWindows && Token::Match(tok, "_snprintf_s|_snwprintf_s (")) { // template int _snprintf_s(char (&buffer)[size], size_t count, const char *format, ...); if (findFormat(2, tok->tokAt(2), &formatStringTok, &argListTok)) { if (!formatStringTok) continue; } // int _snprintf_s(char *buffer, size_t sizeOfBuffer, size_t count, const char *format, ...); else if (findFormat(3, tok->tokAt(2), &formatStringTok, &argListTok)) { if (!formatStringTok) continue; } } else { continue; } if (!formatStringTok) continue; checkFormatString(tok, formatStringTok, argListTok, scan, scanf_s); } } } void CheckIO::checkFormatString(const Token * const tok, const Token * const formatStringTok, const Token * argListTok, const bool scan, const bool scanf_s) { const bool isWindows = mSettings->platform.isWindows(); const bool printWarning = mSettings->severity.isEnabled(Severity::warning); const std::string &formatString = formatStringTok->str(); // Count format string parameters.. int numFormat = 0; int numSecure = 0; bool percent = false; const Token* argListTok2 = argListTok; std::set parameterPositionsUsed; for (std::string::const_iterator i = formatString.cbegin(); i != formatString.cend(); ++i) { if (*i == '%') { percent = !percent; } else if (percent && *i == '[') { while (i != formatString.cend()) { if (*i == ']') { numFormat++; if (argListTok) argListTok = argListTok->nextArgument(); percent = false; break; } ++i; } if (scanf_s) { numSecure++; if (argListTok) { argListTok = argListTok->nextArgument(); } } if (i == formatString.cend()) break; } else if (percent) { percent = false; bool _continue = false; bool skip = false; std::string width; int parameterPosition = 0; bool hasParameterPosition = false; while (i != formatString.cend() && *i != '[' && !std::isalpha((unsigned char)*i)) { if (*i == '*') { skip = true; if (scan) _continue = true; else { numFormat++; if (argListTok) argListTok = argListTok->nextArgument(); } } else if (std::isdigit(*i)) { width += *i; } else if (*i == '$') { parameterPosition = strToInt(width); hasParameterPosition = true; width.clear(); } ++i; } auto bracketBeg = formatString.cend(); if (i != formatString.cend() && *i == '[') { bracketBeg = i; while (i != formatString.cend()) { if (*i == ']') break; ++i; } if (scanf_s && !skip) { numSecure++; if (argListTok) { argListTok = argListTok->nextArgument(); } } } if (i == formatString.cend()) break; if (_continue) continue; if (scan || *i != 'm') { // %m is a non-standard extension that requires no parameter on print functions. ++numFormat; // Handle parameter positions (POSIX extension) - Ticket #4900 if (hasParameterPosition) { if (parameterPositionsUsed.find(parameterPosition) == parameterPositionsUsed.end()) parameterPositionsUsed.insert(parameterPosition); else // Parameter already referenced, hence don't consider it a new format --numFormat; } // Perform type checks ArgumentInfo argInfo(argListTok, mSettings, mTokenizer->isCPP()); if ((argInfo.typeToken && !argInfo.isLibraryType(mSettings)) || *i == ']') { if (scan) { std::string specifier; bool done = false; while (!done) { switch (*i) { case 's': case ']': // charset specifier += (*i == 's' || bracketBeg == formatString.end()) ? std::string{ 's' } : std::string{ bracketBeg, i + 1 }; if (argInfo.variableInfo && argInfo.isKnownType() && argInfo.variableInfo->isArray() && (argInfo.variableInfo->dimensions().size() == 1) && argInfo.variableInfo->dimensions()[0].known) { if (!width.empty()) { const int numWidth = strToInt(width); if (numWidth != (argInfo.variableInfo->dimension(0) - 1)) invalidScanfFormatWidthError(tok, numFormat, numWidth, argInfo.variableInfo, specifier); } } if (argListTok && argListTok->tokType() != Token::eString && argInfo.typeToken && argInfo.isKnownType() && argInfo.isArrayOrPointer() && (!Token::Match(argInfo.typeToken, "char|wchar_t") || argInfo.typeToken->strAt(-1) == "const")) { if (!(argInfo.isArrayOrPointer() && argInfo.element && !argInfo.typeToken->isStandardType())) invalidScanfArgTypeError_s(tok, numFormat, specifier, &argInfo); } if (scanf_s && argInfo.typeToken) { numSecure++; if (argListTok) { argListTok = argListTok->nextArgument(); } } done = true; break; case 'c': if (argInfo.variableInfo && argInfo.isKnownType() && argInfo.variableInfo->isArray() && (argInfo.variableInfo->dimensions().size() == 1) && argInfo.variableInfo->dimensions()[0].known) { if (!width.empty()) { const int numWidth = strToInt(width); if (numWidth > argInfo.variableInfo->dimension(0)) invalidScanfFormatWidthError(tok, numFormat, numWidth, argInfo.variableInfo, std::string(1, *i)); } } if (scanf_s) { numSecure++; if (argListTok) { argListTok = argListTok->nextArgument(); } } done = true; break; case 'x': case 'X': case 'u': case 'o': specifier += *i; if (argInfo.typeToken->tokType() == Token::eString) invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true); else if (argInfo.isKnownType()) { if (!Token::Match(argInfo.typeToken, "char|short|int|long")) { if (argInfo.typeToken->isStandardType() || !argInfo.element) invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true); } else if (!argInfo.typeToken->isUnsigned() || !argInfo.isArrayOrPointer() || argInfo.typeToken->strAt(-1) == "const") { invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true); } else { switch (specifier[0]) { case 'h': if (specifier[1] == 'h') { if (argInfo.typeToken->str() != "char") invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true); } else if (argInfo.typeToken->str() != "short") invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true); break; case 'l': if (specifier[1] == 'l') { if (argInfo.typeToken->str() != "long" || !argInfo.typeToken->isLong()) invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true); else if (typesMatch(argInfo.typeToken->originalName(), "size_t") || typesMatch(argInfo.typeToken->originalName(), "ptrdiff_t") || typesMatch(argInfo.typeToken->originalName(), "uintmax_t")) invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true); } else if (argInfo.typeToken->str() != "long" || argInfo.typeToken->isLong()) invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true); else if (typesMatch(argInfo.typeToken->originalName(), "size_t") || typesMatch(argInfo.typeToken->originalName(), "ptrdiff_t") || typesMatch(argInfo.typeToken->originalName(), "uintmax_t")) invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true); break; case 'I': if (specifier.find("I64") != std::string::npos) { if (argInfo.typeToken->str() != "long" || !argInfo.typeToken->isLong()) invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true); } else if (specifier.find("I32") != std::string::npos) { if (argInfo.typeToken->str() != "int" || argInfo.typeToken->isLong()) invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true); } else if (!typesMatch(argInfo.typeToken->originalName(), "size_t")) invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true); break; case 'j': if (!typesMatch(argInfo.typeToken->originalName(), "uintmax_t")) invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true); break; case 'z': if (!typesMatch(argInfo.typeToken->originalName(), "size_t")) invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true); break; case 't': if (!typesMatch(argInfo.typeToken->originalName(), "ptrdiff_t")) invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true); break; case 'L': if (argInfo.typeToken->str() != "long" || !argInfo.typeToken->isLong()) invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true); else if (typesMatch(argInfo.typeToken->originalName(), "size_t") || typesMatch(argInfo.typeToken->originalName(), "uintmax_t")) invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true); break; default: if (argInfo.typeToken->str() != "int") invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true); else if (typesMatch(argInfo.typeToken->originalName(), "size_t") || typesMatch(argInfo.typeToken->originalName(), "ptrdiff_t") || typesMatch(argInfo.typeToken->originalName(), "uintmax_t")) invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, true); break; } } } done = true; break; case 'n': case 'd': case 'i': specifier += *i; if (argInfo.typeToken->tokType() == Token::eString) invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false); else if (argInfo.isKnownType()) { if (!Token::Match(argInfo.typeToken, "char|short|int|long")) { if (argInfo.typeToken->isStandardType() || !argInfo.element) invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false); } else if (argInfo.typeToken->isUnsigned() || !argInfo.isArrayOrPointer() || argInfo.typeToken->strAt(-1) == "const") { invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false); } else { switch (specifier[0]) { case 'h': if (specifier[1] == 'h') { if (argInfo.typeToken->str() != "char") invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false); } else if (argInfo.typeToken->str() != "short") invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false); break; case 'l': if (specifier[1] == 'l') { if (argInfo.typeToken->str() != "long" || !argInfo.typeToken->isLong()) invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false); else if (typesMatch(argInfo.typeToken->originalName(), "ptrdiff_t") || typesMatch(argInfo.typeToken->originalName(), "intmax_t")) invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false); } else if (argInfo.typeToken->str() != "long" || argInfo.typeToken->isLong()) invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false); else if (typesMatch(argInfo.typeToken->originalName(), "ptrdiff_t") || typesMatch(argInfo.typeToken->originalName(), "intmax_t")) invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false); break; case 'I': if (specifier.find("I64") != std::string::npos) { if (argInfo.typeToken->str() != "long" || !argInfo.typeToken->isLong()) invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false); } else if (specifier.find("I32") != std::string::npos) { if (argInfo.typeToken->str() != "int" || argInfo.typeToken->isLong()) invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false); } else if (!typesMatch(argInfo.typeToken->originalName(), "ptrdiff_t")) invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false); break; case 'j': if (!typesMatch(argInfo.typeToken->originalName(), "intmax_t")) invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false); break; case 'z': if (!(typesMatch(argInfo.typeToken->originalName(), "ssize_t") || (isWindows && typesMatch(argInfo.typeToken->originalName(), "SSIZE_T")))) invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false); break; case 't': if (!typesMatch(argInfo.typeToken->originalName(), "ptrdiff_t")) invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false); break; case 'L': if (argInfo.typeToken->str() != "long" || !argInfo.typeToken->isLong()) invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false); break; default: if (argInfo.typeToken->str() != "int") invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false); else if (typesMatch(argInfo.typeToken->originalName(), "ptrdiff_t") || argInfo.typeToken->originalName() == "intmax_t") invalidScanfArgTypeError_int(tok, numFormat, specifier, &argInfo, false); break; } } } done = true; break; case 'e': case 'E': case 'f': case 'g': case 'G': case 'a': specifier += *i; if (argInfo.typeToken->tokType() == Token::eString) invalidScanfArgTypeError_float(tok, numFormat, specifier, &argInfo); else if (argInfo.isKnownType()) { if (!Token::Match(argInfo.typeToken, "float|double")) { if (argInfo.typeToken->isStandardType()) invalidScanfArgTypeError_float(tok, numFormat, specifier, &argInfo); } else if (!argInfo.isArrayOrPointer() || argInfo.typeToken->strAt(-1) == "const") { invalidScanfArgTypeError_float(tok, numFormat, specifier, &argInfo); } else { switch (specifier[0]) { case 'l': if (argInfo.typeToken->str() != "double" || argInfo.typeToken->isLong()) invalidScanfArgTypeError_float(tok, numFormat, specifier, &argInfo); break; case 'L': if (argInfo.typeToken->str() != "double" || !argInfo.typeToken->isLong()) invalidScanfArgTypeError_float(tok, numFormat, specifier, &argInfo); break; default: if (argInfo.typeToken->str() != "float") invalidScanfArgTypeError_float(tok, numFormat, specifier, &argInfo); break; } } } done = true; break; case 'I': if ((i+1 != formatString.cend() && *(i+1) == '6' && i+2 != formatString.cend() && *(i+2) == '4') || (i+1 != formatString.cend() && *(i+1) == '3' && i+2 != formatString.cend() && *(i+2) == '2')) { specifier += *i++; specifier += *i++; if ((i+1) != formatString.cend() && !isalpha(*(i+1))) { specifier += *i; invalidLengthModifierError(tok, numFormat, specifier); done = true; } else { specifier += *i++; } } else { if ((i+1) != formatString.cend() && !isalpha(*(i+1))) { specifier += *i; invalidLengthModifierError(tok, numFormat, specifier); done = true; } else { specifier += *i++; } } break; case 'h': case 'l': if (i+1 != formatString.cend() && *(i+1) == *i) specifier += *i++; FALLTHROUGH; case 'j': case 'q': case 't': case 'z': case 'L': // Expect an alphabetical character after these specifiers if ((i + 1) != formatString.end() && !isalpha(*(i+1))) { specifier += *i; invalidLengthModifierError(tok, numFormat, specifier); done = true; } else { specifier += *i++; } break; default: done = true; break; } } } else if (printWarning) { std::string specifier; bool done = false; while (!done) { if (i == formatString.end()) { break; } switch (*i) { case 's': if (argListTok->tokType() != Token::eString && argInfo.isKnownType() && !argInfo.isArrayOrPointer()) { if (!Token::Match(argInfo.typeToken, "char|wchar_t")) { if (!argInfo.element) invalidPrintfArgTypeError_s(tok, numFormat, &argInfo); } } done = true; break; case 'n': if ((argInfo.isKnownType() && (!argInfo.isArrayOrPointer() || argInfo.typeToken->strAt(-1) == "const")) || argListTok->tokType() == Token::eString) invalidPrintfArgTypeError_n(tok, numFormat, &argInfo); done = true; break; case 'c': case 'x': case 'X': case 'o': specifier += *i; if (argInfo.typeToken->tokType() == Token::eString) invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); else if (argInfo.isArrayOrPointer() && !argInfo.element) { // use %p on pointers and arrays invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); } else if (argInfo.isKnownType()) { if (!Token::Match(argInfo.typeToken, "bool|short|long|int|char|wchar_t")) { if (!(!argInfo.isArrayOrPointer() && argInfo.element)) invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); } else { switch (specifier[0]) { case 'h': if (specifier[1] == 'h') { if (!(argInfo.typeToken->str() == "char" && argInfo.typeToken->isUnsigned())) invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); } else if (!(argInfo.typeToken->str() == "short" && argInfo.typeToken->isUnsigned())) invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); break; case 'l': if (specifier[1] == 'l') { if (argInfo.typeToken->str() != "long" || !argInfo.typeToken->isLong()) invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); else if (typesMatch(argInfo.typeToken->originalName(), "size_t") || argInfo.typeToken->originalName() == "uintmax_t") invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); } else if (argInfo.typeToken->str() != "long" || argInfo.typeToken->isLong()) invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); else if (typesMatch(argInfo.typeToken->originalName(), "size_t") || argInfo.typeToken->originalName() == "uintmax_t") invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); break; case 'j': if (argInfo.typeToken->originalName() != "uintmax_t") invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); break; case 'z': if (!typesMatch(argInfo.typeToken->originalName(), "size_t")) invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); break; case 't': if (!typesMatch(argInfo.typeToken->originalName(), "ptrdiff_t")) invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); break; case 'I': if (specifier.find("I64") != std::string::npos) { if (argInfo.typeToken->str() != "long" || !argInfo.typeToken->isLong()) invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); } else if (specifier.find("I32") != std::string::npos) { if (argInfo.typeToken->str() != "int" || argInfo.typeToken->isLong()) invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); } else if (!(typesMatch(argInfo.typeToken->originalName(), "size_t") || argInfo.typeToken->originalName() == "WPARAM" || argInfo.typeToken->originalName() == "UINT_PTR" || argInfo.typeToken->originalName() == "LONG_PTR" || argInfo.typeToken->originalName() == "LPARAM" || argInfo.typeToken->originalName() == "LRESULT")) invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); break; default: if (!Token::Match(argInfo.typeToken, "bool|char|short|wchar_t|int")) invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); break; } } } done = true; break; case 'd': case 'i': specifier += *i; if (argInfo.typeToken->tokType() == Token::eString) { invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo); } else if (argInfo.isArrayOrPointer() && !argInfo.element) { // use %p on pointers and arrays invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo); } else if (argInfo.isKnownType()) { if (argInfo.typeToken->isUnsigned() && !Token::Match(argInfo.typeToken, "char|short")) { if (!(!argInfo.isArrayOrPointer() && argInfo.element)) invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo); } else if (!Token::Match(argInfo.typeToken, "bool|char|short|int|long")) { if (!(!argInfo.isArrayOrPointer() && argInfo.element)) invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo); } else { switch (specifier[0]) { case 'h': if (specifier[1] == 'h') { if (!(argInfo.typeToken->str() == "char" && !argInfo.typeToken->isUnsigned())) invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo); } else if (!(argInfo.typeToken->str() == "short" && !argInfo.typeToken->isUnsigned())) invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo); break; case 'l': if (specifier[1] == 'l') { if (argInfo.typeToken->str() != "long" || !argInfo.typeToken->isLong()) invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo); else if (typesMatch(argInfo.typeToken->originalName(), "ptrdiff_t") || argInfo.typeToken->originalName() == "intmax_t") invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo); } else if (argInfo.typeToken->str() != "long" || argInfo.typeToken->isLong()) invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo); else if (typesMatch(argInfo.typeToken->originalName(), "ptrdiff_t") || argInfo.typeToken->originalName() == "intmax_t") invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo); break; case 'j': if (argInfo.typeToken->originalName() != "intmax_t") invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo); break; case 't': if (!typesMatch(argInfo.typeToken->originalName(), "ptrdiff_t")) invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo); break; case 'I': if (specifier.find("I64") != std::string::npos) { if (argInfo.typeToken->str() != "long" || !argInfo.typeToken->isLong()) invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo); } else if (specifier.find("I32") != std::string::npos) { if (argInfo.typeToken->str() != "int" || argInfo.typeToken->isLong()) invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo); } else if (!typesMatch(argInfo.typeToken->originalName(), "ptrdiff_t")) invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo); break; case 'z': if (!(typesMatch(argInfo.typeToken->originalName(), "ssize_t") || (isWindows && typesMatch(argInfo.typeToken->originalName(), "SSIZE_T")))) invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo); break; case 'L': if (argInfo.typeToken->str() != "long" || !argInfo.typeToken->isLong()) invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo); break; default: if (!Token::Match(argInfo.typeToken, "bool|char|short|int")) invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo); else if (typesMatch(argInfo.typeToken->originalName(), "ptrdiff_t") || argInfo.typeToken->originalName() == "intmax_t") invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo); break; } } } done = true; break; case 'u': specifier += *i; if (argInfo.typeToken->tokType() == Token::eString) { invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); } else if (argInfo.isArrayOrPointer() && !argInfo.element) { // use %p on pointers and arrays invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); } else if (argInfo.isKnownType()) { if (!argInfo.typeToken->isUnsigned() && !Token::Match(argInfo.typeToken, "bool|_Bool")) { if (!(!argInfo.isArrayOrPointer() && argInfo.element)) invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); } else if (!Token::Match(argInfo.typeToken, "bool|char|short|long|int")) { if (!(!argInfo.isArrayOrPointer() && argInfo.element)) invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); } else { switch (specifier[0]) { case 'h': if (specifier[1] == 'h') { if (!(argInfo.typeToken->str() == "char" && argInfo.typeToken->isUnsigned())) invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); } else if (!(argInfo.typeToken->str() == "short" && argInfo.typeToken->isUnsigned())) invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); break; case 'l': if (specifier[1] == 'l') { if (argInfo.typeToken->str() != "long" || !argInfo.typeToken->isLong()) invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); else if (typesMatch(argInfo.typeToken->originalName(), "size_t") || argInfo.typeToken->originalName() == "uintmax_t") invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); } else if (argInfo.typeToken->str() != "long" || argInfo.typeToken->isLong()) invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); else if (typesMatch(argInfo.typeToken->originalName(), "size_t") || argInfo.typeToken->originalName() == "uintmax_t") invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); break; case 'j': if (argInfo.typeToken->originalName() != "uintmax_t") invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); break; case 'z': if (!typesMatch(argInfo.typeToken->originalName(), "size_t")) invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); break; case 't': if (!typesMatch(argInfo.typeToken->originalName(), "ptrdiff_t")) invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); break; case 'I': if (specifier.find("I64") != std::string::npos) { if (argInfo.typeToken->str() != "long" || !argInfo.typeToken->isLong()) invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); } else if (specifier.find("I32") != std::string::npos) { if (argInfo.typeToken->str() != "int" || argInfo.typeToken->isLong()) invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); } else if (!typesMatch(argInfo.typeToken->originalName(), "size_t")) invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); break; case 'L': if (argInfo.typeToken->str() != "long" || !argInfo.typeToken->isLong()) invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); break; default: if (!Token::Match(argInfo.typeToken, "bool|char|short|int")) invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); else if (typesMatch(argInfo.typeToken->originalName(), "size_t") || argInfo.typeToken->originalName() == "intmax_t") invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo); break; } } } done = true; break; case 'p': if (argInfo.typeToken->tokType() == Token::eString) ; // string literals are passed as pointers to literal start, okay else if (argInfo.isKnownType() && !argInfo.isArrayOrPointer()) invalidPrintfArgTypeError_p(tok, numFormat, &argInfo); done = true; break; case 'e': case 'E': case 'f': case 'g': case 'G': specifier += *i; if (argInfo.typeToken->tokType() == Token::eString) invalidPrintfArgTypeError_float(tok, numFormat, specifier, &argInfo); else if (argInfo.isArrayOrPointer() && !argInfo.element) { // use %p on pointers and arrays invalidPrintfArgTypeError_float(tok, numFormat, specifier, &argInfo); } else if (argInfo.isKnownType()) { if (!Token::Match(argInfo.typeToken, "float|double")) { if (!(!argInfo.isArrayOrPointer() && argInfo.element)) invalidPrintfArgTypeError_float(tok, numFormat, specifier, &argInfo); } else if ((specifier[0] == 'L' && (!argInfo.typeToken->isLong() || argInfo.typeToken->str() != "double")) || (specifier[0] != 'L' && argInfo.typeToken->isLong())) invalidPrintfArgTypeError_float(tok, numFormat, specifier, &argInfo); } done = true; break; case 'h': // Can be 'hh' (signed char or unsigned char) or 'h' (short int or unsigned short int) case 'l': { // Can be 'll' (long long int or unsigned long long int) or 'l' (long int or unsigned long int) // If the next character is the same (which makes 'hh' or 'll') then expect another alphabetical character if ((i + 1) != formatString.end() && *(i + 1) == *i) { if ((i + 2) != formatString.end() && !isalpha(*(i + 2))) { std::string modifier; modifier += *i; modifier += *(i + 1); invalidLengthModifierError(tok, numFormat, modifier); done = true; } else { specifier = *i++; specifier += *i++; } } else { if ((i + 1) != formatString.end() && !isalpha(*(i + 1))) { std::string modifier; modifier += *i; invalidLengthModifierError(tok, numFormat, modifier); done = true; } else { specifier = *i++; } } } break; case 'I': // Microsoft extension: I for size_t and ptrdiff_t, I32 for __int32, and I64 for __int64 if ((*(i+1) == '3' && *(i+2) == '2') || (*(i+1) == '6' && *(i+2) == '4')) { specifier += *i++; specifier += *i++; } FALLTHROUGH; case 'j': // intmax_t or uintmax_t case 'z': // size_t case 't': // ptrdiff_t case 'L': // long double // Expect an alphabetical character after these specifiers if ((i + 1) != formatString.end() && !isalpha(*(i+1))) { specifier += *i; invalidLengthModifierError(tok, numFormat, specifier); done = true; } else { specifier += *i++; } break; default: done = true; break; } } } } if (argListTok) argListTok = argListTok->nextArgument(); // Find next argument } } } // Count printf/scanf parameters.. int numFunction = 0; while (argListTok2) { if (Token::Match(argListTok2, "%name% ...")) // bailout for parameter pack return; numFunction++; argListTok2 = argListTok2->nextArgument(); // Find next argument } if (printWarning) { // Check that all parameter positions reference an actual parameter for (const int i : parameterPositionsUsed) { if ((i == 0) || (i > numFormat)) wrongPrintfScanfPosixParameterPositionError(tok, tok->str(), i, numFormat); } } // Mismatching number of parameters => warning if ((numFormat + numSecure) != numFunction) wrongPrintfScanfArgumentsError(tok, tok->originalName().empty() ? tok->str() : tok->originalName(), numFormat + numSecure, numFunction); } // We currently only support string literals, variables, and functions. /// @todo add non-string literals, and generic expressions CheckIO::ArgumentInfo::ArgumentInfo(const Token * arg, const Settings *settings, bool _isCPP) : isCPP(_isCPP) { if (!arg) return; // Use AST type info // TODO: This is a bailout so that old code is used in simple cases. Remove the old code and always use the AST type. if (!Token::Match(arg, "%str% ,|)") && !(arg->variable() && arg->variable()->isArray())) { const Token *top = arg; while (top->str() == "(" && !top->isCast()) top = top->next(); while (top->astParent() && top->astParent()->str() != "," && top->astParent() != arg->previous()) top = top->astParent(); const ValueType *valuetype = top->argumentType(); if (valuetype && valuetype->type >= ValueType::Type::BOOL) { typeToken = tempToken = new Token(); if (valuetype->pointer && valuetype->constness & 1) { tempToken->str("const"); tempToken->insertToken("a"); tempToken = tempToken->next(); } if (valuetype->type == ValueType::BOOL) tempToken->str("bool"); else if (valuetype->type == ValueType::CHAR) tempToken->str("char"); else if (valuetype->type == ValueType::SHORT) tempToken->str("short"); else if (valuetype->type == ValueType::WCHAR_T) tempToken->str("wchar_t"); else if (valuetype->type == ValueType::INT) tempToken->str("int"); else if (valuetype->type == ValueType::LONG) tempToken->str("long"); else if (valuetype->type == ValueType::LONGLONG) { tempToken->str("long"); tempToken->isLong(true); } else if (valuetype->type == ValueType::FLOAT) tempToken->str("float"); else if (valuetype->type == ValueType::DOUBLE) tempToken->str("double"); else if (valuetype->type == ValueType::LONGDOUBLE) { tempToken->str("double"); tempToken->isLong(true); } if (valuetype->isIntegral()) { if (valuetype->sign == ValueType::Sign::UNSIGNED) tempToken->isUnsigned(true); else if (valuetype->sign == ValueType::Sign::SIGNED) tempToken->isSigned(true); } if (!valuetype->originalTypeName.empty()) tempToken->originalName(valuetype->originalTypeName); for (int p = 0; p < valuetype->pointer; p++) tempToken->insertToken("*"); tempToken = const_cast(typeToken); if (top->isBinaryOp() && valuetype->pointer == 1 && (valuetype->type == ValueType::CHAR || valuetype->type == ValueType::WCHAR_T)) tempToken->tokType(Token::eString); return; } } if (arg->tokType() == Token::eString) { typeToken = arg; return; } if (arg->str() == "&" || arg->tokType() == Token::eVariable || arg->tokType() == Token::eFunction || Token::Match(arg, "%type% ::") || (Token::Match(arg, "static_cast|reinterpret_cast|const_cast <") && Token::simpleMatch(arg->linkAt(1), "> (") && Token::Match(arg->linkAt(1)->linkAt(1), ") ,|)"))) { if (Token::Match(arg, "static_cast|reinterpret_cast|const_cast")) { typeToken = arg->tokAt(2); while (typeToken->str() == "const" || typeToken->str() == "extern") typeToken = typeToken->next(); return; } if (arg->str() == "&") { address = true; arg = arg->next(); } while (Token::Match(arg, "%type% ::")) arg = arg->tokAt(2); if (!arg || !(arg->tokType() == Token::eVariable || arg->tokType() == Token::eFunction)) return; const Token *varTok = nullptr; const Token *tok1 = arg->next(); for (; tok1; tok1 = tok1->next()) { if (tok1->str() == "," || tok1->str() == ")") { if (tok1->previous()->str() == "]") { varTok = tok1->linkAt(-1)->previous(); if (varTok->str() == ")" && varTok->link()->previous()->tokType() == Token::eFunction) { const Function * function = varTok->link()->previous()->function(); if (function && function->retType && function->retType->isEnumType()) { if (function->retType->classScope->enumType) typeToken = function->retType->classScope->enumType; else { tempToken = new Token(); tempToken->fileIndex(tok1->fileIndex()); tempToken->linenr(tok1->linenr()); tempToken->str("int"); typeToken = tempToken; } } else if (function && function->retDef) { typeToken = function->retDef; while (typeToken->str() == "const" || typeToken->str() == "extern") typeToken = typeToken->next(); functionInfo = function; element = true; } return; } } else if (tok1->previous()->str() == ")" && tok1->linkAt(-1)->previous()->tokType() == Token::eFunction) { const Function * function = tok1->linkAt(-1)->previous()->function(); if (function && function->retType && function->retType->isEnumType()) { if (function->retType->classScope->enumType) typeToken = function->retType->classScope->enumType; else { tempToken = new Token(); tempToken->fileIndex(tok1->fileIndex()); tempToken->linenr(tok1->linenr()); tempToken->str("int"); typeToken = tempToken; } } else if (function && function->retDef) { typeToken = function->retDef; while (typeToken->str() == "const" || typeToken->str() == "extern") typeToken = typeToken->next(); functionInfo = function; element = false; } return; } else varTok = tok1->previous(); break; } if (tok1->str() == "(" || tok1->str() == "{" || tok1->str() == "[") tok1 = tok1->link(); else if (tok1->link() && tok1->str() == "<") tok1 = tok1->link(); // check for some common well known functions else if (isCPP && ((Token::Match(tok1->previous(), "%var% . size|empty|c_str ( ) [,)]") && isStdContainer(tok1->previous())) || (Token::Match(tok1->previous(), "] . size|empty|c_str ( ) [,)]") && isStdContainer(tok1->previous()->link()->previous())))) { tempToken = new Token(); tempToken->fileIndex(tok1->fileIndex()); tempToken->linenr(tok1->linenr()); if (tok1->next()->str() == "size") { // size_t is platform dependent if (settings->platform.sizeof_size_t == 8) { tempToken->str("long"); if (settings->platform.sizeof_long != 8) tempToken->isLong(true); } else if (settings->platform.sizeof_size_t == 4) { if (settings->platform.sizeof_long == 4) { tempToken->str("long"); } else { tempToken->str("int"); } } tempToken->originalName("size_t"); tempToken->isUnsigned(true); } else if (tok1->next()->str() == "empty") { tempToken->str("bool"); } else if (tok1->next()->str() == "c_str") { tempToken->str("const"); tempToken->insertToken("*"); if (typeToken->strAt(2) == "string") tempToken->insertToken("char"); else tempToken->insertToken("wchar_t"); } typeToken = tempToken; return; } // check for std::vector::at() and std::string::at() else if (Token::Match(tok1->previous(), "%var% . at (") && Token::Match(tok1->linkAt(2), ") [,)]")) { varTok = tok1->previous(); variableInfo = varTok->variable(); if (!variableInfo || !isStdVectorOrString()) { variableInfo = nullptr; typeToken = nullptr; } return; } else if (!(tok1->str() == "." || tok1->tokType() == Token::eVariable || tok1->tokType() == Token::eFunction)) return; } if (varTok) { variableInfo = varTok->variable(); element = tok1->previous()->str() == "]"; // look for std::vector operator [] and use template type as return type if (variableInfo) { if (element && isStdVectorOrString()) { // isStdVectorOrString sets type token if true element = false; // not really an array element } else if (variableInfo->isEnumType()) { if (variableInfo->type() && variableInfo->type()->classScope && variableInfo->type()->classScope->enumType) typeToken = variableInfo->type()->classScope->enumType; else { tempToken = new Token(); tempToken->fileIndex(tok1->fileIndex()); tempToken->linenr(tok1->linenr()); tempToken->str("int"); typeToken = tempToken; } } else typeToken = variableInfo->typeStartToken(); } return; } } } CheckIO::ArgumentInfo::~ArgumentInfo() { if (tempToken) { while (tempToken->next()) tempToken->deleteNext(); delete tempToken; } } namespace { const std::set stl_vector = { "array", "vector" }; const std::set stl_string = { "string", "u16string", "u32string", "wstring" }; } bool CheckIO::ArgumentInfo::isStdVectorOrString() { if (!isCPP) return false; if (variableInfo->isStlType(stl_vector)) { typeToken = variableInfo->typeStartToken()->tokAt(4); _template = true; return true; } if (variableInfo->isStlType(stl_string)) { tempToken = new Token(); tempToken->fileIndex(variableInfo->typeStartToken()->fileIndex()); tempToken->linenr(variableInfo->typeStartToken()->linenr()); if (variableInfo->typeStartToken()->strAt(2) == "string") tempToken->str("char"); else tempToken->str("wchar_t"); typeToken = tempToken; return true; } if (variableInfo->type() && !variableInfo->type()->derivedFrom.empty()) { const std::vector& derivedFrom = variableInfo->type()->derivedFrom; for (const Type::BaseInfo & i : derivedFrom) { const Token* nameTok = i.nameTok; if (Token::Match(nameTok, "std :: vector|array <")) { typeToken = nameTok->tokAt(4); _template = true; return true; } if (Token::Match(nameTok, "std :: string|wstring")) { tempToken = new Token(); tempToken->fileIndex(variableInfo->typeStartToken()->fileIndex()); tempToken->linenr(variableInfo->typeStartToken()->linenr()); if (nameTok->strAt(2) == "string") tempToken->str("char"); else tempToken->str("wchar_t"); typeToken = tempToken; return true; } } } else if (variableInfo->type()) { const Scope * classScope = variableInfo->type()->classScope; if (classScope) { for (const Function &func : classScope->functionList) { if (func.name() == "operator[]") { if (Token::Match(func.retDef, "%type% &")) { typeToken = func.retDef; return true; } } } } } return false; } static const std::set stl_container = { "array", "bitset", "deque", "forward_list", "hash_map", "hash_multimap", "hash_set", "list", "map", "multimap", "multiset", "priority_queue", "queue", "set", "stack", "unordered_map", "unordered_multimap", "unordered_multiset", "unordered_set", "vector" }; bool CheckIO::ArgumentInfo::isStdContainer(const Token *tok) { if (!isCPP) return false; if (tok && tok->variable()) { const Variable* variable = tok->variable(); if (variable->isStlType(stl_container)) { typeToken = variable->typeStartToken()->tokAt(4); return true; } if (variable->isStlType(stl_string)) { typeToken = variable->typeStartToken(); return true; } if (variable->type() && !variable->type()->derivedFrom.empty()) { for (const Type::BaseInfo &baseInfo : variable->type()->derivedFrom) { const Token* nameTok = baseInfo.nameTok; 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 <")) { typeToken = nameTok->tokAt(4); return true; } if (Token::Match(nameTok, "std :: string|wstring")) { typeToken = nameTok; return true; } } } } return false; } bool CheckIO::ArgumentInfo::isArrayOrPointer() const { if (address) return true; if (variableInfo && !_template) return variableInfo->isArrayOrPointer(); const Token *tok = typeToken; while (Token::Match(tok, "const|struct")) tok = tok->next(); return tok && tok->strAt(1) == "*"; } bool CheckIO::ArgumentInfo::isComplexType() const { if (variableInfo->type()) return (true); const Token* varTypeTok = typeToken; if (varTypeTok->str() == "std") varTypeTok = varTypeTok->tokAt(2); return ((variableInfo->isStlStringType() || (varTypeTok->strAt(1) == "<" && varTypeTok->linkAt(1) && varTypeTok->linkAt(1)->strAt(1) != "::")) && !variableInfo->isArrayOrPointer()); } bool CheckIO::ArgumentInfo::isKnownType() const { if (variableInfo) return (typeToken->isStandardType() || typeToken->next()->isStandardType() || isComplexType()); if (functionInfo) return (typeToken->isStandardType() || functionInfo->retType || Token::Match(typeToken, "std :: string|wstring")); return typeToken->isStandardType() || Token::Match(typeToken, "std :: string|wstring"); } bool CheckIO::ArgumentInfo::isLibraryType(const Settings *settings) const { return typeToken && typeToken->isStandardType() && settings->library.podtype(typeToken->str()); } void CheckIO::wrongPrintfScanfArgumentsError(const Token* tok, const std::string &functionName, nonneg int numFormat, nonneg int numFunction) { const Severity severity = numFormat > numFunction ? Severity::error : Severity::warning; if (severity != Severity::error && !mSettings->severity.isEnabled(Severity::warning)) return; std::ostringstream errmsg; errmsg << functionName << " format string requires " << numFormat << " parameter" << (numFormat != 1 ? "s" : "") << " but " << (numFormat > numFunction ? "only " : "") << numFunction << (numFunction != 1 ? " are" : " is") << " given."; reportError(tok, severity, "wrongPrintfScanfArgNum", errmsg.str(), CWE685, Certainty::normal); } void CheckIO::wrongPrintfScanfPosixParameterPositionError(const Token* tok, const std::string& functionName, nonneg int index, nonneg int numFunction) { if (!mSettings->severity.isEnabled(Severity::warning)) return; std::ostringstream errmsg; errmsg << functionName << ": "; if (index == 0) { errmsg << "parameter positions start at 1, not 0"; } else { errmsg << "referencing parameter " << index << " while " << numFunction << " arguments given"; } reportError(tok, Severity::warning, "wrongPrintfScanfParameterPositionError", errmsg.str(), CWE685, Certainty::normal); } void CheckIO::invalidScanfArgTypeError_s(const Token* tok, nonneg int numFormat, const std::string& specifier, const ArgumentInfo* argInfo) { const Severity severity = getSeverity(argInfo); if (!mSettings->severity.isEnabled(severity)) return; std::ostringstream errmsg; errmsg << "%" << specifier << " in format string (no. " << numFormat << ") requires a \'"; if (specifier[0] == 's') errmsg << "char"; else if (specifier[0] == 'S') errmsg << "wchar_t"; errmsg << " *\' but the argument type is "; argumentType(errmsg, argInfo); errmsg << "."; reportError(tok, severity, "invalidScanfArgType_s", errmsg.str(), CWE686, Certainty::normal); } void CheckIO::invalidScanfArgTypeError_int(const Token* tok, nonneg int numFormat, const std::string& specifier, const ArgumentInfo* argInfo, bool isUnsigned) { const Severity severity = getSeverity(argInfo); if (!mSettings->severity.isEnabled(severity)) return; std::ostringstream errmsg; errmsg << "%" << specifier << " in format string (no. " << numFormat << ") requires \'"; if (specifier[0] == 'h') { if (specifier[1] == 'h') errmsg << (isUnsigned ? "unsigned " : "") << "char"; else errmsg << (isUnsigned ? "unsigned " : "") << "short"; } else if (specifier[0] == 'l') { if (specifier[1] == 'l') errmsg << (isUnsigned ? "unsigned " : "") << "long long"; else errmsg << (isUnsigned ? "unsigned " : "") << "long"; } else if (specifier.find("I32") != std::string::npos) { errmsg << (isUnsigned ? "unsigned " : "") << "__int32"; } else if (specifier.find("I64") != std::string::npos) { errmsg << (isUnsigned ? "unsigned " : "") << "__int64"; } else if (specifier[0] == 'I') { errmsg << (isUnsigned ? "size_t" : "ptrdiff_t"); } else if (specifier[0] == 'j') { if (isUnsigned) errmsg << "uintmax_t"; else errmsg << "intmax_t"; } else if (specifier[0] == 'z') { if (specifier[1] == 'd' || specifier[1] == 'i') errmsg << "ssize_t"; else errmsg << "size_t"; } else if (specifier[0] == 't') { errmsg << (isUnsigned ? "unsigned " : "") << "ptrdiff_t"; } else if (specifier[0] == 'L') { errmsg << (isUnsigned ? "unsigned " : "") << "long long"; } else { errmsg << (isUnsigned ? "unsigned " : "") << "int"; } errmsg << " *\' but the argument type is "; argumentType(errmsg, argInfo); errmsg << "."; reportError(tok, severity, "invalidScanfArgType_int", errmsg.str(), CWE686, Certainty::normal); } void CheckIO::invalidScanfArgTypeError_float(const Token* tok, nonneg int numFormat, const std::string& specifier, const ArgumentInfo* argInfo) { const Severity severity = getSeverity(argInfo); if (!mSettings->severity.isEnabled(severity)) return; std::ostringstream errmsg; errmsg << "%" << specifier << " in format string (no. " << numFormat << ") requires \'"; if (specifier[0] == 'l' && specifier[1] != 'l') errmsg << "double"; else if (specifier[0] == 'L') errmsg << "long double"; else errmsg << "float"; errmsg << " *\' but the argument type is "; argumentType(errmsg, argInfo); errmsg << "."; reportError(tok, severity, "invalidScanfArgType_float", errmsg.str(), CWE686, Certainty::normal); } void CheckIO::invalidPrintfArgTypeError_s(const Token* tok, nonneg int numFormat, const ArgumentInfo* argInfo) { const Severity severity = getSeverity(argInfo); if (!mSettings->severity.isEnabled(severity)) return; std::ostringstream errmsg; errmsg << "%s in format string (no. " << numFormat << ") requires \'char *\' but the argument type is "; argumentType(errmsg, argInfo); errmsg << "."; reportError(tok, severity, "invalidPrintfArgType_s", errmsg.str(), CWE686, Certainty::normal); } void CheckIO::invalidPrintfArgTypeError_n(const Token* tok, nonneg int numFormat, const ArgumentInfo* argInfo) { const Severity severity = getSeverity(argInfo); if (!mSettings->severity.isEnabled(severity)) return; std::ostringstream errmsg; errmsg << "%n in format string (no. " << numFormat << ") requires \'int *\' but the argument type is "; argumentType(errmsg, argInfo); errmsg << "."; reportError(tok, severity, "invalidPrintfArgType_n", errmsg.str(), CWE686, Certainty::normal); } void CheckIO::invalidPrintfArgTypeError_p(const Token* tok, nonneg int numFormat, const ArgumentInfo* argInfo) { const Severity severity = getSeverity(argInfo); if (!mSettings->severity.isEnabled(severity)) return; std::ostringstream errmsg; errmsg << "%p in format string (no. " << numFormat << ") requires an address but the argument type is "; argumentType(errmsg, argInfo); errmsg << "."; reportError(tok, severity, "invalidPrintfArgType_p", errmsg.str(), CWE686, Certainty::normal); } static void printfFormatType(std::ostream& os, const std::string& specifier, bool isUnsigned) { os << "\'"; if (specifier[0] == 'l') { if (specifier[1] == 'l') os << (isUnsigned ? "unsigned " : "") << "long long"; else os << (isUnsigned ? "unsigned " : "") << "long"; } else if (specifier[0] == 'h') { if (specifier[1] == 'h') os << (isUnsigned ? "unsigned " : "") << "char"; else os << (isUnsigned ? "unsigned " : "") << "short"; } else if (specifier.find("I32") != std::string::npos) { os << (isUnsigned ? "unsigned " : "") << "__int32"; } else if (specifier.find("I64") != std::string::npos) { os << (isUnsigned ? "unsigned " : "") << "__int64"; } else if (specifier[0] == 'I') { os << (isUnsigned ? "size_t" : "ptrdiff_t"); } else if (specifier[0] == 'j') { if (isUnsigned) os << "uintmax_t"; else os << "intmax_t"; } else if (specifier[0] == 'z') { if (specifier[1] == 'd' || specifier[1] == 'i') os << "ssize_t"; else os << "size_t"; } else if (specifier[0] == 't') { os << (isUnsigned ? "unsigned " : "") << "ptrdiff_t"; } else if (specifier[0] == 'L') { os << (isUnsigned ? "unsigned " : "") << "long long"; } else { os << (isUnsigned ? "unsigned " : "") << "int"; } os << "\'"; } void CheckIO::invalidPrintfArgTypeError_uint(const Token* tok, nonneg int numFormat, const std::string& specifier, const ArgumentInfo* argInfo) { const Severity severity = getSeverity(argInfo); if (!mSettings->severity.isEnabled(severity)) return; std::ostringstream errmsg; errmsg << "%" << specifier << " in format string (no. " << numFormat << ") requires "; printfFormatType(errmsg, specifier, true); errmsg << " but the argument type is "; argumentType(errmsg, argInfo); errmsg << "."; reportError(tok, severity, "invalidPrintfArgType_uint", errmsg.str(), CWE686, Certainty::normal); } void CheckIO::invalidPrintfArgTypeError_sint(const Token* tok, nonneg int numFormat, const std::string& specifier, const ArgumentInfo* argInfo) { const Severity severity = getSeverity(argInfo); if (!mSettings->severity.isEnabled(severity)) return; std::ostringstream errmsg; errmsg << "%" << specifier << " in format string (no. " << numFormat << ") requires "; printfFormatType(errmsg, specifier, false); errmsg << " but the argument type is "; argumentType(errmsg, argInfo); errmsg << "."; reportError(tok, severity, "invalidPrintfArgType_sint", errmsg.str(), CWE686, Certainty::normal); } void CheckIO::invalidPrintfArgTypeError_float(const Token* tok, nonneg int numFormat, const std::string& specifier, const ArgumentInfo* argInfo) { const Severity severity = getSeverity(argInfo); if (!mSettings->severity.isEnabled(severity)) return; std::ostringstream errmsg; errmsg << "%" << specifier << " in format string (no. " << numFormat << ") requires \'"; if (specifier[0] == 'L') errmsg << "long "; errmsg << "double\' but the argument type is "; argumentType(errmsg, argInfo); errmsg << "."; reportError(tok, severity, "invalidPrintfArgType_float", errmsg.str(), CWE686, Certainty::normal); } Severity CheckIO::getSeverity(const CheckIO::ArgumentInfo *argInfo) { return (argInfo && argInfo->typeToken && !argInfo->typeToken->originalName().empty()) ? Severity::portability : Severity::warning; } void CheckIO::argumentType(std::ostream& os, const ArgumentInfo * argInfo) { if (argInfo) { os << "\'"; const Token *type = argInfo->typeToken; if (type->tokType() == Token::eString) { if (type->isLong()) os << "const wchar_t *"; else os << "const char *"; } else { if (type->originalName().empty()) { if (type->strAt(-1) == "const") os << "const "; while (Token::Match(type, "const|struct")) { os << type->str() << " "; type = type->next(); } while (Token::Match(type, "%any% ::")) { os << type->str() << "::"; type = type->tokAt(2); } os << type->stringify(false, true, false); if (type->strAt(1) == "*" && !argInfo->element) os << " *"; else if (argInfo->variableInfo && !argInfo->element && argInfo->variableInfo->isArray()) os << " *"; else if (type->strAt(1) == "*" && argInfo->variableInfo && argInfo->element && argInfo->variableInfo->isArray()) os << " *"; if (argInfo->address) os << " *"; } else { if (type->isUnsigned()) { if (type->originalName() == "__int64" || type->originalName() == "__int32" || type->originalName() == "ptrdiff_t") os << "unsigned "; } os << type->originalName(); if (type->strAt(1) == "*" || argInfo->address) os << " *"; os << " {aka " << type->stringify(false, true, false); if (type->strAt(1) == "*" || argInfo->address) os << " *"; os << "}"; } } os << "\'"; } else os << "Unknown"; } void CheckIO::invalidLengthModifierError(const Token* tok, nonneg int numFormat, const std::string& modifier) { if (!mSettings->severity.isEnabled(Severity::warning)) return; std::ostringstream errmsg; errmsg << "'" << modifier << "' in format string (no. " << numFormat << ") is a length modifier and cannot be used without a conversion specifier."; reportError(tok, Severity::warning, "invalidLengthModifierError", errmsg.str(), CWE704, Certainty::normal); } void CheckIO::invalidScanfFormatWidthError(const Token* tok, nonneg int numFormat, int width, const Variable *var, const std::string& specifier) { MathLib::bigint arrlen = 0; std::string varname; if (var) { arrlen = var->dimension(0); varname = var->name(); } std::ostringstream errmsg; if (arrlen > width) { if (tok != nullptr && (!mSettings->certainty.isEnabled(Certainty::inconclusive) || !mSettings->severity.isEnabled(Severity::warning))) return; errmsg << "Width " << width << " given in format string (no. " << numFormat << ") is smaller than destination buffer" << " '" << varname << "[" << arrlen << "]'."; reportError(tok, Severity::warning, "invalidScanfFormatWidth_smaller", errmsg.str(), CWE(0U), Certainty::inconclusive); } else { errmsg << "Width " << width << " given in format string (no. " << numFormat << ") is larger than destination buffer '" << varname << "[" << arrlen << "]', use %" << (specifier == "c" ? arrlen : (arrlen - 1)) << specifier << " to prevent overflowing it."; reportError(tok, Severity::error, "invalidScanfFormatWidth", errmsg.str(), CWE687, Certainty::normal); } }