This pr refactors all recursive AST visitors in `Sema`, `Analyze`, and `StaticAnalysis` to inherit from DRAV instead. This is over half of the visitors that inherit from RAV directly. See also #115132, #110040, #93462 LLVM Compile-Time Tracker link for this branch: https://llvm-compile-time-tracker.com/compare.php?from=5adb5c05a2e9f31385fbba8b0436cbc07d91a44d&to=b58e589a86c06ba28d4d90613864d10be29aa5ba&stat=instructions%3Au
192 lines
7.4 KiB
C++
192 lines
7.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"
|
|
|
|
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);
|
|
});
|
|
}
|