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 | #include "llvm/Support/FormatVariadic.h" |
13 | #include "llvm/Support/TimeProfiler.h" |
14 | |
15 | using namespace clang; |
16 | using namespace ento; |
17 | |
18 | namespace { |
19 | |
20 | using Ranges = llvm::SmallVectorImpl<SourceRange>; |
21 | |
22 | inline bool hasSuppression(const Decl *D) { |
23 | // FIXME: Implement diagnostic identifier arguments |
24 | // (checker names, "hashtags"). |
25 | if (const auto *Suppression = D->getAttr<SuppressAttr>()) |
26 | return !Suppression->isGSL() && |
27 | (Suppression->diagnosticIdentifiers().empty()); |
28 | return false; |
29 | } |
30 | inline bool hasSuppression(const AttributedStmt *S) { |
31 | // FIXME: Implement diagnostic identifier arguments |
32 | // (checker names, "hashtags"). |
33 | return llvm::any_of(Range: S->getAttrs(), P: [](const Attr *A) { |
34 | const auto *Suppression = dyn_cast<SuppressAttr>(A); |
35 | return Suppression && !Suppression->isGSL() && |
36 | (Suppression->diagnosticIdentifiers().empty()); |
37 | }); |
38 | } |
39 | |
40 | template <class NodeType> inline SourceRange getRange(const NodeType *Node) { |
41 | return Node->getSourceRange(); |
42 | } |
43 | template <> inline SourceRange getRange(const AttributedStmt *S) { |
44 | // Begin location for attributed statement node seems to be ALWAYS invalid. |
45 | // |
46 | // It is unlikely that we ever report any warnings on suppression |
47 | // attribute itself, but even if we do, we wouldn't want that warning |
48 | // to be suppressed by that same attribute. |
49 | // |
50 | // Long story short, we can use inner statement and it's not going to break |
51 | // anything. |
52 | return getRange(Node: S->getSubStmt()); |
53 | } |
54 | |
55 | inline bool isLessOrEqual(SourceLocation LHS, SourceLocation RHS, |
56 | const SourceManager &SM) { |
57 | // SourceManager::isBeforeInTranslationUnit tests for strict |
58 | // inequality, when we need a non-strict comparison (bug |
59 | // can be reported directly on the annotated note). |
60 | // For this reason, we use the following equivalence: |
61 | // |
62 | // A <= B <==> !(B < A) |
63 | // |
64 | return !SM.isBeforeInTranslationUnit(LHS: RHS, RHS: LHS); |
65 | } |
66 | |
67 | inline bool fullyContains(SourceRange Larger, SourceRange Smaller, |
68 | const SourceManager &SM) { |
69 | // Essentially this means: |
70 | // |
71 | // Larger.fullyContains(Smaller) |
72 | // |
73 | // However, that method has a very trivial implementation and couldn't |
74 | // compare regular locations and locations from macro expansions. |
75 | // We could've converted everything into regular locations as a solution, |
76 | // but the following solution seems to be the most bulletproof. |
77 | return isLessOrEqual(LHS: Larger.getBegin(), RHS: Smaller.getBegin(), SM) && |
78 | isLessOrEqual(LHS: Smaller.getEnd(), RHS: Larger.getEnd(), SM); |
79 | } |
80 | |
81 | class CacheInitializer : public DynamicRecursiveASTVisitor { |
82 | public: |
83 | static void initialize(const Decl *D, Ranges &ToInit) { |
84 | CacheInitializer(ToInit).TraverseDecl(const_cast<Decl *>(D)); |
85 | } |
86 | |
87 | bool VisitDecl(Decl *D) override { |
88 | // Bug location could be somewhere in the init value of |
89 | // a freshly declared variable. Even though it looks like the |
90 | // user applied attribute to a statement, it will apply to a |
91 | // variable declaration, and this is where we check for it. |
92 | return VisitAttributedNode(Node: D); |
93 | } |
94 | |
95 | bool VisitAttributedStmt(AttributedStmt *AS) override { |
96 | // When we apply attributes to statements, it actually creates |
97 | // a wrapper statement that only contains attributes and the wrapped |
98 | // statement. |
99 | return VisitAttributedNode(Node: AS); |
100 | } |
101 | |
102 | private: |
103 | template <class NodeType> bool VisitAttributedNode(NodeType *Node) { |
104 | if (hasSuppression(Node)) { |
105 | // TODO: In the future, when we come up with good stable IDs for checkers |
106 | // we can return a list of kinds to ignore, or all if no arguments |
107 | // were provided. |
108 | addRange(R: getRange(Node)); |
109 | } |
110 | // We should keep traversing AST. |
111 | return true; |
112 | } |
113 | |
114 | void addRange(SourceRange R) { |
115 | if (R.isValid()) { |
116 | Result.push_back(Elt: R); |
117 | } |
118 | } |
119 | |
120 | CacheInitializer(Ranges &R) : Result(R) {} |
121 | Ranges &Result; |
122 | }; |
123 | |
124 | std::string timeScopeName(const Decl *DeclWithIssue) { |
125 | if (!llvm::timeTraceProfilerEnabled()) |
126 | return "" ; |
127 | return llvm::formatv( |
128 | Fmt: "BugSuppression::isSuppressed init suppressions cache for {0}" , |
129 | Vals: DeclWithIssue->getDeclKindName()) |
130 | .str(); |
131 | } |
132 | |
133 | llvm::TimeTraceMetadata getDeclTimeTraceMetadata(const Decl *DeclWithIssue) { |
134 | assert(DeclWithIssue); |
135 | assert(llvm::timeTraceProfilerEnabled()); |
136 | std::string Name = "<noname>" ; |
137 | if (const auto *ND = dyn_cast<NamedDecl>(Val: DeclWithIssue)) { |
138 | Name = ND->getNameAsString(); |
139 | } |
140 | const auto &SM = DeclWithIssue->getASTContext().getSourceManager(); |
141 | auto Line = SM.getPresumedLineNumber(Loc: DeclWithIssue->getBeginLoc()); |
142 | auto Fname = SM.getFilename(SpellingLoc: DeclWithIssue->getBeginLoc()); |
143 | return llvm::TimeTraceMetadata{.Detail: std::move(Name), .File: Fname.str(), |
144 | .Line: static_cast<int>(Line)}; |
145 | } |
146 | |
147 | } // end anonymous namespace |
148 | |
149 | // TODO: Introduce stable IDs for checkers and check for those here |
150 | // to be more specific. Attribute without arguments should still |
151 | // be considered as "suppress all". |
152 | // It is already much finer granularity than what we have now |
153 | // (i.e. removing the whole function from the analysis). |
154 | bool BugSuppression::isSuppressed(const BugReport &R) { |
155 | PathDiagnosticLocation Location = R.getLocation(); |
156 | PathDiagnosticLocation UniqueingLocation = R.getUniqueingLocation(); |
157 | const Decl *DeclWithIssue = R.getDeclWithIssue(); |
158 | |
159 | return isSuppressed(Location, DeclWithIssue, DiagnosticIdentification: {}) || |
160 | isSuppressed(Location: UniqueingLocation, DeclWithIssue, DiagnosticIdentification: {}); |
161 | } |
162 | |
163 | bool BugSuppression::isSuppressed(const PathDiagnosticLocation &Location, |
164 | const Decl *DeclWithIssue, |
165 | DiagnosticIdentifierList Hashtags) { |
166 | if (!Location.isValid()) |
167 | return false; |
168 | |
169 | if (!DeclWithIssue) { |
170 | // FIXME: This defeats the purpose of passing DeclWithIssue to begin with. |
171 | // If this branch is ever hit, we're re-doing all the work we've already |
172 | // done as well as perform a lot of work we'll never need. |
173 | // Gladly, none of our on-by-default checkers currently need it. |
174 | DeclWithIssue = ACtx.getTranslationUnitDecl(); |
175 | } else { |
176 | // This is the fast path. However, we should still consider the topmost |
177 | // declaration that isn't TranslationUnitDecl, because we should respect |
178 | // attributes on the entire declaration chain. |
179 | while (true) { |
180 | // Use the "lexical" parent. Eg., if the attribute is on a class, suppress |
181 | // warnings in inline methods but not in out-of-line methods. |
182 | const Decl *Parent = |
183 | dyn_cast_or_null<Decl>(Val: DeclWithIssue->getLexicalDeclContext()); |
184 | if (Parent == nullptr || isa<TranslationUnitDecl>(Val: Parent)) |
185 | break; |
186 | |
187 | DeclWithIssue = Parent; |
188 | } |
189 | } |
190 | |
191 | // While some warnings are attached to AST nodes (mostly path-sensitive |
192 | // checks), others are simply associated with a plain source location |
193 | // or range. Figuring out the node based on locations can be tricky, |
194 | // so instead, we traverse the whole body of the declaration and gather |
195 | // information on ALL suppressions. After that we can simply check if |
196 | // any of those suppressions affect the warning in question. |
197 | // |
198 | // Traversing AST of a function is not a heavy operation, but for |
199 | // large functions with a lot of bugs it can make a dent in performance. |
200 | // In order to avoid this scenario, we cache traversal results. |
201 | auto InsertionResult = CachedSuppressionLocations.insert( |
202 | KV: std::make_pair(x&: DeclWithIssue, y: CachedRanges{})); |
203 | Ranges &SuppressionRanges = InsertionResult.first->second; |
204 | if (InsertionResult.second) { |
205 | llvm::TimeTraceScope TimeScope( |
206 | timeScopeName(DeclWithIssue), |
207 | [DeclWithIssue]() { return getDeclTimeTraceMetadata(DeclWithIssue); }); |
208 | // We haven't checked this declaration for suppressions yet! |
209 | CacheInitializer::initialize(D: DeclWithIssue, ToInit&: SuppressionRanges); |
210 | } |
211 | |
212 | SourceRange BugRange = Location.asRange(); |
213 | const SourceManager &SM = Location.getManager(); |
214 | |
215 | return llvm::any_of(Range&: SuppressionRanges, |
216 | P: [BugRange, &SM](SourceRange Suppression) { |
217 | return fullyContains(Larger: Suppression, Smaller: BugRange, SM); |
218 | }); |
219 | } |
220 | |