llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp
Ryosuke Niwa 2158b83f61
Add nodelete annotation for WebKit checkers with a new checker for validation (#177839)
This PR adds the support for specifying
`[[clang::annotate_type("webkit.nodelete")]]` on a function return type,
in which case, the whole function is considered "trivial" or more
precisely that it does not trigger any destruction of an object.

This PR also introduces alpha.webkit.NoDeleteChecker which validates
soundness of the annotation by examining the function body. The checker
will warn if `[[clang::annotate_type("webkit.nodelete")]]` is specified
on a function with a body which does not pass the triviality test.

---------

Co-authored-by: Balazs Benics <benicsbalazs@gmail.com>
2026-01-27 09:18:45 -08:00

512 lines
17 KiB
C++

//=======- RawPtrRefCallArgsChecker.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/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DynamicRecursiveASTVisitor.h"
#include "clang/Analysis/DomainSpecific/CocoaConventions.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 "llvm/Support/SaveAndRestore.h"
#include <optional>
using namespace clang;
using namespace ento;
namespace {
class RawPtrRefCallArgsChecker
: public Checker<check::ASTDecl<TranslationUnitDecl>> {
BugType Bug;
TrivialFunctionAnalysis TFA;
EnsureFunctionAnalysis EFA;
protected:
mutable BugReporter *BR;
mutable std::optional<RetainTypeChecker> RTC;
public:
RawPtrRefCallArgsChecker(const char *description)
: Bug(this, description, "WebKit coding guidelines") {}
virtual std::optional<bool> isUnsafeType(QualType) const = 0;
virtual std::optional<bool> isUnsafePtr(QualType) const = 0;
virtual bool isSafePtr(const CXXRecordDecl *Record) const = 0;
virtual bool isSafePtrType(const QualType type) const = 0;
virtual bool isSafeExpr(const Expr *) const { return false; }
virtual bool isSafeDecl(const Decl *) const { return false; }
virtual const char *ptrKind() const = 0;
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 : DynamicRecursiveASTVisitor {
const RawPtrRefCallArgsChecker *Checker;
Decl *DeclWithIssue{nullptr};
explicit LocalVisitor(const RawPtrRefCallArgsChecker *Checker)
: Checker(Checker) {
assert(Checker);
ShouldVisitTemplateInstantiations = true;
ShouldVisitImplicitCode = false;
}
bool TraverseClassTemplateDecl(ClassTemplateDecl *Decl) override {
if (isSmartPtrClass(safeGetName(Decl)))
return true;
return DynamicRecursiveASTVisitor::TraverseClassTemplateDecl(Decl);
}
bool TraverseDecl(Decl *D) override {
llvm::SaveAndRestore SavedDecl(DeclWithIssue);
if (D && (isa<FunctionDecl>(D) || isa<ObjCMethodDecl>(D)))
DeclWithIssue = D;
return DynamicRecursiveASTVisitor::TraverseDecl(D);
}
bool VisitCallExpr(CallExpr *CE) override {
Checker->visitCallExpr(CE, DeclWithIssue);
return true;
}
bool VisitTypedefDecl(TypedefDecl *TD) override {
if (Checker->RTC)
Checker->RTC->visitTypedef(TD);
return true;
}
bool VisitObjCMessageExpr(ObjCMessageExpr *ObjCMsgExpr) override {
Checker->visitObjCMessageExpr(ObjCMsgExpr, DeclWithIssue);
return true;
}
};
LocalVisitor visitor(this);
if (RTC)
RTC->visitTranslationUnitDecl(TUD);
visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD));
}
void visitCallExpr(const CallExpr *CE, const Decl *D) const {
if (shouldSkipCall(CE))
return;
if (auto *F = CE->getDirectCallee()) {
// Skip the first argument for overloaded member operators (e. g. lambda
// or std::function call operator).
unsigned ArgIdx =
isa<CXXOperatorCallExpr>(CE) && isa_and_nonnull<CXXMethodDecl>(F);
if (auto *MemberCallExpr = dyn_cast<CXXMemberCallExpr>(CE)) {
if (auto *MD = MemberCallExpr->getMethodDecl()) {
auto name = safeGetName(MD);
if (name == "ref" || name == "deref")
return;
if (name == "incrementCheckedPtrCount" ||
name == "decrementCheckedPtrCount")
return;
}
auto *E = MemberCallExpr->getImplicitObjectArgument();
QualType ArgType = MemberCallExpr->getObjectType().getCanonicalType();
std::optional<bool> IsUnsafe = isUnsafeType(ArgType);
if (IsUnsafe && *IsUnsafe && !isPtrOriginSafe(E))
reportBugOnThis(E, D);
}
for (auto P = F->param_begin();
// FIXME: Also check variadic function parameters.
// FIXME: Also check default function arguments. Probably a different
// checker. In case there are default arguments the call can have
// fewer arguments than the callee has parameters.
P < F->param_end() && ArgIdx < CE->getNumArgs(); ++P, ++ArgIdx) {
// TODO: attributes.
// if ((*P)->hasAttr<SafeRefCntblRawPtrAttr>())
// continue;
QualType ArgType = (*P)->getType();
// FIXME: more complex types (arrays, references to raw pointers, etc)
std::optional<bool> IsUncounted = isUnsafePtr(ArgType);
if (!IsUncounted || !(*IsUncounted))
continue;
const auto *Arg = CE->getArg(ArgIdx);
if (auto *defaultArg = dyn_cast<CXXDefaultArgExpr>(Arg))
Arg = defaultArg->getExpr();
if (isPtrOriginSafe(Arg))
continue;
reportBug(Arg, *P, D);
}
for (; ArgIdx < CE->getNumArgs(); ++ArgIdx) {
const auto *Arg = CE->getArg(ArgIdx);
auto ArgType = Arg->getType();
std::optional<bool> IsUncounted = isUnsafePtr(ArgType);
if (!IsUncounted || !(*IsUncounted))
continue;
if (auto *defaultArg = dyn_cast<CXXDefaultArgExpr>(Arg))
Arg = defaultArg->getExpr();
if (isPtrOriginSafe(Arg))
continue;
reportBug(Arg, nullptr, D);
}
}
}
void visitObjCMessageExpr(const ObjCMessageExpr *E, const Decl *D) const {
if (BR->getSourceManager().isInSystemHeader(E->getExprLoc()))
return;
if (auto *Receiver = E->getInstanceReceiver()) {
std::optional<bool> IsUnsafe = isUnsafePtr(E->getReceiverType());
if (IsUnsafe && *IsUnsafe && !isPtrOriginSafe(Receiver)) {
if (isAllocInit(E))
return;
reportBugOnReceiver(Receiver, D);
}
}
auto *MethodDecl = E->getMethodDecl();
if (!MethodDecl)
return;
auto ArgCount = E->getNumArgs();
for (unsigned i = 0; i < ArgCount; ++i) {
auto *Arg = E->getArg(i);
bool hasParam = i < MethodDecl->param_size();
auto *Param = hasParam ? MethodDecl->getParamDecl(i) : nullptr;
auto ArgType = Arg->getType();
std::optional<bool> IsUnsafe = isUnsafePtr(ArgType);
if (!IsUnsafe || !(*IsUnsafe))
continue;
if (isPtrOriginSafe(Arg))
continue;
reportBug(Arg, Param, D);
}
}
bool isPtrOriginSafe(const Expr *Arg) const {
return tryToFindPtrOrigin(
Arg, /*StopAtFirstRefCountedObj=*/true,
[&](const clang::CXXRecordDecl *Record) { return isSafePtr(Record); },
[&](const clang::QualType T) { return isSafePtrType(T); },
[&](const clang::Decl *D) { return isSafeDecl(D); },
[&](const clang::Expr *ArgOrigin, bool IsSafe) {
if (IsSafe)
return true;
if (isNullPtr(ArgOrigin))
return true;
if (isa<IntegerLiteral>(ArgOrigin)) {
// FIXME: Check the value.
// foo(123)
return true;
}
if (isa<CXXBoolLiteralExpr>(ArgOrigin))
return true;
if (isa<ObjCStringLiteral>(ArgOrigin))
return true;
if (isASafeCallArg(ArgOrigin))
return true;
if (EFA.isACallToEnsureFn(ArgOrigin))
return true;
if (isSafeExpr(ArgOrigin))
return true;
return false;
});
}
bool shouldSkipCall(const CallExpr *CE) const {
const auto *Callee = CE->getDirectCallee();
if (BR->getSourceManager().isInSystemHeader(CE->getExprLoc()))
return true;
if (Callee && TFA.isTrivial(Callee))
return true;
if (isTrivialBuiltinFunction(Callee))
return true;
if (CE->getNumArgs() == 0)
return false;
// If an assignment is problematic we should warn about the sole existence
// of object on LHS.
if (auto *MemberOp = dyn_cast<CXXOperatorCallExpr>(CE)) {
// Note: assignemnt to built-in type isn't derived from CallExpr.
if (MemberOp->getOperator() ==
OO_Equal) { // Ignore assignment to Ref/RefPtr.
auto *callee = MemberOp->getDirectCallee();
if (auto *calleeDecl = dyn_cast<CXXMethodDecl>(callee)) {
if (const CXXRecordDecl *classDecl = calleeDecl->getParent()) {
if (isSafePtr(classDecl))
return true;
}
}
}
if (MemberOp->isAssignmentOp())
return false;
}
if (!Callee)
return false;
if (isMethodOnWTFContainerType(Callee))
return true;
auto overloadedOperatorType = Callee->getOverloadedOperator();
if (overloadedOperatorType == OO_EqualEqual ||
overloadedOperatorType == OO_ExclaimEqual ||
overloadedOperatorType == OO_LessEqual ||
overloadedOperatorType == OO_GreaterEqual ||
overloadedOperatorType == OO_Spaceship ||
overloadedOperatorType == OO_AmpAmp ||
overloadedOperatorType == OO_PipePipe)
return true;
if (isCtorOfSafePtr(Callee) || isPtrConversion(Callee))
return true;
auto name = safeGetName(Callee);
if (name == "adoptRef" || name == "getPtr" || name == "WeakPtr" ||
name == "is" || name == "equal" || name == "hash" || name == "isType" ||
// FIXME: Most/all of these should be implemented via attributes.
name == "CFEqual" || name == "equalIgnoringASCIICase" ||
name == "equalIgnoringASCIICaseCommon" ||
name == "equalIgnoringNullity" || name == "toString")
return true;
return false;
}
bool isMethodOnWTFContainerType(const FunctionDecl *Decl) const {
if (!isa<CXXMethodDecl>(Decl))
return false;
auto *ClassDecl = Decl->getParent();
if (!ClassDecl || !isa<CXXRecordDecl>(ClassDecl))
return false;
auto *NsDecl = ClassDecl->getParent();
if (!NsDecl || !isa<NamespaceDecl>(NsDecl))
return false;
auto MethodName = safeGetName(Decl);
auto ClsNameStr = safeGetName(ClassDecl);
StringRef ClsName = ClsNameStr; // FIXME: Make safeGetName return StringRef.
auto NamespaceName = safeGetName(NsDecl);
// FIXME: These should be implemented via attributes.
return NamespaceName == "WTF" &&
(MethodName == "find" || MethodName == "findIf" ||
MethodName == "reverseFind" || MethodName == "reverseFindIf" ||
MethodName == "findIgnoringASCIICase" || MethodName == "get" ||
MethodName == "inlineGet" || MethodName == "contains" ||
MethodName == "containsIf" ||
MethodName == "containsIgnoringASCIICase" ||
MethodName == "startsWith" || MethodName == "endsWith" ||
MethodName == "startsWithIgnoringASCIICase" ||
MethodName == "endsWithIgnoringASCIICase" ||
MethodName == "substring") &&
(ClsName.ends_with("Vector") || ClsName.ends_with("Set") ||
ClsName.ends_with("Map") || ClsName == "StringImpl" ||
ClsName.ends_with("String"));
}
void reportBug(const Expr *CallArg, const ParmVarDecl *Param,
const Decl *DeclWithIssue) const {
assert(CallArg);
SmallString<100> Buf;
llvm::raw_svector_ostream Os(Buf);
const std::string paramName = safeGetName(Param);
Os << "Call argument";
if (!paramName.empty()) {
Os << " for parameter ";
printQuotedQualifiedName(Os, Param);
}
Os << " is " << ptrKind() << " and unsafe.";
bool usesDefaultArgValue = isa<CXXDefaultArgExpr>(CallArg) && Param;
const SourceLocation SrcLocToReport =
usesDefaultArgValue ? Param->getDefaultArg()->getExprLoc()
: CallArg->getSourceRange().getBegin();
PathDiagnosticLocation BSLoc(SrcLocToReport, BR->getSourceManager());
auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
Report->addRange(CallArg->getSourceRange());
Report->setDeclWithIssue(DeclWithIssue);
BR->emitReport(std::move(Report));
}
void reportBugOnThis(const Expr *CallArg, const Decl *DeclWithIssue) const {
assert(CallArg);
const SourceLocation SrcLocToReport = CallArg->getSourceRange().getBegin();
SmallString<100> Buf;
llvm::raw_svector_ostream Os(Buf);
Os << "Call argument for 'this' parameter is " << ptrKind();
Os << " and unsafe.";
PathDiagnosticLocation BSLoc(SrcLocToReport, BR->getSourceManager());
auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
Report->addRange(CallArg->getSourceRange());
Report->setDeclWithIssue(DeclWithIssue);
BR->emitReport(std::move(Report));
}
void reportBugOnReceiver(const Expr *CallArg,
const Decl *DeclWithIssue) const {
assert(CallArg);
const SourceLocation SrcLocToReport = CallArg->getSourceRange().getBegin();
SmallString<100> Buf;
llvm::raw_svector_ostream Os(Buf);
Os << "Receiver is " << ptrKind() << " and unsafe.";
PathDiagnosticLocation BSLoc(SrcLocToReport, BR->getSourceManager());
auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
Report->addRange(CallArg->getSourceRange());
Report->setDeclWithIssue(DeclWithIssue);
BR->emitReport(std::move(Report));
}
};
class UncountedCallArgsChecker final : public RawPtrRefCallArgsChecker {
public:
UncountedCallArgsChecker()
: RawPtrRefCallArgsChecker("Uncounted call argument for a raw "
"pointer/reference parameter") {}
std::optional<bool> isUnsafeType(QualType QT) const final {
return isUncounted(QT);
}
std::optional<bool> isUnsafePtr(QualType QT) const final {
return isUncountedPtr(QT.getCanonicalType());
}
bool isSafePtr(const CXXRecordDecl *Record) const final {
return isRefCounted(Record) || isCheckedPtr(Record);
}
bool isSafePtrType(const QualType type) const final {
return isRefOrCheckedPtrType(type);
}
const char *ptrKind() const final { return "uncounted"; }
};
class UncheckedCallArgsChecker final : public RawPtrRefCallArgsChecker {
public:
UncheckedCallArgsChecker()
: RawPtrRefCallArgsChecker("Unchecked call argument for a raw "
"pointer/reference parameter") {}
std::optional<bool> isUnsafeType(QualType QT) const final {
return isUnchecked(QT);
}
std::optional<bool> isUnsafePtr(QualType QT) const final {
return isUncheckedPtr(QT.getCanonicalType());
}
bool isSafePtr(const CXXRecordDecl *Record) const final {
return isRefCounted(Record) || isCheckedPtr(Record);
}
bool isSafePtrType(const QualType type) const final {
return isRefOrCheckedPtrType(type);
}
bool isSafeExpr(const Expr *E) const final {
return isExprToGetCheckedPtrCapableMember(E);
}
const char *ptrKind() const final { return "unchecked"; }
};
class UnretainedCallArgsChecker final : public RawPtrRefCallArgsChecker {
public:
UnretainedCallArgsChecker()
: RawPtrRefCallArgsChecker("Unretained call argument for a raw "
"pointer/reference parameter") {
RTC = RetainTypeChecker();
}
std::optional<bool> isUnsafeType(QualType QT) const final {
return RTC->isUnretained(QT);
}
std::optional<bool> isUnsafePtr(QualType QT) const final {
return RTC->isUnretained(QT);
}
bool isSafePtr(const CXXRecordDecl *Record) const final {
return isRetainPtrOrOSPtr(Record);
}
bool isSafePtrType(const QualType type) const final {
return isRetainPtrOrOSPtrType(type);
}
bool isSafeExpr(const Expr *E) const final {
return ento::cocoa::isCocoaObjectRef(E->getType()) &&
isa<ObjCMessageExpr>(E);
}
bool isSafeDecl(const Decl *D) const final {
// Treat NS/CF globals in system header as immortal.
return BR->getSourceManager().isInSystemHeader(D->getLocation());
}
const char *ptrKind() const final { return "unretained"; }
};
} // namespace
void ento::registerUncountedCallArgsChecker(CheckerManager &Mgr) {
Mgr.registerChecker<UncountedCallArgsChecker>();
}
bool ento::shouldRegisterUncountedCallArgsChecker(const CheckerManager &) {
return true;
}
void ento::registerUncheckedCallArgsChecker(CheckerManager &Mgr) {
Mgr.registerChecker<UncheckedCallArgsChecker>();
}
bool ento::shouldRegisterUncheckedCallArgsChecker(const CheckerManager &) {
return true;
}
void ento::registerUnretainedCallArgsChecker(CheckerManager &Mgr) {
Mgr.registerChecker<UnretainedCallArgsChecker>();
}
bool ento::shouldRegisterUnretainedCallArgsChecker(const CheckerManager &) {
return true;
}