/* * Cppcheck - A tool for static C/C++ code analysis * Copyright (C) 2007-2019 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "suppressions.h" #include "errorlogger.h" #include "mathlib.h" #include "path.h" #include "utils.h" #include #include #include // std::isdigit, std::isalnum, etc #include #include class ErrorLogger; static bool isAcceptedErrorIdChar(char c) { switch (c) { case '_': case '-': case '.': return true; default: return std::isalnum(c); } } std::string Suppressions::parseFile(std::istream &istr) { // Change '\r' to '\n' in the istr std::string filedata; std::string line; while (std::getline(istr, line)) filedata += line + "\n"; std::replace(filedata.begin(), filedata.end(), '\r', '\n'); // Parse filedata.. std::istringstream istr2(filedata); while (std::getline(istr2, line)) { // Skip empty lines if (line.empty()) continue; // Skip comments if (line.length() > 1 && line[0] == '#') continue; if (line.length() >= 2 && line[0] == '/' && line[1] == '/') continue; const std::string errmsg(addSuppressionLine(line)); if (!errmsg.empty()) return errmsg; } return ""; } std::string Suppressions::parseXmlFile(const char *filename) { tinyxml2::XMLDocument doc; const tinyxml2::XMLError error = doc.LoadFile(filename); if (error == tinyxml2::XML_ERROR_FILE_NOT_FOUND) return "File not found"; if (error != tinyxml2::XML_SUCCESS) return "Failed to parse XML file"; const tinyxml2::XMLElement * const rootnode = doc.FirstChildElement(); for (const tinyxml2::XMLElement * e = rootnode->FirstChildElement(); e; e = e->NextSiblingElement()) { if (std::strcmp(e->Name(), "suppress") != 0) return "Invalid suppression xml file format, expected element but got a <" + std::string(e->Name()) + '>'; Suppression s; for (const tinyxml2::XMLElement * e2 = e->FirstChildElement(); e2; e2 = e2->NextSiblingElement()) { const char *text = e2->GetText() ? e2->GetText() : ""; if (std::strcmp(e2->Name(), "id") == 0) s.errorId = text; else if (std::strcmp(e2->Name(), "fileName") == 0) s.fileName = text; else if (std::strcmp(e2->Name(), "lineNumber") == 0) s.lineNumber = std::atoi(text); else if (std::strcmp(e2->Name(), "symbolName") == 0) s.symbolName = text; else return "Unknown suppression element <" + std::string(e2->Name()) + ">, expected ///"; } const std::string err = addSuppression(s); if (!err.empty()) return err; } return ""; } std::vector Suppressions::parseMultiSuppressComment(std::string comment, std::string *errorMessage) { std::vector suppressions; if (comment.size() < 2) return suppressions; const std::size_t first_non_blank_position = comment.find_first_not_of(" \t\n", 2); const std::size_t suppress_position = comment.find("cppcheck-suppress", 2); if (suppress_position == std::string::npos) return suppressions; if (suppress_position != first_non_blank_position) { if (errorMessage && errorMessage->empty()) *errorMessage = "Bad multi suppression '" + comment + "'. there shouldn't have non-blank character before cppcheck-suppress"; return suppressions; } const std::size_t start_position = comment.find("[", suppress_position); const std::size_t end_position = comment.find("]", suppress_position); if ( start_position == std::string::npos || end_position == std::string::npos || start_position != suppress_position+17 //there must be no space before "[" || start_position >= end_position) { if (errorMessage && errorMessage->empty()) *errorMessage = "Bad multi suppression '" + comment + "'. legal format is cppcheck-suppress[errorId, errorId symbolName=arr, ...]"; return suppressions; } //extract supressions std::size_t current_comma_position = 0; //multi_suppressions_word maybe "[errorId1, errorId2 symbolName=arr", who just has left bracket std::string multi_suppressions_word = comment.substr(start_position, end_position-start_position); do { std::string suppression_word; //single suppression word const std::size_t previous_comma_position=current_comma_position; current_comma_position=multi_suppressions_word.find(",", previous_comma_position+1); //find "," after previous comma if (current_comma_position == std::string::npos) { suppression_word = multi_suppressions_word.substr(previous_comma_position+1); } else { suppression_word = multi_suppressions_word.substr(previous_comma_position+1, current_comma_position-previous_comma_position-1); } //parse single suppression word Suppression s; std::string word; std::string errorId; std::string symbolName; std::istringstream iss(suppression_word); iss >> errorId; if (!iss) { if (errorMessage && errorMessage->empty()) *errorMessage = "Bad multi suppression '" + comment + "'. legal format is cppcheck-suppress[errorId, errorId symbolName=arr, ...]"; suppressions.clear(); return suppressions; } while (iss) { iss >> word; if (!iss) break; if (word.find_first_not_of("+-*/%#;") == std::string::npos) break; if (word.compare(0, 11, "symbolName=") == 0) { symbolName = word.substr(11); } else { if (errorMessage && errorMessage->empty()) *errorMessage = "Bad multi suppression '" + comment + "'. legal format is cppcheck-suppress[errorId, errorId symbolName=arr, ...]"; suppressions.clear(); return suppressions; } } s.errorId = errorId; s.symbolName = symbolName; suppressions.push_back(s); } while(current_comma_position!=std::string::npos); return suppressions; } std::string Suppressions::addSuppressionLine(const std::string &line) { std::istringstream lineStream(line); Suppressions::Suppression suppression; if (std::getline(lineStream, suppression.errorId, ':')) { if (std::getline(lineStream, suppression.fileName)) { // If there is not a dot after the last colon in "file" then // the colon is a separator and the contents after the colon // is a line number.. // Get position of last colon const std::string::size_type pos = suppression.fileName.rfind(':'); // if a colon is found and there is no dot after it.. if (pos != std::string::npos && suppression.fileName.find('.', pos) == std::string::npos) { // Try to parse out the line number try { std::istringstream istr1(suppression.fileName.substr(pos+1)); istr1 >> suppression.lineNumber; } catch (...) { suppression.lineNumber = Suppressions::Suppression::NO_LINE; } if (suppression.lineNumber != Suppressions::Suppression::NO_LINE) { suppression.fileName.erase(pos); } } } } suppression.fileName = Path::simplifyPath(suppression.fileName); return addSuppression(suppression); } std::string Suppressions::addSuppression(const Suppressions::Suppression &suppression) { // Check that errorId is valid.. if (suppression.errorId.empty()) { return "Failed to add suppression. No id."; } if (suppression.errorId != "*") { for (std::string::size_type pos = 0; pos < suppression.errorId.length(); ++pos) { if (suppression.errorId[pos] < 0 || !isAcceptedErrorIdChar(suppression.errorId[pos])) { return "Failed to add suppression. Invalid id \"" + suppression.errorId + "\""; } if (pos == 0 && std::isdigit(suppression.errorId[pos])) { return "Failed to add suppression. Invalid id \"" + suppression.errorId + "\""; } } } if (!isValidGlobPattern(suppression.errorId)) return "Failed to add suppression. Invalid glob pattern '" + suppression.errorId + "'."; if (!isValidGlobPattern(suppression.fileName)) return "Failed to add suppression. Invalid glob pattern '" + suppression.fileName + "'."; mSuppressions.push_back(suppression); return ""; } void Suppressions::ErrorMessage::setFileName(const std::string &s) { mFileName = Path::simplifyPath(s); } bool Suppressions::Suppression::parseComment(std::string comment, std::string *errorMessage) { if (comment.size() < 2) return false; if (comment.find(';') != std::string::npos) comment.erase(comment.find(';')); if (comment.find("//", 2) != std::string::npos) comment.erase(comment.find("//",2)); if (comment.compare(comment.size() - 2, 2, "*/") == 0) comment.erase(comment.size() - 2, 2); std::istringstream iss(comment.substr(2)); std::string word; iss >> word; if (word != "cppcheck-suppress") return false; iss >> errorId; if (!iss) return false; while (iss) { iss >> word; if (!iss) break; if (word.find_first_not_of("+-*/%#;") == std::string::npos) break; if (word.compare(0,11,"symbolName=")==0) symbolName = word.substr(11); else if (errorMessage && errorMessage->empty()) *errorMessage = "Bad suppression attribute '" + word + "'. You can write comments in the comment after a ; or //. Valid suppression attributes; symbolName=sym"; } return true; } bool Suppressions::Suppression::isSuppressed(const Suppressions::ErrorMessage &errmsg) const { if (!errorId.empty() && !matchglob(errorId, errmsg.errorId)) return false; if (!fileName.empty() && !matchglob(fileName, errmsg.getFileName())) return false; if (lineNumber != NO_LINE && lineNumber != errmsg.lineNumber) return false; if (!symbolName.empty()) { for (std::string::size_type pos = 0; pos < errmsg.symbolNames.size();) { const std::string::size_type pos2 = errmsg.symbolNames.find('\n',pos); std::string symname; if (pos2 == std::string::npos) { symname = errmsg.symbolNames.substr(pos); pos = pos2; } else { symname = errmsg.symbolNames.substr(pos,pos2-pos); pos = pos2+1; } if (matchglob(symbolName, symname)) return true; } return false; } return true; } bool Suppressions::Suppression::isMatch(const Suppressions::ErrorMessage &errmsg) { if (!isSuppressed(errmsg)) return false; matched = true; return true; } std::string Suppressions::Suppression::getText() const { std::string ret; if (!errorId.empty()) ret = errorId; if (!fileName.empty()) ret += " fileName=" + fileName; if (lineNumber != NO_LINE) ret += " lineNumber=" + MathLib::toString(lineNumber); if (!symbolName.empty()) ret += " symbolName=" + symbolName; if (ret.compare(0,1," ")==0) return ret.substr(1); return ret; } bool Suppressions::isSuppressed(const Suppressions::ErrorMessage &errmsg) { const bool unmatchedSuppression(errmsg.errorId == "unmatchedSuppression"); for (Suppression &s : mSuppressions) { if (unmatchedSuppression && s.errorId != errmsg.errorId) continue; if (s.isMatch(errmsg)) return true; } return false; } bool Suppressions::isSuppressedLocal(const Suppressions::ErrorMessage &errmsg) { const bool unmatchedSuppression(errmsg.errorId == "unmatchedSuppression"); for (Suppression &s : mSuppressions) { if (!s.isLocal()) continue; if (unmatchedSuppression && s.errorId != errmsg.errorId) continue; if (s.isMatch(errmsg)) return true; } return false; } void Suppressions::dump(std::ostream & out) const { out << " " << std::endl; for (const Suppression &suppression : mSuppressions) { out << " " << std::endl; } out << " " << std::endl; } #include std::list Suppressions::getUnmatchedLocalSuppressions(const std::string &file, const bool unusedFunctionChecking) const { std::list result; for (const Suppression &s : mSuppressions) { if (s.matched) continue; if (!unusedFunctionChecking && s.errorId == "unusedFunction") continue; if (file.empty() || !s.isLocal() || s.fileName != file) continue; result.push_back(s); } return result; } std::list Suppressions::getUnmatchedGlobalSuppressions(const bool unusedFunctionChecking) const { std::list result; for (const Suppression &s : mSuppressions) { if (s.matched) continue; if (!unusedFunctionChecking && s.errorId == "unusedFunction") continue; if (s.isLocal()) continue; result.push_back(s); } return result; }