File indexing completed on 2024-04-14 05:32:08

0001 /*
0002     SPDX-FileCopyrightText: 2016 Sergio Martins <smartins@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "SuppressionManager.h"
0008 #include "SourceCompatibilityHelpers.h"
0009 #include "clazy_stl.h"
0010 
0011 #include <clang/Basic/SourceLocation.h>
0012 #include <clang/Basic/SourceManager.h>
0013 #include <clang/Basic/TokenKinds.h>
0014 #include <clang/Lex/Token.h>
0015 #include <llvm/Support/MemoryBuffer.h>
0016 #include <llvm/Support/raw_ostream.h>
0017 
0018 #include <regex>
0019 #include <vector>
0020 
0021 using namespace clang;
0022 
0023 SuppressionManager::SuppressionManager()
0024 {
0025 }
0026 
0027 bool SuppressionManager::isSuppressed(const std::string &checkName,
0028                                       clang::SourceLocation loc,
0029                                       const clang::SourceManager &sm,
0030                                       const clang::LangOptions &lo) const
0031 {
0032     if (loc.isMacroID()) {
0033         loc = sm.getExpansionLoc(loc);
0034     }
0035 
0036     FileID fileID = sm.getFileID(loc);
0037     if (fileID.isInvalid()) {
0038         return false;
0039     }
0040 
0041     auto it = m_processedFileIDs.find(fileID.getHashValue());
0042     const bool notProcessedYet = (it == m_processedFileIDs.cend());
0043     if (notProcessedYet) {
0044         parseFile(fileID, sm, lo);
0045         it = m_processedFileIDs.find(fileID.getHashValue());
0046     }
0047 
0048     Suppressions &suppressions = (*it).second;
0049 
0050     // Case 1: clazy:skip, the whole file is skipped, regardless of which check
0051     if (suppressions.skipEntireFile) {
0052         return true;
0053     }
0054 
0055     // Case 2: clazy:excludeall=foo, the check foo will be ignored for this file
0056     const bool checkIsSuppressed = suppressions.checksToSkip.find(checkName) != suppressions.checksToSkip.cend();
0057     if (checkIsSuppressed) {
0058         return true;
0059     }
0060 
0061     // Case 3: clazy:exclude=foo, the check foo will be ignored for this file in this line number
0062     if (loc.isInvalid()) {
0063         return false;
0064     }
0065 
0066     const int lineNumber = sm.getSpellingLineNumber(loc);
0067     if (suppressions.skipNextLine.count(lineNumber) > 0) {
0068         suppressions.skipNextLine.erase(lineNumber);
0069         return true;
0070     }
0071     if (suppressions.checksToSkipByLine.find(LineAndCheckName(lineNumber, checkName)) != suppressions.checksToSkipByLine.cend())
0072         return true;
0073 
0074     return false;
0075 }
0076 
0077 void SuppressionManager::parseFile(FileID id, const SourceManager &sm, const clang::LangOptions &lo) const
0078 {
0079     const unsigned hash = id.getHashValue();
0080     auto it = m_processedFileIDs.insert({hash, Suppressions()}).first;
0081     Suppressions &suppressions = (*it).second;
0082 
0083     bool invalid = false;
0084     auto buffer = clazy::getBuffer(sm, id, &invalid);
0085     if (invalid) {
0086         llvm::errs() << "SuppressionManager::parseFile: Invalid buffer ";
0087         if (buffer) {
0088             llvm::errs() << buffer->getBuffer() << "\n";
0089         }
0090         return;
0091     }
0092 
0093     auto lexer = GET_LEXER(id, buffer, sm, lo);
0094     lexer.SetCommentRetentionState(true);
0095 
0096     Token token;
0097     while (!lexer.LexFromRawLexer(token)) {
0098         if (token.getKind() == tok::comment) {
0099             std::string comment = Lexer::getSpelling(token, sm, lo);
0100 
0101             if (clazy::contains(comment, "clazy:skip")) {
0102                 suppressions.skipEntireFile = true;
0103                 return;
0104             }
0105 
0106             if (clazy::contains(comment, "NOLINTNEXTLINE")) {
0107                 bool invalid = false;
0108                 const int nextLineNumber = sm.getSpellingLineNumber(token.getLocation(), &invalid) + 1;
0109                 if (invalid) {
0110                     llvm::errs() << "SuppressionManager::parseFile: Invalid line number for token location where NOLINTNEXTLINE was found\n";
0111                     continue;
0112                 }
0113 
0114                 suppressions.skipNextLine.insert(nextLineNumber);
0115             }
0116 
0117             static std::regex rx(R"(clazy:excludeall=(.*?)(\s|$))");
0118             std::smatch match;
0119             if (regex_search(comment, match, rx) && match.size() > 1) {
0120                 std::vector<std::string> checks = clazy::splitString(match[1], ',');
0121                 suppressions.checksToSkip.insert(checks.cbegin(), checks.cend());
0122             }
0123 
0124             const int lineNumber = sm.getSpellingLineNumber(token.getLocation());
0125             if (lineNumber < 0) {
0126                 llvm::errs() << "SuppressionManager::parseFile: Invalid line number " << lineNumber << "\n";
0127                 continue;
0128             }
0129 
0130             static std::regex rx2(R"(clazy:exclude=(.*?)(\s|$))");
0131             if (regex_search(comment, match, rx2) && match.size() > 1) {
0132                 std::vector<std::string> checks = clazy::splitString(match[1], ',');
0133 
0134                 for (const std::string &checkName : checks) {
0135                     suppressions.checksToSkipByLine.insert(LineAndCheckName(lineNumber, checkName));
0136                 }
0137             }
0138         }
0139     }
0140 }