diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000000..94f9912cd19 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "externals/cryptopp"] + path = externals/cryptopp + url = git@github.com:nirbar/cryptopp.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 4de3c06e588..bae8f6e8875 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,7 @@ if (BUILD_TESTS) endif() add_subdirectory(externals/tinyxml) +add_subdirectory(externals/cryptopp) add_subdirectory(lib) # CppCheck Library add_subdirectory(cli) # Client application add_subdirectory(test) # Tests diff --git a/cli/CMakeLists.txt b/cli/CMakeLists.txt index a7fc54fcca5..d877907fe2a 100644 --- a/cli/CMakeLists.txt +++ b/cli/CMakeLists.txt @@ -1,16 +1,25 @@ include_directories(${PROJECT_SOURCE_DIR}/lib/) include_directories(SYSTEM ${PROJECT_SOURCE_DIR}/externals/tinyxml/) +include_directories(SYSTEM ${PROJECT_SOURCE_DIR}/externals/) file(GLOB hdrs "*.h") file(GLOB srcs "*.cpp") file(GLOB mainfile "main.cpp") list(REMOVE_ITEM srcs ${mainfile}) +set(libs + cryptopp-static + ) +if(WIN32) + set(libs ${libs} Shlwapi.lib) +endif() + add_library(cli_objs OBJECT ${hdrs} ${srcs}) add_executable(cppcheck ${hdrs} ${mainfile} $ $ $) if (HAVE_RULES) - target_link_libraries(cppcheck pcre) + set(libs ${libs} pcre) endif() +target_link_libraries(cppcheck ${libs}) install(TARGETS cppcheck RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR} diff --git a/cli/cmdlineparser.cpp b/cli/cmdlineparser.cpp index e3c5b3f8018..7625d21e201 100644 --- a/cli/cmdlineparser.cpp +++ b/cli/cmdlineparser.cpp @@ -173,6 +173,22 @@ bool CmdLineParser::ParseFromArgs(int argc, const char* const argv[]) } } + // Cache file (--cache=) + else if (std::strncmp(argv[i], "--cache=", strlen("--cache=")) == 0) { + std::string str(argv[i]); + str=str.substr(strlen("--cache=")); + if( str.empty()){ + PrintMessage("cppcheck: No cache file given to '--cache=' option."); + return false; + } + + _settings->cacheFile = str; + if (0 != _settings->cache.Load(str)) + { + PrintMessage("cppcheck: Failed loading cache file " + str); + } + } + // Filter errors else if (std::strncmp(argv[i], "--exitcode-suppressions=", 24) == 0) { // exitcode-suppressions=filename.txt diff --git a/cli/cppcheckexecutor.cpp b/cli/cppcheckexecutor.cpp index fa5e880e165..c97edd627e9 100644 --- a/cli/cppcheckexecutor.cpp +++ b/cli/cppcheckexecutor.cpp @@ -880,6 +880,11 @@ int CppCheckExecutor::check_internal(CppCheck& cppcheck, int /*argc*/, const cha reportErr(ErrorLogger::ErrorMessage::getXMLFooter(settings.xml_version)); } + if (!settings.cacheFile.empty()) + { + settings.cache.Save(); + } + _settings = 0; if (returnValue) return settings.exitCode; diff --git a/externals/cryptopp b/externals/cryptopp new file mode 160000 index 00000000000..55f34935781 --- /dev/null +++ b/externals/cryptopp @@ -0,0 +1 @@ +Subproject commit 55f349357819719a7f439d7f6c68b6ec9720a868 diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 3d5949baa53..3eaaf5e958c 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,4 +1,5 @@ include_directories(SYSTEM ${PROJECT_SOURCE_DIR}/externals/tinyxml/) +include_directories(SYSTEM ${PROJECT_SOURCE_DIR}/externals/) file(GLOB_RECURSE hdrs "*.h") file(GLOB_RECURSE srcs "*.cpp") diff --git a/lib/cache.cpp b/lib/cache.cpp new file mode 100644 index 00000000000..da0143c421d --- /dev/null +++ b/lib/cache.cpp @@ -0,0 +1,301 @@ +#include "cache.h" +#include "errorlogger.h" +#include +#include +#include +#include +using namespace std; +using namespace tinyxml2; +using namespace CryptoPP; + +Cache::Cache() +{ +} + +Cache::Cache(const Cache & other) +{ + *this = other; +} + +Cache & Cache::operator=(const Cache & other) +{ + Clear(); + if (!_cacheFile.empty()) + { + Load(other._cacheFile); + } + return *this; +} + +int Cache::Load(const string& file) +{ + XMLError er = XML_SUCCESS; + + if (!_cacheFile.empty()) + { + return -1; + } + + _cacheFile = file; + er = _cache.LoadFile(file.c_str()); + + return((er == XML_SUCCESS) || (er == XML_NO_ERROR)) ? 0 : (er | XML_SUCCESS); +} + +int Cache::LoadFromCache(const char* filePath, const char* configuration, size_t* pCachedSize, std::string* pHash, const tinyxml2::XMLElement** ppElem) const +{ + const XMLElement *elem = NULL; + const char* sizeStr = NULL; + const char* hashStr = NULL; + size_t tmpSize = 0; + string path; + (*pCachedSize) = 0; + (*ppElem) = NULL; + pHash->clear(); + + path = Normalize(filePath); + + if ((Find(path.c_str(), configuration, &elem) != 0) || (elem == NULL)) + { + return -1; + } + + // Parse size + if ((sizeStr = elem->Attribute("Size")) == NULL) + { + return -1; + } + + if ((tmpSize = ::stoull(sizeStr, NULL)) == 0) + { + return -1; + } + + // Hash + if ((hashStr = elem->Attribute("Hash")) == NULL) + { + return -1; + } + + (*pCachedSize) = tmpSize; + (*pHash) = hashStr; + (*ppElem) = elem; + return 0; +} + +int Cache::Find(const char* filePath, const char* configuration, XMLElement** ppElem) +{ + return Find(filePath, configuration, (const XMLElement**)ppElem); +} + +int Cache::Find(const char* filePath, const char* configuration, const XMLElement** ppElem) const +{ + (*ppElem) = NULL; + + string path = Normalize(filePath); + if (path.empty() || _cacheFile.empty()) + { + return -1; + } + + const XMLElement* root = _cache.RootElement(); + if (root == NULL) + { + return -1; + } + + for (const XMLElement* currElem = root->FirstChildElement("File"); currElem != NULL; currElem = currElem->NextSiblingElement("File")) + { + if (currElem->Attribute("Path", path.c_str()) && currElem->Attribute("Configuration", configuration)) + { + (*ppElem) = currElem; + break; + } + } + + return ((*ppElem) == NULL); +} + +string Cache::CalcHash(const char* code) const +{ + SHA3_512 sha; + string out; + Base64Encoder* enc = new Base64Encoder(new StringSink(out), false); + + StringSource(code, true, new HashFilter(sha, enc)); + + return out; +} + +string Cache::Normalize(const char* filePath) const +{ + size_t i; + string path(filePath); + + while ((i = path.find('\\')) != string::npos) + { + path = path.replace(i, 1, "/"); + } + while ((i = path.find("//")) != string::npos) + { + path = path.replace(i, 2, "/"); + } + return path; +} + +bool Cache::ReportCachedResults(const char* filePath, const char* configuration, const char* code, ErrorLogger* pLogger) const +{ + string cachedHash; + size_t cachedSize = 0; + const XMLElement* pElem = NULL; + + // Cached? + if ((LoadFromCache(filePath, configuration, &cachedSize, &cachedHash, &pElem) != 0) || (cachedSize == 0) || cachedHash.empty() || (pElem == NULL)) + { + return false; + } + + // Compare size + size_t actualSize = strlen(code); + if (actualSize != cachedSize) + { + return false; + } + + // Compare hash + string actualHash = CalcHash(code); + if (actualHash.compare(cachedHash) != 0) + { + return false; + } + + for (const XMLElement* currChild = pElem->FirstChildElement("Report"); currChild != NULL; currChild = currChild->NextSiblingElement("Report")) + { + ErrorLogger::ErrorMessage msg; + string t = currChild->GetText(); + if (!msg.deserialize(t)) + { + return false; + } + if (msg._id.compare("syntaxError") != 0) + { + msg.file0 = filePath; + } + pLogger->reportErr(msg); + } + + return true; +} + +int Cache::CacheFile(const char* filePath, const char* configuration, const char* code, const std::list& reports) +{ + // Cache disabled + if (_cacheFile.empty()) + { + return 0; + } + + string path = Normalize(filePath); + if (path.empty()) + { + return -1; + } + + size_t actualSize = strlen(code); + if (actualSize == 0) + { + return -1; + } + + string actualHash = CalcHash(code); + if (actualHash.empty()) + { + return -1; + } + + XMLElement* pElem = NULL; + if ((Find(path.c_str(), configuration, &pElem) != 0) || (pElem == NULL)) + { + // Create element + XMLElement* root = _cache.RootElement(); + if (root == NULL) + { + root = _cache.NewElement("CppCheckCache"); + if (root == NULL) + { + return -1; + } + + if (_cache.InsertFirstChild(root) == NULL) + { + return -1; + } + } + + pElem = _cache.NewElement("File"); + if (pElem == NULL) + { + return -1; + } + + if (root->InsertEndChild(pElem) == NULL) + { + return -1; + } + } + + pElem->DeleteChildren(); + pElem->SetAttribute("Configuration", configuration); + pElem->SetAttribute("Size", actualSize); + pElem->SetAttribute("Hash", actualHash.c_str()); + pElem->SetAttribute("Path", path.c_str()); + + // Insert reports + std::list::const_iterator currIt, endIt; + for (currIt = reports.begin(), endIt = reports.end(); currIt != endIt; ++currIt) + { + XMLElement *report = _cache.NewElement("Report"); + if (report == NULL) + { + return -1; + } + + report->SetText(currIt->c_str()); + pElem->InsertEndChild(report); + } + + return 0; +} + +int Cache::Remove(const char* filePath, const char* configuration) +{ + XMLElement* pElem = NULL; + if ((Find(filePath, configuration, &pElem) != 0) || (pElem == NULL)) + { + return 0; + } + + XMLElement* root = _cache.RootElement(); + if (root == NULL) + { + return -1; + } + + root->DeleteChild(pElem); + return 0; +} + +void Cache::Clear() +{ + _cache.Clear(); + _cacheFile.clear(); +} + +int Cache::Save() +{ + XMLError er = XML_SUCCESS; + + er = _cache.SaveFile(_cacheFile.c_str()); + + return((er == XML_SUCCESS) || (er == XML_NO_ERROR)) ? 0 : (er | XML_SUCCESS); +} \ No newline at end of file diff --git a/lib/cache.h b/lib/cache.h new file mode 100644 index 00000000000..b82079a9874 --- /dev/null +++ b/lib/cache.h @@ -0,0 +1,122 @@ + +//--------------------------------------------------------------------------- +#ifndef cacheH +#define cacheH +//--------------------------------------------------------------------------- + +#include "config.h" +#include +#include "tinyxml2.h" +#include +#include +class ErrorLogger; +/// @addtogroup Core +/// @{ + +/** + * @brief Class maintains a cache of checked files + * + * Cache is enabled by command line switch --cache=_CacheFile_ + */ +class CPPCHECKLIB Cache { +public: + + /*! @brief Constructor + */ + Cache(); + + /*! @brief Copy Constructor + */ + Cache(const Cache& other); + + /*! @brief Copy another Cache instance + @param other- Cache to copy + @return Reference to this instance. + */ + Cache& operator=(const Cache& other); + + /*! @brief Loads cache from file + @param cacheFile- File used to load and save cached files data + @return Zero if file was successfully loaded to cache + */ + int Load(const std::string& cacheFile); + + /*! @brief Load cached results and report them + @details If a file has not changed since cached, load cached results and report them + @param filePath- Path to the file in question + @param configuration- Preprocessor configuration + @param code- Preprocessor code, as returned from Preprocessor::getcode() + @param pLogger- Logger to report errors to + @return true if and only if the file is in cache and has not changed since cached. + */ + bool ReportCachedResults(const char* filePath, const char* configuration, const char* code, ErrorLogger* pLogger) const; + + /*! @brief Place a file in cache + @details Caches the requested file in cache along with its hash value and size. + If a file is present in cache it is replaced with the new hash and size. + @param filePath- Path to the file to store in cache + @param configuration- Preprocessor configuration + @param code- Preprocessor code, as returned from Preprocessor::getcode() + @return Zero if file was successfully cached + */ + int CacheFile(const char* filePath, const char* configuration, const char* code, const std::list& reports); + + /*! @brief Removes a file from cache + @details Removes the requested file from cache. + @param filePath- Path to the file to store in cache + @param configuration- Preprocessor configuration + @return Zero if file was successfully removed from cache + */ + int Remove(const char* filePath, const char* configuration); + + /*! @brief Clears the cache + @details Clear the cache entirely, without saving to file + */ + void Clear(); + + /*! @brief Saves the cache to file + @details All operations since constructing the cache are performed in memory. Only when Save() is explicitly called will the cache be persisted to file. + @return Zero if cache was successfully saved + */ + int Save(); + +protected: + + /*! @brief Calaculate a file hash + @details For a given file path, calculate the file hash. Currently we use MD5 hash + @param code- Preprocessor code, as returned from Preprocessor::getcode() + @return Hash, or empty string if an error occured + */ + virtual std::string CalcHash(const char* code) const; + + /*! @brief Loads a file data from cache + @details Checks if the requested file is present in cache. If yes, output its cached size and hash + @param filePath- Path to the file in question + @param configuration- Preprocessor configuration + @param[out] pCachedSize- Pointer to a size_t variable to receive the file's cached size, if available + @param[out] pHash- Pointer to a string variable to receive the file's cached hash value, if available + @param[out] ppElem- Pointer to a XML element to receive the cached element, if available + @return Zero if file was successfully retreived from cache + */ + virtual int LoadFromCache(const char* filePath, const char* configuration, size_t* pCachedSize, std::string* pHash, const tinyxml2::XMLElement** ppElem) const; + + /*! @brief Returns a normalized representation of the file path + @details As file path separators are file system dependant, this function ensures a distinct represenatation accross file systems. + In this implementation, '\' is replaced with '/', and '//' is replaced with '/' + @param filePath- Path to normalize + @return A normalized form of path + */ + virtual std::string Normalize(const char* filePath) const; + +private: + + int Find(const char* filePath, const char* configuration, const tinyxml2::XMLElement** ppElem) const; + int Find(const char* filePath, const char* configuration, tinyxml2::XMLElement** ppElem); + + tinyxml2::XMLDocument _cache; + std::string _cacheFile; +}; + +/// @} +//--------------------------------------------------------------------------- +#endif // cacheH diff --git a/lib/cppcheck.cpp b/lib/cppcheck.cpp index 238073dd026..59758e0a6cd 100644 --- a/lib/cppcheck.cpp +++ b/lib/cppcheck.cpp @@ -160,7 +160,10 @@ unsigned int CppCheck::processFile(const std::string& filename, std::istream& fi std::set checksums; unsigned int checkCount = 0; for (std::list::const_iterator it = configurations.begin(); it != configurations.end(); ++it) { - // bail out if terminated + + _tempCache.reset(); // dump previous iteration results. + + // bail out if terminated if (_settings.terminated()) break; @@ -188,6 +191,13 @@ unsigned int CppCheck::processFile(const std::string& filename, std::istream& fi std::string codeWithoutCfg = preprocessor.getcode(filedata, cfg, filename); t.Stop(); + if (_settings.cache.ReportCachedResults(filename.c_str(), cfg.c_str(), codeWithoutCfg.c_str(), this)) + { + reportOut("File is cached: " + filename); + continue; + } + _tempCache.reset(new TempCache(this, &_settings, filename, cfg, codeWithoutCfg)); + codeWithoutCfg += _settings.append(); if (_settings.preprocessOnly) { @@ -263,9 +273,9 @@ unsigned int CppCheck::processFile(const std::string& filename, std::istream& fi // Check simplified tokens checkSimplifiedTokens(_tokenizer); - } - - } catch (const InternalError &e) { + } + + } catch (const InternalError &e) { if (_settings.isEnabled("information") && (_settings.debug || _settings.verbose)) purgedConfigurationMessage(filename, cfg); internalErrorFound=true; @@ -302,6 +312,7 @@ unsigned int CppCheck::processFile(const std::string& filename, std::istream& fi internalError(filename, e.errorMessage); exitcode=1; // e.g. reflect a syntax error } + _tempCache.reset(); // In jointSuppressionReport mode, unmatched suppressions are // collected after all files are processed @@ -567,7 +578,13 @@ void CppCheck::reportErr(const ErrorLogger::ErrorMessage &msg) if (errmsg.empty()) return; - // Alert only about unique errors + // Cache- even non-unique errors, as next test may be we different configurations and we don't want to mask an error just because in this scan it had different configurations. + if (!!_tempCache && !_settings.cacheFile.empty()) + { + _tempCache->Report(msg.serialize()); + } + + // Alert only about unique errors if (std::find(_errorList.begin(), _errorList.end(), errmsg) != _errorList.end()) return; diff --git a/lib/cppcheck.h b/lib/cppcheck.h index 380ba713db0..9fd09db7553 100644 --- a/lib/cppcheck.h +++ b/lib/cppcheck.h @@ -25,10 +25,12 @@ #include "settings.h" #include "errorlogger.h" #include "check.h" +#include "tempcache.h" #include #include #include +#include class Tokenizer; @@ -185,7 +187,8 @@ class CPPCHECKLIB CppCheck : ErrorLogger { */ virtual void reportOut(const std::string &outmsg); - std::list _errorList; + std::list _errorList; + std::shared_ptr _tempCache; Settings _settings; void reportProgress(const std::string &filename, const char stage[], const std::size_t value); diff --git a/lib/cppcheck.vcxproj b/lib/cppcheck.vcxproj index d0980e94878..5335f3420a8 100644 --- a/lib/cppcheck.vcxproj +++ b/lib/cppcheck.vcxproj @@ -37,6 +37,7 @@ + Create Create @@ -91,6 +92,8 @@ + + diff --git a/lib/cppcheck.vcxproj.filters b/lib/cppcheck.vcxproj.filters index c647ce9004f..338565d86b9 100644 --- a/lib/cppcheck.vcxproj.filters +++ b/lib/cppcheck.vcxproj.filters @@ -140,6 +140,9 @@ Source Files + + Source Files + @@ -280,6 +283,12 @@ Header Files + + Header Files + + + Header Files + diff --git a/lib/settings.h b/lib/settings.h index 13532e351dd..01115363fd9 100644 --- a/lib/settings.h +++ b/lib/settings.h @@ -31,7 +31,7 @@ #include "standards.h" #include "errorlogger.h" #include "timer.h" - +#include "cache.h" /// @addtogroup Core /// @{ @@ -114,6 +114,12 @@ class CPPCHECKLIB Settings { /** @brief Paths used as base for conversion to relative paths. */ std::vector basePaths; + /** @brief Path to cache file. */ + std::string cacheFile; + + /** @brief Cache */ + Cache cache; + /** @brief write XML results (--xml) */ bool xml; diff --git a/lib/tempcache.h b/lib/tempcache.h new file mode 100644 index 00000000000..702b7e8527a --- /dev/null +++ b/lib/tempcache.h @@ -0,0 +1,61 @@ + +//--------------------------------------------------------------------------- +#ifndef tempcacheH +#define tempcacheH +//--------------------------------------------------------------------------- + +#include "config.h" +#include +#include "tinyxml2.h" +#include +#include +class ErrorLogger; +/// @addtogroup Core +/// @{ + +/** + * @brief Class maintains a temporary cache for a single file and single configuration + * + * @details A scope-based temporay cache to accumulate check results before dumping them all to global cache. + */ +class CPPCHECKLIB TempCache { +public: + + /*! @brief Constructor + */ + TempCache(ErrorLogger* logger, Settings *settings, const std::string& filename, const std::string& cfg, const std::string& codeWithoutCfg) + : _settings(settings) + , _logger(logger) + , _filename(filename) + , _cfg(cfg) + , _codeWithoutCfg(codeWithoutCfg) + {} + + /*! @brief Dump errors to global cache + */ + ~TempCache() + { + if (0 > _settings->cache.CacheFile(_filename.c_str(), _cfg.c_str(), _codeWithoutCfg.c_str(), _reportCache)) + { + _logger->reportOut("Failed caching file: " + _filename); + } + } + + /*! @brief Report an error + */ + void Report(const std::string& msg) + { + _reportCache.push_back(msg); + } + +private: + + Settings *_settings; + ErrorLogger* _logger; + std::string _filename, _cfg, _codeWithoutCfg; + std::list _reportCache; +}; + +/// @} +//--------------------------------------------------------------------------- +#endif // tempcacheH