blob: 84004b8e5c1cdb8ff2a379f06a24b4d977a181b7 [file] [log] [blame]
//===- 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/RecursiveASTVisitor.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 RecursiveASTVisitor<CacheInitializer> {
public:
static void initialize(const Decl *D, Ranges &ToInit) {
CacheInitializer(ToInit).TraverseDecl(const_cast<Decl *>(D));
}
bool VisitDecl(Decl *D) {
// 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) {
// 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);
});
}