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/DynamicRecursiveASTVisitor.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 DynamicRecursiveASTVisitor { 80 public: 81 static void initialize(const Decl *D, Ranges &ToInit) { 82 CacheInitializer(ToInit).TraverseDecl(const_cast<Decl *>(D)); 83 } 84 85 bool VisitDecl(Decl *D) override { 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(D); 91 } 92 93 bool VisitAttributedStmt(AttributedStmt *AS) override { 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()) 142 return false; 143 144 if (!DeclWithIssue) { 145 // FIXME: This defeats the purpose of passing DeclWithIssue to begin with. 146 // If this branch is ever hit, we're re-doing all the work we've already 147 // done as well as perform a lot of work we'll never need. 148 // Gladly, none of our on-by-default checkers currently need it. 149 DeclWithIssue = ACtx.getTranslationUnitDecl(); 150 } else { 151 // This is the fast path. However, we should still consider the topmost 152 // declaration that isn't TranslationUnitDecl, because we should respect 153 // attributes on the entire declaration chain. 154 while (true) { 155 // Use the "lexical" parent. Eg., if the attribute is on a class, suppress 156 // warnings in inline methods but not in out-of-line methods. 157 const Decl *Parent = 158 dyn_cast_or_null<Decl>(DeclWithIssue->getLexicalDeclContext()); 159 if (Parent == nullptr || isa<TranslationUnitDecl>(Parent)) 160 break; 161 162 DeclWithIssue = Parent; 163 } 164 } 165 166 // While some warnings are attached to AST nodes (mostly path-sensitive 167 // checks), others are simply associated with a plain source location 168 // or range. Figuring out the node based on locations can be tricky, 169 // so instead, we traverse the whole body of the declaration and gather 170 // information on ALL suppressions. After that we can simply check if 171 // any of those suppressions affect the warning in question. 172 // 173 // Traversing AST of a function is not a heavy operation, but for 174 // large functions with a lot of bugs it can make a dent in performance. 175 // In order to avoid this scenario, we cache traversal results. 176 auto InsertionResult = CachedSuppressionLocations.insert( 177 std::make_pair(DeclWithIssue, CachedRanges{})); 178 Ranges &SuppressionRanges = InsertionResult.first->second; 179 if (InsertionResult.second) { 180 // We haven't checked this declaration for suppressions yet! 181 CacheInitializer::initialize(DeclWithIssue, SuppressionRanges); 182 } 183 184 SourceRange BugRange = Location.asRange(); 185 const SourceManager &SM = Location.getManager(); 186 187 return llvm::any_of(SuppressionRanges, 188 [BugRange, &SM](SourceRange Suppression) { 189 return fullyContains(Suppression, BugRange, SM); 190 }); 191 } 192