225 lines · cpp
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-exception6//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 15using namespace clang;16using namespace ento;17 18namespace {19 20using Ranges = llvm::SmallVectorImpl<SourceRange>;21 22inline bool hasSuppression(const Decl *D) {23 // FIXME: Implement diagnostic identifier arguments24 // (checker names, "hashtags").25 if (const auto *Suppression = D->getAttr<SuppressAttr>())26 return !Suppression->isGSL() &&27 (Suppression->diagnosticIdentifiers().empty());28 return false;29}30inline bool hasSuppression(const AttributedStmt *S) {31 // FIXME: Implement diagnostic identifier arguments32 // (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 40template <class NodeType> inline SourceRange getRange(const NodeType *Node) {41 return Node->getSourceRange();42}43template <> 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 suppression47 // attribute itself, but even if we do, we wouldn't want that warning48 // to be suppressed by that same attribute.49 //50 // Long story short, we can use inner statement and it's not going to break51 // anything.52 return getRange(S->getSubStmt());53}54 55inline bool isLessOrEqual(SourceLocation LHS, SourceLocation RHS,56 const SourceManager &SM) {57 // SourceManager::isBeforeInTranslationUnit tests for strict58 // inequality, when we need a non-strict comparison (bug59 // 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 67inline 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't74 // 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 81class CacheInitializer : public DynamicRecursiveASTVisitor {82public:83 static void initialize(const Decl *D, Ranges &ToInit) {84 CacheInitializer(ToInit).TraverseDecl(const_cast<Decl *>(D));85 }86 87 bool VisitDecl(Decl *D) override {88 // Bug location could be somewhere in the init value of89 // a freshly declared variable. Even though it looks like the90 // user applied attribute to a statement, it will apply to a91 // variable declaration, and this is where we check for it.92 return VisitAttributedNode(D);93 }94 95 bool VisitAttributedStmt(AttributedStmt *AS) override {96 // When we apply attributes to statements, it actually creates97 // a wrapper statement that only contains attributes and the wrapped98 // statement.99 return VisitAttributedNode(AS);100 }101 102private: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 checkers106 // we can return a list of kinds to ignore, or all if no arguments107 // were provided.108 addRange(getRange(Node));109 }110 // We should keep traversing AST.111 return true;112 }113 114 void addRange(SourceRange R) {115 if (R.isValid()) {116 Result.push_back(R);117 }118 }119 120 CacheInitializer(Ranges &R) : Result(R) {121 ShouldVisitTemplateInstantiations = true;122 ShouldWalkTypesOfTypeLocs = false;123 ShouldVisitImplicitCode = false;124 ShouldVisitLambdaBody = true;125 }126 Ranges &Result;127};128 129std::string timeScopeName(const Decl *DeclWithIssue) {130 if (!llvm::timeTraceProfilerEnabled())131 return "";132 return llvm::formatv(133 "BugSuppression::isSuppressed init suppressions cache for {0}",134 DeclWithIssue->getDeclKindName())135 .str();136}137 138llvm::TimeTraceMetadata getDeclTimeTraceMetadata(const Decl *DeclWithIssue) {139 assert(DeclWithIssue);140 assert(llvm::timeTraceProfilerEnabled());141 std::string Name = "<noname>";142 if (const auto *ND = dyn_cast<NamedDecl>(DeclWithIssue)) {143 Name = ND->getNameAsString();144 }145 const auto &SM = DeclWithIssue->getASTContext().getSourceManager();146 auto Line = SM.getPresumedLineNumber(DeclWithIssue->getBeginLoc());147 auto Fname = SM.getFilename(DeclWithIssue->getBeginLoc());148 return llvm::TimeTraceMetadata{std::move(Name), Fname.str(),149 static_cast<int>(Line)};150}151 152} // end anonymous namespace153 154// TODO: Introduce stable IDs for checkers and check for those here155// to be more specific. Attribute without arguments should still156// be considered as "suppress all".157// It is already much finer granularity than what we have now158// (i.e. removing the whole function from the analysis).159bool BugSuppression::isSuppressed(const BugReport &R) {160 PathDiagnosticLocation Location = R.getLocation();161 PathDiagnosticLocation UniqueingLocation = R.getUniqueingLocation();162 const Decl *DeclWithIssue = R.getDeclWithIssue();163 164 return isSuppressed(Location, DeclWithIssue, {}) ||165 isSuppressed(UniqueingLocation, DeclWithIssue, {});166}167 168bool BugSuppression::isSuppressed(const PathDiagnosticLocation &Location,169 const Decl *DeclWithIssue,170 DiagnosticIdentifierList Hashtags) {171 if (!Location.isValid())172 return false;173 174 if (!DeclWithIssue) {175 // FIXME: This defeats the purpose of passing DeclWithIssue to begin with.176 // If this branch is ever hit, we're re-doing all the work we've already177 // done as well as perform a lot of work we'll never need.178 // Gladly, none of our on-by-default checkers currently need it.179 DeclWithIssue = ACtx.getTranslationUnitDecl();180 } else {181 // This is the fast path. However, we should still consider the topmost182 // declaration that isn't TranslationUnitDecl, because we should respect183 // attributes on the entire declaration chain.184 while (true) {185 // Use the "lexical" parent. Eg., if the attribute is on a class, suppress186 // warnings in inline methods but not in out-of-line methods.187 const Decl *Parent =188 dyn_cast_or_null<Decl>(DeclWithIssue->getLexicalDeclContext());189 if (Parent == nullptr || isa<TranslationUnitDecl>(Parent))190 break;191 192 DeclWithIssue = Parent;193 }194 }195 196 // While some warnings are attached to AST nodes (mostly path-sensitive197 // checks), others are simply associated with a plain source location198 // or range. Figuring out the node based on locations can be tricky,199 // so instead, we traverse the whole body of the declaration and gather200 // information on ALL suppressions. After that we can simply check if201 // any of those suppressions affect the warning in question.202 //203 // Traversing AST of a function is not a heavy operation, but for204 // large functions with a lot of bugs it can make a dent in performance.205 // In order to avoid this scenario, we cache traversal results.206 auto InsertionResult = CachedSuppressionLocations.insert(207 std::make_pair(DeclWithIssue, CachedRanges{}));208 Ranges &SuppressionRanges = InsertionResult.first->second;209 if (InsertionResult.second) {210 llvm::TimeTraceScope TimeScope(211 timeScopeName(DeclWithIssue),212 [DeclWithIssue]() { return getDeclTimeTraceMetadata(DeclWithIssue); });213 // We haven't checked this declaration for suppressions yet!214 CacheInitializer::initialize(DeclWithIssue, SuppressionRanges);215 }216 217 SourceRange BugRange = Location.asRange();218 const SourceManager &SM = Location.getManager();219 220 return llvm::any_of(SuppressionRanges,221 [BugRange, &SM](SourceRange Suppression) {222 return fullyContains(Suppression, BugRange, SM);223 });224}225