Files
clang-p2996/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLocalVarsChecker.cpp
Ryosuke Niwa 7ce1cfed9a [alpha.webkit.UncountedLocalVarsChecker] Allow uncounted object references within trivial statements (#82229)
This PR makes alpha.webkit.UncountedLocalVarsChecker ignore raw
references and pointers to a ref counted type which appears within
"trival" statements. To do this, this PR extends TrivialFunctionAnalysis
so that it can also analyze "triviality" of statements as well as that
of functions Each Visit* function is now augmented with
withCachedResult, which is responsible for looking up and updating the
cache for each Visit* functions.

As this PR dramatically improves the false positive rate of the checker,
it also deletes the code to ignore raw pointers and references within if
and for statements.
2024-03-07 01:06:20 -08:00

260 lines
7.9 KiB
C++

//=======- UncountedLocalVarsChecker.cpp -------------------------*- C++ -*-==//
//
// 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 "ASTUtils.h"
#include "DiagOutputUtils.h"
#include "PtrTypesSemantics.h"
#include "clang/AST/CXXInheritance.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/ParentMapContext.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include <optional>
using namespace clang;
using namespace ento;
namespace {
// FIXME: should be defined by anotations in the future
bool isRefcountedStringsHack(const VarDecl *V) {
assert(V);
auto safeClass = [](const std::string &className) {
return className == "String" || className == "AtomString" ||
className == "UniquedString" || className == "Identifier";
};
QualType QT = V->getType();
auto *T = QT.getTypePtr();
if (auto *CXXRD = T->getAsCXXRecordDecl()) {
if (safeClass(safeGetName(CXXRD)))
return true;
}
if (T->isPointerType() || T->isReferenceType()) {
if (auto *CXXRD = T->getPointeeCXXRecordDecl()) {
if (safeClass(safeGetName(CXXRD)))
return true;
}
}
return false;
}
bool isGuardedScopeEmbeddedInGuardianScope(const VarDecl *Guarded,
const VarDecl *MaybeGuardian) {
assert(Guarded);
assert(MaybeGuardian);
if (!MaybeGuardian->isLocalVarDecl())
return false;
const CompoundStmt *guardiansClosestCompStmtAncestor = nullptr;
ASTContext &ctx = MaybeGuardian->getASTContext();
for (DynTypedNodeList guardianAncestors = ctx.getParents(*MaybeGuardian);
!guardianAncestors.empty();
guardianAncestors = ctx.getParents(
*guardianAncestors
.begin()) // FIXME - should we handle all of the parents?
) {
for (auto &guardianAncestor : guardianAncestors) {
if (auto *CStmtParentAncestor = guardianAncestor.get<CompoundStmt>()) {
guardiansClosestCompStmtAncestor = CStmtParentAncestor;
break;
}
}
if (guardiansClosestCompStmtAncestor)
break;
}
if (!guardiansClosestCompStmtAncestor)
return false;
// We need to skip the first CompoundStmt to avoid situation when guardian is
// defined in the same scope as guarded variable.
bool HaveSkippedFirstCompoundStmt = false;
for (DynTypedNodeList guardedVarAncestors = ctx.getParents(*Guarded);
!guardedVarAncestors.empty();
guardedVarAncestors = ctx.getParents(
*guardedVarAncestors
.begin()) // FIXME - should we handle all of the parents?
) {
for (auto &guardedVarAncestor : guardedVarAncestors) {
if (auto *CStmtAncestor = guardedVarAncestor.get<CompoundStmt>()) {
if (!HaveSkippedFirstCompoundStmt) {
HaveSkippedFirstCompoundStmt = true;
continue;
}
if (CStmtAncestor == guardiansClosestCompStmtAncestor)
return true;
}
}
}
return false;
}
class UncountedLocalVarsChecker
: public Checker<check::ASTDecl<TranslationUnitDecl>> {
BugType Bug{this,
"Uncounted raw pointer or reference not provably backed by "
"ref-counted variable",
"WebKit coding guidelines"};
mutable BugReporter *BR;
public:
void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR,
BugReporter &BRArg) const {
BR = &BRArg;
// The calls to checkAST* from AnalysisConsumer don't
// visit template instantiations or lambda classes. We
// want to visit those, so we make our own RecursiveASTVisitor.
struct LocalVisitor : public RecursiveASTVisitor<LocalVisitor> {
const UncountedLocalVarsChecker *Checker;
TrivialFunctionAnalysis TFA;
using Base = RecursiveASTVisitor<LocalVisitor>;
explicit LocalVisitor(const UncountedLocalVarsChecker *Checker)
: Checker(Checker) {
assert(Checker);
}
bool shouldVisitTemplateInstantiations() const { return true; }
bool shouldVisitImplicitCode() const { return false; }
bool VisitVarDecl(VarDecl *V) {
Checker->visitVarDecl(V);
return true;
}
bool TraverseIfStmt(IfStmt *IS) {
if (!TFA.isTrivial(IS))
return Base::TraverseIfStmt(IS);
return true;
}
bool TraverseForStmt(ForStmt *FS) {
if (!TFA.isTrivial(FS))
return Base::TraverseForStmt(FS);
return true;
}
bool TraverseCXXForRangeStmt(CXXForRangeStmt *FRS) {
if (!TFA.isTrivial(FRS))
return Base::TraverseCXXForRangeStmt(FRS);
return true;
}
bool TraverseWhileStmt(WhileStmt *WS) {
if (!TFA.isTrivial(WS))
return Base::TraverseWhileStmt(WS);
return true;
}
bool TraverseCompoundStmt(CompoundStmt *CS) {
if (!TFA.isTrivial(CS))
return Base::TraverseCompoundStmt(CS);
return true;
}
};
LocalVisitor visitor(this);
visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD));
}
void visitVarDecl(const VarDecl *V) const {
if (shouldSkipVarDecl(V))
return;
const auto *ArgType = V->getType().getTypePtr();
if (!ArgType)
return;
std::optional<bool> IsUncountedPtr = isUncountedPtr(ArgType);
if (IsUncountedPtr && *IsUncountedPtr) {
const Expr *const InitExpr = V->getInit();
if (!InitExpr)
return; // FIXME: later on we might warn on uninitialized vars too
const clang::Expr *const InitArgOrigin =
tryToFindPtrOrigin(InitExpr, /*StopAtFirstRefCountedObj=*/false)
.first;
if (!InitArgOrigin)
return;
if (isa<CXXThisExpr>(InitArgOrigin))
return;
if (auto *Ref = llvm::dyn_cast<DeclRefExpr>(InitArgOrigin)) {
if (auto *MaybeGuardian =
dyn_cast_or_null<VarDecl>(Ref->getFoundDecl())) {
const auto *MaybeGuardianArgType =
MaybeGuardian->getType().getTypePtr();
if (MaybeGuardianArgType) {
const CXXRecordDecl *const MaybeGuardianArgCXXRecord =
MaybeGuardianArgType->getAsCXXRecordDecl();
if (MaybeGuardianArgCXXRecord) {
if (MaybeGuardian->isLocalVarDecl() &&
(isRefCounted(MaybeGuardianArgCXXRecord) ||
isRefcountedStringsHack(MaybeGuardian)) &&
isGuardedScopeEmbeddedInGuardianScope(V, MaybeGuardian))
return;
}
}
// Parameters are guaranteed to be safe for the duration of the call
// by another checker.
if (isa<ParmVarDecl>(MaybeGuardian))
return;
}
}
reportBug(V);
}
}
bool shouldSkipVarDecl(const VarDecl *V) const {
assert(V);
if (!V->isLocalVarDecl())
return true;
return false;
}
void reportBug(const VarDecl *V) const {
assert(V);
SmallString<100> Buf;
llvm::raw_svector_ostream Os(Buf);
Os << "Local variable ";
printQuotedQualifiedName(Os, V);
Os << " is uncounted and unsafe.";
PathDiagnosticLocation BSLoc(V->getLocation(), BR->getSourceManager());
auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
Report->addRange(V->getSourceRange());
BR->emitReport(std::move(Report));
}
};
} // namespace
void ento::registerUncountedLocalVarsChecker(CheckerManager &Mgr) {
Mgr.registerChecker<UncountedLocalVarsChecker>();
}
bool ento::shouldRegisterUncountedLocalVarsChecker(const CheckerManager &) {
return true;
}