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
15using namespace clang;
16using namespace ento;
17
18namespace {
19
20using Ranges = llvm::SmallVectorImpl<SourceRange>;
21
22inline 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}
30inline 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>(Val: A);
35 return Suppression && !Suppression->isGSL() &&
36 (Suppression->diagnosticIdentifiers().empty());
37 });
38}
39
40template <class NodeType> inline SourceRange getRange(const NodeType *Node) {
41 return Node->getSourceRange();
42}
43template <> 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
55inline 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
67inline 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
81class CacheInitializer : public DynamicRecursiveASTVisitor {
82public:
83 static void initialize(const Decl *D, Ranges &ToInit) {
84 CacheInitializer(ToInit).TraverseDecl(D: 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
102private:
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 ShouldVisitTemplateInstantiations = true;
122 ShouldWalkTypesOfTypeLocs = false;
123 ShouldVisitImplicitCode = false;
124 ShouldVisitLambdaBody = true;
125 }
126 Ranges &Result;
127};
128
129std::string timeScopeName(const Decl *DeclWithIssue) {
130 if (!llvm::timeTraceProfilerEnabled())
131 return "";
132 return llvm::formatv(
133 Fmt: "BugSuppression::isSuppressed init suppressions cache for {0}",
134 Vals: DeclWithIssue->getDeclKindName())
135 .str();
136}
137
138llvm::TimeTraceMetadata getDeclTimeTraceMetadata(const Decl *DeclWithIssue) {
139 assert(DeclWithIssue);
140 assert(llvm::timeTraceProfilerEnabled());
141 std::string Name = "<noname>";
142 if (const auto *ND = dyn_cast<NamedDecl>(Val: DeclWithIssue)) {
143 Name = ND->getNameAsString();
144 }
145 const auto &SM = DeclWithIssue->getASTContext().getSourceManager();
146 auto Line = SM.getPresumedLineNumber(Loc: DeclWithIssue->getBeginLoc());
147 auto Fname = SM.getFilename(SpellingLoc: DeclWithIssue->getBeginLoc());
148 return llvm::TimeTraceMetadata{.Detail: std::move(Name), .File: Fname.str(),
149 .Line: static_cast<int>(Line)};
150}
151
152} // end anonymous namespace
153
154// TODO: Introduce stable IDs for checkers and check for those here
155// to be more specific. Attribute without arguments should still
156// be considered as "suppress all".
157// It is already much finer granularity than what we have now
158// (i.e. removing the whole function from the analysis).
159bool BugSuppression::isSuppressed(const BugReport &R) {
160 PathDiagnosticLocation Location = R.getLocation();
161 PathDiagnosticLocation UniqueingLocation = R.getUniqueingLocation();
162 const Decl *DeclWithIssue = R.getDeclWithIssue();
163
164 return isSuppressed(Location, DeclWithIssue, DiagnosticIdentification: {}) ||
165 isSuppressed(Location: UniqueingLocation, DeclWithIssue, DiagnosticIdentification: {});
166}
167
168// For template specializations, returns the primary template definition or
169// partial specialization that was used to instantiate the specialization.
170// This ensures suppression attributes on templates apply to their
171// specializations.
172//
173// For example, given:
174// template <typename T> class [[clang::suppress]] Wrapper { ... };
175// Wrapper<int> w; // instantiates ClassTemplateSpecializationDecl
176//
177// When analyzing code in Wrapper<int>, this function maps the specialization
178// back to the primary template definition, allowing us to find the suppression
179// attribute.
180//
181// The function handles two cases:
182// 1. Instantiation from a class template - searches redeclarations to find
183// the definition (not just a forward declaration).
184// 2. Instantiation from a partial specialization - returns it directly.
185//
186// For non-template-specialization decls, returns the input unchanged.
187static const Decl *
188preferTemplateDefinitionForTemplateSpecializations(const Decl *D) {
189 const auto *SpecializationDecl = dyn_cast<ClassTemplateSpecializationDecl>(Val: D);
190 if (!SpecializationDecl)
191 return D;
192
193 auto InstantiatedFrom = SpecializationDecl->getInstantiatedFrom();
194 if (!InstantiatedFrom)
195 return D;
196
197 // This might be a class template.
198 if (const auto *Tmpl = InstantiatedFrom.dyn_cast<ClassTemplateDecl *>()) {
199 // Interestingly, the source template might be a forward declaration, so we
200 // need to find the definition redeclaration.
201 for (const auto *Redecl : Tmpl->redecls()) {
202 if (cast<ClassTemplateDecl>(Val: Redecl)->isThisDeclarationADefinition()) {
203 return Redecl;
204 }
205 }
206 assert(false &&
207 "This class template must have a redecl that is a definition");
208 return D;
209 }
210
211 // It might be a partial specialization.
212 const auto *PartialSpecialization =
213 InstantiatedFrom.dyn_cast<ClassTemplatePartialSpecializationDecl *>();
214
215 // The partial specialization should be a definition.
216 assert(PartialSpecialization->isThisDeclarationADefinition());
217 return PartialSpecialization;
218}
219
220bool BugSuppression::isSuppressed(const PathDiagnosticLocation &Location,
221 const Decl *DeclWithIssue,
222 DiagnosticIdentifierList Hashtags) {
223 if (!Location.isValid())
224 return false;
225
226 if (!DeclWithIssue) {
227 // FIXME: This defeats the purpose of passing DeclWithIssue to begin with.
228 // If this branch is ever hit, we're re-doing all the work we've already
229 // done as well as perform a lot of work we'll never need.
230 // Gladly, none of our on-by-default checkers currently need it.
231 DeclWithIssue = ACtx.getTranslationUnitDecl();
232 } else {
233 // This is the fast path. However, we should still consider the topmost
234 // declaration that isn't TranslationUnitDecl, because we should respect
235 // attributes on the entire declaration chain.
236 while (true) {
237
238 // Template specializations (e.g., Wrapper<int>) should inherit
239 // suppression attributes from their primary template or partial
240 // specialization. Transform specializations to their template definitions
241 // before checking for suppressions or walking up the lexical parent
242 // chain.
243 // Simply taking the lexical parent of template specializations might land
244 // us in a completely different namespace.
245 DeclWithIssue =
246 preferTemplateDefinitionForTemplateSpecializations(D: DeclWithIssue);
247
248 // Use the "lexical" parent. Eg., if the attribute is on a class, suppress
249 // warnings in inline methods but not in out-of-line methods.
250 const Decl *Parent =
251 dyn_cast_or_null<Decl>(Val: DeclWithIssue->getLexicalDeclContext());
252 if (Parent == nullptr || isa<TranslationUnitDecl>(Val: Parent))
253 break;
254
255 DeclWithIssue = Parent;
256 }
257 }
258
259 // While some warnings are attached to AST nodes (mostly path-sensitive
260 // checks), others are simply associated with a plain source location
261 // or range. Figuring out the node based on locations can be tricky,
262 // so instead, we traverse the whole body of the declaration and gather
263 // information on ALL suppressions. After that we can simply check if
264 // any of those suppressions affect the warning in question.
265 //
266 // Traversing AST of a function is not a heavy operation, but for
267 // large functions with a lot of bugs it can make a dent in performance.
268 // In order to avoid this scenario, we cache traversal results.
269 auto InsertionResult = CachedSuppressionLocations.insert(
270 KV: std::make_pair(x&: DeclWithIssue, y: CachedRanges{}));
271 Ranges &SuppressionRanges = InsertionResult.first->second;
272 if (InsertionResult.second) {
273 llvm::TimeTraceScope TimeScope(
274 timeScopeName(DeclWithIssue),
275 [DeclWithIssue]() { return getDeclTimeTraceMetadata(DeclWithIssue); });
276 // We haven't checked this declaration for suppressions yet!
277 CacheInitializer::initialize(D: DeclWithIssue, ToInit&: SuppressionRanges);
278 }
279
280 SourceRange BugRange = Location.asRange();
281 const SourceManager &SM = Location.getManager();
282
283 return llvm::any_of(Range&: SuppressionRanges,
284 P: [BugRange, &SM](SourceRange Suppression) {
285 return fullyContains(Larger: Suppression, Smaller: BugRange, SM);
286 });
287}
288