mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-28 09:36:06 +00:00

Specifically, add a scope for - each work-list step, - each entry point, - each checker run within a step, and - bug-suppression phase at the end of the analysis of an entry-point. These scopes add no perceptible run-time overhead when time-tracing is disabled. You can enable it and generate a time trace using the `-ftime-trace=file.json` option. See also the RFC: https://discourse.llvm.org/t/analyzer-rfc-ftime-trace-time-scopes-for-steps-and-entry-points/84343 -- CPP-6065
220 lines
8.4 KiB
C++
220 lines
8.4 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) {}
|
|
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, {});
|
|
}
|
|
|
|
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) {
|
|
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);
|
|
});
|
|
}
|