cppcheck/lib/checkio.cpp

2037 lines
110 KiB
C++

/*
* 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 <http://www.gnu.org/licenses/>.
*/
//---------------------------------------------------------------------------
#include "checkio.h"
#include "astutils.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 <algorithm>
#include <cctype>
#include <cstdlib>
#include <functional>
#include <list>
#include <map>
#include <set>
#include <sstream> // IWYU pragma: keep
#include <unordered_set>
#include <utility>
#include <vector>
//---------------------------------------------------------------------------
// 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<std::string> 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<int, Filepointer> 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 (Token::Match(tok, "%name% (") && isUnevaluated(tok)) {
tok = tok->linkAt(1);
continue;
}
if (tok->str() == "{")
indent++;
else if (tok->str() == "}") {
indent--;
for (std::pair<const int, Filepointer>& 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<const int, Filepointer>& 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<int, Filepointer>::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<const int, Filepointer>& 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<int, Filepointer>::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<const int, Filepointer>& 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 <stdio.h>\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<ValueFlow::Value>::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 <size_t size> 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 <size_t size> 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<int> 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<int>(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<int>(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<int>(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<Token*>(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<std::string> stl_vector = { "array", "vector" };
const std::set<std::string> 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<Type::BaseInfo>& 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<std::string> 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);
}
}