blob: 6c1a55f79b9082812b4b79a46b8fb3361c51051e [file] [edit]
//===- 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"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/TimeProfiler.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) {
ShouldVisitTemplateInstantiations = true;
ShouldWalkTypesOfTypeLocs = false;
ShouldVisitImplicitCode = false;
ShouldVisitLambdaBody = true;
}
Ranges &Result;
};
std::string timeScopeName(const Decl *DeclWithIssue) {
if (!llvm::timeTraceProfilerEnabled())
return "";
return llvm::formatv(
"BugSuppression::isSuppressed init suppressions cache for {0}",
DeclWithIssue->getDeclKindName())
.str();
}
llvm::TimeTraceMetadata getDeclTimeTraceMetadata(const Decl *DeclWithIssue) {
assert(DeclWithIssue);
assert(llvm::timeTraceProfilerEnabled());
std::string Name = "<noname>";
if (const auto *ND = dyn_cast<NamedDecl>(DeclWithIssue)) {
Name = ND->getNameAsString();
}
const auto &SM = DeclWithIssue->getASTContext().getSourceManager();
auto Line = SM.getPresumedLineNumber(DeclWithIssue->getBeginLoc());
auto Fname = SM.getFilename(DeclWithIssue->getBeginLoc());
return llvm::TimeTraceMetadata{std::move(Name), Fname.str(),
static_cast<int>(Line)};
}
} // 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, {});
}
static const ClassTemplateDecl *
walkInstantiatedFromChain(const ClassTemplateDecl *Tmpl) {
// For nested member templates (e.g., S2 inside S1<T>), getInstantiatedFrom
// may return the member template as instantiated within an outer
// specialization (e.g., S2 as it appears in S1<int>). That instantiated
// member template has no definition redeclaration itself; we need to walk
// up the member template chain to reach the primary template definition.
// \code
// template <class> struct S1 {
// template <class> struct S2 {
// int i;
// template <class T> int m(const S2<T>& s2) {
// return s2.i;
// }
// };
// }
// /code
const ClassTemplateDecl *MemberTmpl;
while ((MemberTmpl = Tmpl->getInstantiatedFromMemberTemplate())) {
if (Tmpl->isMemberSpecialization())
break;
Tmpl = MemberTmpl;
}
return Tmpl;
}
static const ClassTemplatePartialSpecializationDecl *walkInstantiatedFromChain(
const ClassTemplatePartialSpecializationDecl *PartialSpec) {
const ClassTemplatePartialSpecializationDecl *MemberPS;
while ((MemberPS = PartialSpec->getInstantiatedFromMember())) {
if (PartialSpec->isMemberSpecialization())
break;
PartialSpec = MemberPS;
}
return PartialSpec;
}
template <class T> static const T *chooseDefinitionRedecl(const T *Tmpl) {
static_assert(llvm::is_one_of<T, ClassTemplateDecl,
ClassTemplatePartialSpecializationDecl>::value);
for (const auto *Redecl : Tmpl->redecls()) {
if (const T *D = cast<T>(Redecl); D->isThisDeclarationADefinition()) {
return D;
}
}
assert(false && "This template must have a redecl that is a definition");
return Tmpl;
}
// For template specializations, returns the primary template definition or
// partial specialization that was used to instantiate the specialization.
// This ensures suppression attributes on templates apply to their
// specializations.
//
// For example, given:
// template <typename T> class [[clang::suppress]] Wrapper { ... };
// Wrapper<int> w; // instantiates ClassTemplateSpecializationDecl
//
// When analyzing code in Wrapper<int>, this function maps the specialization
// back to the primary template definition, allowing us to find the suppression
// attribute.
//
// The function handles specializations (and partial specializations) of
// class and function templates.
// For any other decl, it returns the input unchagned.
static const Decl *
preferTemplateDefinitionForTemplateSpecializations(const Decl *D) {
// For function template specializations (including instantiated friend
// function templates), map back to the primary template's FunctionDecl so
// that the lexical parent chain walk reaches the class where the template
// was defined inline.
//
// This handles the case where a friend function template is defined inline
// inside a [[clang::suppress]]-annotated class but was pre-declared at
// namespace scope. In that case the instantiation's lexical DC is the
// namespace (from the pre-declaration), not the class. Walking back to the
// primary template FunctionDecl — whose lexical DC IS the class — lets the
// existing parent-chain walk find the suppression attribute.
if (const auto *FD = dyn_cast<FunctionDecl>(D)) {
if (const FunctionDecl *Pattern = FD->getTemplateInstantiationPattern())
return Pattern->getDefinition();
}
const auto *SpecializationDecl = dyn_cast<ClassTemplateSpecializationDecl>(D);
if (!SpecializationDecl)
return D;
auto InstantiatedFrom = SpecializationDecl->getInstantiatedFrom();
if (!InstantiatedFrom)
return D;
if (const auto *Tmpl = InstantiatedFrom.dyn_cast<ClassTemplateDecl *>()) {
// Interestingly, the source template might be a forward declaration, so we
// need to find the definition redeclaration.
return chooseDefinitionRedecl(walkInstantiatedFromChain(Tmpl));
}
return chooseDefinitionRedecl(walkInstantiatedFromChain(
cast<ClassTemplatePartialSpecializationDecl *>(InstantiatedFrom)));
}
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) {
// Template specializations (e.g., Wrapper<int>) should inherit
// suppression attributes from their primary template or partial
// specialization. Transform specializations to their template definitions
// before checking for suppressions or walking up the lexical parent
// chain.
// Simply taking the lexical parent of template specializations might land
// us in a completely different namespace.
DeclWithIssue =
preferTemplateDefinitionForTemplateSpecializations(DeclWithIssue);
// 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) {
llvm::TimeTraceScope TimeScope(
timeScopeName(DeclWithIssue),
[DeclWithIssue]() { return getDeclTimeTraceMetadata(DeclWithIssue); });
// 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);
});
}