File indexing completed on 2024-04-28 16:57:52

0001 /*
0002     This file is part of the clazy static checker.
0003 
0004     Copyright (C) 2016 Sergio Martins <smartins@kde.org>
0005 
0006     This library is free software; you can redistribute it and/or
0007     modify it under the terms of the GNU Library General Public
0008     License as published by the Free Software Foundation; either
0009     version 2 of the License, or (at your option) any later version.
0010 
0011     This library is distributed in the hope that it will be useful,
0012     but WITHOUT ANY WARRANTY; without even the implied warranty of
0013     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0014     Library General Public License for more details.
0015 
0016     You should have received a copy of the GNU Library General Public License
0017     along with this library; see the file COPYING.LIB.  If not, write to
0018     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0019     Boston, MA 02110-1301, USA.
0020 */
0021 
0022 #include "SuppressionManager.h"
0023 #include "SourceCompatibilityHelpers.h"
0024 #include "clazy_stl.h"
0025 
0026 #include <clang/Basic/SourceManager.h>
0027 #include <clang/Basic/SourceLocation.h>
0028 #include <clang/Basic/TokenKinds.h>
0029 #include <clang/Lex/Token.h>
0030 #include <llvm/Support/MemoryBuffer.h>
0031 #include <llvm/Support/raw_ostream.h>
0032 
0033 #include <vector>
0034 
0035 using namespace clang;
0036 using namespace std;
0037 
0038 SuppressionManager::SuppressionManager()
0039 {
0040 }
0041 
0042 bool SuppressionManager::isSuppressed(const std::string &checkName, clang::SourceLocation loc,
0043                                       const clang::SourceManager &sm, const clang::LangOptions &lo) const
0044 {
0045     if (loc.isMacroID())
0046         loc = sm.getExpansionLoc(loc);
0047 
0048     FileID fileID = sm.getFileID(loc);
0049     if (fileID.isInvalid())
0050         return false;
0051 
0052     auto it = m_processedFileIDs.find(fileID.getHashValue());
0053     const bool notProcessedYet = (it == m_processedFileIDs.cend());
0054     if (notProcessedYet) {
0055         parseFile(fileID, sm, lo);
0056         it = m_processedFileIDs.find(fileID.getHashValue());
0057     }
0058 
0059     Suppressions &suppressions = (*it).second;
0060 
0061     // Case 1: clazy:skip, the whole file is skipped, regardless of which check
0062     if (suppressions.skipEntireFile)
0063         return true;
0064 
0065     // Case 2: clazy:excludeall=foo, the check foo will be ignored for this file
0066     const bool checkIsSuppressed = suppressions.checksToSkip.find(checkName) != suppressions.checksToSkip.cend();
0067     if (checkIsSuppressed)
0068         return true;
0069 
0070     // Case 3: clazy:exclude=foo, the check foo will be ignored for this file in this line number
0071     if (loc.isInvalid())
0072         return false;
0073 
0074     const int lineNumber = sm.getSpellingLineNumber(loc);
0075     const bool checkIsSuppressedByLine = suppressions.checksToSkipByLine.find(LineAndCheckName(lineNumber, checkName)) != suppressions.checksToSkipByLine.cend();
0076     return checkIsSuppressedByLine;
0077 }
0078 
0079 void SuppressionManager::parseFile(FileID id, const SourceManager &sm, const clang::LangOptions &lo) const
0080 {
0081     const unsigned hash = id.getHashValue();
0082     auto it = m_processedFileIDs.insert({hash, Suppressions()}).first;
0083     Suppressions &suppressions = (*it).second;
0084 
0085     bool invalid = false;
0086     auto buffer = clazy::getBuffer(sm, id, &invalid);
0087     if (invalid) {
0088         llvm::errs() << "SuppressionManager::parseFile: Invalid buffer ";
0089         if (buffer)
0090             llvm::errs() << buffer->getBuffer() << "\n";
0091         return;
0092     }
0093 
0094     auto lexer = GET_LEXER(id, buffer, sm, lo);
0095     lexer.SetCommentRetentionState(true);
0096 
0097     Token token;
0098     while (!lexer.LexFromRawLexer(token)) {
0099         if (token.getKind() == tok::comment) {
0100             std::string comment = Lexer::getSpelling(token, sm, lo);
0101 
0102             if (clazy::contains(comment, "clazy:skip")) {
0103                 suppressions.skipEntireFile = true;
0104                 return;
0105             }
0106 
0107             static regex rx(R"(clazy:excludeall=(.*?)(\s|$))");
0108             smatch match;
0109             if (regex_search(comment, match, rx) && match.size() > 1) {
0110                 vector<string> checks = clazy::splitString(match[1], ',');
0111                 suppressions.checksToSkip.insert(checks.cbegin(), checks.cend());
0112             }
0113 
0114             const int lineNumber = sm.getSpellingLineNumber(token.getLocation());
0115             if (lineNumber < 0) {
0116                 llvm::errs() << "SuppressionManager::parseFile: Invalid line number " << lineNumber << "\n";
0117                 continue;
0118             }
0119 
0120             static regex rx2(R"(clazy:exclude=(.*?)(\s|$))");
0121             if (regex_search(comment, match, rx2) && match.size() > 1) {
0122                 vector<string> checks = clazy::splitString(match[1], ',');
0123 
0124                 for (const string &checkName : checks) {
0125                     suppressions.checksToSkipByLine.insert(LineAndCheckName(lineNumber, checkName));
0126                 }
0127             }
0128         }
0129     }
0130 }