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/ADT/STLExtras.h"
13#include "llvm/Support/FormatVariadic.h"
14#include "llvm/Support/TimeProfiler.h"
15
16using namespace clang;
17using namespace ento;
18
19namespace {
20
21using Ranges = llvm::SmallVectorImpl<SourceRange>;
22
23inline bool hasSuppression(const Decl *D) {
24 // FIXME: Implement diagnostic identifier arguments
25 // (checker names, "hashtags").
26 if (const auto *Suppression = D->getAttr<SuppressAttr>())
27 return !Suppression->isGSL() &&
28 (Suppression->diagnosticIdentifiers().empty());
29 return false;
30}
31inline bool hasSuppression(const AttributedStmt *S) {
32 // FIXME: Implement diagnostic identifier arguments
33 // (checker names, "hashtags").
34 return llvm::any_of(Range: S->getAttrs(), P: [](const Attr *A) {
35 const auto *Suppression = dyn_cast<SuppressAttr>(Val: A);
36 return Suppression && !Suppression->isGSL() &&
37 (Suppression->diagnosticIdentifiers().empty());
38 });
39}
40
41template <class NodeType> inline SourceRange getRange(const NodeType *Node) {
42 return Node->getSourceRange();
43}
44template <> inline SourceRange getRange(const AttributedStmt *S) {
45 // Begin location for attributed statement node seems to be ALWAYS invalid.
46 //
47 // It is unlikely that we ever report any warnings on suppression
48 // attribute itself, but even if we do, we wouldn't want that warning
49 // to be suppressed by that same attribute.
50 //
51 // Long story short, we can use inner statement and it's not going to break
52 // anything.
53 return getRange(Node: S->getSubStmt());
54}
55
56inline bool isLessOrEqual(SourceLocation LHS, SourceLocation RHS,
57 const SourceManager &SM) {
58 // SourceManager::isBeforeInTranslationUnit tests for strict
59 // inequality, when we need a non-strict comparison (bug
60 // can be reported directly on the annotated note).
61 // For this reason, we use the following equivalence:
62 //
63 // A <= B <==> !(B < A)
64 //
65 return !SM.isBeforeInTranslationUnit(LHS: RHS, RHS: LHS);
66}
67
68inline bool fullyContains(SourceRange Larger, SourceRange Smaller,
69 const SourceManager &SM) {
70 // Essentially this means:
71 //
72 // Larger.fullyContains(Smaller)
73 //
74 // However, that method has a very trivial implementation and couldn't
75 // compare regular locations and locations from macro expansions.
76 // We could've converted everything into regular locations as a solution,
77 // but the following solution seems to be the most bulletproof.
78 return isLessOrEqual(LHS: Larger.getBegin(), RHS: Smaller.getBegin(), SM) &&
79 isLessOrEqual(LHS: Smaller.getEnd(), RHS: Larger.getEnd(), SM);
80}
81
82class CacheInitializer : public DynamicRecursiveASTVisitor {
83public:
84 static void initialize(const Decl *D, Ranges &ToInit) {
85 CacheInitializer(ToInit).TraverseDecl(D: const_cast<Decl *>(D));
86 }
87
88 bool VisitDecl(Decl *D) override {
89 // Bug location could be somewhere in the init value of
90 // a freshly declared variable. Even though it looks like the
91 // user applied attribute to a statement, it will apply to a
92 // variable declaration, and this is where we check for it.
93 return VisitAttributedNode(Node: D);
94 }
95
96 bool VisitAttributedStmt(AttributedStmt *AS) override {
97 // When we apply attributes to statements, it actually creates
98 // a wrapper statement that only contains attributes and the wrapped
99 // statement.
100 return VisitAttributedNode(Node: AS);
101 }
102
103private:
104 template <class NodeType> bool VisitAttributedNode(NodeType *Node) {
105 if (hasSuppression(Node)) {
106 // TODO: In the future, when we come up with good stable IDs for checkers
107 // we can return a list of kinds to ignore, or all if no arguments
108 // were provided.
109 addRange(R: getRange(Node));
110 }
111 // We should keep traversing AST.
112 return true;
113 }
114
115 void addRange(SourceRange R) {
116 if (R.isValid()) {
117 Result.push_back(Elt: R);
118 }
119 }
120
121 CacheInitializer(Ranges &R) : Result(R) {
122 ShouldVisitTemplateInstantiations = true;
123 ShouldWalkTypesOfTypeLocs = false;
124 ShouldVisitImplicitCode = false;
125 ShouldVisitLambdaBody = true;
126 }
127 Ranges &Result;
128};
129
130std::string timeScopeName(const Decl *DeclWithIssue) {
131 if (!llvm::timeTraceProfilerEnabled())
132 return "";
133 return llvm::formatv(
134 Fmt: "BugSuppression::isSuppressed init suppressions cache for {0}",
135 Vals: DeclWithIssue->getDeclKindName())
136 .str();
137}
138
139llvm::TimeTraceMetadata getDeclTimeTraceMetadata(const Decl *DeclWithIssue) {
140 assert(DeclWithIssue);
141 assert(llvm::timeTraceProfilerEnabled());
142 std::string Name = "<noname>";
143 if (const auto *ND = dyn_cast<NamedDecl>(Val: DeclWithIssue)) {
144 Name = ND->getNameAsString();
145 }
146 const auto &SM = DeclWithIssue->getASTContext().getSourceManager();
147 auto Line = SM.getPresumedLineNumber(Loc: DeclWithIssue->getBeginLoc());
148 auto Fname = SM.getFilename(SpellingLoc: DeclWithIssue->getBeginLoc());
149 return llvm::TimeTraceMetadata{.Detail: std::move(Name), .File: Fname.str(),
150 .Line: static_cast<int>(Line)};
151}
152
153} // end anonymous namespace
154
155// TODO: Introduce stable IDs for checkers and check for those here
156// to be more specific. Attribute without arguments should still
157// be considered as "suppress all".
158// It is already much finer granularity than what we have now
159// (i.e. removing the whole function from the analysis).
160bool BugSuppression::isSuppressed(const BugReport &R) {
161 PathDiagnosticLocation Location = R.getLocation();
162 PathDiagnosticLocation UniqueingLocation = R.getUniqueingLocation();
163 const Decl *DeclWithIssue = R.getDeclWithIssue();
164
165 return isSuppressed(Location, DeclWithIssue, DiagnosticIdentification: {}) ||
166 isSuppressed(Location: UniqueingLocation, DeclWithIssue, DiagnosticIdentification: {});
167}
168
169static const ClassTemplateDecl *
170walkInstantiatedFromChain(const ClassTemplateDecl *Tmpl) {
171 // For nested member templates (e.g., S2 inside S1<T>), getInstantiatedFrom
172 // may return the member template as instantiated within an outer
173 // specialization (e.g., S2 as it appears in S1<int>). That instantiated
174 // member template has no definition redeclaration itself; we need to walk
175 // up the member template chain to reach the primary template definition.
176 // \code
177 // template <class> struct S1 {
178 // template <class> struct S2 {
179 // int i;
180 // template <class T> int m(const S2<T>& s2) {
181 // return s2.i;
182 // }
183 // };
184 // }
185 // /code
186 const ClassTemplateDecl *MemberTmpl;
187 while ((MemberTmpl = Tmpl->getInstantiatedFromMemberTemplate())) {
188 if (Tmpl->isMemberSpecialization())
189 break;
190 Tmpl = MemberTmpl;
191 }
192 return Tmpl;
193}
194
195static const ClassTemplatePartialSpecializationDecl *walkInstantiatedFromChain(
196 const ClassTemplatePartialSpecializationDecl *PartialSpec) {
197 const ClassTemplatePartialSpecializationDecl *MemberPS;
198 while ((MemberPS = PartialSpec->getInstantiatedFromMember())) {
199 if (PartialSpec->isMemberSpecialization())
200 break;
201 PartialSpec = MemberPS;
202 }
203 return PartialSpec;
204}
205
206template <class T> static const T *chooseDefinitionRedecl(const T *Tmpl) {
207 static_assert(llvm::is_one_of<T, ClassTemplateDecl,
208 ClassTemplatePartialSpecializationDecl>::value);
209 for (const auto *Redecl : Tmpl->redecls()) {
210 if (const T *D = cast<T>(Redecl); D->isThisDeclarationADefinition()) {
211 return D;
212 }
213 }
214 assert(false && "This template must have a redecl that is a definition");
215 return Tmpl;
216}
217
218// For template specializations, returns the primary template definition or
219// partial specialization that was used to instantiate the specialization.
220// This ensures suppression attributes on templates apply to their
221// specializations.
222//
223// For example, given:
224// template <typename T> class [[clang::suppress]] Wrapper { ... };
225// Wrapper<int> w; // instantiates ClassTemplateSpecializationDecl
226//
227// When analyzing code in Wrapper<int>, this function maps the specialization
228// back to the primary template definition, allowing us to find the suppression
229// attribute.
230//
231// The function handles specializations (and partial specializations) of
232// class templates. For any other decl, it returns the input unchagned.
233static const Decl *
234preferTemplateDefinitionForTemplateSpecializations(const Decl *D) {
235 const auto *SpecializationDecl = dyn_cast<ClassTemplateSpecializationDecl>(Val: D);
236 if (!SpecializationDecl)
237 return D;
238
239 auto InstantiatedFrom = SpecializationDecl->getInstantiatedFrom();
240 if (!InstantiatedFrom)
241 return D;
242
243 if (const auto *Tmpl = InstantiatedFrom.dyn_cast<ClassTemplateDecl *>()) {
244 // Interestingly, the source template might be a forward declaration, so we
245 // need to find the definition redeclaration.
246 return chooseDefinitionRedecl(Tmpl: walkInstantiatedFromChain(Tmpl));
247 }
248 return chooseDefinitionRedecl(Tmpl: walkInstantiatedFromChain(
249 PartialSpec: cast<ClassTemplatePartialSpecializationDecl *>(Val&: InstantiatedFrom)));
250}
251
252bool BugSuppression::isSuppressed(const PathDiagnosticLocation &Location,
253 const Decl *DeclWithIssue,
254 DiagnosticIdentifierList Hashtags) {
255 if (!Location.isValid())
256 return false;
257
258 if (!DeclWithIssue) {
259 // FIXME: This defeats the purpose of passing DeclWithIssue to begin with.
260 // If this branch is ever hit, we're re-doing all the work we've already
261 // done as well as perform a lot of work we'll never need.
262 // Gladly, none of our on-by-default checkers currently need it.
263 DeclWithIssue = ACtx.getTranslationUnitDecl();
264 } else {
265 // This is the fast path. However, we should still consider the topmost
266 // declaration that isn't TranslationUnitDecl, because we should respect
267 // attributes on the entire declaration chain.
268 while (true) {
269
270 // Template specializations (e.g., Wrapper<int>) should inherit
271 // suppression attributes from their primary template or partial
272 // specialization. Transform specializations to their template definitions
273 // before checking for suppressions or walking up the lexical parent
274 // chain.
275 // Simply taking the lexical parent of template specializations might land
276 // us in a completely different namespace.
277 DeclWithIssue =
278 preferTemplateDefinitionForTemplateSpecializations(D: DeclWithIssue);
279
280 // Use the "lexical" parent. Eg., if the attribute is on a class, suppress
281 // warnings in inline methods but not in out-of-line methods.
282 const Decl *Parent =
283 dyn_cast_or_null<Decl>(Val: DeclWithIssue->getLexicalDeclContext());
284 if (Parent == nullptr || isa<TranslationUnitDecl>(Val: Parent))
285 break;
286
287 DeclWithIssue = Parent;
288 }
289 }
290
291 // While some warnings are attached to AST nodes (mostly path-sensitive
292 // checks), others are simply associated with a plain source location
293 // or range. Figuring out the node based on locations can be tricky,
294 // so instead, we traverse the whole body of the declaration and gather
295 // information on ALL suppressions. After that we can simply check if
296 // any of those suppressions affect the warning in question.
297 //
298 // Traversing AST of a function is not a heavy operation, but for
299 // large functions with a lot of bugs it can make a dent in performance.
300 // In order to avoid this scenario, we cache traversal results.
301 auto InsertionResult = CachedSuppressionLocations.insert(
302 KV: std::make_pair(x&: DeclWithIssue, y: CachedRanges{}));
303 Ranges &SuppressionRanges = InsertionResult.first->second;
304 if (InsertionResult.second) {
305 llvm::TimeTraceScope TimeScope(
306 timeScopeName(DeclWithIssue),
307 [DeclWithIssue]() { return getDeclTimeTraceMetadata(DeclWithIssue); });
308 // We haven't checked this declaration for suppressions yet!
309 CacheInitializer::initialize(D: DeclWithIssue, ToInit&: SuppressionRanges);
310 }
311
312 SourceRange BugRange = Location.asRange();
313 const SourceManager &SM = Location.getManager();
314
315 return llvm::any_of(Range&: SuppressionRanges,
316 P: [BugRange, &SM](SourceRange Suppression) {
317 return fullyContains(Larger: Suppression, Smaller: BugRange, SM);
318 });
319}
320