diff --git a/lib/checkvaarg.cpp b/lib/checkvaarg.cpp
new file mode 100644
index 000000000..1fd3d5e9c
--- /dev/null
+++ b/lib/checkvaarg.cpp
@@ -0,0 +1,142 @@
+/*
+ * Cppcheck - A tool for static C/C++ code analysis
+ * Copyright (C) 2007-2014 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 .
+ */
+
+#include "checkvaarg.h"
+#include "symboldatabase.h"
+
+//---------------------------------------------------------------------------
+
+// Register this check class (by creating a static instance of it)
+namespace {
+ CheckVaarg instance;
+}
+
+
+//---------------------------------------------------------------------------
+// Ensure that correct parameter is passed to va_start()
+//---------------------------------------------------------------------------
+
+void CheckVaarg::va_start_argument()
+{
+ const SymbolDatabase* const symbolDatabase = _tokenizer->getSymbolDatabase();
+ const std::size_t functions = symbolDatabase->functionScopes.size();
+ for (std::size_t i = 0; i < functions; ++i) {
+ const Scope* scope = symbolDatabase->functionScopes[i];
+ const Function* function = scope->function;
+ for (const Token* tok = scope->classStart->next(); tok != scope->classEnd; tok = tok->next()) {
+ if (Token::simpleMatch(tok, "va_start (")) {
+ const Token* param2 = tok->tokAt(2)->nextArgument();
+ const Variable* var = param2->variable();
+ if (var && var->isReference())
+ referenceAs_va_start_error(param2, var->name());
+ if (var->index() + 2 < function->argCount() && _settings->isEnabled("warning")) {
+ std::list::const_reverse_iterator it = function->argumentList.rbegin();
+ it++;
+ wrongParameterTo_va_start_error(tok, var->name(), it->name());
+ }
+ tok = tok->linkAt(1);
+ }
+ }
+ }
+}
+
+void CheckVaarg::wrongParameterTo_va_start_error(const Token *tok, const std::string& paramIsName, const std::string& paramShouldName)
+{
+ reportError(tok, Severity::warning,
+ "va_start_wrongParameter", "'" + paramIsName + "' given to va_start() is not last named argument of the function. Did you intend to pass '" + paramShouldName + "'?");
+}
+
+void CheckVaarg::referenceAs_va_start_error(const Token *tok, const std::string& paramName)
+{
+ reportError(tok, Severity::error,
+ "va_start_referencePassed", "Using reference '" + paramName + "' as parameter for va_start() results in undefined behaviour.");
+}
+
+//---------------------------------------------------------------------------
+// Detect missing va_end() if va_start() was used
+// Detect va_list usage after va_end()
+//---------------------------------------------------------------------------
+
+void CheckVaarg::va_list_usage()
+{
+ const SymbolDatabase* const symbolDatabase = _tokenizer->getSymbolDatabase();
+ for (unsigned int varid = 1; varid < symbolDatabase->getVariableListSize(); varid++) {
+ const Variable* var = symbolDatabase->getVariableFromVarId(varid);
+ if (!var || var->isPointer() || var->isReference() || var->isArray() || !var->scope() || var->typeStartToken()->str() != "va_list")
+ continue;
+ if (!var->isLocal() && !var->isArgument()) // Check only local variables and arguments
+ continue;
+
+ bool open = var->isArgument(); // va_list passed as argument are opened
+ bool exitOnEndOfStatement = false;
+
+ const Token* tok = var->nameToken()->next();
+ for (const Token* tok = var->nameToken()->next(); tok != var->scope()->classEnd; tok = tok->next()) {
+ if (Token::Match(tok, "va_start ( %varid% ,", var->declarationId())) {
+ if (open)
+ va_start_subsequentCallsError(tok, var->name());
+ open = true;
+ tok = tok->linkAt(1);
+ } else if (Token::Match(tok, "va_end ( %varid% )", var->declarationId())) {
+ if (!open)
+ va_list_usedBeforeStartedError(tok, var->name());
+ open = false;
+ tok = tok->linkAt(1);
+ } else if (Token::simpleMatch(tok, "va_copy (")) {
+ bool nopen = open;
+ if (tok->linkAt(1)->previous()->varId() == var->declarationId()) { // Source
+ if (!open)
+ va_list_usedBeforeStartedError(tok, var->name());
+ nopen = false;
+ }
+ if (tok->tokAt(2)->varId() == var->declarationId()) { // Destination
+ if (open)
+ va_start_subsequentCallsError(tok, var->name());
+ nopen = true;
+ }
+ open = nopen;
+ tok = tok->linkAt(1);
+ } else if (Token::Match(tok, "throw|return"))
+ exitOnEndOfStatement = true;
+ else if (!open && tok->varId() == var->declarationId())
+ va_list_usedBeforeStartedError(tok, var->name());
+ else if (exitOnEndOfStatement && tok->str() == ";")
+ break;
+ }
+ if (open && !var->isArgument())
+ va_end_missingError(tok, var->name());
+ }
+}
+
+void CheckVaarg::va_end_missingError(const Token *tok, const std::string& varname)
+{
+ reportError(tok, Severity::error,
+ "va_end_missing", "va_list '" + varname + "' was opened but not closed by va_end().");
+}
+
+void CheckVaarg::va_list_usedBeforeStartedError(const Token *tok, const std::string& varname)
+{
+ reportError(tok, Severity::error,
+ "va_list_usedBeforeStarted", "va_list '" + varname + "' used before va_start() was called.");
+}
+
+void CheckVaarg::va_start_subsequentCallsError(const Token *tok, const std::string& varname)
+{
+ reportError(tok, Severity::error,
+ "va_start_subsequentCalls", "va_start() or va_copy() called subsequently on '" + varname + "' without va_end() inbetween.");
+}
diff --git a/lib/checkvaarg.h b/lib/checkvaarg.h
new file mode 100644
index 000000000..fb7dced9d
--- /dev/null
+++ b/lib/checkvaarg.h
@@ -0,0 +1,86 @@
+/*
+ * Cppcheck - A tool for static C/C++ code analysis
+ * Copyright (C) 2007-2014 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 .
+ */
+
+
+//---------------------------------------------------------------------------
+#ifndef checkvaargtH
+#define checkvaargtH
+//---------------------------------------------------------------------------
+
+#include "config.h"
+#include "check.h"
+
+/// @addtogroup Checks
+/// @{
+
+/**
+ * @brief Checking for misusage of variable argument lists
+ */
+
+class CPPCHECKLIB CheckVaarg : public Check {
+public:
+ CheckVaarg() : Check(myName()) {
+ }
+
+ CheckVaarg(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger)
+ : Check(myName(), tokenizer, settings, errorLogger) {
+ }
+
+ virtual void runSimplifiedChecks(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) {
+ CheckVaarg check(tokenizer, settings, errorLogger);
+ check.va_start_argument();
+ check.va_list_usage();
+ }
+
+ void va_start_argument();
+ void va_list_usage();
+
+private:
+ void wrongParameterTo_va_start_error(const Token *tok, const std::string& paramIsName, const std::string& paramShouldName);
+ void referenceAs_va_start_error(const Token *tok, const std::string& paramName);
+ void va_end_missingError(const Token *tok, const std::string& varname);
+ void va_list_usedBeforeStartedError(const Token *tok, const std::string& varname);
+ void va_start_subsequentCallsError(const Token *tok, const std::string& varname);
+
+ void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const {
+ CheckVaarg c(0, settings, errorLogger);
+ c.wrongParameterTo_va_start_error(0, "arg1", "arg2");
+ c.referenceAs_va_start_error(0, "arg1");
+ c.va_end_missingError(0, "vl");
+ c.va_list_usedBeforeStartedError(0, "vl");
+ c.va_start_subsequentCallsError(0, "vl");
+ }
+
+ static std::string myName() {
+ return "CheckVaarg";
+ }
+
+ std::string classInfo() const {
+ return "Check for misusage of variable argument lists:\n"
+ "* Wrong parameter passed to va_start()\n"
+ "* Reference passed to va_start()\n"
+ "* Missing va_end()\n"
+ "* Using va_list before it is opened\n"
+ "* Subsequent calls to va_start/va_copy()\n";
+ }
+};
+
+/// @}
+
+//---------------------------------------------------------------------------
+#endif // checkvaargtH
diff --git a/lib/cppcheck.vcxproj b/lib/cppcheck.vcxproj
index e10a02654..77bef2fbe 100644
--- a/lib/cppcheck.vcxproj
+++ b/lib/cppcheck.vcxproj
@@ -60,6 +60,7 @@
+
@@ -103,6 +104,7 @@
+
diff --git a/lib/cppcheck.vcxproj.filters b/lib/cppcheck.vcxproj.filters
index fea6e0f79..634799577 100644
--- a/lib/cppcheck.vcxproj.filters
+++ b/lib/cppcheck.vcxproj.filters
@@ -134,6 +134,9 @@
Source Files
+
+ Source Files
+
@@ -265,6 +268,9 @@
Header Files
+
+ Header Files
+
diff --git a/test/testrunner.vcxproj b/test/testrunner.vcxproj
index 574e3bfe8..eec9d63a4 100644
--- a/test/testrunner.vcxproj
+++ b/test/testrunner.vcxproj
@@ -83,6 +83,7 @@
+
diff --git a/test/testrunner.vcxproj.filters b/test/testrunner.vcxproj.filters
index a7a1a5f9c..36f53488d 100644
--- a/test/testrunner.vcxproj.filters
+++ b/test/testrunner.vcxproj.filters
@@ -184,6 +184,9 @@
Source Files
+
+ Source Files
+
diff --git a/test/testvaarg.cpp b/test/testvaarg.cpp
new file mode 100644
index 000000000..635dbe3a6
--- /dev/null
+++ b/test/testvaarg.cpp
@@ -0,0 +1,184 @@
+/*
+ * Cppcheck - A tool for static C/C++ code analysis
+ * Copyright (C) 2007-2014 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 .
+ */
+
+#include "testsuite.h"
+#include "checkvaarg.h"
+#include "tokenize.h"
+#include
+
+extern std::ostringstream errout;
+
+class TestVaarg : public TestFixture {
+public:
+ TestVaarg() : TestFixture("TestVaarg") {}
+
+private:
+ void check(const char code[]) {
+ // Clear the error buffer..
+ errout.str("");
+
+ Settings settings;
+ settings.addEnabled("warning");
+
+ // Tokenize..
+ Tokenizer tokenizer(&settings, this);
+ std::istringstream istr(code);
+ tokenizer.tokenize(istr, "test.cpp");
+
+ // Check..
+ CheckVaarg checkVaarg(&tokenizer, &settings, this);
+ checkVaarg.runSimplifiedChecks(&tokenizer, &settings, this);
+ }
+
+ void run() {
+ TEST_CASE(wrongParameterTo_va_start);
+ TEST_CASE(referenceAs_va_start);
+ TEST_CASE(va_end_missing);
+ TEST_CASE(va_list_usedBeforeStarted);
+ TEST_CASE(va_start_subsequentCalls);
+ }
+
+ void wrongParameterTo_va_start() {
+ check("void Format(char* szFormat, char* szBuffer, size_t nSize, ...) {\n"
+ " va_list arg_ptr;\n"
+ " va_start(arg_ptr, szFormat);\n"
+ " va_end(arg_ptr);\n"
+ "}");
+ ASSERT_EQUALS("[test.cpp:3]: (warning) 'szFormat' given to va_start() is not last named argument of the function. Did you intend to pass 'nSize'?\n", errout.str());
+
+ check("void Format(char* szFormat, char* szBuffer, size_t nSize, ...) {\n"
+ " va_list arg_ptr;\n"
+ " va_start(arg_ptr, szBuffer);\n"
+ " va_end(arg_ptr);\n"
+ "}");
+ ASSERT_EQUALS("[test.cpp:3]: (warning) 'szBuffer' given to va_start() is not last named argument of the function. Did you intend to pass 'nSize'?\n", errout.str());
+
+ check("void Format(char* szFormat, char* szBuffer, size_t nSize, ...) {\n"
+ " va_list arg_ptr;\n"
+ " va_start(arg_ptr, nSize);\n"
+ " va_end(arg_ptr);\n"
+ "}");
+ ASSERT_EQUALS("", errout.str());
+ }
+
+ void referenceAs_va_start() {
+ check("void Format(char* szFormat, char (&szBuffer)[_Size], ...) {\n"
+ " va_list arg_ptr;\n"
+ " va_start(arg_ptr, szBuffer);\n"
+ " va_end(arg_ptr);\n"
+ "}");
+ ASSERT_EQUALS("[test.cpp:3]: (error) Using reference 'szBuffer' as parameter for va_start() results in undefined behaviour.\n", errout.str());
+
+ check("void Format(char* szFormat, char (*szBuffer)[_Size], ...) {\n"
+ " va_list arg_ptr;\n"
+ " va_start(arg_ptr, szBuffer);\n"
+ " va_end(arg_ptr);\n"
+ "}");
+ ASSERT_EQUALS("", errout.str());
+ }
+
+ void va_end_missing() {
+ check("void Format(char* szFormat, char (*szBuffer)[_Size], ...) {\n"
+ " va_list arg_ptr;\n"
+ " va_start(arg_ptr, szBuffer);\n"
+ " va_end(arg_ptr);\n"
+ "}");
+ ASSERT_EQUALS("", errout.str());
+
+ check("void Format(char* szFormat, char (*szBuffer)[_Size], ...) {\n"
+ " va_list arg_ptr;\n"
+ " va_start(arg_ptr, szBuffer);\n"
+ "}");
+ ASSERT_EQUALS("[test.cpp:2]: (error) va_list 'arg_ptr' was opened but not closed by va_end().\n", errout.str());
+
+ check("void Format(char* szFormat, char (*szBuffer)[_Size], ...) {\n"
+ " va_list arg_ptr;\n"
+ " va_start(arg_ptr, szBuffer);\n"
+ " if(sth) return;\n"
+ " va_end(arg_ptr);\n"
+ "}");
+ ASSERT_EQUALS("[test.cpp:2]: (error) va_list 'arg_ptr' was opened but not closed by va_end().\n", errout.str());
+ }
+
+ void va_list_usedBeforeStarted() {
+ check("void Format(char* szFormat, char (*szBuffer)[_Size], ...) {\n"
+ " va_list arg_ptr;\n"
+ " return va_arg(arg_ptr, float);\n"
+ "}");
+ ASSERT_EQUALS("[test.cpp:3]: (error) va_list 'arg_ptr' used before va_start() was called.\n", errout.str());
+
+ check("void Format(char* szFormat, char (*szBuffer)[_Size], ...) {\n"
+ " va_list arg_ptr;\n"
+ " foo(arg_ptr);\n"
+ "}");
+ ASSERT_EQUALS("[test.cpp:3]: (error) va_list 'arg_ptr' used before va_start() was called.\n", errout.str());
+
+ check("void Format(char* szFormat, char (*szBuffer)[_Size], ...) {\n"
+ " va_list arg_ptr;\n"
+ " va_copy(f, arg_ptr);\n"
+ "}");
+ ASSERT_EQUALS("[test.cpp:3]: (error) va_list 'arg_ptr' used before va_start() was called.\n", errout.str());
+
+ check("void Format(char* szFormat, char (*szBuffer)[_Size], ...) {\n"
+ " va_list arg_ptr;\n"
+ " va_start(arg_ptr, szBuffer);\n"
+ " va_end(arg_ptr);\n"
+ " return va_arg(arg_ptr, float);\n"
+ "}");
+ ASSERT_EQUALS("[test.cpp:5]: (error) va_list 'arg_ptr' used before va_start() was called.\n", errout.str());
+
+ check("void Format(char* szFormat, char (*szBuffer)[_Size], ...) {\n"
+ " va_list arg_ptr;\n"
+ " va_start(arg_ptr, szBuffer);\n"
+ " va_end(arg_ptr);\n"
+ " va_start(arg_ptr, szBuffer);\n"
+ " foo(va_arg(arg_ptr, float));\n"
+ " va_end(arg_ptr);\n"
+ "}");
+ ASSERT_EQUALS("", errout.str());
+ }
+
+ void va_start_subsequentCalls() {
+ check("void Format(char* szFormat, char (*szBuffer)[_Size], ...) {\n"
+ " va_list arg_ptr;\n"
+ " va_start(arg_ptr, szBuffer);\n"
+ " va_start(arg_ptr, szBuffer);\n"
+ " va_end(arg_ptr);\n"
+ "}");
+ ASSERT_EQUALS("[test.cpp:4]: (error) va_start() or va_copy() called subsequently on 'arg_ptr' without va_end() inbetween.\n", errout.str());
+
+ check("void Format(char* szFormat, char (*szBuffer)[_Size], ...) {\n"
+ " va_list vl1;\n"
+ " va_start(vl1, szBuffer);\n"
+ " va_copy(vl1, vl1);\n"
+ " va_end(vl1);\n"
+ "}");
+ ASSERT_EQUALS("[test.cpp:4]: (error) va_start() or va_copy() called subsequently on 'vl1' without va_end() inbetween.\n", errout.str());
+
+ check("void Format(char* szFormat, char (*szBuffer)[_Size], ...) {\n"
+ " va_list arg_ptr;\n"
+ " va_start(arg_ptr, szBuffer);\n"
+ " va_end(arg_ptr);\n"
+ " va_start(arg_ptr, szBuffer);\n"
+ " va_end(arg_ptr);\n"
+ "}");
+ ASSERT_EQUALS("", errout.str());
+ }
+};
+
+REGISTER_TEST(TestVaarg)