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
hasSuppression(const Decl * D)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 }
hasSuppression(const AttributedStmt * S)30 inline bool hasSuppression(const AttributedStmt *S) {
31 // FIXME: Implement diagnostic identifier arguments
32 // (checker names, "hashtags").
33 return llvm::any_of(S->getAttrs(), [](const Attr *A) {
34 const auto *Suppression = dyn_cast<SuppressAttr>(A);
35 return Suppression && !Suppression->isGSL() &&
36 (Suppression->diagnosticIdentifiers().empty());
37 });
38 }
39
getRange(const NodeType * Node)40 template <class NodeType> inline SourceRange getRange(const NodeType *Node) {
41 return Node->getSourceRange();
42 }
getRange(const AttributedStmt * S)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(S->getSubStmt());
53 }
54
isLessOrEqual(SourceLocation LHS,SourceLocation RHS,const SourceManager & SM)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(RHS, LHS);
65 }
66
fullyContains(SourceRange Larger,SourceRange Smaller,const SourceManager & SM)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(Larger.getBegin(), Smaller.getBegin(), SM) &&
78 isLessOrEqual(Smaller.getEnd(), Larger.getEnd(), SM);
79 }
80
81 class CacheInitializer : public DynamicRecursiveASTVisitor {
82 public:
initialize(const Decl * D,Ranges & ToInit)83 static void initialize(const Decl *D, Ranges &ToInit) {
84 CacheInitializer(ToInit).TraverseDecl(const_cast<Decl *>(D));
85 }
86
VisitDecl(Decl * D)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(D);
93 }
94
VisitAttributedStmt(AttributedStmt * AS)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(AS);
100 }
101
102 private:
VisitAttributedNode(NodeType * Node)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(getRange(Node));
109 }
110 // We should keep traversing AST.
111 return true;
112 }
113
addRange(SourceRange R)114 void addRange(SourceRange R) {
115 if (R.isValid()) {
116 Result.push_back(R);
117 }
118 }
119
CacheInitializer(Ranges & R)120 CacheInitializer(Ranges &R) : Result(R) {}
121 Ranges &Result;
122 };
123
timeScopeName(const Decl * DeclWithIssue)124 std::string timeScopeName(const Decl *DeclWithIssue) {
125 if (!llvm::timeTraceProfilerEnabled())
126 return "";
127 return llvm::formatv(
128 "BugSuppression::isSuppressed init suppressions cache for {0}",
129 DeclWithIssue->getDeclKindName())
130 .str();
131 }
132
getDeclTimeTraceMetadata(const Decl * DeclWithIssue)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>(DeclWithIssue)) {
138 Name = ND->getNameAsString();
139 }
140 const auto &SM = DeclWithIssue->getASTContext().getSourceManager();
141 auto Line = SM.getPresumedLineNumber(DeclWithIssue->getBeginLoc());
142 auto Fname = SM.getFilename(DeclWithIssue->getBeginLoc());
143 return llvm::TimeTraceMetadata{std::move(Name), Fname.str(),
144 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).
isSuppressed(const BugReport & R)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, {}) ||
160 isSuppressed(UniqueingLocation, DeclWithIssue, {});
161 }
162
isSuppressed(const PathDiagnosticLocation & Location,const Decl * DeclWithIssue,DiagnosticIdentifierList Hashtags)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>(DeclWithIssue->getLexicalDeclContext());
184 if (Parent == nullptr || isa<TranslationUnitDecl>(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 std::make_pair(DeclWithIssue, 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(DeclWithIssue, SuppressionRanges);
210 }
211
212 SourceRange BugRange = Location.asRange();
213 const SourceManager &SM = Location.getManager();
214
215 return llvm::any_of(SuppressionRanges,
216 [BugRange, &SM](SourceRange Suppression) {
217 return fullyContains(Suppression, BugRange, SM);
218 });
219 }
220