1ef3f4760SArtem Dergachev //===- BugSuppression.cpp - Suppression interface -------------------------===// 2ef3f4760SArtem Dergachev // 3ef3f4760SArtem Dergachev // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4ef3f4760SArtem Dergachev // See https://llvm.org/LICENSE.txt for license information. 5ef3f4760SArtem Dergachev // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6ef3f4760SArtem Dergachev // 7ef3f4760SArtem Dergachev //===----------------------------------------------------------------------===// 8ef3f4760SArtem Dergachev 9ef3f4760SArtem Dergachev #include "clang/StaticAnalyzer/Core/BugReporter/BugSuppression.h" 10*dde802b1SSirraide #include "clang/AST/DynamicRecursiveASTVisitor.h" 11ef3f4760SArtem Dergachev #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" 12ef3f4760SArtem Dergachev 13ef3f4760SArtem Dergachev using namespace clang; 14ef3f4760SArtem Dergachev using namespace ento; 15ef3f4760SArtem Dergachev 16ef3f4760SArtem Dergachev namespace { 17ef3f4760SArtem Dergachev 18ef3f4760SArtem Dergachev using Ranges = llvm::SmallVectorImpl<SourceRange>; 19ef3f4760SArtem Dergachev 20ef3f4760SArtem Dergachev inline bool hasSuppression(const Decl *D) { 21ef3f4760SArtem Dergachev // FIXME: Implement diagnostic identifier arguments 22ef3f4760SArtem Dergachev // (checker names, "hashtags"). 23ef3f4760SArtem Dergachev if (const auto *Suppression = D->getAttr<SuppressAttr>()) 24ef3f4760SArtem Dergachev return !Suppression->isGSL() && 25ef3f4760SArtem Dergachev (Suppression->diagnosticIdentifiers().empty()); 26ef3f4760SArtem Dergachev return false; 27ef3f4760SArtem Dergachev } 28ef3f4760SArtem Dergachev inline bool hasSuppression(const AttributedStmt *S) { 29ef3f4760SArtem Dergachev // FIXME: Implement diagnostic identifier arguments 30ef3f4760SArtem Dergachev // (checker names, "hashtags"). 31ef3f4760SArtem Dergachev return llvm::any_of(S->getAttrs(), [](const Attr *A) { 32ef3f4760SArtem Dergachev const auto *Suppression = dyn_cast<SuppressAttr>(A); 33ef3f4760SArtem Dergachev return Suppression && !Suppression->isGSL() && 34ef3f4760SArtem Dergachev (Suppression->diagnosticIdentifiers().empty()); 35ef3f4760SArtem Dergachev }); 36ef3f4760SArtem Dergachev } 37ef3f4760SArtem Dergachev 38ef3f4760SArtem Dergachev template <class NodeType> inline SourceRange getRange(const NodeType *Node) { 39ef3f4760SArtem Dergachev return Node->getSourceRange(); 40ef3f4760SArtem Dergachev } 41ef3f4760SArtem Dergachev template <> inline SourceRange getRange(const AttributedStmt *S) { 42ef3f4760SArtem Dergachev // Begin location for attributed statement node seems to be ALWAYS invalid. 43ef3f4760SArtem Dergachev // 44ef3f4760SArtem Dergachev // It is unlikely that we ever report any warnings on suppression 45ef3f4760SArtem Dergachev // attribute itself, but even if we do, we wouldn't want that warning 46ef3f4760SArtem Dergachev // to be suppressed by that same attribute. 47ef3f4760SArtem Dergachev // 48ef3f4760SArtem Dergachev // Long story short, we can use inner statement and it's not going to break 49ef3f4760SArtem Dergachev // anything. 50ef3f4760SArtem Dergachev return getRange(S->getSubStmt()); 51ef3f4760SArtem Dergachev } 52ef3f4760SArtem Dergachev 53ef3f4760SArtem Dergachev inline bool isLessOrEqual(SourceLocation LHS, SourceLocation RHS, 54ef3f4760SArtem Dergachev const SourceManager &SM) { 55ef3f4760SArtem Dergachev // SourceManager::isBeforeInTranslationUnit tests for strict 56ef3f4760SArtem Dergachev // inequality, when we need a non-strict comparison (bug 57ef3f4760SArtem Dergachev // can be reported directly on the annotated note). 58ef3f4760SArtem Dergachev // For this reason, we use the following equivalence: 59ef3f4760SArtem Dergachev // 60ef3f4760SArtem Dergachev // A <= B <==> !(B < A) 61ef3f4760SArtem Dergachev // 62ef3f4760SArtem Dergachev return !SM.isBeforeInTranslationUnit(RHS, LHS); 63ef3f4760SArtem Dergachev } 64ef3f4760SArtem Dergachev 65ef3f4760SArtem Dergachev inline bool fullyContains(SourceRange Larger, SourceRange Smaller, 66ef3f4760SArtem Dergachev const SourceManager &SM) { 67ef3f4760SArtem Dergachev // Essentially this means: 68ef3f4760SArtem Dergachev // 69ef3f4760SArtem Dergachev // Larger.fullyContains(Smaller) 70ef3f4760SArtem Dergachev // 71ef3f4760SArtem Dergachev // However, that method has a very trivial implementation and couldn't 72ef3f4760SArtem Dergachev // compare regular locations and locations from macro expansions. 73ef3f4760SArtem Dergachev // We could've converted everything into regular locations as a solution, 74ef3f4760SArtem Dergachev // but the following solution seems to be the most bulletproof. 75ef3f4760SArtem Dergachev return isLessOrEqual(Larger.getBegin(), Smaller.getBegin(), SM) && 76ef3f4760SArtem Dergachev isLessOrEqual(Smaller.getEnd(), Larger.getEnd(), SM); 77ef3f4760SArtem Dergachev } 78ef3f4760SArtem Dergachev 79*dde802b1SSirraide class CacheInitializer : public DynamicRecursiveASTVisitor { 80ef3f4760SArtem Dergachev public: 81ef3f4760SArtem Dergachev static void initialize(const Decl *D, Ranges &ToInit) { 82ef3f4760SArtem Dergachev CacheInitializer(ToInit).TraverseDecl(const_cast<Decl *>(D)); 83ef3f4760SArtem Dergachev } 84ef3f4760SArtem Dergachev 85*dde802b1SSirraide bool VisitDecl(Decl *D) override { 86ef3f4760SArtem Dergachev // Bug location could be somewhere in the init value of 87ef3f4760SArtem Dergachev // a freshly declared variable. Even though it looks like the 88ef3f4760SArtem Dergachev // user applied attribute to a statement, it will apply to a 89ef3f4760SArtem Dergachev // variable declaration, and this is where we check for it. 90017675ffSArtem Dergachev return VisitAttributedNode(D); 91ef3f4760SArtem Dergachev } 92ef3f4760SArtem Dergachev 93*dde802b1SSirraide bool VisitAttributedStmt(AttributedStmt *AS) override { 94ef3f4760SArtem Dergachev // When we apply attributes to statements, it actually creates 95ef3f4760SArtem Dergachev // a wrapper statement that only contains attributes and the wrapped 96ef3f4760SArtem Dergachev // statement. 97ef3f4760SArtem Dergachev return VisitAttributedNode(AS); 98ef3f4760SArtem Dergachev } 99ef3f4760SArtem Dergachev 100ef3f4760SArtem Dergachev private: 101ef3f4760SArtem Dergachev template <class NodeType> bool VisitAttributedNode(NodeType *Node) { 102ef3f4760SArtem Dergachev if (hasSuppression(Node)) { 103ef3f4760SArtem Dergachev // TODO: In the future, when we come up with good stable IDs for checkers 104ef3f4760SArtem Dergachev // we can return a list of kinds to ignore, or all if no arguments 105ef3f4760SArtem Dergachev // were provided. 106ef3f4760SArtem Dergachev addRange(getRange(Node)); 107ef3f4760SArtem Dergachev } 108ef3f4760SArtem Dergachev // We should keep traversing AST. 109ef3f4760SArtem Dergachev return true; 110ef3f4760SArtem Dergachev } 111ef3f4760SArtem Dergachev 112ef3f4760SArtem Dergachev void addRange(SourceRange R) { 113ef3f4760SArtem Dergachev if (R.isValid()) { 114ef3f4760SArtem Dergachev Result.push_back(R); 115ef3f4760SArtem Dergachev } 116ef3f4760SArtem Dergachev } 117ef3f4760SArtem Dergachev 118ef3f4760SArtem Dergachev CacheInitializer(Ranges &R) : Result(R) {} 119ef3f4760SArtem Dergachev Ranges &Result; 120ef3f4760SArtem Dergachev }; 121ef3f4760SArtem Dergachev 122ef3f4760SArtem Dergachev } // end anonymous namespace 123ef3f4760SArtem Dergachev 124ef3f4760SArtem Dergachev // TODO: Introduce stable IDs for checkers and check for those here 125ef3f4760SArtem Dergachev // to be more specific. Attribute without arguments should still 126ef3f4760SArtem Dergachev // be considered as "suppress all". 127ef3f4760SArtem Dergachev // It is already much finer granularity than what we have now 128ef3f4760SArtem Dergachev // (i.e. removing the whole function from the analysis). 129ef3f4760SArtem Dergachev bool BugSuppression::isSuppressed(const BugReport &R) { 130ef3f4760SArtem Dergachev PathDiagnosticLocation Location = R.getLocation(); 131ef3f4760SArtem Dergachev PathDiagnosticLocation UniqueingLocation = R.getUniqueingLocation(); 132ef3f4760SArtem Dergachev const Decl *DeclWithIssue = R.getDeclWithIssue(); 133ef3f4760SArtem Dergachev 134ef3f4760SArtem Dergachev return isSuppressed(Location, DeclWithIssue, {}) || 135ef3f4760SArtem Dergachev isSuppressed(UniqueingLocation, DeclWithIssue, {}); 136ef3f4760SArtem Dergachev } 137ef3f4760SArtem Dergachev 138ef3f4760SArtem Dergachev bool BugSuppression::isSuppressed(const PathDiagnosticLocation &Location, 139ef3f4760SArtem Dergachev const Decl *DeclWithIssue, 140ef3f4760SArtem Dergachev DiagnosticIdentifierList Hashtags) { 14156e241a0SArtem Dergachev if (!Location.isValid()) 142ef3f4760SArtem Dergachev return false; 143ef3f4760SArtem Dergachev 14456e241a0SArtem Dergachev if (!DeclWithIssue) { 14556e241a0SArtem Dergachev // FIXME: This defeats the purpose of passing DeclWithIssue to begin with. 14656e241a0SArtem Dergachev // If this branch is ever hit, we're re-doing all the work we've already 14756e241a0SArtem Dergachev // done as well as perform a lot of work we'll never need. 14856e241a0SArtem Dergachev // Gladly, none of our on-by-default checkers currently need it. 14956e241a0SArtem Dergachev DeclWithIssue = ACtx.getTranslationUnitDecl(); 150017675ffSArtem Dergachev } else { 151017675ffSArtem Dergachev // This is the fast path. However, we should still consider the topmost 152017675ffSArtem Dergachev // declaration that isn't TranslationUnitDecl, because we should respect 153017675ffSArtem Dergachev // attributes on the entire declaration chain. 154017675ffSArtem Dergachev while (true) { 155017675ffSArtem Dergachev // Use the "lexical" parent. Eg., if the attribute is on a class, suppress 156017675ffSArtem Dergachev // warnings in inline methods but not in out-of-line methods. 157017675ffSArtem Dergachev const Decl *Parent = 158017675ffSArtem Dergachev dyn_cast_or_null<Decl>(DeclWithIssue->getLexicalDeclContext()); 159017675ffSArtem Dergachev if (Parent == nullptr || isa<TranslationUnitDecl>(Parent)) 160017675ffSArtem Dergachev break; 161017675ffSArtem Dergachev 162017675ffSArtem Dergachev DeclWithIssue = Parent; 163017675ffSArtem Dergachev } 16456e241a0SArtem Dergachev } 16556e241a0SArtem Dergachev 166ef3f4760SArtem Dergachev // While some warnings are attached to AST nodes (mostly path-sensitive 167ef3f4760SArtem Dergachev // checks), others are simply associated with a plain source location 168ef3f4760SArtem Dergachev // or range. Figuring out the node based on locations can be tricky, 169ef3f4760SArtem Dergachev // so instead, we traverse the whole body of the declaration and gather 170ef3f4760SArtem Dergachev // information on ALL suppressions. After that we can simply check if 171ef3f4760SArtem Dergachev // any of those suppressions affect the warning in question. 172ef3f4760SArtem Dergachev // 173ef3f4760SArtem Dergachev // Traversing AST of a function is not a heavy operation, but for 174ef3f4760SArtem Dergachev // large functions with a lot of bugs it can make a dent in performance. 175ef3f4760SArtem Dergachev // In order to avoid this scenario, we cache traversal results. 176ef3f4760SArtem Dergachev auto InsertionResult = CachedSuppressionLocations.insert( 177ef3f4760SArtem Dergachev std::make_pair(DeclWithIssue, CachedRanges{})); 178ef3f4760SArtem Dergachev Ranges &SuppressionRanges = InsertionResult.first->second; 179ef3f4760SArtem Dergachev if (InsertionResult.second) { 180ef3f4760SArtem Dergachev // We haven't checked this declaration for suppressions yet! 181ef3f4760SArtem Dergachev CacheInitializer::initialize(DeclWithIssue, SuppressionRanges); 182ef3f4760SArtem Dergachev } 183ef3f4760SArtem Dergachev 184ef3f4760SArtem Dergachev SourceRange BugRange = Location.asRange(); 185ef3f4760SArtem Dergachev const SourceManager &SM = Location.getManager(); 186ef3f4760SArtem Dergachev 187ef3f4760SArtem Dergachev return llvm::any_of(SuppressionRanges, 188ef3f4760SArtem Dergachev [BugRange, &SM](SourceRange Suppression) { 189ef3f4760SArtem Dergachev return fullyContains(Suppression, BugRange, SM); 190ef3f4760SArtem Dergachev }); 191ef3f4760SArtem Dergachev } 192