1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
|
//===- BugSuppression.cpp - Suppression interface -------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "clang/StaticAnalyzer/Core/BugReporter/BugSuppression.h"
#include "clang/AST/DynamicRecursiveASTVisitor.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
using namespace clang;
using namespace ento;
namespace {
using Ranges = llvm::SmallVectorImpl<SourceRange>;
inline bool hasSuppression(const Decl *D) {
// FIXME: Implement diagnostic identifier arguments
// (checker names, "hashtags").
if (const auto *Suppression = D->getAttr<SuppressAttr>())
return !Suppression->isGSL() &&
(Suppression->diagnosticIdentifiers().empty());
return false;
}
inline bool hasSuppression(const AttributedStmt *S) {
// FIXME: Implement diagnostic identifier arguments
// (checker names, "hashtags").
return llvm::any_of(S->getAttrs(), [](const Attr *A) {
const auto *Suppression = dyn_cast<SuppressAttr>(A);
return Suppression && !Suppression->isGSL() &&
(Suppression->diagnosticIdentifiers().empty());
});
}
template <class NodeType> inline SourceRange getRange(const NodeType *Node) {
return Node->getSourceRange();
}
template <> inline SourceRange getRange(const AttributedStmt *S) {
// Begin location for attributed statement node seems to be ALWAYS invalid.
//
// It is unlikely that we ever report any warnings on suppression
// attribute itself, but even if we do, we wouldn't want that warning
// to be suppressed by that same attribute.
//
// Long story short, we can use inner statement and it's not going to break
// anything.
return getRange(S->getSubStmt());
}
inline bool isLessOrEqual(SourceLocation LHS, SourceLocation RHS,
const SourceManager &SM) {
// SourceManager::isBeforeInTranslationUnit tests for strict
// inequality, when we need a non-strict comparison (bug
// can be reported directly on the annotated note).
// For this reason, we use the following equivalence:
//
// A <= B <==> !(B < A)
//
return !SM.isBeforeInTranslationUnit(RHS, LHS);
}
inline bool fullyContains(SourceRange Larger, SourceRange Smaller,
const SourceManager &SM) {
// Essentially this means:
//
// Larger.fullyContains(Smaller)
//
// However, that method has a very trivial implementation and couldn't
// compare regular locations and locations from macro expansions.
// We could've converted everything into regular locations as a solution,
// but the following solution seems to be the most bulletproof.
return isLessOrEqual(Larger.getBegin(), Smaller.getBegin(), SM) &&
isLessOrEqual(Smaller.getEnd(), Larger.getEnd(), SM);
}
class CacheInitializer : public DynamicRecursiveASTVisitor {
public:
static void initialize(const Decl *D, Ranges &ToInit) {
CacheInitializer(ToInit).TraverseDecl(const_cast<Decl *>(D));
}
bool VisitDecl(Decl *D) override {
// Bug location could be somewhere in the init value of
// a freshly declared variable. Even though it looks like the
// user applied attribute to a statement, it will apply to a
// variable declaration, and this is where we check for it.
return VisitAttributedNode(D);
}
bool VisitAttributedStmt(AttributedStmt *AS) override {
// When we apply attributes to statements, it actually creates
// a wrapper statement that only contains attributes and the wrapped
// statement.
return VisitAttributedNode(AS);
}
private:
template <class NodeType> bool VisitAttributedNode(NodeType *Node) {
if (hasSuppression(Node)) {
// TODO: In the future, when we come up with good stable IDs for checkers
// we can return a list of kinds to ignore, or all if no arguments
// were provided.
addRange(getRange(Node));
}
// We should keep traversing AST.
return true;
}
void addRange(SourceRange R) {
if (R.isValid()) {
Result.push_back(R);
}
}
CacheInitializer(Ranges &R) : Result(R) {}
Ranges &Result;
};
} // end anonymous namespace
// TODO: Introduce stable IDs for checkers and check for those here
// to be more specific. Attribute without arguments should still
// be considered as "suppress all".
// It is already much finer granularity than what we have now
// (i.e. removing the whole function from the analysis).
bool BugSuppression::isSuppressed(const BugReport &R) {
PathDiagnosticLocation Location = R.getLocation();
PathDiagnosticLocation UniqueingLocation = R.getUniqueingLocation();
const Decl *DeclWithIssue = R.getDeclWithIssue();
return isSuppressed(Location, DeclWithIssue, {}) ||
isSuppressed(UniqueingLocation, DeclWithIssue, {});
}
bool BugSuppression::isSuppressed(const PathDiagnosticLocation &Location,
const Decl *DeclWithIssue,
DiagnosticIdentifierList Hashtags) {
if (!Location.isValid())
return false;
if (!DeclWithIssue) {
// FIXME: This defeats the purpose of passing DeclWithIssue to begin with.
// If this branch is ever hit, we're re-doing all the work we've already
// done as well as perform a lot of work we'll never need.
// Gladly, none of our on-by-default checkers currently need it.
DeclWithIssue = ACtx.getTranslationUnitDecl();
} else {
// This is the fast path. However, we should still consider the topmost
// declaration that isn't TranslationUnitDecl, because we should respect
// attributes on the entire declaration chain.
while (true) {
// Use the "lexical" parent. Eg., if the attribute is on a class, suppress
// warnings in inline methods but not in out-of-line methods.
const Decl *Parent =
dyn_cast_or_null<Decl>(DeclWithIssue->getLexicalDeclContext());
if (Parent == nullptr || isa<TranslationUnitDecl>(Parent))
break;
DeclWithIssue = Parent;
}
}
// While some warnings are attached to AST nodes (mostly path-sensitive
// checks), others are simply associated with a plain source location
// or range. Figuring out the node based on locations can be tricky,
// so instead, we traverse the whole body of the declaration and gather
// information on ALL suppressions. After that we can simply check if
// any of those suppressions affect the warning in question.
//
// Traversing AST of a function is not a heavy operation, but for
// large functions with a lot of bugs it can make a dent in performance.
// In order to avoid this scenario, we cache traversal results.
auto InsertionResult = CachedSuppressionLocations.insert(
std::make_pair(DeclWithIssue, CachedRanges{}));
Ranges &SuppressionRanges = InsertionResult.first->second;
if (InsertionResult.second) {
// We haven't checked this declaration for suppressions yet!
CacheInitializer::initialize(DeclWithIssue, SuppressionRanges);
}
SourceRange BugRange = Location.asRange();
const SourceManager &SM = Location.getManager();
return llvm::any_of(SuppressionRanges,
[BugRange, &SM](SourceRange Suppression) {
return fullyContains(Suppression, BugRange, SM);
});
}
|