1 //===- BugSuppression.cpp - Suppression interface -------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "clang/StaticAnalyzer/Core/BugReporter/BugSuppression.h"
10 #include "clang/AST/DynamicRecursiveASTVisitor.h"
11 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
13 using namespace clang
;
18 using Ranges
= llvm::SmallVectorImpl
<SourceRange
>;
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());
28 inline bool hasSuppression(const AttributedStmt
*S
) {
29 // FIXME: Implement diagnostic identifier arguments
30 // (checker names, "hashtags").
31 return llvm::any_of(S
->getAttrs(), [](const Attr
*A
) {
32 const auto *Suppression
= dyn_cast
<SuppressAttr
>(A
);
33 return Suppression
&& !Suppression
->isGSL() &&
34 (Suppression
->diagnosticIdentifiers().empty());
38 template <class NodeType
> inline SourceRange
getRange(const NodeType
*Node
) {
39 return Node
->getSourceRange();
41 template <> inline SourceRange
getRange(const AttributedStmt
*S
) {
42 // Begin location for attributed statement node seems to be ALWAYS invalid.
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.
48 // Long story short, we can use inner statement and it's not going to break
50 return getRange(S
->getSubStmt());
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:
60 // A <= B <==> !(B < A)
62 return !SM
.isBeforeInTranslationUnit(RHS
, LHS
);
65 inline bool fullyContains(SourceRange Larger
, SourceRange Smaller
,
66 const SourceManager
&SM
) {
67 // Essentially this means:
69 // Larger.fullyContains(Smaller)
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(Larger
.getBegin(), Smaller
.getBegin(), SM
) &&
76 isLessOrEqual(Smaller
.getEnd(), Larger
.getEnd(), SM
);
79 class CacheInitializer
: public DynamicRecursiveASTVisitor
{
81 static void initialize(const Decl
*D
, Ranges
&ToInit
) {
82 CacheInitializer(ToInit
).TraverseDecl(const_cast<Decl
*>(D
));
85 bool VisitDecl(Decl
*D
) override
{
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(D
);
93 bool VisitAttributedStmt(AttributedStmt
*AS
) override
{
94 // When we apply attributes to statements, it actually creates
95 // a wrapper statement that only contains attributes and the wrapped
97 return VisitAttributedNode(AS
);
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
106 addRange(getRange(Node
));
108 // We should keep traversing AST.
112 void addRange(SourceRange R
) {
118 CacheInitializer(Ranges
&R
) : Result(R
) {}
122 } // end anonymous namespace
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();
134 return isSuppressed(Location
, DeclWithIssue
, {}) ||
135 isSuppressed(UniqueingLocation
, DeclWithIssue
, {});
138 bool BugSuppression::isSuppressed(const PathDiagnosticLocation
&Location
,
139 const Decl
*DeclWithIssue
,
140 DiagnosticIdentifierList Hashtags
) {
141 if (!Location
.isValid())
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();
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.
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.
158 dyn_cast_or_null
<Decl
>(DeclWithIssue
->getLexicalDeclContext());
159 if (Parent
== nullptr || isa
<TranslationUnitDecl
>(Parent
))
162 DeclWithIssue
= Parent
;
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.
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 std::make_pair(DeclWithIssue
, CachedRanges
{}));
178 Ranges
&SuppressionRanges
= InsertionResult
.first
->second
;
179 if (InsertionResult
.second
) {
180 // We haven't checked this declaration for suppressions yet!
181 CacheInitializer::initialize(DeclWithIssue
, SuppressionRanges
);
184 SourceRange BugRange
= Location
.asRange();
185 const SourceManager
&SM
= Location
.getManager();
187 return llvm::any_of(SuppressionRanges
,
188 [BugRange
, &SM
](SourceRange Suppression
) {
189 return fullyContains(Suppression
, BugRange
, SM
);