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
13using namespace clang;
14using namespace ento;
15
16namespace {
17
18using Ranges = llvm::SmallVectorImpl<SourceRange>;
19
20inline 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}
28inline 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>(Val: A);
33 return Suppression && !Suppression->isGSL() &&
34 (Suppression->diagnosticIdentifiers().empty());
35 });
36}
37
38template <class NodeType> inline SourceRange getRange(const NodeType *Node) {
39 return Node->getSourceRange();
40}
41template <> 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
53inline 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
65inline 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
79class CacheInitializer : public RecursiveASTVisitor<CacheInitializer> {
80public:
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
100private:
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).
129bool 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
138bool 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