Skip to content

Introduce a new WebKit checker for a unchecked call arguments (#113708) #114522

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions clang/docs/analyzer/checkers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3560,6 +3560,12 @@ We also define a set of safe transformations which if passed a safe value as an
- casts
- unary operators like ``&`` or ``*``

alpha.webkit.UncheckedCallArgsChecker
"""""""""""""""""""""""""""""""""""""
The goal of this rule is to make sure that lifetime of any dynamically allocated CheckedPtr capable object passed as a call argument keeps its memory region past the end of the call. This applies to call to any function, method, lambda, function pointer or functor. CheckedPtr capable objects aren't supposed to be allocated on stack so we check arguments for parameters of raw pointers and references to unchecked types.

The rules of when to use and not to use CheckedPtr / CheckedRef are same as alpha.webkit.UncountedCallArgsChecker for ref-counted objects.

alpha.webkit.UncountedLocalVarsChecker
""""""""""""""""""""""""""""""""""""""
The goal of this rule is to make sure that any uncounted local variable is backed by a ref-counted object with lifetime that is strictly larger than the scope of the uncounted local variable. To be on the safe side we require the scope of an uncounted variable to be embedded in the scope of ref-counted object that backs it.
Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
Original file line number Diff line number Diff line change
Expand Up @@ -1760,6 +1760,10 @@ def UncountedCallArgsChecker : Checker<"UncountedCallArgsChecker">,
HelpText<"Check uncounted call arguments.">,
Documentation<HasDocumentation>;

def UncheckedCallArgsChecker : Checker<"UncheckedCallArgsChecker">,
HelpText<"Check unchecked call arguments.">,
Documentation<HasDocumentation>;

def UncountedLocalVarsChecker : Checker<"UncountedLocalVarsChecker">,
HelpText<"Check uncounted local variables.">,
Documentation<HasDocumentation>;
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ add_clang_library(clangStaticAnalyzerCheckers
WebKit/ASTUtils.cpp
WebKit/PtrTypesSemantics.cpp
WebKit/RefCntblBaseVirtualDtorChecker.cpp
WebKit/UncountedCallArgsChecker.cpp
WebKit/RawPtrRefCallArgsChecker.cpp
WebKit/UncountedLambdaCapturesChecker.cpp
WebKit/RawPtrRefLocalVarsChecker.cpp

Expand Down
12 changes: 11 additions & 1 deletion clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,16 @@ std::optional<bool> isUncounted(const QualType T) {
return isUncounted(T->getAsCXXRecordDecl());
}

std::optional<bool> isUnchecked(const QualType T) {
if (auto *Subst = dyn_cast<SubstTemplateTypeParmType>(T)) {
if (auto *Decl = Subst->getAssociatedDecl()) {
if (isCheckedPtr(safeGetName(Decl)))
return false;
}
}
return isUnchecked(T->getAsCXXRecordDecl());
}

std::optional<bool> isUncounted(const CXXRecordDecl* Class)
{
// Keep isRefCounted first as it's cheaper.
Expand All @@ -188,7 +198,7 @@ std::optional<bool> isUncounted(const CXXRecordDecl* Class)
}

std::optional<bool> isUnchecked(const CXXRecordDecl *Class) {
if (isCheckedPtr(Class))
if (!Class || isCheckedPtr(Class))
return false; // Cheaper than below
return isCheckedPtrCapable(Class);
}
Expand Down
8 changes: 8 additions & 0 deletions clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,18 @@ bool isCheckedPtr(const clang::CXXRecordDecl *Class);
/// not, std::nullopt if inconclusive.
std::optional<bool> isUncounted(const clang::QualType T);

/// \returns true if \p Class is CheckedPtr capable AND not checked, false if
/// not, std::nullopt if inconclusive.
std::optional<bool> isUnchecked(const clang::QualType T);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't seem to find a reference to this overload of isUnchecked. Do you see a use of it in future?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UncheckedCallArgsChecker::isUnsafeType calls it.


/// \returns true if \p Class is ref-countable AND not ref-counted, false if
/// not, std::nullopt if inconclusive.
std::optional<bool> isUncounted(const clang::CXXRecordDecl* Class);

/// \returns true if \p Class is CheckedPtr capable AND not checked, false if
/// not, std::nullopt if inconclusive.
std::optional<bool> isUnchecked(const clang::CXXRecordDecl *Class);

/// \returns true if \p T is either a raw pointer or reference to an uncounted
/// class, false if not, std::nullopt if inconclusive.
std::optional<bool> isUncountedPtr(const clang::QualType T);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//=======- UncountedCallArgsChecker.cpp --------------------------*- C++ -*-==//
//=======- RawPtrRefCallArgsChecker.cpp --------------------------*- C++ -*-==//
//
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to have documentation for RawPtrCallArgsChecker in checkers.rst? Do you anticipate more generic checkers calling this checker besides UncountedCallArgsChecker and UncheckedCallArgsChecker?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. I could imagine UncountedCallArgsChecker and UncheckedCallArgsChecker getting merged in the future but I don't think we'd add a new checker using RawPtrCallArgsChecker.

// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
Expand Down Expand Up @@ -27,16 +27,20 @@ using namespace ento;

namespace {

class UncountedCallArgsChecker
class RawPtrRefCallArgsChecker
: public Checker<check::ASTDecl<TranslationUnitDecl>> {
BugType Bug{this,
"Uncounted call argument for a raw pointer/reference parameter",
"WebKit coding guidelines"};
BugType Bug;
mutable BugReporter *BR;

TrivialFunctionAnalysis TFA;

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 const char *ptrKind() const = 0;

void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR,
BugReporter &BRArg) const {
Expand All @@ -48,10 +52,10 @@ class UncountedCallArgsChecker
struct LocalVisitor : public RecursiveASTVisitor<LocalVisitor> {
using Base = RecursiveASTVisitor<LocalVisitor>;

const UncountedCallArgsChecker *Checker;
const RawPtrRefCallArgsChecker *Checker;
Decl *DeclWithIssue{nullptr};

explicit LocalVisitor(const UncountedCallArgsChecker *Checker)
explicit LocalVisitor(const RawPtrRefCallArgsChecker *Checker)
: Checker(Checker) {
assert(Checker);
}
Expand Down Expand Up @@ -89,7 +93,8 @@ class UncountedCallArgsChecker
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);
unsigned ArgIdx =
isa<CXXOperatorCallExpr>(CE) && isa_and_nonnull<CXXMethodDecl>(F);

if (auto *MemberCallExpr = dyn_cast<CXXMemberCallExpr>(CE)) {
if (auto *MD = MemberCallExpr->getMethodDecl()) {
Expand All @@ -102,8 +107,8 @@ class UncountedCallArgsChecker
}
auto *E = MemberCallExpr->getImplicitObjectArgument();
QualType ArgType = MemberCallExpr->getObjectType().getCanonicalType();
std::optional<bool> IsUncounted = isUncounted(ArgType);
if (IsUncounted && *IsUncounted && !isPtrOriginSafe(E))
std::optional<bool> IsUnsafe = isUnsafeType(ArgType);
if (IsUnsafe && *IsUnsafe && !isPtrOriginSafe(E))
reportBugOnThis(E, D);
}

Expand All @@ -119,7 +124,7 @@ class UncountedCallArgsChecker

QualType ArgType = (*P)->getType().getCanonicalType();
// FIXME: more complex types (arrays, references to raw pointers, etc)
std::optional<bool> IsUncounted = isUncountedPtr(ArgType);
std::optional<bool> IsUncounted = isUnsafePtr(ArgType);
if (!IsUncounted || !(*IsUncounted))
continue;

Expand Down Expand Up @@ -265,7 +270,7 @@ class UncountedCallArgsChecker
Os << " for parameter ";
printQuotedQualifiedName(Os, Param);
}
Os << " is uncounted and unsafe.";
Os << " is " << ptrKind() << " and unsafe.";

const SourceLocation SrcLocToReport =
isa<CXXDefaultArgExpr>(CallArg) ? Param->getDefaultArg()->getExprLoc()
Expand All @@ -283,15 +288,53 @@ class UncountedCallArgsChecker

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, "Call argument for 'this' parameter is uncounted and unsafe.",
BSLoc);
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);
}

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);
}

const char *ptrKind() const final { return "unchecked"; }
};

} // namespace

void ento::registerUncountedCallArgsChecker(CheckerManager &Mgr) {
Expand All @@ -301,3 +344,11 @@ void ento::registerUncountedCallArgsChecker(CheckerManager &Mgr) {
bool ento::shouldRegisterUncountedCallArgsChecker(const CheckerManager &) {
return true;
}

void ento::registerUncheckedCallArgsChecker(CheckerManager &Mgr) {
Mgr.registerChecker<UncheckedCallArgsChecker>();
}

bool ento::shouldRegisterUncheckedCallArgsChecker(const CheckerManager &) {
return true;
}
Loading
Loading