diff --git a/.github/workflows/iwyu.yml b/.github/workflows/iwyu.yml index dd6d10278ec..e796155c544 100644 --- a/.github/workflows/iwyu.yml +++ b/.github/workflows/iwyu.yml @@ -2,7 +2,10 @@ # Environment reference https://help.github.com/en/actions/reference/virtual-environments-for-github-hosted-runners name: include-what-you-use -on: workflow_dispatch +on: + schedule: + - cron: '0 0 * * 0' + workflow_dispatch: permissions: contents: read diff --git a/AUTHORS b/AUTHORS index 442ad24f44a..a3a02656e08 100644 --- a/AUTHORS +++ b/AUTHORS @@ -76,6 +76,7 @@ Changkyoon Kim Chris Lalancette Christian Ehrlicher Christian Franke +Christoph GrĂ¼ninger Christoph Schmidt Christoph Strehle Chuck Larson diff --git a/Makefile b/Makefile index 9153bf4b300..d4e8ad837fa 100644 --- a/Makefile +++ b/Makefile @@ -518,7 +518,7 @@ $(libcppdir)/checkfunctions.o: lib/checkfunctions.cpp lib/addoninfo.h lib/astuti $(libcppdir)/checkinternal.o: lib/checkinternal.cpp lib/addoninfo.h lib/astutils.h lib/check.h lib/checkinternal.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkinternal.cpp -$(libcppdir)/checkio.o: lib/checkio.cpp lib/addoninfo.h lib/check.h lib/checkio.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/sourcelocation.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h +$(libcppdir)/checkio.o: lib/checkio.cpp lib/addoninfo.h lib/astutils.h lib/check.h lib/checkio.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkio.cpp $(libcppdir)/checkleakautovar.o: lib/checkleakautovar.cpp lib/addoninfo.h lib/astutils.h lib/check.h lib/checkleakautovar.h lib/checkmemoryleak.h lib/checknullpointer.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h @@ -758,7 +758,7 @@ test/testfunctions.o: test/testfunctions.cpp lib/addoninfo.h lib/check.h lib/che test/testgarbage.o: test/testgarbage.cpp externals/simplecpp/simplecpp.h lib/addoninfo.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/preprocessor.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testgarbage.cpp -test/testimportproject.o: test/testimportproject.cpp lib/addoninfo.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/utils.h test/fixture.h +test/testimportproject.o: test/testimportproject.cpp lib/addoninfo.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/utils.h test/fixture.h test/redirect.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testimportproject.cpp test/testincompletestatement.o: test/testincompletestatement.cpp lib/addoninfo.h lib/check.h lib/checkother.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h @@ -887,7 +887,7 @@ test/testvaarg.o: test/testvaarg.cpp lib/addoninfo.h lib/check.h lib/checkvaarg. test/testvalueflow.o: test/testvalueflow.cpp lib/addoninfo.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testvalueflow.cpp -test/testvarid.o: test/testvarid.cpp lib/addoninfo.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h +test/testvarid.o: test/testvarid.cpp lib/addoninfo.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testvarid.cpp externals/simplecpp/simplecpp.o: externals/simplecpp/simplecpp.cpp externals/simplecpp/simplecpp.h diff --git a/cfg/posix.cfg b/cfg/posix.cfg index 25712fcebdd..73197fad4c1 100644 --- a/cfg/posix.cfg +++ b/cfg/posix.cfg @@ -6268,6 +6268,7 @@ The function 'mktemp' is considered to be dangerous due to race conditions and s openat socket close + fdopen opendir diff --git a/cli/cmdlineparser.cpp b/cli/cmdlineparser.cpp index dc8a9006337..df9bd320428 100644 --- a/cli/cmdlineparser.cpp +++ b/cli/cmdlineparser.cpp @@ -198,6 +198,10 @@ bool CmdLineParser::fillSettingsFromArgs(int argc, const char* const argv[]) assert(!(!pathnamesRef.empty() && !fileSettingsRef.empty())); if (!fileSettingsRef.empty()) { + // TODO: handle ignored? + + // TODO: de-duplicate + std::list fileSettings; if (!mSettings.fileFilters.empty()) { // filter only for the selected filenames from all project files @@ -244,6 +248,19 @@ bool CmdLineParser::fillSettingsFromArgs(int argc, const char* const argv[]) } } + // de-duplicate files + { + auto it = filesResolved.begin(); + while (it != filesResolved.end()) { + const std::string& name = it->first; + // TODO: log if duplicated files were dropped + filesResolved.erase(std::remove_if(std::next(it), filesResolved.end(), [&](const std::pair& entry) { + return entry.first == name; + }), filesResolved.end()); + ++it; + } + } + std::list> files; if (!mSettings.fileFilters.empty()) { std::copy_if(filesResolved.cbegin(), filesResolved.cend(), std::inserter(files, files.end()), [&](const decltype(filesResolved)::value_type& entry) { diff --git a/createrelease b/createrelease index f0aecf27c13..a2de51afd2b 100755 --- a/createrelease +++ b/createrelease @@ -29,7 +29,9 @@ # Create 2.8.x branch # git checkout -b 2.8.x ; git push -u origin 2.8.x # -# Empty the releasenotes.txt in main branch +# Release notes: +# - ensure safety critical issues are listed properly +# - empty the releasenotes.txt in main branch # # Update version numbers in: # sed -i -r "s/version 2[.][0-9]+([.]99)*/version 2.9/" cli/main.cpp diff --git a/externals/picojson/picojson.h b/externals/picojson/picojson.h index 48bb64e6723..76742fe06ac 100644 --- a/externals/picojson/picojson.h +++ b/externals/picojson/picojson.h @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -39,972 +40,1161 @@ #include #include #include +#include // for isnan/isinf -#if __cplusplus>=201103L -# include +#if __cplusplus >= 201103L +#include #else extern "C" { -# ifdef _MSC_VER -# include -# elif defined(__INTEL_COMPILER) -# include -# else -# include -# endif +#ifdef _MSC_VER +#include +#elif defined(__INTEL_COMPILER) +#include +#else +#include +#endif } #endif +#ifndef PICOJSON_USE_RVALUE_REFERENCE +#if (defined(__cpp_rvalue_references) && __cpp_rvalue_references >= 200610) || (defined(_MSC_VER) && _MSC_VER >= 1600) +#define PICOJSON_USE_RVALUE_REFERENCE 1 +#else +#define PICOJSON_USE_RVALUE_REFERENCE 0 +#endif +#endif // PICOJSON_USE_RVALUE_REFERENCE + +#ifndef PICOJSON_NOEXCEPT +#if PICOJSON_USE_RVALUE_REFERENCE +#define PICOJSON_NOEXCEPT noexcept +#else +#define PICOJSON_NOEXCEPT throw() +#endif +#endif + // experimental support for int64_t (see README.mkdn for detail) #ifdef PICOJSON_USE_INT64 -# define __STDC_FORMAT_MACROS -# include -# include +#define __STDC_FORMAT_MACROS +#include +#if __cplusplus >= 201103L +#include +#else +extern "C" { +#include +} +#endif #endif // to disable the use of localeconv(3), set PICOJSON_USE_LOCALE to 0 #ifndef PICOJSON_USE_LOCALE -# define PICOJSON_USE_LOCALE 1 +#define PICOJSON_USE_LOCALE 1 #endif #if PICOJSON_USE_LOCALE extern "C" { -# include +#include } #endif #ifndef PICOJSON_ASSERT -# define PICOJSON_ASSERT(e) do { if (! (e)) throw std::runtime_error(#e); } while (0) +#define PICOJSON_ASSERT(e) \ + do { \ + if (!(e)) \ + throw std::runtime_error(#e); \ + } while (0) #endif #ifdef _MSC_VER - #define SNPRINTF _snprintf_s - #pragma warning(push) - #pragma warning(disable : 4244) // conversion from int to char - #pragma warning(disable : 4127) // conditional expression is constant - #pragma warning(disable : 4702) // unreachable code +#define SNPRINTF _snprintf_s +#pragma warning(push) +#pragma warning(disable : 4244) // conversion from int to char +#pragma warning(disable : 4127) // conditional expression is constant +#pragma warning(disable : 4702) // unreachable code +#pragma warning(disable : 4706) // assignment within conditional expression #else - #define SNPRINTF snprintf +#define SNPRINTF snprintf #endif namespace picojson { - - enum { - null_type, - boolean_type, - number_type, - string_type, - array_type, - object_type + +enum { + null_type, + boolean_type, + number_type, + string_type, + array_type, + object_type #ifdef PICOJSON_USE_INT64 - , int64_type + , + int64_type #endif - }; - - enum { - INDENT_WIDTH = 2 - }; +}; - struct null {}; - - class value { - public: - typedef std::vector array; - typedef std::map object; - union _storage { - bool boolean_; - double number_; -#ifdef PICOJSON_USE_INT64 - int64_t int64_; -#endif - std::string* string_; - array* array_; - object* object_; - }; - protected: - int type_; - _storage u_; - public: - value(); - value(int type, bool); - explicit value(bool b); +enum { INDENT_WIDTH = 2, DEFAULT_MAX_DEPTHS = 100 }; + +struct null {}; + +class value { +public: + typedef std::vector array; + typedef std::map object; + union _storage { + bool boolean_; + double number_; #ifdef PICOJSON_USE_INT64 - explicit value(int64_t i); -#endif - explicit value(double n); - explicit value(const std::string& s); - explicit value(const array& a); - explicit value(const object& o); - explicit value(const char* s); - value(const char* s, size_t len); - ~value(); - value(const value& x); - value& operator=(const value& x); - void swap(value& x); - template bool is() const; - template const T& get() const; - template T& get(); - bool evaluate_as_boolean() const; - const value& get(size_t idx) const; - const value& get(const std::string& key) const; - value& get(size_t idx); - value& get(const std::string& key); - - bool contains(size_t idx) const; - bool contains(const std::string& key) const; - std::string to_str() const; - template void serialize(Iter os, bool prettify = false) const; - std::string serialize(bool prettify = false) const; - private: - template value(const T*); // intentionally defined to block implicit conversion of pointer to bool - template static void _indent(Iter os, int indent); - template void _serialize(Iter os, int indent) const; - std::string _serialize(int indent) const; + int64_t int64_; +#endif + std::string *string_; + array *array_; + object *object_; }; - - typedef value::array array; - typedef value::object object; - - inline value::value() : type_(null_type) {} - - inline value::value(int type, bool) : type_(type) { - switch (type) { -#define INIT(p, v) case p##type: u_.p = v; break - INIT(boolean_, false); - INIT(number_, 0.0); + +protected: + int type_; + _storage u_; + +public: + value(); + value(int type, bool); + explicit value(bool b); #ifdef PICOJSON_USE_INT64 - INIT(int64_, 0); + explicit value(int64_t i); +#endif + explicit value(double n); + explicit value(const std::string &s); + explicit value(const array &a); + explicit value(const object &o); +#if PICOJSON_USE_RVALUE_REFERENCE + explicit value(std::string &&s); + explicit value(array &&a); + explicit value(object &&o); +#endif + explicit value(const char *s); + value(const char *s, size_t len); + ~value(); + value(const value &x); + value &operator=(const value &x); +#if PICOJSON_USE_RVALUE_REFERENCE + value(value &&x) PICOJSON_NOEXCEPT; + value &operator=(value &&x) PICOJSON_NOEXCEPT; #endif - INIT(string_, new std::string()); - INIT(array_, new array()); - INIT(object_, new object()); + void swap(value &x) PICOJSON_NOEXCEPT; + template bool is() const; + template const T &get() const; + template T &get(); + template void set(const T &); +#if PICOJSON_USE_RVALUE_REFERENCE + template void set(T &&); +#endif + bool evaluate_as_boolean() const; + const value &get(const size_t idx) const; + const value &get(const std::string &key) const; + value &get(const size_t idx); + value &get(const std::string &key); + + bool contains(const size_t idx) const; + bool contains(const std::string &key) const; + std::string to_str() const; + template void serialize(Iter os, bool prettify = false) const; + std::string serialize(bool prettify = false) const; + +private: + template value(const T *); // intentionally defined to block implicit conversion of pointer to bool + template static void _indent(Iter os, int indent); + template void _serialize(Iter os, int indent) const; + std::string _serialize(int indent) const; + void clear(); +}; + +typedef value::array array; +typedef value::object object; + +inline value::value() : type_(null_type), u_() { +} + +inline value::value(int type, bool) : type_(type), u_() { + switch (type) { +#define INIT(p, v) \ + case p##type: \ + u_.p = v; \ + break + INIT(boolean_, false); + INIT(number_, 0.0); +#ifdef PICOJSON_USE_INT64 + INIT(int64_, 0); +#endif + INIT(string_, new std::string()); + INIT(array_, new array()); + INIT(object_, new object()); #undef INIT - default: break; - } - } - - inline value::value(bool b) : type_(boolean_type) { - u_.boolean_ = b; + default: + break; } +} + +inline value::value(bool b) : type_(boolean_type), u_() { + u_.boolean_ = b; +} #ifdef PICOJSON_USE_INT64 - inline value::value(int64_t i) : type_(int64_type) { - u_.int64_ = i; - } +inline value::value(int64_t i) : type_(int64_type), u_() { + u_.int64_ = i; +} #endif - inline value::value(double n) : type_(number_type) { - if ( +inline value::value(double n) : type_(number_type), u_() { + if ( #ifdef _MSC_VER - ! _finite(n) -#elif __cplusplus>=201103L || !(defined(isnan) && defined(isinf)) - std::isnan(n) || std::isinf(n) + !_finite(n) +#elif __cplusplus >= 201103L + std::isnan(n) || std::isinf(n) #else - isnan(n) || isinf(n) + isnan(n) || isinf(n) #endif - ) { - throw std::overflow_error(""); - } - u_.number_ = n; - } - - inline value::value(const std::string& s) : type_(string_type) { - u_.string_ = new std::string(s); - } - - inline value::value(const array& a) : type_(array_type) { - u_.array_ = new array(a); - } - - inline value::value(const object& o) : type_(object_type) { - u_.object_ = new object(o); - } - - inline value::value(const char* s) : type_(string_type) { - u_.string_ = new std::string(s); - } - - inline value::value(const char* s, size_t len) : type_(string_type) { - u_.string_ = new std::string(s, len); - } - - inline value::~value() { - switch (type_) { -#define DEINIT(p) case p##type: delete u_.p; break - DEINIT(string_); - DEINIT(array_); - DEINIT(object_); + ) { + throw std::overflow_error(""); + } + u_.number_ = n; +} + +inline value::value(const std::string &s) : type_(string_type), u_() { + u_.string_ = new std::string(s); +} + +inline value::value(const array &a) : type_(array_type), u_() { + u_.array_ = new array(a); +} + +inline value::value(const object &o) : type_(object_type), u_() { + u_.object_ = new object(o); +} + +#if PICOJSON_USE_RVALUE_REFERENCE +inline value::value(std::string &&s) : type_(string_type), u_() { + u_.string_ = new std::string(std::move(s)); +} + +inline value::value(array &&a) : type_(array_type), u_() { + u_.array_ = new array(std::move(a)); +} + +inline value::value(object &&o) : type_(object_type), u_() { + u_.object_ = new object(std::move(o)); +} +#endif + +inline value::value(const char *s) : type_(string_type), u_() { + u_.string_ = new std::string(s); +} + +inline value::value(const char *s, size_t len) : type_(string_type), u_() { + u_.string_ = new std::string(s, len); +} + +inline void value::clear() { + switch (type_) { +#define DEINIT(p) \ + case p##type: \ + delete u_.p; \ + break + DEINIT(string_); + DEINIT(array_); + DEINIT(object_); #undef DEINIT - default: break; - } + default: + break; } - - inline value::value(const value& x) : type_(x.type_) { - switch (type_) { -#define INIT(p, v) case p##type: u_.p = v; break - INIT(string_, new std::string(*x.u_.string_)); - INIT(array_, new array(*x.u_.array_)); - INIT(object_, new object(*x.u_.object_)); +} + +inline value::~value() { + clear(); +} + +inline value::value(const value &x) : type_(x.type_), u_() { + switch (type_) { +#define INIT(p, v) \ + case p##type: \ + u_.p = v; \ + break + INIT(string_, new std::string(*x.u_.string_)); + INIT(array_, new array(*x.u_.array_)); + INIT(object_, new object(*x.u_.object_)); #undef INIT - default: - u_ = x.u_; - break; - } + default: + u_ = x.u_; + break; } - - inline value& value::operator=(const value& x) { - if (this != &x) { - value t(x); - swap(t); - } - return *this; - } - - inline void value::swap(value& x) { - std::swap(type_, x.type_); - std::swap(u_, x.u_); +} + +inline value &value::operator=(const value &x) { + if (this != &x) { + value t(x); + swap(t); } - -#define IS(ctype, jtype) \ - template <> inline bool value::is() const { \ - return type_ == jtype##_type; \ + return *this; +} + +#if PICOJSON_USE_RVALUE_REFERENCE +inline value::value(value &&x) PICOJSON_NOEXCEPT : type_(null_type), u_() { + swap(x); +} +inline value &value::operator=(value &&x) PICOJSON_NOEXCEPT { + swap(x); + return *this; +} +#endif +inline void value::swap(value &x) PICOJSON_NOEXCEPT { + std::swap(type_, x.type_); + std::swap(u_, x.u_); +} + +#define IS(ctype, jtype) \ + template <> inline bool value::is() const { \ + return type_ == jtype##_type; \ } - IS(null, null) - IS(bool, boolean) +IS(null, null) +IS(bool, boolean) #ifdef PICOJSON_USE_INT64 - IS(int64_t, int64) +IS(int64_t, int64) #endif - IS(std::string, string) - IS(array, array) - IS(object, object) +IS(std::string, string) +IS(array, array) +IS(object, object) #undef IS - template <> inline bool value::is() const { - return type_ == number_type +template <> inline bool value::is() const { + return type_ == number_type #ifdef PICOJSON_USE_INT64 - || type_ == int64_type + || type_ == int64_type #endif ; +} + +#define GET(ctype, var) \ + template <> inline const ctype &value::get() const { \ + PICOJSON_ASSERT("type mismatch! call is() before get()" && is()); \ + return var; \ + } \ + template <> inline ctype &value::get() { \ + PICOJSON_ASSERT("type mismatch! call is() before get()" && is()); \ + return var; \ } - -#define GET(ctype, var) \ - template <> inline const ctype& value::get() const { \ - PICOJSON_ASSERT("type mismatch! call is() before get()" \ - && is()); \ - return var; \ - } \ - template <> inline ctype& value::get() { \ - PICOJSON_ASSERT("type mismatch! call is() before get()" \ - && is()); \ - return var; \ - } - GET(bool, u_.boolean_) - GET(std::string, *u_.string_) - GET(array, *u_.array_) - GET(object, *u_.object_) +GET(bool, u_.boolean_) +GET(std::string, *u_.string_) +GET(array, *u_.array_) +GET(object, *u_.object_) #ifdef PICOJSON_USE_INT64 - GET(double, (type_ == int64_type && (const_cast(this)->type_ = number_type, const_cast(this)->u_.number_ = u_.int64_), u_.number_)) - GET(int64_t, u_.int64_) +GET(double, + (type_ == int64_type && (const_cast(this)->type_ = number_type, (const_cast(this)->u_.number_ = u_.int64_)), + u_.number_)) +GET(int64_t, u_.int64_) #else - GET(double, u_.number_) +GET(double, u_.number_) #endif #undef GET - - inline bool value::evaluate_as_boolean() const { - switch (type_) { - case null_type: - return false; - case boolean_type: - return u_.boolean_; - case number_type: - return u_.number_ != 0; - case string_type: - return ! u_.string_->empty(); - default: - return true; - } - } - - inline const value& value::get(size_t idx) const { - static value s_null; - PICOJSON_ASSERT(is()); - return idx < u_.array_->size() ? (*u_.array_)[idx] : s_null; - } - inline value& value::get(size_t idx) { - static value s_null; - PICOJSON_ASSERT(is()); - return idx < u_.array_->size() ? (*u_.array_)[idx] : s_null; +#define SET(ctype, jtype, setter) \ + template <> inline void value::set(const ctype &_val) { \ + clear(); \ + type_ = jtype##_type; \ + setter \ } +SET(bool, boolean, u_.boolean_ = _val;) +SET(std::string, string, u_.string_ = new std::string(_val);) +SET(array, array, u_.array_ = new array(_val);) +SET(object, object, u_.object_ = new object(_val);) +SET(double, number, u_.number_ = _val;) +#ifdef PICOJSON_USE_INT64 +SET(int64_t, int64, u_.int64_ = _val;) +#endif +#undef SET - inline const value& value::get(const std::string& key) const { - static value s_null; - PICOJSON_ASSERT(is()); - object::const_iterator i = u_.object_->find(key); - return i != u_.object_->end() ? i->second : s_null; +#if PICOJSON_USE_RVALUE_REFERENCE +#define MOVESET(ctype, jtype, setter) \ + template <> inline void value::set(ctype && _val) { \ + clear(); \ + type_ = jtype##_type; \ + setter \ } +MOVESET(std::string, string, u_.string_ = new std::string(std::move(_val));) +MOVESET(array, array, u_.array_ = new array(std::move(_val));) +MOVESET(object, object, u_.object_ = new object(std::move(_val));) +#undef MOVESET +#endif - inline value& value::get(const std::string& key) { - static value s_null; - PICOJSON_ASSERT(is()); - object::iterator i = u_.object_->find(key); - return i != u_.object_->end() ? i->second : s_null; +inline bool value::evaluate_as_boolean() const { + switch (type_) { + case null_type: + return false; + case boolean_type: + return u_.boolean_; + case number_type: + return u_.number_ != 0; +#ifdef PICOJSON_USE_INT64 + case int64_type: + return u_.int64_ != 0; +#endif + case string_type: + return !u_.string_->empty(); + default: + return true; } +} - inline bool value::contains(size_t idx) const { - PICOJSON_ASSERT(is()); - return idx < u_.array_->size(); - } +inline const value &value::get(const size_t idx) const { + static value s_null; + PICOJSON_ASSERT(is()); + return idx < u_.array_->size() ? (*u_.array_)[idx] : s_null; +} - inline bool value::contains(const std::string& key) const { - PICOJSON_ASSERT(is()); - object::const_iterator i = u_.object_->find(key); - return i != u_.object_->end(); - } - - inline std::string value::to_str() const { - switch (type_) { - case null_type: return "null"; - case boolean_type: return u_.boolean_ ? "true" : "false"; +inline value &value::get(const size_t idx) { + static value s_null; + PICOJSON_ASSERT(is()); + return idx < u_.array_->size() ? (*u_.array_)[idx] : s_null; +} + +inline const value &value::get(const std::string &key) const { + static value s_null; + PICOJSON_ASSERT(is()); + object::const_iterator i = u_.object_->find(key); + return i != u_.object_->end() ? i->second : s_null; +} + +inline value &value::get(const std::string &key) { + static value s_null; + PICOJSON_ASSERT(is()); + object::iterator i = u_.object_->find(key); + return i != u_.object_->end() ? i->second : s_null; +} + +inline bool value::contains(const size_t idx) const { + PICOJSON_ASSERT(is()); + return idx < u_.array_->size(); +} + +inline bool value::contains(const std::string &key) const { + PICOJSON_ASSERT(is()); + object::const_iterator i = u_.object_->find(key); + return i != u_.object_->end(); +} + +inline std::string value::to_str() const { + switch (type_) { + case null_type: + return "null"; + case boolean_type: + return u_.boolean_ ? "true" : "false"; #ifdef PICOJSON_USE_INT64 - case int64_type: { - char buf[sizeof("-9223372036854775808")]; - SNPRINTF(buf, sizeof(buf), "%" PRId64, u_.int64_); - return buf; - } + case int64_type: { + char buf[sizeof("-9223372036854775808")]; + SNPRINTF(buf, sizeof(buf), "%" PRId64, u_.int64_); + return buf; + } #endif - case number_type: { - char buf[256]; - double tmp; - SNPRINTF(buf, sizeof(buf), fabs(u_.number_) < (1ULL << 53) && modf(u_.number_, &tmp) == 0 ? "%.f" : "%.17g", u_.number_); + case number_type: { + char buf[256]; + double tmp; + SNPRINTF(buf, sizeof(buf), fabs(u_.number_) < (1ULL << 53) && modf(u_.number_, &tmp) == 0 ? "%.f" : "%.17g", u_.number_); #if PICOJSON_USE_LOCALE - char *decimal_point = localeconv()->decimal_point; - if (strcmp(decimal_point, ".") != 0) { - size_t decimal_point_len = strlen(decimal_point); - for (char *p = buf; *p != '\0'; ++p) { - if (strncmp(p, decimal_point, decimal_point_len) == 0) { - return std::string(buf, p) + "." + (p + decimal_point_len); - } + char *decimal_point = localeconv()->decimal_point; + if (strcmp(decimal_point, ".") != 0) { + size_t decimal_point_len = strlen(decimal_point); + for (char *p = buf; *p != '\0'; ++p) { + if (strncmp(p, decimal_point, decimal_point_len) == 0) { + return std::string(buf, p) + "." + (p + decimal_point_len); } } -#endif - return buf; } - case string_type: return *u_.string_; - case array_type: return "array"; - case object_type: return "object"; - default: PICOJSON_ASSERT(0); +#endif + return buf; + } + case string_type: + return *u_.string_; + case array_type: + return "array"; + case object_type: + return "object"; + default: + PICOJSON_ASSERT(0); #ifdef _MSC_VER - __assume(0); + __assume(0); #endif - } - return std::string(); - } - - template void copy(const std::string& s, Iter oi) { - std::copy(s.begin(), s.end(), oi); - } - - template void serialize_str(const std::string& s, Iter oi) { - *oi++ = '"'; - for (std::string::const_iterator i = s.begin(); i != s.end(); ++i) { - switch (*i) { -#define MAP(val, sym) case val: copy(sym, oi); break - MAP('"', "\\\""); - MAP('\\', "\\\\"); - MAP('/', "\\/"); - MAP('\b', "\\b"); - MAP('\f', "\\f"); - MAP('\n', "\\n"); - MAP('\r', "\\r"); - MAP('\t', "\\t"); + } + return std::string(); +} + +template void copy(const std::string &s, Iter oi) { + std::copy(s.begin(), s.end(), oi); +} + +template struct serialize_str_char { + Iter oi; + void operator()(char c) { + switch (c) { +#define MAP(val, sym) \ + case val: \ + copy(sym, oi); \ + break + MAP('"', "\\\""); + MAP('\\', "\\\\"); + MAP('/', "\\/"); + MAP('\b', "\\b"); + MAP('\f', "\\f"); + MAP('\n', "\\n"); + MAP('\r', "\\r"); + MAP('\t', "\\t"); #undef MAP - default: - if (static_cast(*i) < 0x20 || *i == 0x7f) { - char buf[7]; - SNPRINTF(buf, sizeof(buf), "\\u%04x", *i & 0xff); - copy(buf, buf + 6, oi); - } else { - *oi++ = *i; - } - break; + default: + if (static_cast(c) < 0x20 || c == 0x7f) { + char buf[7]; + SNPRINTF(buf, sizeof(buf), "\\u%04x", c & 0xff); + copy(buf, buf + 6, oi); + } else { + *oi++ = c; } + break; } - *oi++ = '"'; } +}; - template void value::serialize(Iter oi, bool prettify) const { - return _serialize(oi, prettify ? 0 : -1); - } - - inline std::string value::serialize(bool prettify) const { - return _serialize(prettify ? 0 : -1); - } +template void serialize_str(const std::string &s, Iter oi) { + *oi++ = '"'; + serialize_str_char process_char = {oi}; + std::for_each(s.begin(), s.end(), process_char); + *oi++ = '"'; +} - template void value::_indent(Iter oi, int indent) { - *oi++ = '\n'; - for (int i = 0; i < indent * INDENT_WIDTH; ++i) { - *oi++ = ' '; - } +template void value::serialize(Iter oi, bool prettify) const { + return _serialize(oi, prettify ? 0 : -1); +} + +inline std::string value::serialize(bool prettify) const { + return _serialize(prettify ? 0 : -1); +} + +template void value::_indent(Iter oi, int indent) { + *oi++ = '\n'; + for (int i = 0; i < indent * INDENT_WIDTH; ++i) { + *oi++ = ' '; } +} - template void value::_serialize(Iter oi, int indent) const { - switch (type_) { - case string_type: - serialize_str(*u_.string_, oi); - break; - case array_type: { - *oi++ = '['; - if (indent != -1) { - ++indent; - } - for (array::const_iterator i = u_.array_->begin(); - i != u_.array_->end(); - ++i) { - if (i != u_.array_->begin()) { - *oi++ = ','; - } - if (indent != -1) { - _indent(oi, indent); - } - i->_serialize(oi, indent); +template void value::_serialize(Iter oi, int indent) const { + switch (type_) { + case string_type: + serialize_str(*u_.string_, oi); + break; + case array_type: { + *oi++ = '['; + if (indent != -1) { + ++indent; + } + for (array::const_iterator i = u_.array_->begin(); i != u_.array_->end(); ++i) { + if (i != u_.array_->begin()) { + *oi++ = ','; } if (indent != -1) { - --indent; - if (! u_.array_->empty()) { - _indent(oi, indent); - } + _indent(oi, indent); } - *oi++ = ']'; - break; + i->_serialize(oi, indent); } - case object_type: { - *oi++ = '{'; - if (indent != -1) { - ++indent; + if (indent != -1) { + --indent; + if (!u_.array_->empty()) { + _indent(oi, indent); } - for (object::const_iterator i = u_.object_->begin(); - i != u_.object_->end(); - ++i) { - if (i != u_.object_->begin()) { - *oi++ = ','; - } - if (indent != -1) { - _indent(oi, indent); - } - serialize_str(i->first, oi); - *oi++ = ':'; - if (indent != -1) { - *oi++ = ' '; - } - i->second._serialize(oi, indent); + } + *oi++ = ']'; + break; + } + case object_type: { + *oi++ = '{'; + if (indent != -1) { + ++indent; + } + for (object::const_iterator i = u_.object_->begin(); i != u_.object_->end(); ++i) { + if (i != u_.object_->begin()) { + *oi++ = ','; } if (indent != -1) { - --indent; - if (! u_.object_->empty()) { - _indent(oi, indent); - } + _indent(oi, indent); } - *oi++ = '}'; - break; - } - default: - copy(to_str(), oi); - break; + serialize_str(i->first, oi); + *oi++ = ':'; + if (indent != -1) { + *oi++ = ' '; + } + i->second._serialize(oi, indent); } - if (indent == 0) { - *oi++ = '\n'; + if (indent != -1) { + --indent; + if (!u_.object_->empty()) { + _indent(oi, indent); + } } + *oi++ = '}'; + break; } - - inline std::string value::_serialize(int indent) const { - std::string s; - _serialize(std::back_inserter(s), indent); - return s; - } - - template class input { - protected: - Iter cur_, end_; - int last_ch_; - bool ungot_; - int line_; - public: - input(const Iter& first, const Iter& last) : cur_(first), end_(last), last_ch_(-1), ungot_(false), line_(1) {} - int getc() { - if (ungot_) { - ungot_ = false; - return last_ch_; - } - if (cur_ == end_) { - last_ch_ = -1; - return -1; - } - if (last_ch_ == '\n') { - line_++; + default: + copy(to_str(), oi); + break; + } + if (indent == 0) { + *oi++ = '\n'; + } +} + +inline std::string value::_serialize(int indent) const { + std::string s; + _serialize(std::back_inserter(s), indent); + return s; +} + +template class input { +protected: + Iter cur_, end_; + bool consumed_; + int line_; + +public: + input(const Iter &first, const Iter &last) : cur_(first), end_(last), consumed_(false), line_(1) { + } + int getc() { + if (consumed_) { + if (*cur_ == '\n') { + ++line_; } - last_ch_ = *cur_ & 0xff; ++cur_; - return last_ch_; } - void ungetc() { - if (last_ch_ != -1) { - PICOJSON_ASSERT(! ungot_); - ungot_ = true; - } + if (cur_ == end_) { + consumed_ = false; + return -1; } - Iter cur() const { return cur_; } - int line() const { return line_; } - void skip_ws() { - while (1) { - int ch = getc(); - if (! (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r')) { - ungetc(); - break; - } - } + consumed_ = true; + return *cur_ & 0xff; + } + void ungetc() { + consumed_ = false; + } + Iter cur() const { + if (consumed_) { + input *self = const_cast *>(this); + self->consumed_ = false; + ++self->cur_; } - bool expect(int expect) { - skip_ws(); - if (getc() != expect) { - ungetc(); - return false; + return cur_; + } + int line() const { + return line_; + } + void skip_ws() { + while (1) { + int ch = getc(); + if (!(ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r')) { + ungetc(); + break; } - return true; } - bool match(const std::string& pattern) { - for (std::string::const_iterator pi(pattern.begin()); - pi != pattern.end(); - ++pi) { - if (getc() != *pi) { - ungetc(); - return false; - } - } - return true; + } + bool expect(const int expected) { + skip_ws(); + if (getc() != expected) { + ungetc(); + return false; } - }; - - template inline int _parse_quadhex(input &in) { - int uni_ch = 0, hex; - for (int i = 0; i < 4; i++) { - if ((hex = in.getc()) == -1) { - return -1; - } - if ('0' <= hex && hex <= '9') { - hex -= '0'; - } else if ('A' <= hex && hex <= 'F') { - hex -= 'A' - 0xa; - } else if ('a' <= hex && hex <= 'f') { - hex -= 'a' - 0xa; - } else { - in.ungetc(); - return -1; + return true; + } + bool match(const std::string &pattern) { + for (std::string::const_iterator pi(pattern.begin()); pi != pattern.end(); ++pi) { + if (getc() != *pi) { + ungetc(); + return false; } - uni_ch = uni_ch * 16 + hex; } - return uni_ch; + return true; + } +}; + +template inline int _parse_quadhex(input &in) { + int uni_ch = 0, hex; + for (int i = 0; i < 4; i++) { + if ((hex = in.getc()) == -1) { + return -1; + } + if ('0' <= hex && hex <= '9') { + hex -= '0'; + } else if ('A' <= hex && hex <= 'F') { + hex -= 'A' - 0xa; + } else if ('a' <= hex && hex <= 'f') { + hex -= 'a' - 0xa; + } else { + in.ungetc(); + return -1; + } + uni_ch = uni_ch * 16 + hex; } - - template inline bool _parse_codepoint(String& out, input& in) { - int uni_ch; - if ((uni_ch = _parse_quadhex(in)) == -1) { + return uni_ch; +} + +template inline bool _parse_codepoint(String &out, input &in) { + int uni_ch; + if ((uni_ch = _parse_quadhex(in)) == -1) { + return false; + } + if (0xd800 <= uni_ch && uni_ch <= 0xdfff) { + if (0xdc00 <= uni_ch) { + // a second 16-bit of a surrogate pair appeared return false; } - if (0xd800 <= uni_ch && uni_ch <= 0xdfff) { - if (0xdc00 <= uni_ch) { - // a second 16-bit of a surrogate pair appeared - return false; - } - // first 16-bit of surrogate pair, get the next one - if (in.getc() != '\\' || in.getc() != 'u') { - in.ungetc(); - return false; - } - int second = _parse_quadhex(in); - if (! (0xdc00 <= second && second <= 0xdfff)) { - return false; - } - uni_ch = ((uni_ch - 0xd800) << 10) | ((second - 0xdc00) & 0x3ff); - uni_ch += 0x10000; + // first 16-bit of surrogate pair, get the next one + if (in.getc() != '\\' || in.getc() != 'u') { + in.ungetc(); + return false; + } + int second = _parse_quadhex(in); + if (!(0xdc00 <= second && second <= 0xdfff)) { + return false; } - if (uni_ch < 0x80) { - out.push_back(uni_ch); + uni_ch = ((uni_ch - 0xd800) << 10) | ((second - 0xdc00) & 0x3ff); + uni_ch += 0x10000; + } + if (uni_ch < 0x80) { + out.push_back(static_cast(uni_ch)); + } else { + if (uni_ch < 0x800) { + out.push_back(static_cast(0xc0 | (uni_ch >> 6))); } else { - if (uni_ch < 0x800) { - out.push_back(0xc0 | (uni_ch >> 6)); + if (uni_ch < 0x10000) { + out.push_back(static_cast(0xe0 | (uni_ch >> 12))); } else { - if (uni_ch < 0x10000) { - out.push_back(0xe0 | (uni_ch >> 12)); - } else { - out.push_back(0xf0 | (uni_ch >> 18)); - out.push_back(0x80 | ((uni_ch >> 12) & 0x3f)); - } - out.push_back(0x80 | ((uni_ch >> 6) & 0x3f)); + out.push_back(static_cast(0xf0 | (uni_ch >> 18))); + out.push_back(static_cast(0x80 | ((uni_ch >> 12) & 0x3f))); } - out.push_back(0x80 | (uni_ch & 0x3f)); + out.push_back(static_cast(0x80 | ((uni_ch >> 6) & 0x3f))); } - return true; + out.push_back(static_cast(0x80 | (uni_ch & 0x3f))); } - - template inline bool _parse_string(String& out, input& in) { - while (1) { - int ch = in.getc(); - if (ch < ' ') { - in.ungetc(); - return false; - } else if (ch == '"') { - return true; - } else if (ch == '\\') { - if ((ch = in.getc()) == -1) { - return false; - } - switch (ch) { -#define MAP(sym, val) case sym: out.push_back(val); break - MAP('"', '\"'); - MAP('\\', '\\'); - MAP('/', '/'); - MAP('b', '\b'); - MAP('f', '\f'); - MAP('n', '\n'); - MAP('r', '\r'); - MAP('t', '\t'); + return true; +} + +template inline bool _parse_string(String &out, input &in) { + while (1) { + int ch = in.getc(); + if (ch < ' ') { + in.ungetc(); + return false; + } else if (ch == '"') { + return true; + } else if (ch == '\\') { + if ((ch = in.getc()) == -1) { + return false; + } + switch (ch) { +#define MAP(sym, val) \ + case sym: \ + out.push_back(val); \ + break + MAP('"', '\"'); + MAP('\\', '\\'); + MAP('/', '/'); + MAP('b', '\b'); + MAP('f', '\f'); + MAP('n', '\n'); + MAP('r', '\r'); + MAP('t', '\t'); #undef MAP - case 'u': - if (! _parse_codepoint(out, in)) { - return false; - } - break; - default: - return false; - } - } else { - out.push_back(ch); + case 'u': + if (!_parse_codepoint(out, in)) { + return false; + } + break; + default: + return false; } + } else { + out.push_back(static_cast(ch)); } + } + return false; +} + +template inline bool _parse_array(Context &ctx, input &in) { + if (!ctx.parse_array_start()) { return false; } - - template inline bool _parse_array(Context& ctx, input& in) { - if (! ctx.parse_array_start()) { + size_t idx = 0; + if (in.expect(']')) { + return ctx.parse_array_stop(idx); + } + do { + if (!ctx.parse_array_item(in, idx)) { return false; } - size_t idx = 0; - if (in.expect(']')) { - return ctx.parse_array_stop(idx); - } - do { - if (! ctx.parse_array_item(in, idx)) { - return false; - } - idx++; - } while (in.expect(',')); - return in.expect(']') && ctx.parse_array_stop(idx); + idx++; + } while (in.expect(',')); + return in.expect(']') && ctx.parse_array_stop(idx); +} + +template inline bool _parse_object(Context &ctx, input &in) { + if (!ctx.parse_object_start()) { + return false; } - - template inline bool _parse_object(Context& ctx, input& in) { - if (! ctx.parse_object_start()) { + if (in.expect('}')) { + return ctx.parse_object_stop(); + } + do { + std::string key; + if (!in.expect('"') || !_parse_string(key, in) || !in.expect(':')) { return false; } - if (in.expect('}')) { - return true; + if (!ctx.parse_object_item(in, key)) { + return false; } - do { - std::string key; - if (! in.expect('"') - || ! _parse_string(key, in) - || ! in.expect(':')) { - return false; - } - if (! ctx.parse_object_item(in, key)) { - return false; - } - } while (in.expect(',')); - return in.expect('}'); - } - - template inline std::string _parse_number(input& in) { - std::string num_str; - while (1) { - int ch = in.getc(); - if (('0' <= ch && ch <= '9') || ch == '+' || ch == '-' - || ch == 'e' || ch == 'E') { - num_str.push_back(ch); - } else if (ch == '.') { + } while (in.expect(',')); + return in.expect('}') && ctx.parse_object_stop(); +} + +template inline std::string _parse_number(input &in) { + std::string num_str; + while (1) { + int ch = in.getc(); + if (('0' <= ch && ch <= '9') || ch == '+' || ch == '-' || ch == 'e' || ch == 'E') { + num_str.push_back(static_cast(ch)); + } else if (ch == '.') { #if PICOJSON_USE_LOCALE - num_str += localeconv()->decimal_point; + num_str += localeconv()->decimal_point; #else - num_str.push_back('.'); + num_str.push_back('.'); #endif - } else { - in.ungetc(); - break; - } + } else { + in.ungetc(); + break; } - return num_str; } - - template inline bool _parse(Context& ctx, input& in) { - in.skip_ws(); - int ch = in.getc(); - switch (ch) { -#define IS(ch, text, op) case ch: \ - if (in.match(text) && op) { \ - return true; \ - } else { \ - return false; \ - } - IS('n', "ull", ctx.set_null()); - IS('f', "alse", ctx.set_bool(false)); - IS('t', "rue", ctx.set_bool(true)); + return num_str; +} + +template inline bool _parse(Context &ctx, input &in) { + in.skip_ws(); + int ch = in.getc(); + switch (ch) { +#define IS(ch, text, op) \ + case ch: \ + if (in.match(text) && op) { \ + return true; \ + } else { \ + return false; \ + } + IS('n', "ull", ctx.set_null()); + IS('f', "alse", ctx.set_bool(false)); + IS('t', "rue", ctx.set_bool(true)); #undef IS - case '"': - return ctx.parse_string(in); - case '[': - return _parse_array(ctx, in); - case '{': - return _parse_object(ctx, in); - default: - if (('0' <= ch && ch <= '9') || ch == '-') { - double f; - char *endp; - in.ungetc(); - std::string num_str = _parse_number(in); - if (num_str.empty()) { - return false; - } + case '"': + return ctx.parse_string(in); + case '[': + return _parse_array(ctx, in); + case '{': + return _parse_object(ctx, in); + default: + if (('0' <= ch && ch <= '9') || ch == '-') { + double f; + char *endp; + in.ungetc(); + std::string num_str(_parse_number(in)); + if (num_str.empty()) { + return false; + } #ifdef PICOJSON_USE_INT64 - { - errno = 0; - intmax_t ival = strtoimax(num_str.c_str(), &endp, 10); - if (errno == 0 - && std::numeric_limits::min() <= ival - && ival <= std::numeric_limits::max() - && endp == num_str.c_str() + num_str.size()) { - ctx.set_int64(ival); - return true; - } - } -#endif - f = strtod(num_str.c_str(), &endp); - if (endp == num_str.c_str() + num_str.size()) { - ctx.set_number(f); + { + errno = 0; + intmax_t ival = strtoimax(num_str.c_str(), &endp, 10); + if (errno == 0 && std::numeric_limits::min() <= ival && ival <= std::numeric_limits::max() && + endp == num_str.c_str() + num_str.size()) { + ctx.set_int64(ival); return true; } - return false; } - break; +#endif + f = strtod(num_str.c_str(), &endp); + if (endp == num_str.c_str() + num_str.size()) { + ctx.set_number(f); + return true; + } + return false; } - in.ungetc(); + break; + } + in.ungetc(); + return false; +} + +class deny_parse_context { +public: + bool set_null() { + return false; + } + bool set_bool(bool) { return false; } - - class deny_parse_context { - public: - bool set_null() { return false; } - bool set_bool(bool) { return false; } #ifdef PICOJSON_USE_INT64 - bool set_int64(int64_t) { return false; } + bool set_int64(int64_t) { + return false; + } #endif - bool set_number(double) { return false; } - template bool parse_string(input&) { return false; } - bool parse_array_start() { return false; } - template bool parse_array_item(input&, size_t) { - return false; - } - bool parse_array_stop(size_t) { return false; } - bool parse_object_start() { return false; } - template bool parse_object_item(input&, const std::string&) { - return false; - } - }; - - class default_parse_context { - protected: - value* out_; - public: - default_parse_context(value* out) : out_(out) {} - bool set_null() { - *out_ = value(); - return true; - } - bool set_bool(bool b) { - *out_ = value(b); - return true; - } + bool set_number(double) { + return false; + } + template bool parse_string(input &) { + return false; + } + bool parse_array_start() { + return false; + } + template bool parse_array_item(input &, size_t) { + return false; + } + bool parse_array_stop(size_t) { + return false; + } + bool parse_object_start() { + return false; + } + template bool parse_object_item(input &, const std::string &) { + return false; + } +}; + +class default_parse_context { +protected: + value *out_; + size_t depths_; + +public: + default_parse_context(value *out, size_t depths = DEFAULT_MAX_DEPTHS) : out_(out), depths_(depths) { + } + bool set_null() { + *out_ = value(); + return true; + } + bool set_bool(bool b) { + *out_ = value(b); + return true; + } #ifdef PICOJSON_USE_INT64 - bool set_int64(int64_t i) { - *out_ = value(i); - return true; - } + bool set_int64(int64_t i) { + *out_ = value(i); + return true; + } #endif - bool set_number(double f) { - *out_ = value(f); - return true; - } - template bool parse_string(input& in) { - *out_ = value(string_type, false); - return _parse_string(out_->get(), in); - } - bool parse_array_start() { - *out_ = value(array_type, false); - return true; - } - template bool parse_array_item(input& in, size_t) { - array& a = out_->get(); - a.push_back(value()); - default_parse_context ctx(&a.back()); - return _parse(ctx, in); - } - bool parse_array_stop(size_t) { return true; } - bool parse_object_start() { - *out_ = value(object_type, false); - return true; - } - template bool parse_object_item(input& in, const std::string& key) { - object& o = out_->get(); - default_parse_context ctx(&o[key]); - return _parse(ctx, in); + bool set_number(double f) { + *out_ = value(f); + return true; + } + template bool parse_string(input &in) { + *out_ = value(string_type, false); + return _parse_string(out_->get(), in); + } + bool parse_array_start() { + if (depths_ == 0) + return false; + --depths_; + *out_ = value(array_type, false); + return true; + } + template bool parse_array_item(input &in, size_t) { + array &a = out_->get(); + a.push_back(value()); + default_parse_context ctx(&a.back(), depths_); + return _parse(ctx, in); + } + bool parse_array_stop(size_t) { + ++depths_; + return true; + } + bool parse_object_start() { + if (depths_ == 0) + return false; + *out_ = value(object_type, false); + return true; + } + template bool parse_object_item(input &in, const std::string &key) { + object &o = out_->get(); + default_parse_context ctx(&o[key], depths_); + return _parse(ctx, in); + } + bool parse_object_stop() { + ++depths_; + return true; + } + +private: + default_parse_context(const default_parse_context &); + default_parse_context &operator=(const default_parse_context &); +}; + +class null_parse_context { +protected: + size_t depths_; + +public: + struct dummy_str { + void push_back(int) { } - private: - default_parse_context(const default_parse_context&); - default_parse_context& operator=(const default_parse_context&); }; - class null_parse_context { - public: - struct dummy_str { - void push_back(int) {} - }; - public: - null_parse_context() {} - bool set_null() { return true; } - bool set_bool(bool) { return true; } +public: + null_parse_context(size_t depths = DEFAULT_MAX_DEPTHS) : depths_(depths) { + } + bool set_null() { + return true; + } + bool set_bool(bool) { + return true; + } #ifdef PICOJSON_USE_INT64 - bool set_int64(int64_t) { return true; } + bool set_int64(int64_t) { + return true; + } #endif - bool set_number(double) { return true; } - template bool parse_string(input& in) { - dummy_str s; - return _parse_string(s, in); - } - bool parse_array_start() { return true; } - template bool parse_array_item(input& in, size_t) { - return _parse(*this, in); - } - bool parse_array_stop(size_t) { return true; } - bool parse_object_start() { return true; } - template bool parse_object_item(input& in, const std::string&) { - return _parse(*this, in); - } - private: - null_parse_context(const null_parse_context&); - null_parse_context& operator=(const null_parse_context&); - }; - - // obsolete, use the version below - template inline std::string parse(value& out, Iter& pos, const Iter& last) { - std::string err; - pos = parse(out, pos, last, &err); - return err; - } - - template inline Iter _parse(Context& ctx, const Iter& first, const Iter& last, std::string* err) { - input in(first, last); - if (! _parse(ctx, in) && err != NULL) { - char buf[64]; - SNPRINTF(buf, sizeof(buf), "syntax error at line %d near: ", in.line()); - *err = buf; - while (1) { - int ch = in.getc(); - if (ch == -1 || ch == '\n') { - break; - } else if (ch >= ' ') { - err->push_back(ch); - } - } - } - return in.cur(); + bool set_number(double) { + return true; + } + template bool parse_string(input &in) { + dummy_str s; + return _parse_string(s, in); + } + bool parse_array_start() { + if (depths_ == 0) + return false; + --depths_; + return true; + } + template bool parse_array_item(input &in, size_t) { + return _parse(*this, in); + } + bool parse_array_stop(size_t) { + ++depths_; + return true; } - - template inline Iter parse(value& out, const Iter& first, const Iter& last, std::string* err) { - default_parse_context ctx(&out); - return _parse(ctx, first, last, err); + bool parse_object_start() { + if (depths_ == 0) + return false; + --depths_; + return true; + } + template bool parse_object_item(input &in, const std::string &) { + ++depths_; + return _parse(*this, in); } - - inline std::string parse(value& out, const std::string& s) { - std::string err; - parse(out, s.begin(), s.end(), &err); - return err; + bool parse_object_stop() { + return true; } - inline std::string parse(value& out, std::istream& is) { - std::string err; - parse(out, std::istreambuf_iterator(is.rdbuf()), - std::istreambuf_iterator(), &err); - return err; +private: + null_parse_context(const null_parse_context &); + null_parse_context &operator=(const null_parse_context &); +}; + +// obsolete, use the version below +template inline std::string parse(value &out, Iter &pos, const Iter &last) { + std::string err; + pos = parse(out, pos, last, &err); + return err; +} + +template inline Iter _parse(Context &ctx, const Iter &first, const Iter &last, std::string *err) { + input in(first, last); + if (!_parse(ctx, in) && err != NULL) { + char buf[64]; + SNPRINTF(buf, sizeof(buf), "syntax error at line %d near: ", in.line()); + *err = buf; + while (1) { + int ch = in.getc(); + if (ch == -1 || ch == '\n') { + break; + } else if (ch >= ' ') { + err->push_back(static_cast(ch)); + } + } } - - template struct last_error_t { - static std::string s; - }; - template std::string last_error_t::s; - - inline void set_last_error(const std::string& s) { - last_error_t::s = s; - } - - inline const std::string& get_last_error() { - return last_error_t::s; - } - - inline bool operator==(const value& x, const value& y) { - if (x.is()) - return y.is(); -#define PICOJSON_CMP(type) \ - if (x.is()) \ - return y.is() && x.get() == y.get() - PICOJSON_CMP(bool); - PICOJSON_CMP(double); - PICOJSON_CMP(std::string); - PICOJSON_CMP(array); - PICOJSON_CMP(object); + return in.cur(); +} + +template inline Iter parse(value &out, const Iter &first, const Iter &last, std::string *err) { + default_parse_context ctx(&out); + return _parse(ctx, first, last, err); +} + +inline std::string parse(value &out, const std::string &s) { + std::string err; + parse(out, s.begin(), s.end(), &err); + return err; +} + +inline std::string parse(value &out, std::istream &is) { + std::string err; + parse(out, std::istreambuf_iterator(is.rdbuf()), std::istreambuf_iterator(), &err); + return err; +} + +template struct last_error_t { static std::string s; }; +template std::string last_error_t::s; + +inline void set_last_error(const std::string &s) { + last_error_t::s = s; +} + +inline const std::string &get_last_error() { + return last_error_t::s; +} + +inline bool operator==(const value &x, const value &y) { + if (x.is()) + return y.is(); +#define PICOJSON_CMP(type) \ + if (x.is()) \ + return y.is() && x.get() == y.get() + PICOJSON_CMP(bool); + PICOJSON_CMP(double); + PICOJSON_CMP(std::string); + PICOJSON_CMP(array); + PICOJSON_CMP(object); #undef PICOJSON_CMP - PICOJSON_ASSERT(0); + PICOJSON_ASSERT(0); #ifdef _MSC_VER - __assume(0); + __assume(0); #endif - return false; - } - - inline bool operator!=(const value& x, const value& y) { - return ! (x == y); - } + return false; } +inline bool operator!=(const value &x, const value &y) { + return !(x == y); +} +} + +#if !PICOJSON_USE_RVALUE_REFERENCE namespace std { - template<> inline void swap(picojson::value& x, picojson::value& y) - { - x.swap(y); - } +template <> inline void swap(picojson::value &x, picojson::value &y) { + x.swap(y); +} } +#endif -inline std::istream& operator>>(std::istream& is, picojson::value& x) -{ +inline std::istream &operator>>(std::istream &is, picojson::value &x) { picojson::set_last_error(std::string()); - std::string err = picojson::parse(x, is); - if (! err.empty()) { + const std::string err(picojson::parse(x, is)); + if (!err.empty()) { picojson::set_last_error(err); is.setstate(std::ios::failbit); } return is; } -inline std::ostream& operator<<(std::ostream& os, const picojson::value& x) -{ +inline std::ostream &operator<<(std::ostream &os, const picojson::value &x) { x.serialize(std::ostream_iterator(os)); return os; } #ifdef _MSC_VER - #pragma warning(pop) +#pragma warning(pop) #endif #endif diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt index daf55a164f0..d7fb059111b 100644 --- a/gui/CMakeLists.txt +++ b/gui/CMakeLists.txt @@ -42,6 +42,7 @@ CheckOptions: else() target_include_directories(cppcheck-gui SYSTEM PRIVATE ${tinyxml2_INCLUDE_DIRS}) endif() + target_include_directories(cppcheck-gui PRIVATE ${PROJECT_SOURCE_DIR}/externals/picojson/) if (NOT CMAKE_DISABLE_PRECOMPILE_HEADERS) target_precompile_headers(cppcheck-gui PRIVATE precompiled.h) endif() diff --git a/gui/checkthread.cpp b/gui/checkthread.cpp index 0da46e6faca..e7d682ba0ef 100644 --- a/gui/checkthread.cpp +++ b/gui/checkthread.cpp @@ -177,7 +177,7 @@ void CheckThread::runAddonsAndTools(const FileSettings *fileSettings, const QStr const QString clangPath = CheckThread::clangTidyCmd(); if (!clangPath.isEmpty()) { QDir dir(clangPath + "/../lib/clang"); - for (QString ver : dir.entryList()) { + for (const QString& ver : dir.entryList()) { QString includePath = dir.absolutePath() + '/' + ver + "/include"; if (ver[0] != '.' && QDir(includePath).exists()) { args << "-isystem" << includePath; diff --git a/gui/cppchecklibrarydata.cpp b/gui/cppchecklibrarydata.cpp index 7c7d20261a4..58caadf03e1 100644 --- a/gui/cppchecklibrarydata.cpp +++ b/gui/cppchecklibrarydata.cpp @@ -697,14 +697,14 @@ static void writeFunction(QXmlStreamWriter &xmlWriter, const CppcheckLibraryData } if (!function.notOverlappingDataArgs.isEmpty()) { xmlWriter.writeStartElement("not-overlapping-data"); - foreach (const QString value, function.notOverlappingDataArgs) { + foreach (const QString& value, function.notOverlappingDataArgs) { xmlWriter.writeAttribute(function.notOverlappingDataArgs.key(value), value); } xmlWriter.writeEndElement(); } if (!function.containerAttributes.isEmpty()) { xmlWriter.writeStartElement("container"); - foreach (const QString value, function.containerAttributes) { + foreach (const QString& value, function.containerAttributes) { xmlWriter.writeAttribute(function.containerAttributes.key(value), value); } xmlWriter.writeEndElement(); diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 3be305ee5f8..b454ee39585 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -94,6 +94,8 @@ #include #include +#include "json.h" + static const QString compile_commands_json("compile_commands.json"); static QString fromNativePath(const QString& p) { @@ -903,7 +905,43 @@ bool MainWindow::tryLoadLibrary(Library *library, const QString& filename) return true; } -Settings MainWindow::getCppcheckSettings() { +void MainWindow::loadAddon(Settings &settings, const QString &filesDir, const QString &pythonCmd, const QString& addon) +{ + QString addonFilePath = ProjectFile::getAddonFilePath(filesDir, addon); + if (addonFilePath.isEmpty()) + return; // TODO: report an error + + addonFilePath.replace(QChar('\\'), QChar('/')); + + picojson::object obj; + obj["script"] = picojson::value(addonFilePath.toStdString()); + if (!pythonCmd.isEmpty()) + obj["python"] = picojson::value(pythonCmd.toStdString()); + + if (!isCppcheckPremium() && addon == "misra") { + const QString misraFile = fromNativePath(mSettings->value(SETTINGS_MISRA_FILE).toString()); + if (!misraFile.isEmpty()) { + QString arg; + if (misraFile.endsWith(".pdf", Qt::CaseInsensitive)) + arg = "--misra-pdf=" + misraFile; + else + arg = "--rule-texts=" + misraFile; + obj["args"] = picojson::value(arg.toStdString()); + } + } + picojson::value json; + json.set(std::move(obj)); + std::string json_str = json.serialize(); + + AddonInfo addonInfo; + addonInfo.getAddonInfo(json_str, settings.exename); // TODO: handle error + settings.addonInfos.emplace_back(std::move(addonInfo)); + + settings.addons.emplace(std::move(json_str)); +} + +Settings MainWindow::getCppcheckSettings() +{ saveSettings(); // Save settings Settings result; @@ -914,10 +952,20 @@ Settings MainWindow::getCppcheckSettings() { if (!std) QMessageBox::critical(this, tr("Error"), tr("Failed to load %1. Your Cppcheck installation is broken. You can use --data-dir= at the command line to specify where this file is located. Please note that --data-dir is supposed to be used by installation scripts and therefore the GUI does not start when it is used, all that happens is that the setting is configured.").arg("std.cfg")); + const QString filesDir(getDataDir()); + const QString pythonCmd = fromNativePath(mSettings->value(SETTINGS_PYTHON_PATH).toString()); + { const QString cfgErr = QString::fromStdString(result.loadCppcheckCfg()); if (!cfgErr.isEmpty()) QMessageBox::critical(this, tr("Error"), tr("Failed to load %1 - %2").arg("cppcheck.cfg").arg(cfgErr)); + + const auto cfgAddons = result.addons; + result.addons.clear(); + for (const std::string& addon : cfgAddons) { + // TODO: support addons which are a script and not a file + loadAddon(result, filesDir, pythonCmd, QString::fromStdString(addon)); + } } // If project file loaded, read settings from it @@ -996,34 +1044,8 @@ Settings MainWindow::getCppcheckSettings() { for (const QString& s : mProjectFile->getCheckUnknownFunctionReturn()) result.checkUnknownFunctionReturn.insert(s.toStdString()); - QString filesDir(getDataDir()); - const QString pythonCmd = fromNativePath(mSettings->value(SETTINGS_PYTHON_PATH).toString()); for (const QString& addon : mProjectFile->getAddons()) { - QString addonFilePath = ProjectFile::getAddonFilePath(filesDir, addon); - if (addonFilePath.isEmpty()) - continue; - - addonFilePath.replace(QChar('\\'), QChar('/')); - - // TODO: use picojson to generate the JSON - QString json; - json += "{ \"script\":\"" + addonFilePath + "\""; - if (!pythonCmd.isEmpty()) - json += ", \"python\":\"" + pythonCmd + "\""; - const QString misraFile = fromNativePath(mSettings->value(SETTINGS_MISRA_FILE).toString()); - if (!isCppcheckPremium() && addon == "misra" && !misraFile.isEmpty()) { - QString arg; - if (misraFile.endsWith(".pdf", Qt::CaseInsensitive)) - arg = "--misra-pdf=" + misraFile; - else - arg = "--rule-texts=" + misraFile; - json += ", \"args\":[\"" + arg + "\"]"; - } - json += " }"; - result.addons.emplace(json.toStdString()); - AddonInfo addonInfo; - addonInfo.getAddonInfo(json.toStdString(), result.exename); - result.addonInfos.emplace_back(std::move(addonInfo)); + loadAddon(result, filesDir, pythonCmd, addon); } if (isCppcheckPremium()) { diff --git a/gui/mainwindow.h b/gui/mainwindow.h index 969e1c28e9d..4b6b20f9ad3 100644 --- a/gui/mainwindow.h +++ b/gui/mainwindow.h @@ -401,6 +401,8 @@ private slots: */ bool tryLoadLibrary(Library *library, const QString& filename); + void loadAddon(Settings &settings, const QString &filesDir, const QString &pythonCmd, const QString& addon); + /** * @brief Update project MRU items in File-menu. */ diff --git a/lib/astutils.cpp b/lib/astutils.cpp index ca8835ea12a..3b4d7231103 100644 --- a/lib/astutils.cpp +++ b/lib/astutils.cpp @@ -2249,7 +2249,7 @@ static T* getTokenArgumentFunctionImpl(T* tok, int& argn) parent = parent->astParent(); // passing variable to subfunction? - if (Token::Match(parent, "[*[(,{]") || Token::Match(parent, "%oror%|&&")) + if (Token::Match(parent, "[*[(,{.]") || Token::Match(parent, "%oror%|&&")) ; else if (Token::simpleMatch(parent, ":")) { while (Token::Match(parent, "[?:]")) @@ -3305,8 +3305,13 @@ ExprUsage getExprUsage(const Token* tok, int indirect, const Settings* settings, const Token* op = parent->astParent(); while (Token::simpleMatch(op, ".")) op = op->astParent(); - if (Token::Match(op, "%assign%|++|--") && op->str() != "=") - return ExprUsage::Used; + if (Token::Match(op, "%assign%|++|--")) { + if (op->str() == "=") { + if (precedes(tok, op)) + return ExprUsage::NotUsed; + } else + return ExprUsage::Used; + } } if (Token::simpleMatch(parent, "=") && astIsRHS(tok)) { const Token* const lhs = parent->astOperand1(); diff --git a/lib/checkio.cpp b/lib/checkio.cpp index 2d52f9ad4e1..a3805dc7ac3 100644 --- a/lib/checkio.cpp +++ b/lib/checkio.cpp @@ -19,6 +19,7 @@ //--------------------------------------------------------------------------- #include "checkio.h" +#include "astutils.h" #include "errortypes.h" #include "library.h" #include "mathlib.h" @@ -152,6 +153,10 @@ void CheckIO::checkFileUsage() for (const Scope * scope : symbolDatabase->functionScopes) { int indent = 0; for (const Token *tok = scope->bodyStart; tok != scope->bodyEnd; tok = tok->next()) { + if (Token::Match(tok, "%name% (") && isUnevaluated(tok)) { + tok = tok->linkAt(1); + continue; + } if (tok->str() == "{") indent++; else if (tok->str() == "}") { diff --git a/lib/checkleakautovar.cpp b/lib/checkleakautovar.cpp index 2f9f5909dd4..530e8c3df62 100644 --- a/lib/checkleakautovar.cpp +++ b/lib/checkleakautovar.cpp @@ -350,6 +350,11 @@ bool CheckLeakAutoVar::checkScope(const Token * const startToken, if (!tok || tok == endToken) break; + if (Token::Match(tok, "%name% (") && isUnevaluated(tok)) { + tok = tok->linkAt(1); + continue; + } + if (Token::Match(tok, "const %type%")) tok = tok->tokAt(2); @@ -962,7 +967,7 @@ void CheckLeakAutoVar::functionCall(const Token *tokName, const Token *tokOpenin while (Token::Match(arg, "%name% .|:: %name%")) arg = arg->tokAt(2); - if (Token::Match(arg, "%var% [-,)] !!.") || Token::Match(arg, "& %var%")) { + if (Token::Match(arg, "%var% [-,)] !!.") || Token::Match(arg, "& %var% !!.")) { // goto variable if (arg->str() == "&") arg = arg->next(); @@ -974,6 +979,9 @@ void CheckLeakAutoVar::functionCall(const Token *tokName, const Token *tokOpenin const Library::AllocFunc* deallocFunc = mSettings->library.getDeallocFuncInfo(tokName); VarInfo::AllocInfo dealloc(deallocFunc ? deallocFunc->groupId : 0, VarInfo::DEALLOC, tokName); if (const Library::AllocFunc* allocFunc = mSettings->library.getAllocFuncInfo(tokName)) { + if (mSettings->library.getDeallocFuncInfo(tokName)) { + changeAllocStatus(varInfo, dealloc.type == 0 ? allocation : dealloc, tokName, arg); + } if (allocFunc->arg == argNr && !(arg->variable() && arg->variable()->isArgument() && arg->valueType() && arg->valueType()->pointer > 1)) { leakIfAllocated(arg, varInfo); VarInfo::AllocInfo& varAlloc = varInfo.alloctype[arg->varId()]; @@ -1058,6 +1066,21 @@ void CheckLeakAutoVar::leakIfAllocated(const Token *vartok, } } +static const Token* getOutparamAllocation(const Token* tok, const Settings* settings) +{ + if (!tok) + return nullptr; + int argn{}; + const Token* ftok = getTokenArgumentFunction(tok, argn); + if (!ftok) + return nullptr; + if (const Library::AllocFunc* allocFunc = settings->library.getAllocFuncInfo(ftok)) { + if (allocFunc->arg == argn + 1) + return ftok; + } + return nullptr; +} + void CheckLeakAutoVar::ret(const Token *tok, VarInfo &varInfo, const bool isEndOfScope) { const std::map &alloctype = varInfo.alloctype; @@ -1112,7 +1135,9 @@ void CheckLeakAutoVar::ret(const Token *tok, VarInfo &varInfo, const bool isEndO } // don't warn when returning after checking return value of outparam allocation - if (it->second.allocTok && (tok->scope()->type == Scope::ScopeType::eIf || tok->scope()->type== Scope::ScopeType::eElse)) { + const Token* outparamFunc{}; + if ((tok->scope()->type == Scope::ScopeType::eIf || tok->scope()->type== Scope::ScopeType::eElse) && + (outparamFunc = getOutparamAllocation(it->second.allocTok, mSettings))) { const Scope* scope = tok->scope(); if (scope->type == Scope::ScopeType::eElse) { scope = scope->bodyStart->tokAt(-2)->scope(); @@ -1120,11 +1145,22 @@ void CheckLeakAutoVar::ret(const Token *tok, VarInfo &varInfo, const bool isEndO const Token* const ifEnd = scope->bodyStart->previous(); const Token* const ifStart = ifEnd->link(); const Token* const alloc = it->second.allocTok; - if (precedes(ifStart, alloc) && succeeds(ifEnd, alloc)) { - int argn{}; - if (const Token* ftok = getTokenArgumentFunction(alloc, argn)) - if (Token::Match(ftok->next()->astParent(), "%comp%")) + if (precedes(ifStart, alloc) && succeeds(ifEnd, alloc)) { // allocation and check in if + if (Token::Match(outparamFunc->next()->astParent(), "%comp%")) + continue; + } else { // allocation result assigned to variable + const Token* const retAssign = outparamFunc->next()->astParent(); + if (Token::simpleMatch(retAssign, "=") && retAssign->astOperand1()->varId()) { + bool isRetComp = false; + for (const Token* tok2 = ifStart; tok2 != ifEnd; tok2 = tok2->next()) { + if (tok2->varId() == retAssign->astOperand1()->varId()) { + isRetComp = true; + break; + } + } + if (isRetComp) continue; + } } } diff --git a/lib/checkother.cpp b/lib/checkother.cpp index 041b9d022cc..3e324a6833d 100644 --- a/lib/checkother.cpp +++ b/lib/checkother.cpp @@ -1244,10 +1244,14 @@ void CheckOther::checkPassByReference() const SymbolDatabase * const symbolDatabase = mTokenizer->getSymbolDatabase(); for (const Variable* var : symbolDatabase->variableList()) { - if (!var || !var->isArgument() || !var->isClass() || var->isPointer() || var->isArray() || var->isReference() || var->isEnumType()) + if (!var || !var->isClass() || var->isPointer() || var->isArray() || var->isReference() || var->isEnumType()) continue; - if (var->scope() && var->scope()->function->arg->link()->strAt(-1) == "...") + const bool isRangeBasedFor = astIsRangeBasedForDecl(var->nameToken()); + if (!var->isArgument() && !isRangeBasedFor) + continue; + + if (!isRangeBasedFor && var->scope() && var->scope()->function->arg->link()->strAt(-1) == "...") continue; // references could not be used as va_start parameters (#5824) const Token * const varDeclEndToken = var->declEndToken(); @@ -1275,25 +1279,27 @@ void CheckOther::checkPassByReference() const bool isConst = var->isConst(); if (isConst) { - passedByValueError(var, inconclusive); + passedByValueError(var, inconclusive, isRangeBasedFor); continue; } // Check if variable could be const - if (!var->scope() || var->scope()->function->isImplicitlyVirtual()) + if (!isRangeBasedFor && (!var->scope() || var->scope()->function->isImplicitlyVirtual())) continue; if (!isVariableChanged(var, mSettings, mTokenizer->isCPP())) { - passedByValueError(var, inconclusive); + passedByValueError(var, inconclusive, isRangeBasedFor); } } } -void CheckOther::passedByValueError(const Variable* var, bool inconclusive) +void CheckOther::passedByValueError(const Variable* var, bool inconclusive, bool isRangeBasedFor) { - std::string id = "passedByValue"; - std::string msg = "$symbol:" + (var ? var->name() : "") + "\n" - "Function parameter '$symbol' should be passed by const reference."; + std::string id = isRangeBasedFor ? "iterateByValue" : "passedByValue"; + const std::string action = isRangeBasedFor ? "declared as": "passed by"; + const std::string type = isRangeBasedFor ? "Range variable" : "Function parameter"; + std::string msg = "$symbol:" + (var ? var->name() : "") + "\n" + + type + " '$symbol' should be " + action + " const reference."; ErrorPath errorPath; if (var && var->scope() && var->scope()->function && var->scope()->function->functionPointerUsage) { id += "Callback"; @@ -1302,7 +1308,10 @@ void CheckOther::passedByValueError(const Variable* var, bool inconclusive) } if (var) errorPath.emplace_back(var->nameToken(), msg); - msg += "\nParameter '$symbol' is passed by value. It could be passed as a const reference which is usually faster and recommended in C++."; + if (isRangeBasedFor) + msg += "\nVariable '$symbol' is used to iterate by value. It could be declared as a const reference which is usually faster and recommended in C++."; + else + msg += "\nParameter '$symbol' is passed by value. It could be passed as a const reference which is usually faster and recommended in C++."; reportError(errorPath, Severity::performance, id.c_str(), msg, CWE398, inconclusive ? Certainty::inconclusive : Certainty::normal); } diff --git a/lib/checkother.h b/lib/checkother.h index 2499ef1b581..8f84007032a 100644 --- a/lib/checkother.h +++ b/lib/checkother.h @@ -241,7 +241,7 @@ class CPPCHECKLIB CheckOther : public Check { void clarifyStatementError(const Token* tok); void cstyleCastError(const Token *tok); void invalidPointerCastError(const Token* tok, const std::string& from, const std::string& to, bool inconclusive, bool toIsInt); - void passedByValueError(const Variable* var, bool inconclusive); + void passedByValueError(const Variable* var, bool inconclusive, bool isRangeBasedFor = false); void constVariableError(const Variable *var, const Function *function); void constStatementError(const Token *tok, const std::string &type, bool inconclusive); void signedCharArrayIndexError(const Token *tok); diff --git a/lib/json.h b/lib/json.h index 0f2da5c346c..f4177b5cf42 100644 --- a/lib/json.h +++ b/lib/json.h @@ -23,7 +23,6 @@ SUPPRESS_WARNING_PUSH("-Wfloat-equal") SUPPRESS_WARNING_CLANG_PUSH("-Wtautological-type-limit-compare") -SUPPRESS_WARNING_GCC_PUSH("-Wparentheses") SUPPRESS_WARNING_CLANG_PUSH("-Wextra-semi-stmt") SUPPRESS_WARNING_CLANG_PUSH("-Wzero-as-null-pointer-constant") @@ -32,7 +31,6 @@ SUPPRESS_WARNING_CLANG_PUSH("-Wzero-as-null-pointer-constant") SUPPRESS_WARNING_CLANG_POP SUPPRESS_WARNING_CLANG_POP -SUPPRESS_WARNING_GCC_POP SUPPRESS_WARNING_CLANG_POP SUPPRESS_WARNING_POP diff --git a/lib/symboldatabase.cpp b/lib/symboldatabase.cpp index 960deb886c6..fa8dd86b06a 100644 --- a/lib/symboldatabase.cpp +++ b/lib/symboldatabase.cpp @@ -1571,16 +1571,101 @@ static std::string getIncompleteNameID(const Token* tok) return result + tok->expressionString(); } +namespace { + struct ExprIdKey { + std::string parentOp; + nonneg int operand1; + nonneg int operand2; + bool operator<(const ExprIdKey& k) const { + return std::tie(parentOp, operand1, operand2) < std::tie(k.parentOp, k.operand1, k.operand2); + } + }; + using ExprIdMap = std::map; + void setParentExprId(Token* tok, bool cpp, ExprIdMap& exprIdMap, nonneg int &id) { + if (!tok->astParent() || tok->astParent()->isControlFlowKeyword()) + return; + const Token* op1 = tok->astParent()->astOperand1(); + if (op1 && op1->exprId() == 0) + return; + const Token* op2 = tok->astParent()->astOperand2(); + if (op2 && op2->exprId() == 0 && !(isLambdaCaptureList(op2) || (op2->str() == "(" && isLambdaCaptureList(op2->astOperand1())))) + return; + + if (tok->astParent()->isExpandedMacro() || Token::Match(tok->astParent(), "++|--")) { + tok->astParent()->exprId(id); + ++id; + setParentExprId(tok->astParent(), cpp, exprIdMap, id); + return; + } + + ExprIdKey key; + key.parentOp = tok->astParent()->str(); + key.operand1 = op1 ? op1->exprId() : 0; + key.operand2 = op2 ? op2->exprId() : 0; + + if (tok->astParent()->isCast() && tok->astParent()->str() == "(") { + const Token* typeStartToken; + const Token* typeEndToken; + if (tok->astParent()->astOperand2()) { + typeStartToken = tok->astParent()->astOperand1(); + typeEndToken = tok; + } else { + typeStartToken = tok->astParent()->next(); + typeEndToken = tok->astParent()->link(); + } + std::string type; + for (const Token* t = typeStartToken; t != typeEndToken; t = t->next()) { + type += " " + t->str(); + } + key.parentOp += type; + } + + for (const auto& ref: followAllReferences(op1)) { + if (ref.token->exprId() != 0) { // cppcheck-suppress useStlAlgorithm + key.operand1 = ref.token->exprId(); + break; + } + } + for (const auto& ref: followAllReferences(op2)) { + if (ref.token->exprId() != 0) { // cppcheck-suppress useStlAlgorithm + key.operand2 = ref.token->exprId(); + break; + } + } + + if (key.operand1 > key.operand2 && key.operand2 && + Token::Match(tok->astParent(), "%or%|%oror%|+|*|&|&&|^|==|!=")) { + // In C++ the order of operands of + might matter + if (!cpp || + key.parentOp != "+" || + !tok->astParent()->valueType() || + tok->astParent()->valueType()->isIntegral() || + tok->astParent()->valueType()->isFloat() || + tok->astParent()->valueType()->pointer > 0) + std::swap(key.operand1, key.operand2); + } + + const auto it = exprIdMap.find(key); + if (it == exprIdMap.end()) { + exprIdMap[key] = id; + tok->astParent()->exprId(id); + ++id; + } else { + tok->astParent()->exprId(it->second); + } + setParentExprId(tok->astParent(), cpp, exprIdMap, id); + } +} + void SymbolDatabase::createSymbolDatabaseExprIds() { - nonneg int base = 0; // Find highest varId - for (const Variable *var : mVariableList) { - if (!var) - continue; - base = std::max(base, var->declarationId()); + nonneg int maximumVarId = 0; + for (const Token* tok = mTokenizer.list.front(); tok; tok = tok->next()) { + if (tok->varId() > maximumVarId) + maximumVarId = tok->varId(); } - nonneg int id = base + 1; + nonneg int id = maximumVarId + 1; // Find incomplete vars that are used in constant context std::unordered_map unknownConstantIds; const Token* inConstExpr = nullptr; @@ -1611,7 +1696,6 @@ void SymbolDatabase::createSymbolDatabaseExprIds() }); for (const Scope * scope : exprScopes) { - nonneg int thisId = 0; std::unordered_map> exprs; std::unordered_map unknownIds; @@ -1638,62 +1722,65 @@ void SymbolDatabase::createSymbolDatabaseExprIds() } // Assign IDs + ExprIdMap exprIdMap; + std::map baseIds; for (Token* tok = const_cast(scope->bodyStart); tok != scope->bodyEnd; tok = tok->next()) { if (tok->varId() > 0) { tok->exprId(tok->varId()); - } else if (isExpression(tok)) { - exprs[tok->str()].push_back(tok); - tok->exprId(id++); + if (tok->astParent() && tok->astParent()->exprId() == 0) + setParentExprId(tok, mTokenizer.isCPP(), exprIdMap, id); + } else if (tok->astParent() && !tok->astOperand1() && !tok->astOperand2()) { + if (tok->tokType() == Token::Type::eBracket) + continue; + if (tok->astParent()->str() == "=") + continue; + if (tok->isControlFlowKeyword()) + continue; - if (id == std::numeric_limits::max() / 4) { - throw InternalError(nullptr, "Ran out of expression ids.", InternalError::INTERNAL); - } - } else if (isCPP() && Token::simpleMatch(tok, "this")) { - if (thisId == 0) - thisId = id++; - tok->exprId(thisId); - } - } - - // Apply CSE - for (const auto& p:exprs) { - const std::vector& tokens = p.second; - const std::size_t N = tokens.size(); - for (std::size_t i = 0; i < N; ++i) { - Token* const tok1 = tokens[i]; - for (std::size_t j = i + 1; j < N; ++j) { - Token* const tok2 = tokens[j]; - if (tok1->exprId() == tok2->exprId()) - continue; - if (!isSameExpression(isCPP(), true, tok1, tok2, mSettings.library, false, false)) - continue; - nonneg int const cid = std::min(tok1->exprId(), tok2->exprId()); - tok1->exprId(cid); - tok2->exprId(cid); + if (Token::Match(tok, "%name% <") && tok->next()->link()) { + tok->exprId(id); + ++id; + } else { + const auto it = baseIds.find(tok->str()); + if (it != baseIds.end()) { + tok->exprId(it->second); + } else { + baseIds[tok->str()] = id; + tok->exprId(id); + ++id; + } } + + setParentExprId(tok, mTokenizer.isCPP(), exprIdMap, id); } } - // Mark expressions that are unique - std::unordered_map exprMap; for (Token* tok = const_cast(scope->bodyStart); tok != scope->bodyEnd; tok = tok->next()) { - if (tok->exprId() == 0) - continue; - auto p = exprMap.emplace(tok->exprId(), tok); - // Already exists so set it to null - if (!p.second) { - p.first->second = nullptr; + if (tok->varId() == 0 && tok->exprId() > 0 && tok->astParent() && !tok->astOperand1() && !tok->astOperand2()) { + if (tok->isNumber() || tok->isKeyword() || Token::Match(tok->astParent(), ".|::") || + (Token::simpleMatch(tok->astParent(), "(") && precedes(tok, tok->astParent()))) + tok->exprId(0); } } - for (const auto& p : exprMap) { - if (!p.second) + } + + // Mark expressions that are unique + std::vector> uniqueExprId(id); + for (Token* tok = const_cast(mTokenizer.list.front()); tok; tok = tok->next()) { + const auto id2 = tok->exprId(); + if (id2 == 0 || id2 <= maximumVarId) + continue; + uniqueExprId[id2].first = tok; + uniqueExprId[id2].second++; + } + for (const auto& p : uniqueExprId) { + if (!p.first || p.second != 1) + continue; + if (p.first->variable()) { + const Variable* var = p.first->variable(); + if (var->nameToken() != p.first) continue; - if (p.second->variable()) { - const Variable* var = p.second->variable(); - if (var->nameToken() != p.second) - continue; - } - p.second->setUniqueExprId(); } + p.first->setUniqueExprId(); } } @@ -5943,47 +6030,59 @@ const Scope *SymbolDatabase::findScopeByName(const std::string& name) const //--------------------------------------------------------------------------- -const Scope *Scope::findRecordInNestedList(const std::string & name, bool isC) const +template ), REQUIRES("T must be a Type class", std::is_convertible )> +S* findRecordInNestedListImpl(S& thisScope, const std::string & name, bool isC) { - for (const Scope* scope: nestedList) { - if (scope->className == name && scope->type != eFunction) + for (S* scope: thisScope.nestedList) { + if (scope->className == name && scope->type != Scope::eFunction) return scope; if (isC) { - const Scope* nestedScope = scope->findRecordInNestedList(name, isC); + S* nestedScope = scope->findRecordInNestedList(name, isC); if (nestedScope) return nestedScope; } } - const Type * nested_type = findType(name); + T * nested_type = thisScope.findType(name); if (nested_type) { if (nested_type->isTypeAlias()) { if (nested_type->typeStart == nested_type->typeEnd) - return findRecordInNestedList(nested_type->typeStart->str()); + return thisScope.findRecordInNestedList(nested_type->typeStart->str()); // TODO: pass isC? } else - return nested_type->classScope; + return const_cast(nested_type->classScope); } return nullptr; } +const Scope* Scope::findRecordInNestedList(const std::string & name, bool isC) const +{ + return findRecordInNestedListImpl(*this, name, isC); +} + +Scope* Scope::findRecordInNestedList(const std::string & name, bool isC) +{ + return findRecordInNestedListImpl(*this, name, isC); +} + //--------------------------------------------------------------------------- -const Type* Scope::findType(const std::string & name) const +template ), REQUIRES("T must be a Type class", std::is_convertible )> +T* findTypeImpl(S& thisScope, const std::string & name) { - auto it = definedTypesMap.find(name); + auto it = thisScope.definedTypesMap.find(name); // Type was found - if (definedTypesMap.end() != it) + if (thisScope.definedTypesMap.end() != it) return it->second; // is type defined in anonymous namespace.. - it = definedTypesMap.find(emptyString); - if (it != definedTypesMap.end()) { - for (const Scope *scope : nestedList) { - if (scope->className.empty() && (scope->type == eNamespace || scope->isClassOrStructOrUnion())) { - const Type *t = scope->findType(name); + it = thisScope.definedTypesMap.find(emptyString); + if (it != thisScope.definedTypesMap.end()) { + for (S *scope : thisScope.nestedList) { + if (scope->className.empty() && (scope->type == thisScope.eNamespace || scope->isClassOrStructOrUnion())) { + T *t = scope->findType(name); if (t) return t; } @@ -5994,6 +6093,16 @@ const Type* Scope::findType(const std::string & name) const return nullptr; } +const Type* Scope::findType(const std::string& name) const +{ + return findTypeImpl(*this, name); +} + +Type* Scope::findType(const std::string& name) +{ + return findTypeImpl(*this, name); +} + //--------------------------------------------------------------------------- Scope *Scope::findInNestedListRecursive(const std::string & name) diff --git a/lib/symboldatabase.h b/lib/symboldatabase.h index 83b0fb33119..6f59ebc8a27 100644 --- a/lib/symboldatabase.h +++ b/lib/symboldatabase.h @@ -1130,14 +1130,10 @@ class CPPCHECKLIB Scope { const Function *findFunction(const Token *tok, bool requireConst=false) const; const Scope *findRecordInNestedList(const std::string & name, bool isC = false) const; - Scope *findRecordInNestedList(const std::string & name) { - return const_cast(const_cast(this)->findRecordInNestedList(name)); - } + Scope *findRecordInNestedList(const std::string & name, bool isC = false); const Type* findType(const std::string& name) const; - Type* findType(const std::string& name) { - return const_cast(const_cast(this)->findType(name)); - } + Type* findType(const std::string& name); /** * @brief find if name is in nested list diff --git a/lib/templatesimplifier.cpp b/lib/templatesimplifier.cpp index e156e4be792..86a10d5a7ee 100644 --- a/lib/templatesimplifier.cpp +++ b/lib/templatesimplifier.cpp @@ -554,12 +554,8 @@ unsigned int TemplateSimplifier::templateParameters(const Token *tok) return 0; } -const Token *TemplateSimplifier::findTemplateDeclarationEnd(const Token *tok) -{ - return const_cast(findTemplateDeclarationEnd(const_cast(tok))); -} - -Token *TemplateSimplifier::findTemplateDeclarationEnd(Token *tok) +template )> +static T *findTemplateDeclarationEndImpl(T *tok) { if (Token::simpleMatch(tok, "template <")) { tok = tok->next()->findClosingBracket(); @@ -570,7 +566,7 @@ Token *TemplateSimplifier::findTemplateDeclarationEnd(Token *tok) if (!tok) return nullptr; - Token * tok2 = tok; + T * tok2 = tok; bool in_init = false; while (tok2 && !Token::Match(tok2, ";|{")) { if (tok2->str() == "<") @@ -599,6 +595,16 @@ Token *TemplateSimplifier::findTemplateDeclarationEnd(Token *tok) return tok; } +Token *TemplateSimplifier::findTemplateDeclarationEnd(Token *tok) +{ + return findTemplateDeclarationEndImpl(tok); +} + +const Token *TemplateSimplifier::findTemplateDeclarationEnd(const Token *tok) +{ + return findTemplateDeclarationEndImpl(tok); +} + void TemplateSimplifier::eraseTokens(Token *begin, const Token *end) { if (!begin || begin == end) diff --git a/lib/token.cpp b/lib/token.cpp index e77b2dfcf38..686b5a0e23c 100644 --- a/lib/token.cpp +++ b/lib/token.cpp @@ -402,9 +402,9 @@ void Token::replace(Token *replaceThis, Token *start, Token *end) delete replaceThis; } -const Token *Token::tokAt(int index) const +template )> +static T *tokAtImpl(T *tok, int index) { - const Token *tok = this; while (index > 0 && tok) { tok = tok->next(); --index; @@ -416,15 +416,36 @@ const Token *Token::tokAt(int index) const return tok; } -const Token *Token::linkAt(int index) const +const Token *Token::tokAt(int index) const { - const Token *tok = this->tokAt(index); + return tokAtImpl(this, index); +} + +Token *Token::tokAt(int index) +{ + return tokAtImpl(this, index); +} + +template )> +static T *linkAtImpl(T *thisTok, int index) +{ + T *tok = thisTok->tokAt(index); if (!tok) { - throw InternalError(this, "Internal error. Token::linkAt called with index outside the tokens range."); + throw InternalError(thisTok, "Internal error. Token::linkAt called with index outside the tokens range."); } return tok->link(); } +const Token *Token::linkAt(int index) const +{ + return linkAtImpl(this, index); +} + +Token *Token::linkAt(int index) +{ + return linkAtImpl(this, index); +} + const std::string &Token::strAt(int index) const { const Token *tok = this->tokAt(index); @@ -857,9 +878,10 @@ void Token::move(Token *srcStart, Token *srcEnd, Token *newLocation) tok->mImpl->mProgressValue = newLocation->mImpl->mProgressValue; } -const Token* Token::nextArgument() const +template )> +static T* nextArgumentImpl(T *thisTok) { - for (const Token* tok = this; tok; tok = tok->next()) { + for (T* tok = thisTok; tok; tok = tok->next()) { if (tok->str() == ",") return tok->next(); if (tok->link() && Token::Match(tok, "(|{|[|<")) @@ -870,6 +892,16 @@ const Token* Token::nextArgument() const return nullptr; } +const Token* Token::nextArgument() const +{ + return nextArgumentImpl(this); +} + +Token *Token::nextArgument() +{ + return nextArgumentImpl(this); +} + const Token* Token::nextArgumentBeforeCreateLinks2() const { for (const Token* tok = this; tok; tok = tok->next()) { @@ -1007,42 +1039,83 @@ Token * Token::findOpeningBracket() //--------------------------------------------------------------------------- -const Token *Token::findsimplematch(const Token * const startTok, const char pattern[], size_t pattern_len) +template )> +static T *findsimplematchImpl(T * const startTok, const char pattern[], size_t pattern_len) { - for (const Token* tok = startTok; tok; tok = tok->next()) { + for (T* tok = startTok; tok; tok = tok->next()) { if (Token::simpleMatch(tok, pattern, pattern_len)) return tok; } return nullptr; } -const Token *Token::findsimplematch(const Token * const startTok, const char pattern[], size_t pattern_len, const Token * const end) +const Token *Token::findsimplematch(const Token * const startTok, const char pattern[], size_t pattern_len) +{ + return findsimplematchImpl(startTok, pattern, pattern_len); +} + +Token *Token::findsimplematch(Token * const startTok, const char pattern[], size_t pattern_len) +{ + return findsimplematchImpl(startTok, pattern, pattern_len); +} + +template )> +static T *findsimplematchImpl(T * const startTok, const char pattern[], size_t pattern_len, const Token * const end) { - for (const Token* tok = startTok; tok && tok != end; tok = tok->next()) { + for (T* tok = startTok; tok && tok != end; tok = tok->next()) { if (Token::simpleMatch(tok, pattern, pattern_len)) return tok; } return nullptr; } -const Token *Token::findmatch(const Token * const startTok, const char pattern[], const nonneg int varId) +const Token *Token::findsimplematch(const Token * const startTok, const char pattern[], size_t pattern_len, const Token * const end) +{ + return findsimplematchImpl(startTok, pattern, pattern_len, end); +} + +Token *Token::findsimplematch(Token * const startTok, const char pattern[], size_t pattern_len, const Token * const end) { + return findsimplematchImpl(startTok, pattern, pattern_len, end); +} + +template )> +static T *findmatchImpl(T * const startTok, const char pattern[], const nonneg int varId) { - for (const Token* tok = startTok; tok; tok = tok->next()) { + for (T* tok = startTok; tok; tok = tok->next()) { if (Token::Match(tok, pattern, varId)) return tok; } return nullptr; } -const Token *Token::findmatch(const Token * const startTok, const char pattern[], const Token * const end, const nonneg int varId) +const Token *Token::findmatch(const Token * const startTok, const char pattern[], const nonneg int varId) +{ + return findmatchImpl(startTok, pattern, varId); +} + +Token *Token::findmatch(Token * const startTok, const char pattern[], const nonneg int varId) { + return findmatchImpl(startTok, pattern, varId); +} + +template )> +static T *findmatchImpl(T * const startTok, const char pattern[], const Token * const end, const nonneg int varId) { - for (const Token* tok = startTok; tok && tok != end; tok = tok->next()) { + for (T* tok = startTok; tok && tok != end; tok = tok->next()) { if (Token::Match(tok, pattern, varId)) return tok; } return nullptr; } +const Token *Token::findmatch(const Token * const startTok, const char pattern[], const Token * const end, const nonneg int varId) +{ + return findmatchImpl(startTok, pattern, end, varId); +} + +Token *Token::findmatch(Token * const startTok, const char pattern[], const Token * const end, const nonneg int varId) { + return findmatchImpl(startTok, pattern, end, varId); +} + void Token::function(const Function *f) { mImpl->mFunction = f; @@ -1269,7 +1342,10 @@ std::string Token::stringify(const stringifyOptions& options) const } else if (options.exprid && mImpl->mExprId != 0) { ret += '@'; ret += (options.idtype ? "expr" : ""); - ret += std::to_string(mImpl->mExprId); + if ((mImpl->mExprId & (1U << efIsUnique)) != 0) + ret += "UNIQUE"; + else + ret += std::to_string(mImpl->mExprId); } return ret; diff --git a/lib/token.h b/lib/token.h index 9d35486349c..91d8281be76 100644 --- a/lib/token.h +++ b/lib/token.h @@ -212,18 +212,14 @@ class CPPCHECKLIB Token { * would return next from that one. */ const Token *tokAt(int index) const; - Token *tokAt(int index) { - return const_cast(const_cast(this)->tokAt(index)); - } + Token *tokAt(int index); /** * @return the link to the token in given index, related to this token. * For example index 1 would return the link to next token. */ const Token *linkAt(int index) const; - Token *linkAt(int index) { - return const_cast(const_cast(this)->linkAt(index)); - } + Token *linkAt(int index); /** * @return String of the token in given index, related to this token. @@ -780,23 +776,15 @@ class CPPCHECKLIB Token { static Token *findsimplematch(Token * const startTok, const char (&pattern)[count]) { return findsimplematch(startTok, pattern, count-1); } - static Token *findsimplematch(Token * const startTok, const char pattern[], size_t pattern_len) { - return const_cast(findsimplematch(const_cast(startTok), pattern, pattern_len)); - } + static Token *findsimplematch(Token * const startTok, const char pattern[], size_t pattern_len); template static Token *findsimplematch(Token * const startTok, const char (&pattern)[count], const Token * const end) { return findsimplematch(startTok, pattern, count-1, end); } - static Token *findsimplematch(Token * const startTok, const char pattern[], size_t pattern_len, const Token * const end) { - return const_cast(findsimplematch(const_cast(startTok), pattern, pattern_len, end)); - } + static Token *findsimplematch(Token * const startTok, const char pattern[], size_t pattern_len, const Token * const end); - static Token *findmatch(Token * const startTok, const char pattern[], const nonneg int varId = 0) { - return const_cast(findmatch(const_cast(startTok), pattern, varId)); - } - static Token *findmatch(Token * const startTok, const char pattern[], const Token * const end, const nonneg int varId = 0) { - return const_cast(findmatch(const_cast(startTok), pattern, end, varId)); - } + static Token *findmatch(Token * const startTok, const char pattern[], const nonneg int varId = 0); + static Token *findmatch(Token * const startTok, const char pattern[], const Token * const end, const nonneg int varId = 0); private: /** @@ -907,10 +895,7 @@ class CPPCHECKLIB Token { bool isUniqueExprId() const { - if (mImpl->mExprId > 0) { - return (mImpl->mExprId & (1 << efIsUnique)) != 0; - } - return false; + return (mImpl->mExprId & (1 << efIsUnique)) != 0; } /** @@ -1173,9 +1158,7 @@ class CPPCHECKLIB Token { * Returns 0, if there is no next argument. */ const Token* nextArgument() const; - Token *nextArgument() { - return const_cast(const_cast(this)->nextArgument()); - } + Token *nextArgument(); /** * @return the first token of the next argument. Does only work on argument diff --git a/lib/valueflow.cpp b/lib/valueflow.cpp index d4b99f2fb67..6cf189e24c4 100644 --- a/lib/valueflow.cpp +++ b/lib/valueflow.cpp @@ -7326,9 +7326,16 @@ struct MultiValueFlowAnalyzer : ValueFlowAnalyzer { return false; } - bool stopOnCondition(const Token* /*condTok*/) const override { - // TODO fix false negatives - return true; // isConditional(); + bool stopOnCondition(const Token* condTok) const override { + if (isConditional()) + return true; + if (!condTok->hasKnownIntValue() && values.count(condTok->varId()) == 0) { + const auto& values_ = condTok->values(); + return std::any_of(values_.cbegin(), values_.cend(), [](const ValueFlow::Value& v) { + return v.isSymbolicValue() && Token::Match(v.tokvalue, "%oror%|&&"); + }); + } + return false; } bool updateScope(const Token* endBlock, bool /*modified*/) const override { diff --git a/man/manual.md b/man/manual.md index bc710d6d947..819760925a7 100644 --- a/man/manual.md +++ b/man/manual.md @@ -321,7 +321,7 @@ If you can generate a compile database, then it is possible to import that in Cp In Linux you can use for instance the `bear` (build ear) utility to generate a compile database from arbitrary build tools: - bear make + bear -- make # Preprocessor Settings @@ -966,7 +966,7 @@ To use a .cfg file shipped with cppcheck, pass the `--library=` option. The | avr.cfg | | | bento4.cfg | [Bento4](http://www.bento4.com/) | | boost.cfg | [Boost](http://www.boost.org/)| -| bsd.cfg | [*BSD](https://www.freebsd.org/) | +| bsd.cfg | [BSD](https://www.freebsd.org/) | | cairo.cfg | [cairo](https://www.cairographics.org/) | | cppcheck-lib.cfg | [Cppcheck](http://cppcheck.net/) | Used in selfcheck of the Cppcheck code base | cppunit.cfg | [CppUnit](https://sourceforge.net/projects/cppunit/) | @@ -974,7 +974,7 @@ To use a .cfg file shipped with cppcheck, pass the `--library=` option. The | embedded_sql.cfg | | | emscripten.cfg | | | ginac.cfg | | -| gnu.cfg | [*nix](https://www.gnu.org/) | +| gnu.cfg | [GNU](https://www.gnu.org/) | | googletest.cfg | [GoogleTest](https://github.com/google/googletest) | | gtk.cfg | [GTK](https://www.gtk.org/) | | icu.cfg | | @@ -997,7 +997,7 @@ To use a .cfg file shipped with cppcheck, pass the `--library=` option. The | pcre.cfg | [PCRE](https://pcre.org/) | | posix.cfg | [POSIX](https://pubs.opengroup.org/onlinepubs/9699919799/) | | python.cfg | | -| qt.cfg | [Qt](https://www.qt.io/) | +| qt.cfg | [Qt](https://doc.qt.io/qt.html) | | ruby.cfg | | | sdl.cfg | | | sfml.cfg | | diff --git a/philosophy.md b/philosophy.md index 96cda35a357..af61a1cd38f 100644 --- a/philosophy.md +++ b/philosophy.md @@ -5,6 +5,12 @@ It is important that everybody in the Cppcheck team has a consistent idea about This is a static analyzer tool. +## Usability + +Usability is very important. It's more important that Cppcheck is usable than finding all bugs. + - We don't want to have tons of configurations options. + - It's very important that warning messages are well written and with enough details. + - Speed is very important. --check-level=exhaustive can be used when user accept slow analysis. ## Normal analysis - No false positives @@ -12,9 +18,9 @@ A fundamental goal is "no false positives". It is not possible to achieve "no false positives" completely. One case where false positives are OK is when the code is garbage. -If the code is written as it is by design, then our goal is to not warn. +If the code is written as it is by design, then our goal is to not show any false positives. -If it is not known if there is a problem, then in general we need to bailout. We can only warn when we see that there is a problem. +If it is not known if there is a problem, then in general we need to bailout to avoid false positives. We can only warn when we see that there is a problem. Stylistic checks are much more prone to false positives and therefore we should avoid writing stylistic checks mostly. @@ -24,7 +30,10 @@ Reporting issues in Trac: ### Inconclusive messages -Inconclusive messages will be created if cppcheck cannot be sure there is an issue to warn but 50-50 probability. User shall enable inconclusive messages if they are willing to spend substantially more time on message verification in order to find more issues within a high false positive rate. +If cppcheck can't determine that there is a problem or not, then the analysis is inconclusive. + +If the user enables inconclusive warnings and we guess that the probability there is a real problem is at least 50-50 then it's OK to write a inconclusive warning. + Inconclusive messages shall not be used for new checks which are just being developed. There `settings.experimental` can be used. diff --git a/releasenotes.txt b/releasenotes.txt index 5e9b0d6db0e..345239f7fbf 100644 --- a/releasenotes.txt +++ b/releasenotes.txt @@ -3,6 +3,7 @@ Release Notes for Cppcheck 2.13 New checks: - passedByValueCallback for functions which take a parameter by value but are used as callbacks (subset of passedByValue) - returnImplicitInt for C functions without return type (previously reported as a debug message) +- iterateByValue for iterating by value in a range-based for loop when a const reference could be used Improved checking: - @@ -33,3 +34,4 @@ Other: - fixed CMake build with UBSAN and GCC - Added command-line options "--fsigned-char" and "--funsigned-char" to control the signess of the "char" type. This overrides previously specified "--platform" options and is overrides by following ones. - An error is now reported when the "cppcheck.cfg" is invalid. The CLI version will also exit with a failure in that case. +- Fixed loading of addons from "cppcheck.cfg" in the GUI application. diff --git a/test/cfg/gnu.c b/test/cfg/gnu.c index 35b65e816c7..e2ca818f938 100644 --- a/test/cfg/gnu.c +++ b/test/cfg/gnu.c @@ -398,6 +398,17 @@ void memleak_asprintf7(const char* fmt, const int arg) { return; } +void memleak_asprintf8(const char *fmt, const int arg) // #12204 +{ + char* ptr; + int ret = asprintf(&ptr, fmt, arg); + if (-1 == ret) { + return; + } + printf("%s", ptr); + free(ptr); +} + void memleak_xmalloc() { char *p = (char*)xmalloc(10); diff --git a/test/cfg/posix.c b/test/cfg/posix.c index a163f4ed8fb..474f160245b 100644 --- a/test/cfg/posix.c +++ b/test/cfg/posix.c @@ -891,6 +891,22 @@ void validCode(va_list valist_arg1, va_list valist_arg2) } } +typedef struct { + size_t N; + int* data; +} S_memalign; + +S_memalign* posix_memalign_memleak(size_t n) { // #12248 + S_memalign* s = malloc(sizeof(*s)); + s->N = n; + if (0 != posix_memalign((void**)&s->data, 16, n * sizeof(int))) { + free(s); + return NULL; + } + memset(s->data, 0, n * sizeof(int)); + return s; +} + ssize_t nullPointer_send(int socket, const void *buf, size_t len, int flags) { // cppcheck-suppress nullPointer @@ -1060,6 +1076,13 @@ void resourceLeak_fdopen(int fd) // cppcheck-suppress resourceLeak } +void resourceLeak_fdopen2(const char* fn) // #2767 +{ + int fi = open(fn, O_RDONLY); + FILE* fd = fdopen(fi, "r"); + fclose(fd); +} + void resourceLeak_mkstemp(char *template) { // cppcheck-suppress unreadVariable @@ -1125,12 +1148,6 @@ void noleak(int x, int y, int z) close(fd1); int fd2 = open("a", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); close(fd2); - /* TODO: add configuration for open/fdopen - // #2830 - int fd = open("path", O_RDONLY); - FILE *f = fdopen(fd, "rt"); - fclose(f); - */ } diff --git a/test/cfg/std.cpp b/test/cfg/std.cpp index b8305bb62bf..86dde6e17f4 100644 --- a/test/cfg/std.cpp +++ b/test/cfg/std.cpp @@ -602,6 +602,15 @@ void nullPointer_localtime_s(const std::time_t *restrict time, struct tm *restri (void)std::localtime_s(time, NULL); (void)std::localtime_s(time, result); } + +void memleak_localtime_s(const std::time_t *restrict time, struct tm *restrict result) // #9258 +{ + const time_t t = time(0); + const struct tm* const now = new tm(); + if (localtime_s(now, &t) == 0) + std::cout << now->tm_mday << std::endl; + // cppcheck-suppress memleak +} #endif // __STDC_LIB_EXT1__ size_t nullPointer_strftime(char *s, size_t max, const char *fmt, const struct tm *p) diff --git a/test/cfg/windows.cpp b/test/cfg/windows.cpp index 9c5ab3bb7e3..5fbff780aaf 100644 --- a/test/cfg/windows.cpp +++ b/test/cfg/windows.cpp @@ -593,6 +593,20 @@ void memleak_LocalAlloc() // cppcheck-suppress memleak } +void memleak_dupenv_s() // #10646 +{ + char* pValue; + size_t len; + errno_t err = _dupenv_s(&pValue, &len, "pathext"); + if (err) return -1; + printf("pathext = %s\n", pValue); + free(pValue); + err = _dupenv_s(&pValue, &len, "nonexistentvariable"); + if (err) return -1; + printf("nonexistentvariable = %s\n", pValue); + // cppcheck-suppress memleak +} + void resourceLeak_CreateSemaphoreA() { HANDLE hSemaphore; diff --git a/test/cli/test-more-projects.py b/test/cli/test-more-projects.py index 3693f930d0c..d84ff874633 100644 --- a/test/cli/test-more-projects.py +++ b/test/cli/test-more-projects.py @@ -432,3 +432,75 @@ def test_project_file_order(tmpdir): '4/4 files checked 0% done' ] assert stderr == '' + + +def test_project_file_duplicate(tmpdir): + test_file_a = os.path.join(tmpdir, 'a.c') + with open(test_file_a, 'wt'): + pass + + project_file = os.path.join(tmpdir, 'test.cppcheck') + with open(project_file, 'wt') as f: + f.write( + """ + + + + + + +""".format(test_file_a, test_file_a, tmpdir)) + + args = ['--project={}'.format(project_file)] + + exitcode, stdout, stderr = cppcheck(args) + assert exitcode == 0 + lines = stdout.splitlines() + assert lines == [ + 'Checking {} ...'.format(test_file_a) + ] + assert stderr == '' + + +def test_project_file_duplicate_2(tmpdir): + test_file_a = os.path.join(tmpdir, 'a.c') + with open(test_file_a, 'wt'): + pass + test_file_b = os.path.join(tmpdir, 'b.c') + with open(test_file_b, 'wt'): + pass + test_file_c = os.path.join(tmpdir, 'c.c') + with open(test_file_c, 'wt'): + pass + + project_file = os.path.join(tmpdir, 'test.cppcheck') + with open(project_file, 'wt') as f: + f.write( + """ + + + + + + + + + + + +""".format(test_file_c, test_file_a, test_file_b, tmpdir, test_file_b, test_file_c, test_file_a, tmpdir)) + + args = ['--project={}'.format(project_file)] + + exitcode, stdout, stderr = cppcheck(args) + assert exitcode == 0 + lines = stdout.splitlines() + assert lines == [ + 'Checking {} ...'.format(test_file_c), + '1/3 files checked 0% done', + 'Checking {} ...'.format(test_file_a), + '2/3 files checked 0% done', + 'Checking {} ...'.format(test_file_b), + '3/3 files checked 0% done' + ] + assert stderr == '' diff --git a/test/cli/test-other.py b/test/cli/test-other.py index 2d39cc74179..0ad6573f694 100644 --- a/test/cli/test-other.py +++ b/test/cli/test-other.py @@ -153,115 +153,6 @@ def test_progress_j(tmpdir): assert stderr == "" -@pytest.mark.timeout(10) -def test_slow_array_many_floats(tmpdir): - # 11649 - # cppcheck valueflow takes a long time when an array has many floats - filename = os.path.join(tmpdir, 'hang.c') - with open(filename, 'wt') as f: - f.write("const float f[] = {\n") - for i in range(20000): - f.write(' 13.6f,\n') - f.write("};\n") - cppcheck([filename]) # should not take more than ~1 second - - -@pytest.mark.timeout(10) -def test_slow_array_many_strings(tmpdir): - # 11901 - # cppcheck valueflow takes a long time when analyzing a file with many strings - filename = os.path.join(tmpdir, 'hang.c') - with open(filename, 'wt') as f: - f.write("const char *strings[] = {\n") - for i in range(20000): - f.write(' "abc",\n') - f.write("};\n") - cppcheck([filename]) # should not take more than ~1 second - - -@pytest.mark.timeout(10) -def test_slow_long_line(tmpdir): - # simplecpp #314 - filename = os.path.join(tmpdir, 'hang.c') - with open(filename, 'wt') as f: - f.write("#define A() static const int a[] = {\\\n") - for i in range(5000): - f.write(" -123, 456, -789,\\\n") - f.write("};\n") - cppcheck([filename]) # should not take more than ~1 second - - -@pytest.mark.timeout(60) -def test_slow_large_constant_expression(tmpdir): - # 12182 - filename = os.path.join(tmpdir, 'hang.c') - with open(filename, 'wt') as f: - f.write(""" -#define FLAG1 0 -#define FLAG2 0 -#define FLAG3 0 -#define FLAG4 0 -#define FLAG5 0 -#define FLAG6 0 -#define FLAG7 0 -#define FLAG8 0 -#define FLAG9 0 -#define FLAG10 0 -#define FLAG11 0 -#define FLAG12 0 -#define FLAG13 0 -#define FLAG14 0 -#define FLAG15 0 -#define FLAG16 0 -#define FLAG17 0 -#define FLAG18 0 -#define FLAG19 0 -#define FLAG20 0 -#define FLAG21 0 -#define FLAG22 0 -#define FLAG23 0 -#define FLAG24 0 - -#define maxval(x, y) ((x) > (y) ? (x) : (y)) - -#define E_SAMPLE_SIZE maxval( FLAG1, \ - maxval( FLAG2, \ - maxval( FLAG3, \ - maxval( FLAG4, \ - maxval( FLAG5, \ - maxval( FLAG6, \ - maxval( FLAG7, \ - maxval( FLAG8, \ - maxval( FLAG9, \ - maxval( FLAG10, \ - maxval( FLAG11, \ - maxval( FLAG12, \ - maxval( FLAG13, \ - maxval( FLAG14, \ - FLAG15 )))))))))))))) - -#define SAMPLE_SIZE maxval( E_SAMPLE_SIZE, \ - maxval( sizeof(st), \ - maxval( FLAG16, \ - maxval( FLAG17, \ - maxval( FLAG18, \ - maxval( FLAG19, \ - maxval( FLAG20, \ - maxval( FLAG21, \ - maxval( FLAG22, \ - maxval( FLAG23, \ - FLAG24 )))))))))) - -typedef struct { - int n; -} st; - -x = SAMPLE_SIZE; - """) - - cppcheck([filename]) - - def test_execute_addon_failure(tmpdir): test_file = os.path.join(tmpdir, 'test.cpp') with open(test_file, 'wt') as f: @@ -852,7 +743,7 @@ def test_valueflow_debug(tmpdir): ##file {} 2: void f2 ( ) 3: {{ -4: int i@var1 ; i@var1 =@expr1073741828 0 ; +4: int i@var1 ; i@var1 = 0 ; 5: }} ##file {} @@ -861,7 +752,7 @@ def test_valueflow_debug(tmpdir): 2: 3: void f1 ( ) 4: {{ -5: int i@var2 ; i@var2 =@expr1073741829 0 ; +5: int i@var2 ; i@var2 = 0 ; 6: }} ##file {} @@ -871,7 +762,7 @@ def test_valueflow_debug(tmpdir): 3: 4: void f ( ) 5: {{ -6: int i@var3 ; i@var3 =@expr1073741830 0 ; +6: int i@var3 ; i@var3 = 0 ; 7: }} @@ -891,3 +782,46 @@ def test_valueflow_debug(tmpdir): 0 always 0 '''.format(test_file_cpp, test_file_h_2, test_file_h, test_file_cpp, test_file_h_2, test_file_h, test_file_cpp) assert stderr == '' + + +def test_file_duplicate(tmpdir): + test_file_a = os.path.join(tmpdir, 'a.c') + with open(test_file_a, 'wt'): + pass + + args = [test_file_a, test_file_a, str(tmpdir)] + + exitcode, stdout, stderr = cppcheck(args) + assert exitcode == 0 + lines = stdout.splitlines() + assert lines == [ + 'Checking {} ...'.format(test_file_a) + ] + assert stderr == '' + + +def test_file_duplicate_2(tmpdir): + test_file_a = os.path.join(tmpdir, 'a.c') + with open(test_file_a, 'wt'): + pass + test_file_b = os.path.join(tmpdir, 'b.c') + with open(test_file_b, 'wt'): + pass + test_file_c = os.path.join(tmpdir, 'c.c') + with open(test_file_c, 'wt'): + pass + + args = [test_file_c, test_file_a, test_file_b, str(tmpdir), test_file_b, test_file_c, test_file_a, str(tmpdir)] + + exitcode, stdout, stderr = cppcheck(args) + assert exitcode == 0 + lines = stdout.splitlines() + assert lines == [ + 'Checking {} ...'.format(test_file_c), + '1/3 files checked 0% done', + 'Checking {} ...'.format(test_file_a), + '2/3 files checked 0% done', + 'Checking {} ...'.format(test_file_b), + '3/3 files checked 0% done' + ] + assert stderr == '' diff --git a/test/cli/test-performance.py b/test/cli/test-performance.py new file mode 100644 index 00000000000..305994869c5 --- /dev/null +++ b/test/cli/test-performance.py @@ -0,0 +1,149 @@ + +# python -m pytest test-other.py + +import os +import sys +import pytest + +from testutils import cppcheck, assert_cppcheck + + + +@pytest.mark.timeout(10) +def test_slow_array_many_floats(tmpdir): + # 11649 + # cppcheck valueflow takes a long time when an array has many floats + filename = os.path.join(tmpdir, 'hang.c') + with open(filename, 'wt') as f: + f.write("const float f[] = {\n") + for i in range(20000): + f.write(' 13.6f,\n') + f.write("};\n") + cppcheck([filename]) # should not take more than ~1 second + + +@pytest.mark.timeout(10) +def test_slow_array_many_strings(tmpdir): + # 11901 + # cppcheck valueflow takes a long time when analyzing a file with many strings + filename = os.path.join(tmpdir, 'hang.c') + with open(filename, 'wt') as f: + f.write("const char *strings[] = {\n") + for i in range(20000): + f.write(' "abc",\n') + f.write("};\n") + cppcheck([filename]) # should not take more than ~1 second + + +@pytest.mark.timeout(10) +def test_slow_long_line(tmpdir): + # simplecpp #314 + filename = os.path.join(tmpdir, 'hang.c') + with open(filename, 'wt') as f: + f.write("#define A() static const int a[] = {\\\n") + for i in range(5000): + f.write(" -123, 456, -789,\\\n") + f.write("};\n") + cppcheck([filename]) # should not take more than ~1 second + + +@pytest.mark.timeout(60) +def test_slow_large_constant_expression(tmpdir): + # 12182 + filename = os.path.join(tmpdir, 'hang.c') + with open(filename, 'wt') as f: + f.write(""" +#define FLAG1 0 +#define FLAG2 0 +#define FLAG3 0 +#define FLAG4 0 +#define FLAG5 0 +#define FLAG6 0 +#define FLAG7 0 +#define FLAG8 0 +#define FLAG9 0 +#define FLAG10 0 +#define FLAG11 0 +#define FLAG12 0 +#define FLAG13 0 +#define FLAG14 0 +#define FLAG15 0 +#define FLAG16 0 +#define FLAG17 0 +#define FLAG18 0 +#define FLAG19 0 +#define FLAG20 0 +#define FLAG21 0 +#define FLAG22 0 +#define FLAG23 0 +#define FLAG24 0 + +#define maxval(x, y) ((x) > (y) ? (x) : (y)) + +#define E_SAMPLE_SIZE maxval( FLAG1, \ + maxval( FLAG2, \ + maxval( FLAG3, \ + maxval( FLAG4, \ + maxval( FLAG5, \ + maxval( FLAG6, \ + maxval( FLAG7, \ + maxval( FLAG8, \ + maxval( FLAG9, \ + maxval( FLAG10, \ + maxval( FLAG11, \ + maxval( FLAG12, \ + maxval( FLAG13, \ + maxval( FLAG14, \ + FLAG15 )))))))))))))) + +#define SAMPLE_SIZE maxval( E_SAMPLE_SIZE, \ + maxval( sizeof(st), \ + maxval( FLAG16, \ + maxval( FLAG17, \ + maxval( FLAG18, \ + maxval( FLAG19, \ + maxval( FLAG20, \ + maxval( FLAG21, \ + maxval( FLAG22, \ + maxval( FLAG23, \ + FLAG24 )))))))))) + +typedef struct { + int n; +} st; + +x = SAMPLE_SIZE; + """) + + cppcheck([filename]) + +@pytest.mark.timeout(10) +def test_slow_exprid(tmpdir): + # 11885 + filename = os.path.join(tmpdir, 'hang.c') + with open(filename, 'wt') as f: + f.write(""" +int foo(int a, int b) +{ +#define A0(a, b) ((a) + (b)) +#define A1(a, b) ((a) > (b)) ? A0((a) - (b), (b)) : A0((b) - (a), (a)) +#define A2(a, b) ((a) > (b)) ? A1((a) - (b), (b)) : A1((b) - (a), (a)) +#define A3(a, b) ((a) > (b)) ? A2((a) - (b), (b)) : A2((b) - (a), (a)) +#define A4(a, b) ((a) > (b)) ? A3((a) - (b), (b)) : A3((b) - (a), (a)) +#define A5(a, b) ((a) > (b)) ? A4((a) - (b), (b)) : A4((b) - (a), (a)) +#define A6(a, b) ((a) > (b)) ? A5((a) - (b), (b)) : A5((b) - (a), (a)) +#define A7(a, b) ((a) > (b)) ? A6((a) - (b), (b)) : A6((b) - (a), (a)) +#define A8(a, b) ((a) > (b)) ? A7((a) - (b), (b)) : A7((b) - (a), (a)) +#define A9(a, b) ((a) > (b)) ? A8((a) - (b), (b)) : A8((b) - (a), (a)) +#define A10(a, b) ((a) > (b)) ? A9((a) - (b), (b)) : A9((b) - (a), (a)) +#define A11(a, b) ((a) > (b)) ? A10((a) - (b), (b)) : A10((b) - (a), (a)) + return A8(a, b); +} + """) + + my_env = os.environ.copy() + my_env["DISABLE_VALUEFLOW"] = "1" + cppcheck([filename], env=my_env) + + + diff --git a/test/fixture.cpp b/test/fixture.cpp index 32863bbc637..67d022bc3e4 100644 --- a/test/fixture.cpp +++ b/test/fixture.cpp @@ -114,6 +114,20 @@ bool TestFixture::prepareTest(const char testname[]) void TestFixture::teardownTest() { teardownTestInternal(); + + // TODO: enable + /* + { + const std::string s = errout.str(); + if (!s.empty()) + throw std::runtime_error("unconsumed ErrorLogger err: " + s); + } + */ + { + const std::string s = output_str(); + if (!s.empty()) + throw std::runtime_error("unconsumed ErrorLogger out: " + s); + } } std::string TestFixture::getLocationStr(const char * const filename, const unsigned int linenr) const @@ -381,7 +395,7 @@ std::size_t TestFixture::runTests(const options& args) void TestFixture::reportOut(const std::string & outmsg, Color /*c*/) { - output << outmsg << std::endl; + mOutput << outmsg << std::endl; } void TestFixture::reportErr(const ErrorMessage &msg) @@ -389,6 +403,7 @@ void TestFixture::reportErr(const ErrorMessage &msg) if (msg.severity == Severity::none && msg.id == "logChecker") return; const std::string errormessage(msg.toString(mVerbose, mTemplateFormat, mTemplateLocation)); + // TODO: remove the unique error handling? if (errout.str().find(errormessage) == std::string::npos) errout << errormessage << std::endl; } diff --git a/test/fixture.h b/test/fixture.h index dc1c62dcfe8..f5d94cd026a 100644 --- a/test/fixture.h +++ b/test/fixture.h @@ -236,11 +236,17 @@ class TestFixture : public ErrorLogger { return SettingsBuilder(*this, std::move(settings)); } - // TODO: make sure the output has been consumed in the test + std::string output_str() { + std::string s = mOutput.str(); + mOutput.str(""); + return s; + } + std::ostringstream errout; - std::ostringstream output; private: + std::ostringstream mOutput; + void reportOut(const std::string &outmsg, Color c = Color::Reset) override; void reportErr(const ErrorMessage &msg) override; void run(const std::string &str); diff --git a/test/helpers.cpp b/test/helpers.cpp index b4c46a90d2c..e363c1266dd 100644 --- a/test/helpers.cpp +++ b/test/helpers.cpp @@ -51,6 +51,8 @@ ScopedFile::ScopedFile(std::string name, const std::string &content, std::string , mFullPath(Path::join(mPath, mName)) { if (!mPath.empty() && mPath != Path::getCurrentPath()) { + if (Path::isDirectory(mPath)) + throw std::runtime_error("ScopedFile(" + mFullPath + ") - directory already exists"); #ifdef _WIN32 if (!CreateDirectoryA(mPath.c_str(), nullptr)) throw std::runtime_error("ScopedFile(" + mFullPath + ") - could not create directory"); @@ -60,6 +62,9 @@ ScopedFile::ScopedFile(std::string name, const std::string &content, std::string #endif } + if (Path::isFile(mFullPath)) + throw std::runtime_error("ScopedFile(" + mFullPath + ") - file already exists"); + std::ofstream of(mFullPath); if (!of.is_open()) throw std::runtime_error("ScopedFile(" + mFullPath + ") - could not open file"); diff --git a/test/testautovariables.cpp b/test/testautovariables.cpp index ede111f1cdd..b48f49fe727 100644 --- a/test/testautovariables.cpp +++ b/test/testautovariables.cpp @@ -2933,6 +2933,13 @@ class TestAutoVariables : public TestFixture { ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:2] -> [test.cpp:3]: (error) Using object that is a temporary.\n", errout.str()); + // #10833 + check("struct A { std::string s; };\n" + "const std::string& f(A* a) {\n" + " return a ? a->s : \"\";\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:3]: (error) Reference to temporary returned.\n", errout.str()); + check("std::span f() {\n" " std::vector v{};\n" " return v;\n" diff --git a/test/testcmdlineparser.cpp b/test/testcmdlineparser.cpp index 9c493b8aa34..c4b6ed38ff2 100644 --- a/test/testcmdlineparser.cpp +++ b/test/testcmdlineparser.cpp @@ -436,7 +436,7 @@ class TestCmdlineParser : public TestFixture { "{\n"); const char * const argv[] = {"cppcheck", "--version"}; ASSERT_EQUALS(CmdLineParser::Result::Fail, parser->parseFromArgs(2, argv)); - ASSERT_EQUALS("cppcheck: error: could not load cppcheck.cfg - not a valid JSON - syntax error at line 1 near: \n", logger->str()); + ASSERT_EQUALS("cppcheck: error: could not load cppcheck.cfg - not a valid JSON - syntax error at line 2 near: \n", logger->str()); } void onefile() { @@ -1706,7 +1706,7 @@ class TestCmdlineParser : public TestFixture { "{\n"); const char * const argv[] = {"cppcheck", "--errorlist"}; ASSERT_EQUALS(CmdLineParser::Result::Fail, parser->parseFromArgs(2, argv)); - ASSERT_EQUALS("cppcheck: error: could not load cppcheck.cfg - not a valid JSON - syntax error at line 1 near: \n", logger->str()); + ASSERT_EQUALS("cppcheck: error: could not load cppcheck.cfg - not a valid JSON - syntax error at line 2 near: \n", logger->str()); } void ignorepathsnopath() { @@ -2310,7 +2310,7 @@ class TestCmdlineParser : public TestFixture { "{\n"); const char * const argv[] = {"cppcheck", "test.cpp"}; ASSERT_EQUALS(CmdLineParser::Result::Fail, parser->parseFromArgs(2, argv)); - ASSERT_EQUALS("cppcheck: error: could not load cppcheck.cfg - not a valid JSON - syntax error at line 1 near: \n", logger->str()); + ASSERT_EQUALS("cppcheck: error: could not load cppcheck.cfg - not a valid JSON - syntax error at line 2 near: \n", logger->str()); } }; diff --git a/test/testcondition.cpp b/test/testcondition.cpp index cea348d50f9..17a28fbe892 100644 --- a/test/testcondition.cpp +++ b/test/testcondition.cpp @@ -5104,6 +5104,11 @@ class TestCondition : public TestFixture { " return fwrite(s.c_str(), 1, s.length(), fp) == s.length();\n" "}\n"); ASSERT_EQUALS("", errout.str()); + + check("void f(const std::string& s) {\n" // #9148 + " if (s.empty() || s.size() < 1) {}\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:2]: (style) Condition 's.size()<1' is always false\n", errout.str()); } void alwaysTrueLoop() diff --git a/test/testimportproject.cpp b/test/testimportproject.cpp index fb6a667073d..df88f8ce1ca 100644 --- a/test/testimportproject.cpp +++ b/test/testimportproject.cpp @@ -20,6 +20,7 @@ #include "settings.h" #include "filesettings.h" #include "fixture.h" +#include "redirect.h" #include #include @@ -112,6 +113,7 @@ class TestImportProject : public TestFixture { } void importCompileCommands1() const { + REDIRECT; constexpr char json[] = R"([{ "directory": "/tmp", "command": "gcc -DTEST1 -DTEST2=2 -o /tmp/src.o -c /tmp/src.c", @@ -125,6 +127,7 @@ class TestImportProject : public TestFixture { } void importCompileCommands2() const { + REDIRECT; // Absolute file path #ifdef _WIN32 const char json[] = R"([{ @@ -152,6 +155,7 @@ class TestImportProject : public TestFixture { } void importCompileCommands3() const { + REDIRECT; const char json[] = R"([{ "directory": "/tmp/", "command": "gcc -c src.c", @@ -165,6 +169,7 @@ class TestImportProject : public TestFixture { } void importCompileCommands4() const { + REDIRECT; constexpr char json[] = R"([{ "directory": "/tmp/", "command": "gcc -c src.mm", @@ -177,6 +182,7 @@ class TestImportProject : public TestFixture { } void importCompileCommands5() const { + REDIRECT; constexpr char json[] = R"([{ "directory": "C:/Users/dan/git/build-test-cppcheck-Desktop_Qt_5_15_0_MSVC2019_64bit-Debug", @@ -196,6 +202,7 @@ class TestImportProject : public TestFixture { } void importCompileCommands6() const { + REDIRECT; constexpr char json[] = R"([{ "directory": "C:/Users/dan/git/build-test-cppcheck-Desktop_Qt_5_15_0_MSVC2019_64bit-Debug", @@ -217,6 +224,7 @@ class TestImportProject : public TestFixture { void importCompileCommands7() const { + REDIRECT; // cmake -DFILESDIR="/some/path" .. constexpr char json[] = R"([{ @@ -237,6 +245,7 @@ class TestImportProject : public TestFixture { } void importCompileCommands8() const { + REDIRECT; // cmake -DFILESDIR="C:\Program Files\Cppcheck" -G"NMake Makefiles" .. constexpr char json[] = R"([{ @@ -250,6 +259,7 @@ class TestImportProject : public TestFixture { } void importCompileCommands9() const { + REDIRECT; // IAR output (https://sourceforge.net/p/cppcheck/discussion/general/thread/608af51e0a/) constexpr char json[] = R"([{ @@ -266,6 +276,7 @@ class TestImportProject : public TestFixture { } void importCompileCommands10() const { // #10887 + REDIRECT; constexpr char json[] = R"([{ "file": "/home/danielm/cppcheck/1/test folder/1.c" , @@ -285,6 +296,7 @@ class TestImportProject : public TestFixture { } void importCompileCommands11() const { // include path order + REDIRECT; constexpr char json[] = R"([{ "file": "1.c" , @@ -307,6 +319,7 @@ class TestImportProject : public TestFixture { } void importCompileCommandsArgumentsSection() const { + REDIRECT; constexpr char json[] = "[ { \"directory\": \"/tmp/\"," "\"arguments\": [\"gcc\", \"-c\", \"src.c\"]," "\"file\": \"src.c\" } ]"; @@ -318,15 +331,18 @@ class TestImportProject : public TestFixture { } void importCompileCommandsNoCommandSection() const { + REDIRECT; constexpr char json[] = "[ { \"directory\": \"/tmp/\"," "\"file\": \"src.mm\" } ]"; std::istringstream istr(json); TestImporter importer; ASSERT_EQUALS(false, importer.importCompileCommands(istr)); ASSERT_EQUALS(0, importer.fileSettings.size()); + ASSERT_EQUALS("cppcheck: error: no 'arguments' or 'command' field found in compilation database entry\n", GET_REDIRECT_OUTPUT); } void importCppcheckGuiProject() const { + REDIRECT; constexpr char xml[] = "\n" "\n" " \n" diff --git a/test/testio.cpp b/test/testio.cpp index 8f89f0a7bd5..4f01d6b6fc7 100644 --- a/test/testio.cpp +++ b/test/testio.cpp @@ -549,6 +549,14 @@ class TestIO : public TestFixture { " fclose(f[1]);\n" "}"); ASSERT_EQUALS("", errout.str()); + + // #12236 + check("void f() {\n" + " FILE* f = fopen(\"abc\", \"r\");\n" + " decltype(fclose(f)) y;\n" + " y = fclose(f);\n" + "}\n"); + ASSERT_EQUALS("", errout.str()); } void fileIOwithoutPositioning() { diff --git a/test/testleakautovar.cpp b/test/testleakautovar.cpp index 6ca90726d4d..ed6983ec3ea 100644 --- a/test/testleakautovar.cpp +++ b/test/testleakautovar.cpp @@ -111,6 +111,7 @@ class TestLeakAutoVar : public TestFixture { TEST_CASE(deallocuse10); TEST_CASE(deallocuse11); // #8302 TEST_CASE(deallocuse12); + TEST_CASE(deallocuse13); TEST_CASE(doublefree1); TEST_CASE(doublefree2); @@ -127,6 +128,7 @@ class TestLeakAutoVar : public TestFixture { TEST_CASE(doublefree13); // #11008 TEST_CASE(doublefree14); // #9708 TEST_CASE(doublefree15); + TEST_CASE(doublefree16); // exit TEST_CASE(exit1); @@ -908,6 +910,20 @@ class TestLeakAutoVar : public TestFixture { ASSERT_EQUALS("", errout.str()); } + void deallocuse13() { + check("void f() {\n" // #9695 + " auto* a = new int[2];\n" + " delete[] a;\n" + " a[1] = 0;\n" + " auto* b = static_cast(malloc(8));\n" + " free(b);\n" + " b[1] = 0;\n" + "}\n", true); + ASSERT_EQUALS("[test.cpp:4]: (error) Dereferencing 'a' after it is deallocated / released\n" + "[test.cpp:7]: (error) Dereferencing 'b' after it is deallocated / released\n", + errout.str()); + } + void doublefree1() { // #3895 check("void f(char *p) {\n" " if (x)\n" @@ -1566,6 +1582,15 @@ class TestLeakAutoVar : public TestFixture { ASSERT_EQUALS("", errout.str()); } + void doublefree16() { // #12236 + check("void f() {\n" + " FILE* f = fopen(\"abc\", \"r\");\n" + " decltype(fclose(f)) y;\n" + " y = fclose(f);\n" + "}\n"); + ASSERT_EQUALS("", errout.str()); + } + void exit1() { check("void f() {\n" " char *p = malloc(10);\n" diff --git a/test/testnullpointer.cpp b/test/testnullpointer.cpp index b1757014e71..aa498142a9d 100644 --- a/test/testnullpointer.cpp +++ b/test/testnullpointer.cpp @@ -140,6 +140,7 @@ class TestNullPointer : public TestFixture { TEST_CASE(nullpointer99); // #10602 TEST_CASE(nullpointer100); // #11636 TEST_CASE(nullpointer101); // #11382 + TEST_CASE(nullpointer102); TEST_CASE(nullpointer_addressOf); // address of TEST_CASE(nullpointerSwitch); // #2626 TEST_CASE(nullpointer_cast); // #4692 @@ -990,7 +991,7 @@ class TestNullPointer : public TestFixture { check("struct S { struct T { char c; } *p; };\n" // #6541 "char f(S* s) { return s->p ? 'a' : s->p->c; }\n"); - ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:2]: (warning) Either the condition 's->p' is redundant or there is possible null pointer dereference: p.\n", + ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:2]: (warning) Either the condition 's->p' is redundant or there is possible null pointer dereference: s->p.\n", errout.str()); } @@ -2843,6 +2844,20 @@ class TestNullPointer : public TestFixture { ASSERT_EQUALS("", errout.str()); } + void nullpointer102() + { + check("struct S { std::string str; };\n" // #11534 + "struct T { S s; };\n" + "struct U { T t[1]; };\n" + "void f(const T& t, const U& u, std::string& str) {\n" + " if (str.empty())\n" + " str = t.s.str;\n" + " else\n" + " str = u.t[0].s.str;\n" + "}\n"); + ASSERT_EQUALS("", errout.str()); + } + void nullpointer_addressOf() { // address of check("void f() {\n" " struct X *x = 0;\n" diff --git a/test/testother.cpp b/test/testother.cpp index a97f52b8dad..d221570159c 100644 --- a/test/testother.cpp +++ b/test/testother.cpp @@ -289,6 +289,7 @@ class TestOther : public TestFixture { TEST_CASE(constVariableArrayMember); // #10371 TEST_CASE(knownPointerToBool); + TEST_CASE(iterateByValue); } #define check(...) check_(__FILE__, __LINE__, __VA_ARGS__) @@ -3395,6 +3396,29 @@ class TestOther : public TestFixture { " std::transform(v1.begin(), v1.end(), v2.begin(), [](auto& x) { return &x; });\n" "}\n"); ASSERT_EQUALS("", errout.str()); + + check("class T;\n" // #11869 + "class E {\n" + "public:\n" + " class F {\n" + " public:\n" + " explicit F(const T* t);\n" + " };\n" + "};\n" + "void f(T& t) {\n" + " std::list c(1, E::F(&t));\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:9]: (style) Parameter 't' can be declared as reference to const\n", errout.str()); + + check("struct T;\n" + "struct U {\n" + " struct V { explicit V(const T* p); };\n" + "};\n" + "void g(U::V v);\n" + "void f(T& t) {\n" + " g(U::V(&t));\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:6]: (style) Parameter 't' can be declared as reference to const\n", errout.str()); } void constParameterCallback() { @@ -3910,6 +3934,13 @@ class TestOther : public TestFixture { "}\n"); ASSERT_EQUALS("[test.cpp:2]: (style) Variable 'p' can be declared as pointer to const\n", errout.str()); + + check("struct S { const T* t; };\n" // #12206 + "void f(S* s) {\n" + " if (s->t.i) {}\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:2]: (style) Parameter 's' can be declared as pointer to const\n", + errout.str()); } void switchRedundantAssignmentTest() { @@ -11677,6 +11708,16 @@ class TestOther : public TestFixture { "}\n"); ASSERT_EQUALS("", errout.str()); } + + void iterateByValue() { + check("void f() {\n" // #9684 + " const std::set ss = { \"a\", \"b\", \"c\" };\n" + " for (auto s : ss)\n" + " (void)s.size();\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:3]: (performance) Range variable 's' should be declared as const reference.\n", + errout.str()); + } }; REGISTER_TEST(TestOther) diff --git a/test/testprocessexecutor.cpp b/test/testprocessexecutor.cpp index 4e624c02ea1..59c5577e8d3 100644 --- a/test/testprocessexecutor.cpp +++ b/test/testprocessexecutor.cpp @@ -67,7 +67,6 @@ class TestProcessExecutorBase : public TestFixture { */ void check(unsigned int jobs, int files, int result, const std::string &data, const CheckOptions& opt = make_default_obj{}) { errout.str(""); - output.str(""); std::list fileSettings; @@ -265,7 +264,7 @@ class TestProcessExecutorBase : public TestFixture { $.executeCommandCalled = true, $.exe = exe, $.args = {"-quiet", "-checks=*,-clang-analyzer-*,-llvm*", file, "--"}*/)); - ASSERT_EQUALS("Checking " + file + " ...\n", output.str()); + ASSERT_EQUALS("Checking " + file + " ...\n", output_str()); } // TODO: provide data which actually shows values above 0 diff --git a/test/testsettings.cpp b/test/testsettings.cpp index c3598300837..f09dfed1aa4 100644 --- a/test/testsettings.cpp +++ b/test/testsettings.cpp @@ -104,7 +104,7 @@ class TestSettings : public TestFixture { Settings s; ScopedFile file("cppcheck.cfg", "{\n"); - ASSERT_EQUALS("not a valid JSON - syntax error at line 1 near: ", s.loadCppcheckCfg()); + ASSERT_EQUALS("not a valid JSON - syntax error at line 2 near: ", s.loadCppcheckCfg()); } { Settings s; diff --git a/test/testsingleexecutor.cpp b/test/testsingleexecutor.cpp index 53f9be99edb..6ee535838ff 100644 --- a/test/testsingleexecutor.cpp +++ b/test/testsingleexecutor.cpp @@ -72,7 +72,6 @@ class TestSingleExecutorBase : public TestFixture { void check(int files, int result, const std::string &data, const CheckOptions& opt = make_default_obj{}) { errout.str(""); - output.str(""); std::list fileSettings; @@ -169,7 +168,7 @@ class TestSingleExecutorBase : public TestFixture { expected += "Checking " + fprefix() + "_" + zpad3(i) + ".cpp ...\n"; expected += std::to_string(i) + "/100 files checked " + std::to_string(i) + "% done\n"; } - ASSERT_EQUALS(expected, output.str()); + ASSERT_EQUALS(expected, output_str()); } void many_files_showtime() { @@ -259,7 +258,7 @@ class TestSingleExecutorBase : public TestFixture { $.executeCommandCalled = true, $.exe = exe, $.args = {"-quiet", "-checks=*,-clang-analyzer-*,-llvm*", file, "--"})); - ASSERT_EQUALS("Checking " + file + " ...\n", output.str()); + ASSERT_EQUALS("Checking " + file + " ...\n", output_str()); } // TODO: provide data which actually shows values above 0 diff --git a/test/teststl.cpp b/test/teststl.cpp index 523a0c59b52..190978c9cd8 100644 --- a/test/teststl.cpp +++ b/test/teststl.cpp @@ -2403,12 +2403,11 @@ class TestStl : public TestFixture { "void g(const std::vector& w) {\n" " f(-1, w);\n" "}\n"); - TODO_ASSERT_EQUALS("test.cpp:5:warning:Array index -1 is out of bounds.\n" - "test.cpp:8:note:Calling function 'f', 1st argument '-1' value is -1\n" - "test.cpp:3:note:Assuming condition is false\n" - "test.cpp:5:note:Negative array index\n", - "", - errout.str()); + ASSERT_EQUALS("test.cpp:5:warning:Array index -1 is out of bounds.\n" + "test.cpp:8:note:Calling function 'f', 1st argument '-1' value is -1\n" + "test.cpp:3:note:Assuming condition is false\n" + "test.cpp:5:note:Negative array index\n", + errout.str()); settings = oldSettings; } diff --git a/test/testsuppressions.cpp b/test/testsuppressions.cpp index 2c8eccf06bd..2f91cbddef5 100644 --- a/test/testsuppressions.cpp +++ b/test/testsuppressions.cpp @@ -200,7 +200,6 @@ class TestSuppressions : public TestFixture { unsigned int checkSuppression(std::map &f, const std::string &suppression = emptyString) { // Clear the error log errout.str(""); - output.str(""); std::list> files; for (std::map::const_iterator i = f.cbegin(); i != f.cend(); ++i) { @@ -210,6 +209,7 @@ class TestSuppressions : public TestFixture { CppCheck cppCheck(*this, true, nullptr); Settings& settings = cppCheck.settings(); settings.jobs = 1; + settings.quiet = true; settings.inlineSuppressions = true; settings.severity.enable(Severity::information); if (suppression == "unusedFunction") @@ -234,13 +234,13 @@ class TestSuppressions : public TestFixture { unsigned int checkSuppressionThreads(const char code[], const std::string &suppression = emptyString) { errout.str(""); - output.str(""); std::list> files; files.emplace_back("test.cpp", strlen(code)); Settings settings; settings.jobs = 2; + settings.quiet = true; settings.inlineSuppressions = true; settings.severity.enable(Severity::information); if (!suppression.empty()) { @@ -264,13 +264,13 @@ class TestSuppressions : public TestFixture { #if !defined(WIN32) && !defined(__MINGW32__) && !defined(__CYGWIN__) unsigned int checkSuppressionProcesses(const char code[], const std::string &suppression = emptyString) { errout.str(""); - output.str(""); std::list> files; files.emplace_back("test.cpp", strlen(code)); Settings settings; settings.jobs = 2; + settings.quiet = true; settings.inlineSuppressions = true; settings.severity.enable(Severity::information); if (!suppression.empty()) { @@ -1089,6 +1089,7 @@ class TestSuppressions : public TestFixture { CppCheck cppCheck(*this, false, nullptr); // <- do not "use global suppressions". pretend this is a thread that just checks a file. Settings& settings = cppCheck.settings(); + settings.quiet = true; settings.nomsg.addSuppressionLine("uninitvar"); settings.exitCode = 1; @@ -1123,6 +1124,7 @@ class TestSuppressions : public TestFixture { CppCheck cppCheck(*this, true, nullptr); Settings& settings = cppCheck.settings(); + settings.quiet = true; settings.severity.enable(Severity::style); settings.inlineSuppressions = true; settings.relativePaths = true; diff --git a/test/testsymboldatabase.cpp b/test/testsymboldatabase.cpp index 9a6a6f07884..a0965e3a82f 100644 --- a/test/testsymboldatabase.cpp +++ b/test/testsymboldatabase.cpp @@ -16,13 +16,14 @@ * along with this program. If not, see . */ +#include "errortypes.h" +#include "fixture.h" +#include "helpers.h" #include "library.h" #include "platform.h" #include "settings.h" +#include "sourcelocation.h" #include "symboldatabase.h" -#include "errortypes.h" -#include "fixture.h" -#include "helpers.h" #include "token.h" #include "tokenize.h" #include "tokenlist.h" @@ -82,6 +83,60 @@ class TestSymbolDatabase : public TestFixture { return tokenizer.tokenize(istr, filename) ? tokenizer.getSymbolDatabase() : nullptr; } + static const Token* findToken(Tokenizer& tokenizer, const std::string& expr, unsigned int exprline) + { + for (const Token* tok = tokenizer.tokens(); tok; tok = tok->next()) { + if (Token::simpleMatch(tok, expr.c_str(), expr.size()) && tok->linenr() == exprline) { + return tok; + } + } + return nullptr; + } + + static std::string asExprIdString(const Token* tok) + { + return tok->expressionString() + "@" + std::to_string(tok->exprId()); + } + + std::string testExprIdEqual(const char code[], + const std::string& expr1, + unsigned int exprline1, + const std::string& expr2, + unsigned int exprline2, + SourceLocation loc = SourceLocation::current()) + { + Tokenizer tokenizer(&settings1, this); + std::istringstream istr(code); + ASSERT_LOC(tokenizer.tokenize(istr, "test.cpp"), loc.file_name(), loc.line()); + + const Token* tok1 = findToken(tokenizer, expr1, exprline1); + const Token* tok2 = findToken(tokenizer, expr2, exprline2); + + if (!tok1) + return "'" + expr1 + "'" + " not found"; + if (!tok2) + return "'" + expr2 + "'" + " not found"; + if (tok1->exprId() == 0) + return asExprIdString(tok1) + " has not exprId"; + if (tok2->exprId() == 0) + return asExprIdString(tok2) + " has not exprId"; + + if (tok1->exprId() != tok2->exprId()) + return asExprIdString(tok1) + " != " + asExprIdString(tok2); + + return ""; + } + bool testExprIdNotEqual(const char code[], + const std::string& expr1, + unsigned int exprline1, + const std::string& expr2, + unsigned int exprline2, + SourceLocation loc = SourceLocation::current()) + { + std::string result = testExprIdEqual(code, expr1, exprline1, expr2, exprline2, loc); + return !result.empty(); + } + static const Scope *findFunctionScopeByToken(const SymbolDatabase * db, const Token *tok) { std::list::const_iterator scope; @@ -530,6 +585,7 @@ class TestSymbolDatabase : public TestFixture { TEST_CASE(unionWithConstructor); TEST_CASE(incomplete_type); // #9255 (infinite recursion) + TEST_CASE(exprIds); } void array() { @@ -10000,6 +10056,214 @@ class TestSymbolDatabase : public TestFixture { ASSERT_EQUALS("", errout.str()); } + + void exprIds() + { + const char* code; + + code = "int f(int a) {\n" + " return a +\n" + " a;\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, "a", 2U, "a", 3U)); + + code = "int f(int a, int b) {\n" + " return a +\n" + " b;\n" + "}\n"; + ASSERT_EQUALS(true, testExprIdNotEqual(code, "a", 2U, "b", 3U)); + + code = "int f(int a) {\n" + " int x = a++;\n" + " int y = a++;\n" + " return x + a;\n" + "}\n"; + ASSERT_EQUALS(true, testExprIdNotEqual(code, "++", 2U, "++", 3U)); + + code = "int f(int a) {\n" + " int x = a;\n" + " return x + a;\n" + "}\n"; + ASSERT_EQUALS(true, testExprIdNotEqual(code, "x", 3U, "a", 3U)); + + code = "int f(int a) {\n" + " int& x = a;\n" + " return x + a;\n" + "}\n"; + TODO_ASSERT_EQUALS("", "x@2 != a@1", testExprIdEqual(code, "x", 3U, "a", 3U)); + + code = "int f(int a) {\n" + " int& x = a;\n" + " return (x + 1) +\n" + " (a + 1);\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, "+", 3U, "+", 4U)); + + code = "int& g(int& x) { return x; }\n" + "int f(int a) {\n" + " return (g(a) + 1) +\n" + " (a + 1);\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, "+", 3U, "+", 4U)); + + code = "int f(int a, int b) {\n" + " int x = (b-a)-a;\n" + " int y = (b-a)-a;\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, "- a ;", 2U, "- a ;", 3U)); + ASSERT_EQUALS("", testExprIdEqual(code, "- a )", 2U, "- a )", 3U)); + ASSERT_EQUALS(true, testExprIdNotEqual(code, "- a )", 2U, "- a ;", 3U)); + + code = "int f(int a, int b) {\n" + " int x = a-(b-a);\n" + " int y = a-(b-a);\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, "- ( b", 2U, "- ( b", 3U)); + ASSERT_EQUALS("", testExprIdEqual(code, "- a )", 2U, "- a )", 3U)); + ASSERT_EQUALS(true, testExprIdNotEqual(code, "- a )", 2U, "- ( b", 3U)); + + code = "void f(int a, int b) {\n" + " int x = (b+a)+a;\n" + " int y = a+(b+a);\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, "+ a ;", 2U, "+ ( b", 3U)); + ASSERT_EQUALS("", testExprIdEqual(code, "+ a ) +", 2U, "+ a ) ;", 3U)); + ASSERT_EQUALS(true, testExprIdNotEqual(code, "+ a ;", 2U, "+ a )", 3U)); + + code = "void f(int a, int b) {\n" + " int x = (b+a)+a;\n" + " int y = a+(a+b);\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, "+ a ;", 2U, "+ ( a", 3U)); + ASSERT_EQUALS("", testExprIdEqual(code, "+ a ) +", 2U, "+ b ) ;", 3U)); + ASSERT_EQUALS(true, testExprIdNotEqual(code, "+ a ;", 2U, "+ b", 3U)); + + code = "struct A { int x; };\n" + "void f(A a, int b) {\n" + " int x = (b-a.x)-a.x;\n" + " int y = (b-a.x)-a.x;\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, "- a . x ;", 3U, "- a . x ;", 4U)); + ASSERT_EQUALS("", testExprIdEqual(code, "- a . x )", 3U, "- a . x )", 4U)); + + code = "struct A { int x; };\n" + "void f(A a, int b) {\n" + " int x = a.x-(b-a.x);\n" + " int y = a.x-(b-a.x);\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, "- ( b", 3U, "- ( b", 4U)); + ASSERT_EQUALS("", testExprIdEqual(code, "- a . x )", 3U, "- a . x )", 4U)); + + code = "struct A { int x; };\n" + "void f(A a) {\n" + " int x = a.x;\n" + " int y = a.x;\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, ". x", 3U, ". x", 4U)); + + code = "struct A { int x; };\n" + "void f(A a, A b) {\n" + " int x = a.x;\n" + " int y = b.x;\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS(true, testExprIdNotEqual(code, ". x", 3U, ". x", 4U)); + + code = "struct A { int y; };\n" + "struct B { A x; }\n" + "void f(B a) {\n" + " int x = a.x.y;\n" + " int y = a.x.y;\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, ". x . y", 4U, ". x . y", 5U)); + ASSERT_EQUALS("", testExprIdEqual(code, ". y", 4U, ". y", 5U)); + + code = "struct A { int y; };\n" + "struct B { A x; }\n" + "void f(B a, B b) {\n" + " int x = a.x.y;\n" + " int y = b.x.y;\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS(true, testExprIdNotEqual(code, ". x . y", 4U, ". x . y", 5U)); + ASSERT_EQUALS(true, testExprIdNotEqual(code, ". y", 4U, ". y", 5U)); + + code = "struct A { int g(); };\n" + "struct B { A x; }\n" + "void f(B a) {\n" + " int x = a.x.g();\n" + " int y = a.x.g();\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, ". x . g ( )", 4U, ". x . g ( )", 5U)); + ASSERT_EQUALS("", testExprIdEqual(code, ". g ( )", 4U, ". g ( )", 5U)); + + code = "struct A { int g(int, int); };\n" + "struct B { A x; }\n" + "void f(B a, int b, int c) {\n" + " int x = a.x.g(b, c);\n" + " int y = a.x.g(b, c);\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, ". x . g ( b , c )", 4U, ". x . g ( b , c )", 5U)); + ASSERT_EQUALS("", testExprIdEqual(code, ". g ( b , c )", 4U, ". g ( b , c )", 5U)); + + code = "int g();\n" + "void f() {\n" + " int x = g();\n" + " int y = g();\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, "(", 3U, "(", 4U)); + + code = "struct A { int g(); };\n" + "void f() {\n" + " int x = A::g();\n" + " int y = A::g();\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, "(", 3U, "(", 4U)); + + code = "int g();\n" + "void f(int a, int b) {\n" + " int x = g(a, b);\n" + " int y = g(a, b);\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, "(", 3U, "(", 4U)); + + code = "struct A { int g(); };\n" + "void f() {\n" + " int x = A::g(a, b);\n" + " int y = A::g(a, b);\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS("", testExprIdEqual(code, "(", 3U, "(", 4U)); + + code = "int g();\n" + "void f(int a, int b) {\n" + " int x = g(a, b);\n" + " int y = g(b, a);\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS(true, testExprIdNotEqual(code, "(", 3U, "(", 4U)); + + code = "struct A { int g(); };\n" + "void f() {\n" + " int x = A::g(a, b);\n" + " int y = A::g(b, a);\n" + " return x + y;\n" + "}\n"; + ASSERT_EQUALS(true, testExprIdNotEqual(code, "(", 3U, "(", 4U)); + } }; REGISTER_TEST(TestSymbolDatabase) diff --git a/test/testthreadexecutor.cpp b/test/testthreadexecutor.cpp index f41def3351d..ea4e3a9e162 100644 --- a/test/testthreadexecutor.cpp +++ b/test/testthreadexecutor.cpp @@ -67,7 +67,6 @@ class TestThreadExecutorBase : public TestFixture { */ void check(unsigned int jobs, int files, int result, const std::string &data, const CheckOptions& opt = make_default_obj{}) { errout.str(""); - output.str(""); std::list fileSettings; @@ -262,7 +261,7 @@ class TestThreadExecutorBase : public TestFixture { $.executeCommandCalled = true, $.exe = exe, $.args = {"-quiet", "-checks=*,-clang-analyzer-*,-llvm*", file, "--"})); - ASSERT_EQUALS("Checking " + file + " ...\n", output.str()); + ASSERT_EQUALS("Checking " + file + " ...\n", output_str()); } // TODO: provide data which actually shows values above 0 diff --git a/test/testtype.cpp b/test/testtype.cpp index dbf6c07ae4f..a55bfa50516 100644 --- a/test/testtype.cpp +++ b/test/testtype.cpp @@ -490,6 +490,8 @@ class TestType : public TestFixture { dui.std = "c++11"; // this is set by the production code via cppcheck::Platform::getLimitDefines() dui.defines.emplace_back("INT_MIN=-2147483648"); + dui.defines.emplace_back("INT32_MAX=2147483647"); + dui.defines.emplace_back("int32_t=int"); checkP("int fun(int x)\n" "{\n" @@ -501,6 +503,13 @@ class TestType : public TestFixture { " fun(INT_MIN);\n" "}", settings, "test.cpp", dui); ASSERT_EQUALS("[test.cpp:3]: (error) Signed integer overflow for expression '-x'.\n", errout.str()); + + checkP("void f() {\n" // #8399 + " int32_t i = INT32_MAX;\n" + " i << 1;\n" + " i << 2;\n" + "}", settings, "test.cpp", dui); + ASSERT_EQUALS("[test.cpp:4]: (error) Signed integer overflow for expression 'i<<2'.\n", errout.str()); } }; diff --git a/test/testuninitvar.cpp b/test/testuninitvar.cpp index ff6490afb85..3b4c548c77d 100644 --- a/test/testuninitvar.cpp +++ b/test/testuninitvar.cpp @@ -6306,6 +6306,34 @@ class TestUninitVar : public TestFixture { " if ((b[0] = g()) == 0) {}\n" "}"); ASSERT_EQUALS("", errout.str()); + + valueFlowUninit("void f(const char *x, char *y);\n" // #4527 + "void g(char* b) {\n" + " char a[1000];\n" + " f(a, b);\n" + " printf(\"%s\", a);\n" + "}"); + ASSERT_EQUALS("[test.cpp:5]: (error) Uninitialized variable: a\n", errout.str()); + + valueFlowUninit("void usage(const char *);\n" // #10330 + "int main(int argc, char* argv[]) {\n" + " int user = 0;\n" + " struct passwd* pwd;\n" + " while (1) {\n" + " int c = getc();\n" + " if (c == -1)\n" + " break;\n" + " switch (c) {\n" + " case 'u': user = 123; break;\n" + " }\n" + " }\n" + " if (argc == 1)\n" + " usage(argv[0]);\n" + " if (user)\n" + " pwd = getpwnam(user);\n" + " if (pwd == NULL) {}\n" + "}"); + ASSERT_EQUALS("[test.cpp:15] -> [test.cpp:17]: (warning) Uninitialized variable: pwd\n", errout.str()); } void valueFlowUninitBreak() { // Do not show duplicate warnings about the same uninitialized value @@ -6559,7 +6587,7 @@ class TestUninitVar : public TestFixture { " bool copied_all = true;\n" " g(&copied_all, 5, 6, &bytesCopied);\n" "}"); - TODO_ASSERT_EQUALS("[test.cpp:7] -> [test.cpp:2]: (warning) Uninitialized variable: *buflen\n", "", errout.str()); + ASSERT_EQUALS("[test.cpp:7] -> [test.cpp:2]: (warning) Uninitialized variable: *buflen\n", errout.str()); // # 9953 valueFlowUninit("uint32_t f(uint8_t *mem) {\n" diff --git a/test/testunusedvar.cpp b/test/testunusedvar.cpp index fc04d144a39..fa46a644cfc 100644 --- a/test/testunusedvar.cpp +++ b/test/testunusedvar.cpp @@ -6574,6 +6574,25 @@ class TestUnusedVar : public TestFixture { " return 0;\n" "}\n"); ASSERT_EQUALS("[test.cpp:2]: (style) Unused variable: a\n", errout.str()); + + functionVariableUsage("void f() {\n" // #9823 + " auto cb = []() {\n" + " int i;\n" + " };\n" + " (void)cb;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:3]: (style) Unused variable: i\n", errout.str()); + + functionVariableUsage("void f() {\n" // #9822 + " int i;\n" + " auto cb = []() {\n" + " int i;\n" + " };\n" + " (void)cb;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:2]: (style) Unused variable: i\n" + "[test.cpp:4]: (style) Unused variable: i\n", + errout.str()); } diff --git a/test/testvalueflow.cpp b/test/testvalueflow.cpp index 3441b3b04ff..315cc73c3d0 100644 --- a/test/testvalueflow.cpp +++ b/test/testvalueflow.cpp @@ -4511,7 +4511,7 @@ class TestValueFlow : public TestFixture { "void f(Object *obj) {\n" " if (valid(obj, K0)) {}\n" "}\n"; - TODO_ASSERT_EQUALS(true, false, testValueOfX(code, 7U, 0)); + ASSERT_EQUALS(true, testValueOfX(code, 7U, 0)); ASSERT_EQUALS(false, testValueOfXKnown(code, 7U, 0)); code = "int f(int i) {\n" @@ -5644,6 +5644,17 @@ class TestValueFlow : public TestFixture { "}\n"; values = tokenValues(code, "x <", ValueFlow::Value::ValueType::UNINIT); ASSERT_EQUALS(0, values.size()); + + code = "void g(bool *result, size_t *buflen) {\n" // #12091 + " if (*result && *buflen >= 5) {}\n" // <- *buflen might not be initialized + "}\n" + "void f() {\n" + " size_t bytesCopied;\n" + " bool copied_all = true;\n" + " g(&copied_all, &bytesCopied);\n" + "}"; + values = tokenValues(code, "buflen >=", ValueFlow::Value::ValueType::UNINIT); + ASSERT_EQUALS(1, values.size()); } void valueFlowConditionExpressions() { diff --git a/test/testvarid.cpp b/test/testvarid.cpp index 6e835f2dfe9..f9140fa42d5 100644 --- a/test/testvarid.cpp +++ b/test/testvarid.cpp @@ -17,6 +17,7 @@ */ #include "errortypes.h" +#include "helpers.h" #include "platform.h" #include "settings.h" #include "standards.h" @@ -236,6 +237,12 @@ class TestVarID : public TestFixture { TEST_CASE(exprid1); TEST_CASE(exprid2); + TEST_CASE(exprid3); + TEST_CASE(exprid4); + TEST_CASE(exprid5); + TEST_CASE(exprid6); + TEST_CASE(exprid7); + TEST_CASE(exprid8); TEST_CASE(structuredBindings); } @@ -260,9 +267,11 @@ class TestVarID : public TestFixture { std::string tokenizeExpr_(const char* file, int line, const char code[], const char filename[] = "test.cpp") { errout.str(""); + std::vector files(1, filename); Tokenizer tokenizer(&settings, this); - std::istringstream istr(code); - ASSERT_LOC((tokenizer.tokenize)(istr, filename), file, line); + PreprocessorHelper::preprocess(code, files, tokenizer); + + ASSERT_LOC(tokenizer.simplifyTokens1(""), file, line); // result.. Token::stringifyOptions options = Token::stringifyOptions::forDebugExprId(); @@ -3859,9 +3868,9 @@ class TestVarID : public TestFixture { "2: int x ; int y ;\n" "3: } ;\n" "4: int f ( A a , A b ) {\n" - "5: int x@5 ; x@5 =@9 a@3 .@10 x@6 +@11 b@4 .@12 x@7 ;\n" - "6: int y@8 ; y@8 =@1073741837 b@4 .@12 x@7 +@11 a@3 .@10 x@6 ;\n" - "7: return x@5 +@1073741841 y@8 +@1073741842 a@3 .@1073741843 y@9 +@1073741844 b@4 .@1073741845 y@10 ;\n" + "5: int x@5 ; x@5 =@UNIQUE a@3 .@11 x@6 +@13 b@4 .@12 x@7 ;\n" + "6: int y@8 ; y@8 =@UNIQUE b@4 .@12 x@7 +@13 a@3 .@11 x@6 ;\n" + "7: return x@5 +@UNIQUE y@8 +@UNIQUE a@3 .@UNIQUE y@9 +@UNIQUE b@4 .@UNIQUE y@10 ;\n" "8: }\n"; ASSERT_EQUALS(expected, actual); @@ -3878,14 +3887,101 @@ class TestVarID : public TestFixture { const char expected[] = "1: struct S { std :: unique_ptr < int > u ; } ;\n" "2: auto f ; f = [ ] ( const S & s ) . std :: unique_ptr < int > {\n" - "3: if (@5 auto p@4 =@1073741830 s@3 .@1073741831 u@5 .@1073741832 get (@1073741833 ) ) {\n" - "4: return std ::@1073741834 make_unique < int > (@1073741835 *@1073741836 p@4 ) ; }\n" + "3: if ( auto p@4 =@UNIQUE s@3 .@UNIQUE u@5 .@UNIQUE get (@UNIQUE ) ) {\n" + "4: return std ::@UNIQUE make_unique < int > (@UNIQUE *@UNIQUE p@4 ) ; }\n" "5: return nullptr ;\n" "6: } ;\n"; ASSERT_EQUALS(expected, actual); } + void exprid3() { + const char code[] = "void f(bool b, int y) {\n" + " if (b && y > 0) {}\n" + " while (b && y > 0) {}\n" + "}\n"; + const char expected[] = "1: void f ( bool b , int y ) {\n" + "2: if ( b@1 &&@5 y@2 >@4 0 ) { }\n" + "3: while ( b@1 &&@5 y@2 >@4 0 ) { }\n" + "4: }\n"; + ASSERT_EQUALS(expected, tokenizeExpr(code)); + } + + void exprid4() { + // expanded macro.. + const char code[] = "#define ADD(x,y) x+y\n" + "int f(int a, int b) {\n" + " return ADD(a,b) + ADD(a,b);\n" + "}\n"; + const char expected[] = "2: int f ( int a , int b ) {\n" + "3: return a@1 $+@UNIQUE b@2 +@UNIQUE a@1 $+@UNIQUE b@2 ;\n" + "4: }\n"; + ASSERT_EQUALS(expected, tokenizeExpr(code)); + } + + void exprid5() { + // references.. + const char code[] = "int foo(int a) {\n" + " int& r = a;\n" + " return (a+a)*(r+r);\n" + "}\n"; + const char expected[] = "1: int foo ( int a ) {\n" + "2: int & r@2 =@UNIQUE a@1 ;\n" + "3: return ( a@1 +@4 a@1 ) *@UNIQUE ( r@2 +@4 r@2 ) ;\n" + "4: }\n"; + ASSERT_EQUALS(expected, tokenizeExpr(code)); + } + + void exprid6() { + // ++ and -- should have UNIQUE exprid + const char code[] = "void foo(int *a) {\n" + " *a++ = 0;\n" + " if (*a++ == 32) {}\n" + "}\n"; + const char expected[] = "1: void foo ( int * a ) {\n" + "2: *@UNIQUE a@1 ++@UNIQUE = 0 ;\n" + "3: if ( *@UNIQUE a@1 ++@UNIQUE ==@UNIQUE 32 ) { }\n" + "4: }\n"; + ASSERT_EQUALS(expected, tokenizeExpr(code)); + } + + void exprid7() { + // different casts + const char code[] = "void foo(int a) {\n" + " if ((char)a == (short)a) {}\n" + " if ((char)a == (short)a) {}\n" + "}\n"; + const char expected[] = "1: void foo ( int a ) {\n" + "2: if ( (@2 char ) a@1 ==@4 (@3 short ) a@1 ) { }\n" + "3: if ( (@2 char ) a@1 ==@4 (@3 short ) a@1 ) { }\n" + "4: }\n"; + ASSERT_EQUALS(expected, tokenizeExpr(code)); + } + + void exprid8() { + const char code[] = "void f() {\n" // #12249 + " std::string s;\n" + " (((s += \"--\") += std::string()) += \"=\");\n" + "}\n"; + const char expected[] = "1: void f ( ) {\n" + "2: std ::@UNIQUE string s@1 ;\n" + "3: ( ( s@1 +=@UNIQUE \"--\"@UNIQUE ) +=@UNIQUE std ::@UNIQUE string (@UNIQUE ) ) +=@UNIQUE \"=\"@UNIQUE ;\n" + "4: }\n"; + ASSERT_EQUALS(expected, tokenizeExpr(code)); + + const char code2[] = "struct S { std::function* p; };\n" + "S f() { return S{ std::make_unique>([]() {}).release()}; }"; + const char expected2[] = "1: struct S { std :: function < void ( ) > * p ; } ;\n" + "2: S f ( ) { return S@UNIQUE {@UNIQUE std ::@UNIQUE make_unique < std :: function < void ( ) > > (@UNIQUE [ ] ( ) { } ) .@UNIQUE release (@UNIQUE ) } ; }\n"; + ASSERT_EQUALS(expected2, tokenizeExpr(code2)); + + const char code3[] = "struct S { int* p; };\n" + "S f() { return S{ std::make_unique([]() { return 4; }()).release()}; }\n"; + const char expected3[] = "1: struct S { int * p ; } ;\n" + "2: S f ( ) { return S@UNIQUE {@UNIQUE std ::@UNIQUE make_unique < int > (@UNIQUE [ ] ( ) { return 4 ; } ( ) ) .@UNIQUE release (@UNIQUE ) } ; }\n"; + ASSERT_EQUALS(expected3, tokenizeExpr(code3)); + } + void structuredBindings() { const char code[] = "int foo() { auto [x,y] = xy(); return x+y; }"; ASSERT_EQUALS("1: int foo ( ) { auto [ x@1 , y@2 ] = xy ( ) ; return x@1 + y@2 ; }\n",