2026 lines
110 KiB
C++
2026 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 "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;
|
|
|
|
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;
|
|
else if (str.find('w') != std::string::npos || str.find('a') != std::string::npos)
|
|
return OpenMode::WRITE_MODE;
|
|
else if (str.find('r') != std::string::npos)
|
|
return OpenMode::READ_MODE;
|
|
return OpenMode::UNKNOWN_OM;
|
|
}
|
|
|
|
struct Filepointer {
|
|
OpenMode mode;
|
|
nonneg int mode_indent;
|
|
enum class Operation {NONE, UNIMPORTANT, READ, WRITE, POSITIONING, OPEN, CLOSE, UNKNOWN_OP} lastOperation;
|
|
nonneg int op_indent;
|
|
enum class AppendMode { UNKNOWN_AM, APPEND, APPEND_EX };
|
|
AppendMode append_mode;
|
|
std::string filename;
|
|
explicit Filepointer(OpenMode mode_ = OpenMode::UNKNOWN_OM)
|
|
: mode(mode_), mode_indent(0), lastOperation(Operation::NONE), op_indent(0), append_mode(AppendMode::UNKNOWN_AM) {}
|
|
};
|
|
|
|
namespace {
|
|
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;
|
|
|
|
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<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;
|
|
} else 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();
|
|
|
|
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)
|
|
: variableInfo(nullptr)
|
|
, typeToken(nullptr)
|
|
, functionInfo(nullptr)
|
|
, tempToken(nullptr)
|
|
, element(false)
|
|
, _template(false)
|
|
, address(false)
|
|
, 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;
|
|
} else 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;
|
|
} else 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;
|
|
} else 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;
|
|
} else 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;
|
|
} else 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;
|
|
} else if (variable->isStlType(stl_string)) {
|
|
typeToken = variable->typeStartToken();
|
|
return true;
|
|
} else 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;
|
|
} else if (Token::Match(nameTok, "std :: string|wstring")) {
|
|
typeToken = nameTok;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CheckIO::ArgumentInfo::isArrayOrPointer() const
|
|
{
|
|
if (address)
|
|
return true;
|
|
else if (variableInfo && !_template) {
|
|
return variableInfo->isArrayOrPointer();
|
|
} else {
|
|
const Token *tok = typeToken;
|
|
while (Token::Match(tok, "const|struct"))
|
|
tok = tok->next();
|
|
if (tok && tok->strAt(1) == "*")
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
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());
|
|
else 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::SeverityType 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::SeverityType 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::SeverityType 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::SeverityType 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::SeverityType 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::SeverityType 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::SeverityType 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::SeverityType 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::SeverityType 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::SeverityType 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::SeverityType 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);
|
|
}
|
|
}
|