/*
 * Cppcheck - A tool for static C/C++ code analysis
 * Copyright (C) 2007-2012 Daniel Marjamäki and 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 "tokenize.h"
#include "token.h"
#include "errorlogger.h"
#include "symboldatabase.h"

#include <cctype>
#include <cstdlib>

//---------------------------------------------------------------------------

// Register CheckIO..
namespace {
    CheckIO instance;
}


//---------------------------------------------------------------------------
//    std::cout << std::cout;
//---------------------------------------------------------------------------
void CheckIO::checkCoutCerrMisusage()
{
    bool firstCout = false;
    for (const Token *tok = _tokenizer->tokens(); tok; tok = tok->next()) {
        if (tok->str() == "(")
            tok = tok->link();

        if (Token::Match(tok, "std :: cout|cerr")) {
            if (firstCout && tok->strAt(-1) == "<<" && tok->strAt(3) != ".") {
                coutCerrMisusageError(tok, tok->strAt(2));
                firstCout = false;
            } else if (tok->strAt(3) == "<<")
                firstCout = true;
        } else if (firstCout && tok->str() == ";")
            firstCout = false;
    }
}

void CheckIO::coutCerrMisusageError(const Token* tok, const std::string& streamName)
{
    reportError(tok, Severity::error, "coutCerrMisusage", "Invalid usage of output stream: '<< std::" + streamName + "'.");
}

//---------------------------------------------------------------------------
// fflush(stdin) <- fflush only applies to output streams in ANSI C
// fread(); fwrite(); <- consecutive read/write statements require repositioning inbetween
// fopen("","r"); fwrite(); <- write to read-only file (or vice versa)
// fclose(); fread(); <- Use closed file
//---------------------------------------------------------------------------
enum OpenMode {CLOSED, READ_MODE, WRITE_MODE, RW_MODE, UNKNOWN};
static OpenMode getMode(const std::string& str)
{
    if (str.find('+', 1) != std::string::npos)
        return RW_MODE;
    else if (str.find('w') != std::string::npos || str.find('a') != std::string::npos)
        return WRITE_MODE;
    else if (str.find('r') != std::string::npos)
        return READ_MODE;
    return UNKNOWN;
}

struct Filepointer {
    OpenMode mode;
    unsigned int mode_indent;
    enum Operation {NONE, UNIMPORTANT, READ, WRITE, POSITIONING, OPEN, CLOSE, UNKNOWN_OP} lastOperation;
    unsigned int op_indent;
    Filepointer(OpenMode mode_ = UNKNOWN)
        : mode(mode_), mode_indent(0), lastOperation(NONE), op_indent(0) {
    }
};

void CheckIO::checkFileUsage()
{
    static const char* _whitelist[] = {
        "clearerr", "feof", "ferror", "fgetpos", "ftell", "setbuf", "setvbuf", "ungetc"
    };
    static const std::set<std::string> whitelist(_whitelist, _whitelist + sizeof(_whitelist)/sizeof(*_whitelist));

    std::map<unsigned int, Filepointer> filepointers;

    const SymbolDatabase* symbolDatabase = _tokenizer->getSymbolDatabase();
    size_t varListSize = symbolDatabase->getVariableListSize();
    for (size_t i = 1; i < varListSize; ++i) {
        const Variable* var = symbolDatabase->getVariableFromVarId(i);
        if (!var || !var->varId() || !Token::Match(var->typeStartToken(), "FILE *"))
            continue;

        if (var->isLocal()) {
            if (var->nameToken()->strAt(1) == "(") // initialize by calling "ctor"
                filepointers.insert(std::make_pair(var->varId(), Filepointer(UNKNOWN)));
            else
                filepointers.insert(std::make_pair(var->varId(), Filepointer(CLOSED)));
        } else {
            filepointers.insert(std::make_pair(var->varId(), Filepointer(UNKNOWN)));
            // TODO: If all fopen calls we find open the file in the same type, we can set Filepointer::mode
        }
    }

    unsigned int indent = 0;
    for (const Token* tok = _tokenizer->list.front(); tok; tok = tok->next()) {
        if (tok->str() == "{")
            indent++;
        else if (tok->str() == "}") {
            indent--;
            for (std::map<unsigned int, Filepointer>::iterator i = filepointers.begin(); i != filepointers.end(); ++i) {
                if (indent < i->second.mode_indent) {
                    i->second.mode_indent = 0;
                    i->second.mode = UNKNOWN;
                }
                if (indent < i->second.op_indent) {
                    i->second.op_indent = 0;
                    i->second.lastOperation = Filepointer::UNKNOWN_OP;
                }
            }
        } else if (tok->varId() && Token::Match(tok, "%var% =") && (tok->strAt(2) != "fopen" && tok->strAt(2) != "freopen" && tok->strAt(2) != "tmpfile")) {
            std::map<unsigned int, Filepointer>::iterator i = filepointers.find(tok->varId());
            if (i != filepointers.end()) {
                i->second.mode = UNKNOWN;
                i->second.lastOperation = Filepointer::UNKNOWN_OP;
            }
        } else if (Token::Match(tok, "%var% (") && tok->previous() && (!tok->previous()->isName() || Token::Match(tok->previous(), "return|throw"))) {
            std::string mode;
            const Token* fileTok = 0;
            Filepointer::Operation operation = Filepointer::NONE;

            if ((tok->str() == "fopen" || tok->str() == "freopen" || tok->str() == "tmpfile") && tok->strAt(-1) == "=") {
                if (tok->str() != "tmpfile") {
                    const Token* modeTok = tok->tokAt(2)->nextArgument();
                    if (modeTok && modeTok->type() == Token::eString)
                        mode = modeTok->strValue();
                } else
                    mode = "wb+";
                fileTok = tok->tokAt(-2);
                operation = Filepointer::OPEN;
            } else if (tok->str() == "rewind" || tok->str() == "fseek" || tok->str() == "fsetpos" || tok->str() == "fflush") {
                if (Token::Match(tok, "fflush ( stdin )"))
                    fflushOnInputStreamError(tok, tok->strAt(2));
                else {
                    fileTok = tok->tokAt(2);
                    operation = Filepointer::POSITIONING;
                }
            } else if (tok->str() == "fgetc" || tok->str() == "fgets" || tok->str() == "fread" || tok->str() == "fscanf" || tok->str() == "getc") {
                if (tok->str() == "fscanf")
                    fileTok = tok->tokAt(2);
                else
                    fileTok = tok->linkAt(1)->previous();
                operation = Filepointer::READ;
            } else if (tok->str() == "fputc" || tok->str() == "fputs" || tok->str() == "fwrite" || tok->str() == "fprintf" || tok->str() == "putcc") {
                if (tok->str() == "fprintf")
                    fileTok = tok->tokAt(2);
                else
                    fileTok = tok->linkAt(1)->previous();
                operation = Filepointer::WRITE;
            } else if (tok->str() == "fclose") {
                fileTok = tok->tokAt(2);
                operation = Filepointer::CLOSE;
            } else if (whitelist.find(tok->str()) != whitelist.end()) {
                fileTok = tok->tokAt(2);
                if (tok->str() == "ungetc" && fileTok)
                    fileTok = fileTok->nextArgument();
                operation = Filepointer::UNIMPORTANT;
            } else if (!Token::Match(tok, "if|for|while|catch|return")) {
                const Token* const end2 = tok->linkAt(1);
                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::UNKNOWN_OP; // Assume that repositioning was last operation and that the file is opened now
                        break;
                    }
                }
            }

            if (!fileTok || !fileTok->varId())
                continue;

            if (filepointers.find(fileTok->varId()) == filepointers.end()) { // function call indicates: Its a File
                filepointers.insert(std::make_pair(fileTok->varId(), Filepointer(UNKNOWN)));
            }
            Filepointer& f = filepointers[fileTok->varId()];

            switch (operation) {
            case Filepointer::OPEN:
                f.mode = getMode(mode);
                f.mode_indent = indent;
                break;
            case Filepointer::POSITIONING:
                if (f.mode == CLOSED)
                    useClosedFileError(tok);
                break;
            case Filepointer::READ:
                if (f.mode == CLOSED)
                    useClosedFileError(tok);
                else if (f.mode == WRITE_MODE)
                    readWriteOnlyFileError(tok);
                else if (f.lastOperation == Filepointer::WRITE)
                    ioWithoutPositioningError(tok);
                break;
            case Filepointer::WRITE:
                if (f.mode == CLOSED)
                    useClosedFileError(tok);
                else if (f.mode == READ_MODE)
                    writeReadOnlyFileError(tok);
                else if (f.lastOperation == Filepointer::READ)
                    ioWithoutPositioningError(tok);
                break;
            case Filepointer::CLOSE:
                if (f.mode == CLOSED)
                    useClosedFileError(tok);
                else
                    f.mode = CLOSED;
                f.mode_indent = indent;
                break;
            case Filepointer::UNIMPORTANT:
                if (f.mode == CLOSED)
                    useClosedFileError(tok);
                break;
            case Filepointer::UNKNOWN_OP:
                f.mode = UNKNOWN;
                f.mode_indent = 0;
                break;
            default:
                break;
            }
            if (operation != Filepointer::NONE && operation != Filepointer::UNIMPORTANT) {
                f.op_indent = indent;
                f.lastOperation = operation;
            }
        }
    }
}

void CheckIO::fflushOnInputStreamError(const Token *tok, const std::string &varname)
{
    reportError(tok, Severity::error,
                "fflushOnInputStream", "fflush() called on input stream \"" + varname + "\" may result in undefined behaviour.");
}

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 inbetween result in undefined behaviour.");
}

void CheckIO::readWriteOnlyFileError(const Token *tok)
{

    reportError(tok, Severity::error,
                "readWriteOnlyFile", "Read operation on a file that was only opened for writing.");
}

void CheckIO::writeReadOnlyFileError(const Token *tok)
{
    reportError(tok, Severity::error,
                "writeReadOnlyFile", "Write operation on a file that was only opened for reading.");
}

void CheckIO::useClosedFileError(const Token *tok)
{
    reportError(tok, Severity::error,
                "useClosedFile", "Used file that is not opened.");
}


//---------------------------------------------------------------------------
// scanf without field width limits can crash with huge input data
//---------------------------------------------------------------------------
void CheckIO::invalidScanf()
{
    if (!_settings->isEnabled("style"))
        return;
    for (const Token *tok = _tokenizer->tokens(); tok; tok = tok->next()) {
        const Token *formatToken = 0;
        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->type() == Token::eString)
                formatToken = nextArg;
            else
                continue;
        } else
            continue;

        bool format = false;

        // scan the string backwards, so we dont need to keep states
        const std::string &formatstr(formatToken->str());
        for (unsigned int i = 1; i < formatstr.length(); i++) {
            if (formatstr[i] == '%')
                format = !format;

            else if (!format)
                continue;

            else if (std::isdigit(formatstr[i])) {
                format = false;
            }

            else if (std::isalpha(formatstr[i])) {
                if (formatstr[i] != 'c')  // #3490 - field width limits are not necessary for %c
                    invalidScanfError(tok);
                format = false;
            }
        }
    }
}

void CheckIO::invalidScanfError(const Token *tok)
{
    reportError(tok, Severity::warning,
                "invalidscanf", "scanf without field width limits can crash with huge input data\n"
                "scanf without field width limits can crash with huge input data. To fix this error "
                "message add a field width specifier:\n"
                "    %s => %20s\n"
                "    %i => %3i\n"
                "\n"
                "Sample program that can crash:\n"
                "\n"
                "#include <stdio.h>\n"
                "int main()\n"
                "{\n"
                "    int a;\n"
                "    scanf(\"%i\", &a);\n"
                "    return 0;\n"
                "}\n"
                "\n"
                "To make it crash:\n"
                "perl -e 'print \"5\"x2100000' | ./a.out");
}

//---------------------------------------------------------------------------
//    printf("%u", "xyz"); // Wrong argument type
//    printf("%u%s", 1); // Too few arguments
//    printf("", 1); // Too much arguments
//---------------------------------------------------------------------------
static bool isComplexType(const Variable* var, const Token* varTypeTok)
{
    if (var->type())
        return(true);

    static std::set<std::string> knownTypes;
    if (knownTypes.empty()) {
        knownTypes.insert("struct"); // If a type starts with the struct keyword, its a complex type
        knownTypes.insert("string");
    }

    if (varTypeTok->str() == "std")
        varTypeTok = varTypeTok->tokAt(2);
    return(knownTypes.find(varTypeTok->str()) != knownTypes.end() && !var->isPointer() && !var->isArray());
}

static bool isKnownType(const Variable* var, const Token* varTypeTok)
{
    return(varTypeTok->isStandardType() || varTypeTok->next()->isStandardType() || isComplexType(var, varTypeTok));
}

void CheckIO::checkWrongPrintfScanfArguments()
{
    const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase();

    for (const Token *tok = _tokenizer->tokens(); tok; tok = tok->next()) {
        if (!tok->isName()) continue;

        const Token* argListTok = 0; // Points to first va_list argument
        std::string formatString;

        if (Token::Match(tok, "printf|scanf ( %str%")) {
            formatString = tok->strAt(2);
            if (tok->strAt(3) == ",") {
                argListTok = tok->tokAt(4);
            } else if (tok->strAt(3) == ")") {
                argListTok = 0;
            } else {
                continue;
            }
        } else if (Token::Match(tok, "sprintf|fprintf|sscanf|fscanf ( %any%")) {
            const Token* formatStringTok = tok->tokAt(2)->nextArgument(); // Find second parameter (format string)
            if (Token::Match(formatStringTok, "%str% ,")) {
                argListTok = formatStringTok->nextArgument(); // Find third parameter (first argument of va_args)
                formatString = formatStringTok->str();
            } else if (Token::Match(formatStringTok, "%str% )")) {
                argListTok = 0; // Find third parameter (first argument of va_args)
                formatString = formatStringTok->str();
            } else {
                continue;
            }
        } else if (Token::Match(tok, "snprintf|fnprintf (")) {
            const Token* formatStringTok = tok->tokAt(2);
            for (int i = 0; i < 2 && formatStringTok; i++) {
                formatStringTok = formatStringTok->nextArgument(); // Find third parameter (format string)
            }
            if (Token::Match(formatStringTok, "%str% ,")) {
                argListTok = formatStringTok->nextArgument(); // Find fourth parameter (first argument of va_args)
                formatString = formatStringTok->str();
            } else if (Token::Match(formatStringTok, "%str% )")) {
                argListTok = 0; // Find fourth parameter (first argument of va_args)
                formatString = formatStringTok->str();
            } else {
                continue;
            }
        } else {
            continue;
        }

        // Count format string parameters..
        bool scan = Token::Match(tok, "sscanf|fscanf|scanf");
        unsigned int numFormat = 0;
        bool percent = false;
        const Token* argListTok2 = argListTok;
        for (std::string::iterator i = formatString.begin(); i != formatString.end(); ++i) {
            if (*i == '%') {
                percent = !percent;
            } else if (percent && *i == '[') {
                while (i != formatString.end()) {
                    if (*i == ']') {
                        numFormat++;
                        if (argListTok)
                            argListTok = argListTok->nextArgument();
                        percent = false;
                        break;
                    }
                    ++i;
                }
                if (i == formatString.end())
                    break;
            } else if (percent) {
                percent = false;

                bool _continue = false;
                std::string width;
                while (i != formatString.end() && *i != ']' && !std::isalpha(*i)) {
                    if (*i == '*') {
                        if (scan)
                            _continue = true;
                        else {
                            numFormat++;
                            if (argListTok)
                                argListTok = argListTok->nextArgument();
                        }
                    } else if (std::isdigit(*i))
                        width += *i;
                    ++i;
                }
                if (i == formatString.end())
                    break;
                if (_continue)
                    continue;

                if (scan || *i != 'm') { // %m is a non-standard extension that requires no parameter on print functions.
                    numFormat++;

                    // Perform type checks
                    if (_settings->isEnabled("style") && argListTok && Token::Match(argListTok->next(), "[,)]")) { // We can currently only check the type of arguments matching this simple pattern.
                        const Variable* variableInfo = symbolDatabase->getVariableFromVarId(argListTok->varId());
                        const Token* varTypeTok = variableInfo ? variableInfo->typeStartToken() : NULL;
                        if (varTypeTok && varTypeTok->str() == "static")
                            varTypeTok = varTypeTok->next();

                        if (scan && varTypeTok) {
                            if ((!variableInfo->isPointer() && !variableInfo->isArray()) || varTypeTok->strAt(-1) == "const")
                                invalidScanfArgTypeError(tok, tok->str(), numFormat);

                            if (*i == 's' && variableInfo && isKnownType(variableInfo, varTypeTok) && variableInfo->isArray() && (variableInfo->dimensions().size() == 1)) {
                                if (width.empty())
                                    missingScanfFormatWidthError(tok, tok->str(), numFormat, variableInfo);
                                else {
                                    int numWidth = std::atoi(width.c_str());
                                    if (numWidth  != (variableInfo->dimension(0) - 1))
                                        invalidScanfFormatWidthError(tok, tok->str(), numFormat, numWidth, variableInfo);
                                }
                            }
                        } else if (!scan) {
                            switch (*i) {
                            case 's':
                                if (variableInfo && argListTok->type() != Token::eString && isKnownType(variableInfo, varTypeTok) && (!variableInfo->isPointer() && !variableInfo->isArray()))
                                    invalidPrintfArgTypeError_s(tok, numFormat);
                                break;
                            case 'n':
                                if ((varTypeTok && isKnownType(variableInfo, varTypeTok) && ((!variableInfo->isPointer() && !variableInfo->isArray()) || varTypeTok->strAt(-1) == "const")) || argListTok->type() == Token::eString)
                                    invalidPrintfArgTypeError_n(tok, numFormat);
                                break;
                            case 'c':
                            case 'd':
                            case 'i':
                            case 'u':
                            case 'x':
                            case 'X':
                            case 'o':
                                if (varTypeTok && varTypeTok->str() == "const")
                                    varTypeTok = varTypeTok->next();
                                if ((varTypeTok && isKnownType(variableInfo, varTypeTok) && !Token::Match(varTypeTok, "unsigned|signed| bool|short|long|int|char|size_t|unsigned|signed") && !variableInfo->isPointer() && !variableInfo->isArray()))
                                    invalidPrintfArgTypeError_int(tok, numFormat, *i);
                                else if (argListTok->type() == Token::eString)
                                    invalidPrintfArgTypeError_int(tok, numFormat, *i);
                                break;
                            case 'p':
                                if (varTypeTok && varTypeTok->str() == "const")
                                    varTypeTok = varTypeTok->next();
                                if (varTypeTok && isKnownType(variableInfo, varTypeTok) && !Token::Match(varTypeTok, "unsigned|signed| short|long|int|size_t|unsigned|signed") && !variableInfo->isPointer() && !variableInfo->isArray())
                                    invalidPrintfArgTypeError_p(tok, numFormat);
                                else if (argListTok->type() == Token::eString)
                                    invalidPrintfArgTypeError_p(tok, numFormat);
                                break;
                            case 'e':
                            case 'E':
                            case 'f':
                            case 'g':
                            case 'G':
                                if (varTypeTok && varTypeTok->str() == "const")
                                    varTypeTok = varTypeTok->next();
                                if (varTypeTok && ((isKnownType(variableInfo, varTypeTok) && !Token::Match(varTypeTok, "float|double")) || variableInfo->isPointer() || variableInfo->isArray()))
                                    invalidPrintfArgTypeError_float(tok, numFormat, *i);
                                else if (argListTok->type() == Token::eString)
                                    invalidPrintfArgTypeError_float(tok, numFormat, *i);
                                break;
                            default:
                                break;
                            }
                        }
                    }

                    if (argListTok)
                        argListTok = argListTok->nextArgument(); // Find next argument
                }
            }
        }

        // Count printf/scanf parameters..
        unsigned int numFunction = 0;
        while (argListTok2) {
            numFunction++;
            argListTok2 = argListTok2->nextArgument(); // Find next argument
        }

        // Mismatching number of parameters => warning
        if (numFormat != numFunction)
            wrongPrintfScanfArgumentsError(tok, tok->str(), numFormat, numFunction);
    }
}

void CheckIO::wrongPrintfScanfArgumentsError(const Token* tok,
        const std::string &functionName,
        unsigned int numFormat,
        unsigned int numFunction)
{
    Severity::SeverityType severity = numFormat > numFunction ? Severity::error : Severity::warning;
    if (severity != Severity::error && !_settings->isEnabled("style"))
        return;

    std::ostringstream errmsg;
    errmsg << functionName
           << " format string has "
           << numFormat
           << " parameters but "
           << (numFormat > numFunction ? "only " : "")
           << numFunction
           << " are given";

    reportError(tok, severity, "wrongPrintfScanfArgNum", errmsg.str());
}

void CheckIO::invalidScanfArgTypeError(const Token* tok, const std::string &functionName, unsigned int numFormat)
{
    std::ostringstream errmsg;
    errmsg << functionName << " argument no. " << numFormat << ": requires non-const pointers or arrays as arguments";
    reportError(tok, Severity::warning, "invalidScanfArgType", errmsg.str());
}
void CheckIO::invalidPrintfArgTypeError_s(const Token* tok, unsigned int numFormat)
{
    std::ostringstream errmsg;
    errmsg << "%s in format string (no. " << numFormat << ") requires a char* given in the argument list";
    reportError(tok, Severity::warning, "invalidPrintfArgType_s", errmsg.str());
}
void CheckIO::invalidPrintfArgTypeError_n(const Token* tok, unsigned int numFormat)
{
    std::ostringstream errmsg;
    errmsg << "%n in format string (no. " << numFormat << ") requires a pointer to an non-const integer given in the argument list";
    reportError(tok, Severity::warning, "invalidPrintfArgType_n", errmsg.str());
}
void CheckIO::invalidPrintfArgTypeError_p(const Token* tok, unsigned int numFormat)
{
    std::ostringstream errmsg;
    errmsg << "%p in format string (no. " << numFormat << ") requires an integer or pointer given in the argument list";
    reportError(tok, Severity::warning, "invalidPrintfArgType_p", errmsg.str());
}
void CheckIO::invalidPrintfArgTypeError_int(const Token* tok, unsigned int numFormat, char c)
{
    std::ostringstream errmsg;
    errmsg << "%" << c << " in format string (no. " << numFormat << ") requires an integer given in the argument list";
    reportError(tok, Severity::warning, "invalidPrintfArgType_int", errmsg.str());
}
void CheckIO::invalidPrintfArgTypeError_float(const Token* tok, unsigned int numFormat, char c)
{
    std::ostringstream errmsg;
    errmsg << "%" << c << " in format string (no. " << numFormat << ") requires a floating point number given in the argument list";
    reportError(tok, Severity::warning, "invalidPrintfArgType_float", errmsg.str());
}
void CheckIO::missingScanfFormatWidthError(const Token* tok, const std::string &functionName, unsigned int numFormat, const Variable *var)
{
    std::ostringstream errmsg;
    errmsg << functionName << " %s in format string (no. " << numFormat << ") does not specify a width";
    if (var)
        errmsg << ", use %" << (var->dimension(0) - 1) << "s to prevent overflowing destination: " << var->name() << "[" << var->dimension(0) << "]";
    reportError(tok, Severity::warning, "missingScanfFormatWidth", errmsg.str());
}
void CheckIO::invalidScanfFormatWidthError(const Token* tok, const std::string &functionName, unsigned int numFormat, int width, const Variable *var)
{
    std::ostringstream errmsg;
    Severity::SeverityType severity = Severity::warning;
    bool inconclusive = false;

    if (var) {
        if (var->dimension(0) > width) {
            if (!_settings->inconclusive)
                return;
            inconclusive = true;
            errmsg << functionName << " width " << width << " in format string (no. " << numFormat << ") is smaller than destination"
                   << ": " << var->name() << "[" << var->dimension(0) << "]";
        } else {
            errmsg << functionName << " width " << width << " in format string (no. " << numFormat << ") is larger than destination"
                   << ", use %" << (var->dimension(0) - 1) << "s to prevent overflowing destination: " << var->name() << "[" << var->dimension(0) << "]";
            severity = Severity::error;
        }

    } else
        errmsg << functionName << " width " << width << " in format string (no. " << numFormat << ") doesn't match destination";

    reportError(tok, severity, "invalidScanfFormatWidth", errmsg.str(), inconclusive);
}
