xref: /freebsd/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/BugSuppression.cpp (revision 700637cbb5e582861067a11aaca4d053546871d2)
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