The code assumed that taking the lexical parent decl context of a node and traversing it will eventually visit the node itself. While this is certeanly true for most AST constructs, template specializations (aka. instantiations) might inject their AST to surprising lexical parents, depending on when they get instantiated. This means that just taking the lexical parent of a template specialization might land us on some AST node that won't contain (thus visit) the definition, and consequently, miss the Suppress attribute... To fix this, we must take special care for template specializations. For a regular instantiation select the primary template (that has the definition). For an instantiation coming from a partial specialization, pretend if it was the partial specialization instead. Once we canonicalize to the primary template/partial specialization definition, the usual "walk the lexical parents" logic covers the rest as usual. Assisted-by: claude rdar://168941095
288 lines
11 KiB
C++
288 lines
11 KiB
C++
//===- 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/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, {});
|
|
}
|
|
|
|
// 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 two cases:
|
|
// 1. Instantiation from a class template - searches redeclarations to find
|
|
// the definition (not just a forward declaration).
|
|
// 2. Instantiation from a partial specialization - returns it directly.
|
|
//
|
|
// For non-template-specialization decls, returns the input unchanged.
|
|
static const Decl *
|
|
preferTemplateDefinitionForTemplateSpecializations(const Decl *D) {
|
|
const auto *SpecializationDecl = dyn_cast<ClassTemplateSpecializationDecl>(D);
|
|
if (!SpecializationDecl)
|
|
return D;
|
|
|
|
auto InstantiatedFrom = SpecializationDecl->getInstantiatedFrom();
|
|
if (!InstantiatedFrom)
|
|
return D;
|
|
|
|
// This might be a class template.
|
|
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.
|
|
for (const auto *Redecl : Tmpl->redecls()) {
|
|
if (cast<ClassTemplateDecl>(Redecl)->isThisDeclarationADefinition()) {
|
|
return Redecl;
|
|
}
|
|
}
|
|
assert(false &&
|
|
"This class template must have a redecl that is a definition");
|
|
return D;
|
|
}
|
|
|
|
// It might be a partial specialization.
|
|
const auto *PartialSpecialization =
|
|
InstantiatedFrom.dyn_cast<ClassTemplatePartialSpecializationDecl *>();
|
|
|
|
// The partial specialization should be a definition.
|
|
assert(PartialSpecialization->isThisDeclarationADefinition());
|
|
return PartialSpecialization;
|
|
}
|
|
|
|
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);
|
|
});
|
|
}
|