xref: /llvm-project/clang/lib/StaticAnalyzer/Core/BugSuppression.cpp (revision dde802b153d5cb41505bf4d377be753576991297)
1ef3f4760SArtem Dergachev //===- BugSuppression.cpp - Suppression interface -------------------------===//
2ef3f4760SArtem Dergachev //
3ef3f4760SArtem Dergachev // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4ef3f4760SArtem Dergachev // See https://llvm.org/LICENSE.txt for license information.
5ef3f4760SArtem Dergachev // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6ef3f4760SArtem Dergachev //
7ef3f4760SArtem Dergachev //===----------------------------------------------------------------------===//
8ef3f4760SArtem Dergachev 
9ef3f4760SArtem Dergachev #include "clang/StaticAnalyzer/Core/BugReporter/BugSuppression.h"
10*dde802b1SSirraide #include "clang/AST/DynamicRecursiveASTVisitor.h"
11ef3f4760SArtem Dergachev #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
12ef3f4760SArtem Dergachev 
13ef3f4760SArtem Dergachev using namespace clang;
14ef3f4760SArtem Dergachev using namespace ento;
15ef3f4760SArtem Dergachev 
16ef3f4760SArtem Dergachev namespace {
17ef3f4760SArtem Dergachev 
18ef3f4760SArtem Dergachev using Ranges = llvm::SmallVectorImpl<SourceRange>;
19ef3f4760SArtem Dergachev 
20ef3f4760SArtem Dergachev inline bool hasSuppression(const Decl *D) {
21ef3f4760SArtem Dergachev   // FIXME: Implement diagnostic identifier arguments
22ef3f4760SArtem Dergachev   // (checker names, "hashtags").
23ef3f4760SArtem Dergachev   if (const auto *Suppression = D->getAttr<SuppressAttr>())
24ef3f4760SArtem Dergachev     return !Suppression->isGSL() &&
25ef3f4760SArtem Dergachev            (Suppression->diagnosticIdentifiers().empty());
26ef3f4760SArtem Dergachev   return false;
27ef3f4760SArtem Dergachev }
28ef3f4760SArtem Dergachev inline bool hasSuppression(const AttributedStmt *S) {
29ef3f4760SArtem Dergachev   // FIXME: Implement diagnostic identifier arguments
30ef3f4760SArtem Dergachev   // (checker names, "hashtags").
31ef3f4760SArtem Dergachev   return llvm::any_of(S->getAttrs(), [](const Attr *A) {
32ef3f4760SArtem Dergachev     const auto *Suppression = dyn_cast<SuppressAttr>(A);
33ef3f4760SArtem Dergachev     return Suppression && !Suppression->isGSL() &&
34ef3f4760SArtem Dergachev            (Suppression->diagnosticIdentifiers().empty());
35ef3f4760SArtem Dergachev   });
36ef3f4760SArtem Dergachev }
37ef3f4760SArtem Dergachev 
38ef3f4760SArtem Dergachev template <class NodeType> inline SourceRange getRange(const NodeType *Node) {
39ef3f4760SArtem Dergachev   return Node->getSourceRange();
40ef3f4760SArtem Dergachev }
41ef3f4760SArtem Dergachev template <> inline SourceRange getRange(const AttributedStmt *S) {
42ef3f4760SArtem Dergachev   // Begin location for attributed statement node seems to be ALWAYS invalid.
43ef3f4760SArtem Dergachev   //
44ef3f4760SArtem Dergachev   // It is unlikely that we ever report any warnings on suppression
45ef3f4760SArtem Dergachev   // attribute itself, but even if we do, we wouldn't want that warning
46ef3f4760SArtem Dergachev   // to be suppressed by that same attribute.
47ef3f4760SArtem Dergachev   //
48ef3f4760SArtem Dergachev   // Long story short, we can use inner statement and it's not going to break
49ef3f4760SArtem Dergachev   // anything.
50ef3f4760SArtem Dergachev   return getRange(S->getSubStmt());
51ef3f4760SArtem Dergachev }
52ef3f4760SArtem Dergachev 
53ef3f4760SArtem Dergachev inline bool isLessOrEqual(SourceLocation LHS, SourceLocation RHS,
54ef3f4760SArtem Dergachev                           const SourceManager &SM) {
55ef3f4760SArtem Dergachev   // SourceManager::isBeforeInTranslationUnit tests for strict
56ef3f4760SArtem Dergachev   // inequality, when we need a non-strict comparison (bug
57ef3f4760SArtem Dergachev   // can be reported directly on the annotated note).
58ef3f4760SArtem Dergachev   // For this reason, we use the following equivalence:
59ef3f4760SArtem Dergachev   //
60ef3f4760SArtem Dergachev   //   A <= B <==> !(B < A)
61ef3f4760SArtem Dergachev   //
62ef3f4760SArtem Dergachev   return !SM.isBeforeInTranslationUnit(RHS, LHS);
63ef3f4760SArtem Dergachev }
64ef3f4760SArtem Dergachev 
65ef3f4760SArtem Dergachev inline bool fullyContains(SourceRange Larger, SourceRange Smaller,
66ef3f4760SArtem Dergachev                           const SourceManager &SM) {
67ef3f4760SArtem Dergachev   // Essentially this means:
68ef3f4760SArtem Dergachev   //
69ef3f4760SArtem Dergachev   //   Larger.fullyContains(Smaller)
70ef3f4760SArtem Dergachev   //
71ef3f4760SArtem Dergachev   // However, that method has a very trivial implementation and couldn't
72ef3f4760SArtem Dergachev   // compare regular locations and locations from macro expansions.
73ef3f4760SArtem Dergachev   // We could've converted everything into regular locations as a solution,
74ef3f4760SArtem Dergachev   // but the following solution seems to be the most bulletproof.
75ef3f4760SArtem Dergachev   return isLessOrEqual(Larger.getBegin(), Smaller.getBegin(), SM) &&
76ef3f4760SArtem Dergachev          isLessOrEqual(Smaller.getEnd(), Larger.getEnd(), SM);
77ef3f4760SArtem Dergachev }
78ef3f4760SArtem Dergachev 
79*dde802b1SSirraide class CacheInitializer : public DynamicRecursiveASTVisitor {
80ef3f4760SArtem Dergachev public:
81ef3f4760SArtem Dergachev   static void initialize(const Decl *D, Ranges &ToInit) {
82ef3f4760SArtem Dergachev     CacheInitializer(ToInit).TraverseDecl(const_cast<Decl *>(D));
83ef3f4760SArtem Dergachev   }
84ef3f4760SArtem Dergachev 
85*dde802b1SSirraide   bool VisitDecl(Decl *D) override {
86ef3f4760SArtem Dergachev     // Bug location could be somewhere in the init value of
87ef3f4760SArtem Dergachev     // a freshly declared variable.  Even though it looks like the
88ef3f4760SArtem Dergachev     // user applied attribute to a statement, it will apply to a
89ef3f4760SArtem Dergachev     // variable declaration, and this is where we check for it.
90017675ffSArtem Dergachev     return VisitAttributedNode(D);
91ef3f4760SArtem Dergachev   }
92ef3f4760SArtem Dergachev 
93*dde802b1SSirraide   bool VisitAttributedStmt(AttributedStmt *AS) override {
94ef3f4760SArtem Dergachev     // When we apply attributes to statements, it actually creates
95ef3f4760SArtem Dergachev     // a wrapper statement that only contains attributes and the wrapped
96ef3f4760SArtem Dergachev     // statement.
97ef3f4760SArtem Dergachev     return VisitAttributedNode(AS);
98ef3f4760SArtem Dergachev   }
99ef3f4760SArtem Dergachev 
100ef3f4760SArtem Dergachev private:
101ef3f4760SArtem Dergachev   template <class NodeType> bool VisitAttributedNode(NodeType *Node) {
102ef3f4760SArtem Dergachev     if (hasSuppression(Node)) {
103ef3f4760SArtem Dergachev       // TODO: In the future, when we come up with good stable IDs for checkers
104ef3f4760SArtem Dergachev       //       we can return a list of kinds to ignore, or all if no arguments
105ef3f4760SArtem Dergachev       //       were provided.
106ef3f4760SArtem Dergachev       addRange(getRange(Node));
107ef3f4760SArtem Dergachev     }
108ef3f4760SArtem Dergachev     // We should keep traversing AST.
109ef3f4760SArtem Dergachev     return true;
110ef3f4760SArtem Dergachev   }
111ef3f4760SArtem Dergachev 
112ef3f4760SArtem Dergachev   void addRange(SourceRange R) {
113ef3f4760SArtem Dergachev     if (R.isValid()) {
114ef3f4760SArtem Dergachev       Result.push_back(R);
115ef3f4760SArtem Dergachev     }
116ef3f4760SArtem Dergachev   }
117ef3f4760SArtem Dergachev 
118ef3f4760SArtem Dergachev   CacheInitializer(Ranges &R) : Result(R) {}
119ef3f4760SArtem Dergachev   Ranges &Result;
120ef3f4760SArtem Dergachev };
121ef3f4760SArtem Dergachev 
122ef3f4760SArtem Dergachev } // end anonymous namespace
123ef3f4760SArtem Dergachev 
124ef3f4760SArtem Dergachev // TODO: Introduce stable IDs for checkers and check for those here
125ef3f4760SArtem Dergachev //       to be more specific.  Attribute without arguments should still
126ef3f4760SArtem Dergachev //       be considered as "suppress all".
127ef3f4760SArtem Dergachev //       It is already much finer granularity than what we have now
128ef3f4760SArtem Dergachev //       (i.e. removing the whole function from the analysis).
129ef3f4760SArtem Dergachev bool BugSuppression::isSuppressed(const BugReport &R) {
130ef3f4760SArtem Dergachev   PathDiagnosticLocation Location = R.getLocation();
131ef3f4760SArtem Dergachev   PathDiagnosticLocation UniqueingLocation = R.getUniqueingLocation();
132ef3f4760SArtem Dergachev   const Decl *DeclWithIssue = R.getDeclWithIssue();
133ef3f4760SArtem Dergachev 
134ef3f4760SArtem Dergachev   return isSuppressed(Location, DeclWithIssue, {}) ||
135ef3f4760SArtem Dergachev          isSuppressed(UniqueingLocation, DeclWithIssue, {});
136ef3f4760SArtem Dergachev }
137ef3f4760SArtem Dergachev 
138ef3f4760SArtem Dergachev bool BugSuppression::isSuppressed(const PathDiagnosticLocation &Location,
139ef3f4760SArtem Dergachev                                   const Decl *DeclWithIssue,
140ef3f4760SArtem Dergachev                                   DiagnosticIdentifierList Hashtags) {
14156e241a0SArtem Dergachev   if (!Location.isValid())
142ef3f4760SArtem Dergachev     return false;
143ef3f4760SArtem Dergachev 
14456e241a0SArtem Dergachev   if (!DeclWithIssue) {
14556e241a0SArtem Dergachev     // FIXME: This defeats the purpose of passing DeclWithIssue to begin with.
14656e241a0SArtem Dergachev     // If this branch is ever hit, we're re-doing all the work we've already
14756e241a0SArtem Dergachev     // done as well as perform a lot of work we'll never need.
14856e241a0SArtem Dergachev     // Gladly, none of our on-by-default checkers currently need it.
14956e241a0SArtem Dergachev     DeclWithIssue = ACtx.getTranslationUnitDecl();
150017675ffSArtem Dergachev   } else {
151017675ffSArtem Dergachev     // This is the fast path. However, we should still consider the topmost
152017675ffSArtem Dergachev     // declaration that isn't TranslationUnitDecl, because we should respect
153017675ffSArtem Dergachev     // attributes on the entire declaration chain.
154017675ffSArtem Dergachev     while (true) {
155017675ffSArtem Dergachev       // Use the "lexical" parent. Eg., if the attribute is on a class, suppress
156017675ffSArtem Dergachev       // warnings in inline methods but not in out-of-line methods.
157017675ffSArtem Dergachev       const Decl *Parent =
158017675ffSArtem Dergachev           dyn_cast_or_null<Decl>(DeclWithIssue->getLexicalDeclContext());
159017675ffSArtem Dergachev       if (Parent == nullptr || isa<TranslationUnitDecl>(Parent))
160017675ffSArtem Dergachev         break;
161017675ffSArtem Dergachev 
162017675ffSArtem Dergachev       DeclWithIssue = Parent;
163017675ffSArtem Dergachev     }
16456e241a0SArtem Dergachev   }
16556e241a0SArtem Dergachev 
166ef3f4760SArtem Dergachev   // While some warnings are attached to AST nodes (mostly path-sensitive
167ef3f4760SArtem Dergachev   // checks), others are simply associated with a plain source location
168ef3f4760SArtem Dergachev   // or range.  Figuring out the node based on locations can be tricky,
169ef3f4760SArtem Dergachev   // so instead, we traverse the whole body of the declaration and gather
170ef3f4760SArtem Dergachev   // information on ALL suppressions.  After that we can simply check if
171ef3f4760SArtem Dergachev   // any of those suppressions affect the warning in question.
172ef3f4760SArtem Dergachev   //
173ef3f4760SArtem Dergachev   // Traversing AST of a function is not a heavy operation, but for
174ef3f4760SArtem Dergachev   // large functions with a lot of bugs it can make a dent in performance.
175ef3f4760SArtem Dergachev   // In order to avoid this scenario, we cache traversal results.
176ef3f4760SArtem Dergachev   auto InsertionResult = CachedSuppressionLocations.insert(
177ef3f4760SArtem Dergachev       std::make_pair(DeclWithIssue, CachedRanges{}));
178ef3f4760SArtem Dergachev   Ranges &SuppressionRanges = InsertionResult.first->second;
179ef3f4760SArtem Dergachev   if (InsertionResult.second) {
180ef3f4760SArtem Dergachev     // We haven't checked this declaration for suppressions yet!
181ef3f4760SArtem Dergachev     CacheInitializer::initialize(DeclWithIssue, SuppressionRanges);
182ef3f4760SArtem Dergachev   }
183ef3f4760SArtem Dergachev 
184ef3f4760SArtem Dergachev   SourceRange BugRange = Location.asRange();
185ef3f4760SArtem Dergachev   const SourceManager &SM = Location.getManager();
186ef3f4760SArtem Dergachev 
187ef3f4760SArtem Dergachev   return llvm::any_of(SuppressionRanges,
188ef3f4760SArtem Dergachev                       [BugRange, &SM](SourceRange Suppression) {
189ef3f4760SArtem Dergachev                         return fullyContains(Suppression, BugRange, SM);
190ef3f4760SArtem Dergachev                       });
191ef3f4760SArtem Dergachev }
192