xref: /freebsd-src/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/BugSuppression.cpp (revision 0fca6ea1d4eea4c934cfff25ac9ee8ad6fe95583)
15f757f3fSDimitry Andric //===- BugSuppression.cpp - Suppression interface -------------------------===//
25f757f3fSDimitry Andric //
35f757f3fSDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
45f757f3fSDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
55f757f3fSDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
65f757f3fSDimitry Andric //
75f757f3fSDimitry Andric //===----------------------------------------------------------------------===//
85f757f3fSDimitry Andric 
95f757f3fSDimitry Andric #include "clang/StaticAnalyzer/Core/BugReporter/BugSuppression.h"
105f757f3fSDimitry Andric #include "clang/AST/RecursiveASTVisitor.h"
115f757f3fSDimitry Andric #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
125f757f3fSDimitry Andric 
135f757f3fSDimitry Andric using namespace clang;
145f757f3fSDimitry Andric using namespace ento;
155f757f3fSDimitry Andric 
165f757f3fSDimitry Andric namespace {
175f757f3fSDimitry Andric 
185f757f3fSDimitry Andric using Ranges = llvm::SmallVectorImpl<SourceRange>;
195f757f3fSDimitry Andric 
205f757f3fSDimitry Andric inline bool hasSuppression(const Decl *D) {
215f757f3fSDimitry Andric   // FIXME: Implement diagnostic identifier arguments
225f757f3fSDimitry Andric   // (checker names, "hashtags").
235f757f3fSDimitry Andric   if (const auto *Suppression = D->getAttr<SuppressAttr>())
245f757f3fSDimitry Andric     return !Suppression->isGSL() &&
255f757f3fSDimitry Andric            (Suppression->diagnosticIdentifiers().empty());
265f757f3fSDimitry Andric   return false;
275f757f3fSDimitry Andric }
285f757f3fSDimitry Andric inline bool hasSuppression(const AttributedStmt *S) {
295f757f3fSDimitry Andric   // FIXME: Implement diagnostic identifier arguments
305f757f3fSDimitry Andric   // (checker names, "hashtags").
315f757f3fSDimitry Andric   return llvm::any_of(S->getAttrs(), [](const Attr *A) {
325f757f3fSDimitry Andric     const auto *Suppression = dyn_cast<SuppressAttr>(A);
335f757f3fSDimitry Andric     return Suppression && !Suppression->isGSL() &&
345f757f3fSDimitry Andric            (Suppression->diagnosticIdentifiers().empty());
355f757f3fSDimitry Andric   });
365f757f3fSDimitry Andric }
375f757f3fSDimitry Andric 
385f757f3fSDimitry Andric template <class NodeType> inline SourceRange getRange(const NodeType *Node) {
395f757f3fSDimitry Andric   return Node->getSourceRange();
405f757f3fSDimitry Andric }
415f757f3fSDimitry Andric template <> inline SourceRange getRange(const AttributedStmt *S) {
425f757f3fSDimitry Andric   // Begin location for attributed statement node seems to be ALWAYS invalid.
435f757f3fSDimitry Andric   //
445f757f3fSDimitry Andric   // It is unlikely that we ever report any warnings on suppression
455f757f3fSDimitry Andric   // attribute itself, but even if we do, we wouldn't want that warning
465f757f3fSDimitry Andric   // to be suppressed by that same attribute.
475f757f3fSDimitry Andric   //
485f757f3fSDimitry Andric   // Long story short, we can use inner statement and it's not going to break
495f757f3fSDimitry Andric   // anything.
505f757f3fSDimitry Andric   return getRange(S->getSubStmt());
515f757f3fSDimitry Andric }
525f757f3fSDimitry Andric 
535f757f3fSDimitry Andric inline bool isLessOrEqual(SourceLocation LHS, SourceLocation RHS,
545f757f3fSDimitry Andric                           const SourceManager &SM) {
555f757f3fSDimitry Andric   // SourceManager::isBeforeInTranslationUnit tests for strict
565f757f3fSDimitry Andric   // inequality, when we need a non-strict comparison (bug
575f757f3fSDimitry Andric   // can be reported directly on the annotated note).
585f757f3fSDimitry Andric   // For this reason, we use the following equivalence:
595f757f3fSDimitry Andric   //
605f757f3fSDimitry Andric   //   A <= B <==> !(B < A)
615f757f3fSDimitry Andric   //
625f757f3fSDimitry Andric   return !SM.isBeforeInTranslationUnit(RHS, LHS);
635f757f3fSDimitry Andric }
645f757f3fSDimitry Andric 
655f757f3fSDimitry Andric inline bool fullyContains(SourceRange Larger, SourceRange Smaller,
665f757f3fSDimitry Andric                           const SourceManager &SM) {
675f757f3fSDimitry Andric   // Essentially this means:
685f757f3fSDimitry Andric   //
695f757f3fSDimitry Andric   //   Larger.fullyContains(Smaller)
705f757f3fSDimitry Andric   //
715f757f3fSDimitry Andric   // However, that method has a very trivial implementation and couldn't
725f757f3fSDimitry Andric   // compare regular locations and locations from macro expansions.
735f757f3fSDimitry Andric   // We could've converted everything into regular locations as a solution,
745f757f3fSDimitry Andric   // but the following solution seems to be the most bulletproof.
755f757f3fSDimitry Andric   return isLessOrEqual(Larger.getBegin(), Smaller.getBegin(), SM) &&
765f757f3fSDimitry Andric          isLessOrEqual(Smaller.getEnd(), Larger.getEnd(), SM);
775f757f3fSDimitry Andric }
785f757f3fSDimitry Andric 
795f757f3fSDimitry Andric class CacheInitializer : public RecursiveASTVisitor<CacheInitializer> {
805f757f3fSDimitry Andric public:
815f757f3fSDimitry Andric   static void initialize(const Decl *D, Ranges &ToInit) {
825f757f3fSDimitry Andric     CacheInitializer(ToInit).TraverseDecl(const_cast<Decl *>(D));
835f757f3fSDimitry Andric   }
845f757f3fSDimitry Andric 
85*0fca6ea1SDimitry Andric   bool VisitDecl(Decl *D) {
865f757f3fSDimitry Andric     // Bug location could be somewhere in the init value of
875f757f3fSDimitry Andric     // a freshly declared variable.  Even though it looks like the
885f757f3fSDimitry Andric     // user applied attribute to a statement, it will apply to a
895f757f3fSDimitry Andric     // variable declaration, and this is where we check for it.
90*0fca6ea1SDimitry Andric     return VisitAttributedNode(D);
915f757f3fSDimitry Andric   }
925f757f3fSDimitry Andric 
935f757f3fSDimitry Andric   bool VisitAttributedStmt(AttributedStmt *AS) {
945f757f3fSDimitry Andric     // When we apply attributes to statements, it actually creates
955f757f3fSDimitry Andric     // a wrapper statement that only contains attributes and the wrapped
965f757f3fSDimitry Andric     // statement.
975f757f3fSDimitry Andric     return VisitAttributedNode(AS);
985f757f3fSDimitry Andric   }
995f757f3fSDimitry Andric 
1005f757f3fSDimitry Andric private:
1015f757f3fSDimitry Andric   template <class NodeType> bool VisitAttributedNode(NodeType *Node) {
1025f757f3fSDimitry Andric     if (hasSuppression(Node)) {
1035f757f3fSDimitry Andric       // TODO: In the future, when we come up with good stable IDs for checkers
1045f757f3fSDimitry Andric       //       we can return a list of kinds to ignore, or all if no arguments
1055f757f3fSDimitry Andric       //       were provided.
1065f757f3fSDimitry Andric       addRange(getRange(Node));
1075f757f3fSDimitry Andric     }
1085f757f3fSDimitry Andric     // We should keep traversing AST.
1095f757f3fSDimitry Andric     return true;
1105f757f3fSDimitry Andric   }
1115f757f3fSDimitry Andric 
1125f757f3fSDimitry Andric   void addRange(SourceRange R) {
1135f757f3fSDimitry Andric     if (R.isValid()) {
1145f757f3fSDimitry Andric       Result.push_back(R);
1155f757f3fSDimitry Andric     }
1165f757f3fSDimitry Andric   }
1175f757f3fSDimitry Andric 
1185f757f3fSDimitry Andric   CacheInitializer(Ranges &R) : Result(R) {}
1195f757f3fSDimitry Andric   Ranges &Result;
1205f757f3fSDimitry Andric };
1215f757f3fSDimitry Andric 
1225f757f3fSDimitry Andric } // end anonymous namespace
1235f757f3fSDimitry Andric 
1245f757f3fSDimitry Andric // TODO: Introduce stable IDs for checkers and check for those here
1255f757f3fSDimitry Andric //       to be more specific.  Attribute without arguments should still
1265f757f3fSDimitry Andric //       be considered as "suppress all".
1275f757f3fSDimitry Andric //       It is already much finer granularity than what we have now
1285f757f3fSDimitry Andric //       (i.e. removing the whole function from the analysis).
1295f757f3fSDimitry Andric bool BugSuppression::isSuppressed(const BugReport &R) {
1305f757f3fSDimitry Andric   PathDiagnosticLocation Location = R.getLocation();
1315f757f3fSDimitry Andric   PathDiagnosticLocation UniqueingLocation = R.getUniqueingLocation();
1325f757f3fSDimitry Andric   const Decl *DeclWithIssue = R.getDeclWithIssue();
1335f757f3fSDimitry Andric 
1345f757f3fSDimitry Andric   return isSuppressed(Location, DeclWithIssue, {}) ||
1355f757f3fSDimitry Andric          isSuppressed(UniqueingLocation, DeclWithIssue, {});
1365f757f3fSDimitry Andric }
1375f757f3fSDimitry Andric 
1385f757f3fSDimitry Andric bool BugSuppression::isSuppressed(const PathDiagnosticLocation &Location,
1395f757f3fSDimitry Andric                                   const Decl *DeclWithIssue,
1405f757f3fSDimitry Andric                                   DiagnosticIdentifierList Hashtags) {
141*0fca6ea1SDimitry Andric   if (!Location.isValid())
1425f757f3fSDimitry Andric     return false;
1435f757f3fSDimitry Andric 
144*0fca6ea1SDimitry Andric   if (!DeclWithIssue) {
145*0fca6ea1SDimitry Andric     // FIXME: This defeats the purpose of passing DeclWithIssue to begin with.
146*0fca6ea1SDimitry Andric     // If this branch is ever hit, we're re-doing all the work we've already
147*0fca6ea1SDimitry Andric     // done as well as perform a lot of work we'll never need.
148*0fca6ea1SDimitry Andric     // Gladly, none of our on-by-default checkers currently need it.
149*0fca6ea1SDimitry Andric     DeclWithIssue = ACtx.getTranslationUnitDecl();
150*0fca6ea1SDimitry Andric   } else {
151*0fca6ea1SDimitry Andric     // This is the fast path. However, we should still consider the topmost
152*0fca6ea1SDimitry Andric     // declaration that isn't TranslationUnitDecl, because we should respect
153*0fca6ea1SDimitry Andric     // attributes on the entire declaration chain.
154*0fca6ea1SDimitry Andric     while (true) {
155*0fca6ea1SDimitry Andric       // Use the "lexical" parent. Eg., if the attribute is on a class, suppress
156*0fca6ea1SDimitry Andric       // warnings in inline methods but not in out-of-line methods.
157*0fca6ea1SDimitry Andric       const Decl *Parent =
158*0fca6ea1SDimitry Andric           dyn_cast_or_null<Decl>(DeclWithIssue->getLexicalDeclContext());
159*0fca6ea1SDimitry Andric       if (Parent == nullptr || isa<TranslationUnitDecl>(Parent))
160*0fca6ea1SDimitry Andric         break;
161*0fca6ea1SDimitry Andric 
162*0fca6ea1SDimitry Andric       DeclWithIssue = Parent;
163*0fca6ea1SDimitry Andric     }
164*0fca6ea1SDimitry Andric   }
165*0fca6ea1SDimitry Andric 
1665f757f3fSDimitry Andric   // While some warnings are attached to AST nodes (mostly path-sensitive
1675f757f3fSDimitry Andric   // checks), others are simply associated with a plain source location
1685f757f3fSDimitry Andric   // or range.  Figuring out the node based on locations can be tricky,
1695f757f3fSDimitry Andric   // so instead, we traverse the whole body of the declaration and gather
1705f757f3fSDimitry Andric   // information on ALL suppressions.  After that we can simply check if
1715f757f3fSDimitry Andric   // any of those suppressions affect the warning in question.
1725f757f3fSDimitry Andric   //
1735f757f3fSDimitry Andric   // Traversing AST of a function is not a heavy operation, but for
1745f757f3fSDimitry Andric   // large functions with a lot of bugs it can make a dent in performance.
1755f757f3fSDimitry Andric   // In order to avoid this scenario, we cache traversal results.
1765f757f3fSDimitry Andric   auto InsertionResult = CachedSuppressionLocations.insert(
1775f757f3fSDimitry Andric       std::make_pair(DeclWithIssue, CachedRanges{}));
1785f757f3fSDimitry Andric   Ranges &SuppressionRanges = InsertionResult.first->second;
1795f757f3fSDimitry Andric   if (InsertionResult.second) {
1805f757f3fSDimitry Andric     // We haven't checked this declaration for suppressions yet!
1815f757f3fSDimitry Andric     CacheInitializer::initialize(DeclWithIssue, SuppressionRanges);
1825f757f3fSDimitry Andric   }
1835f757f3fSDimitry Andric 
1845f757f3fSDimitry Andric   SourceRange BugRange = Location.asRange();
1855f757f3fSDimitry Andric   const SourceManager &SM = Location.getManager();
1865f757f3fSDimitry Andric 
1875f757f3fSDimitry Andric   return llvm::any_of(SuppressionRanges,
1885f757f3fSDimitry Andric                       [BugRange, &SM](SourceRange Suppression) {
1895f757f3fSDimitry Andric                         return fullyContains(Suppression, BugRange, SM);
1905f757f3fSDimitry Andric                       });
1915f757f3fSDimitry Andric }
192