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