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