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(Range: S->getAttrs(), P: [](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(Node: 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(LHS: RHS, 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(LHS: Larger.getBegin(), RHS: Smaller.getBegin(), SM) && |
76 | isLessOrEqual(LHS: Smaller.getEnd(), RHS: 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(D: const_cast<Decl *>(D)); |
83 | } |
84 | |
85 | bool VisitDecl(Decl *D) { |
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(Node: D); |
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(Node: 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(R: 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(Elt: 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, DiagnosticIdentification: {}) || |
135 | isSuppressed(Location: UniqueingLocation, DeclWithIssue, DiagnosticIdentification: {}); |
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>(Val: DeclWithIssue->getLexicalDeclContext()); |
159 | if (Parent == nullptr || isa<TranslationUnitDecl>(Val: 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 | KV: std::make_pair(x&: DeclWithIssue, y: CachedRanges{})); |
178 | Ranges &SuppressionRanges = InsertionResult.first->second; |
179 | if (InsertionResult.second) { |
180 | // We haven't checked this declaration for suppressions yet! |
181 | CacheInitializer::initialize(D: DeclWithIssue, ToInit&: SuppressionRanges); |
182 | } |
183 | |
184 | SourceRange BugRange = Location.asRange(); |
185 | const SourceManager &SM = Location.getManager(); |
186 | |
187 | return llvm::any_of(Range&: SuppressionRanges, |
188 | P: [BugRange, &SM](SourceRange Suppression) { |
189 | return fullyContains(Larger: Suppression, Smaller: BugRange, SM); |
190 | }); |
191 | } |
192 | |