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 
13 using namespace clang;
14 using namespace ento;
15 
16 namespace {
17 
18 using Ranges = llvm::SmallVectorImpl<SourceRange>;
19 
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());
26   return false;
27 }
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());
35   });
36 }
37 
38 template <class NodeType> inline SourceRange getRange(const NodeType *Node) {
39   return Node->getSourceRange();
40 }
41 template <> 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(S->getSubStmt());
51 }
52 
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:
59   //
60   //   A <= B <==> !(B < A)
61   //
62   return !SM.isBeforeInTranslationUnit(RHS, LHS);
63 }
64 
65 inline 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(Larger.getBegin(), Smaller.getBegin(), SM) &&
76          isLessOrEqual(Smaller.getEnd(), Larger.getEnd(), SM);
77 }
78 
79 class CacheInitializer : public RecursiveASTVisitor<CacheInitializer> {
80 public:
81   static void initialize(const Decl *D, Ranges &ToInit) {
82     CacheInitializer(ToInit).TraverseDecl(const_cast<Decl *>(D));
83   }
84 
85   bool VisitVarDecl(VarDecl *VD) {
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(VD);
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(AS);
98   }
99 
100 private:
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(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(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).
129 bool 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, {}) ||
135          isSuppressed(UniqueingLocation, DeclWithIssue, {});
136 }
137 
138 bool BugSuppression::isSuppressed(const PathDiagnosticLocation &Location,
139                                   const Decl *DeclWithIssue,
140                                   DiagnosticIdentifierList Hashtags) {
141   if (!Location.isValid() || DeclWithIssue == nullptr)
142     return false;
143 
144   // While some warnings are attached to AST nodes (mostly path-sensitive
145   // checks), others are simply associated with a plain source location
146   // or range.  Figuring out the node based on locations can be tricky,
147   // so instead, we traverse the whole body of the declaration and gather
148   // information on ALL suppressions.  After that we can simply check if
149   // any of those suppressions affect the warning in question.
150   //
151   // Traversing AST of a function is not a heavy operation, but for
152   // large functions with a lot of bugs it can make a dent in performance.
153   // In order to avoid this scenario, we cache traversal results.
154   auto InsertionResult = CachedSuppressionLocations.insert(
155       std::make_pair(DeclWithIssue, CachedRanges{}));
156   Ranges &SuppressionRanges = InsertionResult.first->second;
157   if (InsertionResult.second) {
158     // We haven't checked this declaration for suppressions yet!
159     CacheInitializer::initialize(DeclWithIssue, SuppressionRanges);
160   }
161 
162   SourceRange BugRange = Location.asRange();
163   const SourceManager &SM = Location.getManager();
164 
165   return llvm::any_of(SuppressionRanges,
166                       [BugRange, &SM](SourceRange Suppression) {
167                         return fullyContains(Suppression, BugRange, SM);
168                       });
169 }
170