Skip to content
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

[webkit.UncountedLambdaCapturesChecker] Ignore trivial functions and [[clang::noescape]]. #114374

Closed
wants to merge 3 commits into from
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,13 @@ std::optional<bool> isUncheckedPtr(const QualType T) {
std::optional<bool> isUnsafePtr(const QualType T) {
if (T->isPointerType() || T->isReferenceType()) {
if (auto *CXXRD = T->getPointeeCXXRecordDecl()) {
return isUncounted(CXXRD) || isUnchecked(CXXRD);
auto isUncountedPtr = isUncounted(CXXRD);
auto isUncheckedPtr = isUnchecked(CXXRD);
if (isUncountedPtr && isUncheckedPtr)
return *isUncountedPtr || *isUncheckedPtr;
if (isUncountedPtr)
return *isUncountedPtr;
return isUncheckedPtr;
}
}
return false;
Expand Down
4 changes: 4 additions & 0 deletions clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ std::optional<bool> isUncountedPtr(const clang::QualType T);
/// class, false if not, std::nullopt if inconclusive.
std::optional<bool> isUncheckedPtr(const clang::QualType T);

/// \returns true if \p T is either a raw pointer or reference to an uncounted
/// or unchecked class, false if not, std::nullopt if inconclusive.
std::optional<bool> isUnsafePtr(const QualType T);

/// \returns true if \p T is a RefPtr, Ref, CheckedPtr, CheckedRef, or its
/// variant, false if not.
bool isSafePtrType(const clang::QualType T);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//
//===----------------------------------------------------------------------===//

#include "ASTUtils.h"
#include "DiagOutputUtils.h"
#include "PtrTypesSemantics.h"
#include "clang/AST/CXXInheritance.h"
Expand All @@ -26,6 +27,7 @@ class UncountedLambdaCapturesChecker
BugType Bug{this, "Lambda capture of uncounted variable",
"WebKit coding guidelines"};
mutable BugReporter *BR = nullptr;
TrivialFunctionAnalysis TFA;

public:
void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR,
Expand All @@ -37,6 +39,11 @@ class UncountedLambdaCapturesChecker
// want to visit those, so we make our own RecursiveASTVisitor.
struct LocalVisitor : public RecursiveASTVisitor<LocalVisitor> {
const UncountedLambdaCapturesChecker *Checker;
llvm::DenseSet<const DeclRefExpr *> DeclRefExprsToIgnore;
QualType ClsType;

using Base = RecursiveASTVisitor<LocalVisitor>;

explicit LocalVisitor(const UncountedLambdaCapturesChecker *Checker)
: Checker(Checker) {
assert(Checker);
Expand All @@ -45,32 +52,119 @@ class UncountedLambdaCapturesChecker
bool shouldVisitTemplateInstantiations() const { return true; }
bool shouldVisitImplicitCode() const { return false; }

bool VisitLambdaExpr(LambdaExpr *L) {
Checker->visitLambdaExpr(L);
bool TraverseDecl(Decl *D) {
if (auto *CXXMD = dyn_cast<CXXMethodDecl>(D)) {
llvm::SaveAndRestore SavedDecl(ClsType);
ClsType = CXXMD->getThisType();
return Base::TraverseDecl(D);
}
return Base::TraverseDecl(D);
}

bool shouldCheckThis() {
auto result = !ClsType.isNull() ? isUnsafePtr(ClsType) : std::nullopt;
return result && *result;
}

bool VisitDeclRefExpr(DeclRefExpr *DRE) {
if (DeclRefExprsToIgnore.contains(DRE))
return true;
auto *VD = dyn_cast_or_null<VarDecl>(DRE->getDecl());
if (!VD)
return true;
auto *Init = VD->getInit();
if (!Init)
return true;
auto *L = dyn_cast_or_null<LambdaExpr>(Init->IgnoreParenCasts());
if (!L)
return true;
Checker->visitLambdaExpr(L, shouldCheckThis());
return true;
}

// WTF::switchOn(T, F... f) is a variadic template function and couldn't
// be annotated with NOESCAPE. We hard code it here to workaround that.
bool shouldTreatAllArgAsNoEscape(FunctionDecl *Decl) {
auto *NsDecl = Decl->getParent();
if (!NsDecl || !isa<NamespaceDecl>(NsDecl))
return false;
return safeGetName(NsDecl) == "WTF" && safeGetName(Decl) == "switchOn";
}

bool VisitCallExpr(CallExpr *CE) {
checkCalleeLambda(CE);
if (auto *Callee = CE->getDirectCallee()) {
bool TreatAllArgsAsNoEscape = shouldTreatAllArgAsNoEscape(Callee);
unsigned ArgIndex = 0;
for (auto *Param : Callee->parameters()) {
if (ArgIndex >= CE->getNumArgs())
break;
auto *Arg = CE->getArg(ArgIndex)->IgnoreParenCasts();
if (!Param->hasAttr<NoEscapeAttr>() && !TreatAllArgsAsNoEscape) {
if (auto *L = dyn_cast_or_null<LambdaExpr>(Arg)) {
Checker->visitLambdaExpr(L, shouldCheckThis());
}
}
++ArgIndex;
}
}
return true;
}

void checkCalleeLambda(CallExpr *CE) {
auto *Callee = CE->getCallee();
if (!Callee)
return;
auto *DRE = dyn_cast<DeclRefExpr>(Callee->IgnoreParenCasts());
if (!DRE)
return;
auto *MD = dyn_cast_or_null<CXXMethodDecl>(DRE->getDecl());
if (!MD || CE->getNumArgs() != 1)
return;
auto *Arg = CE->getArg(0)->IgnoreParenCasts();
auto *ArgRef = dyn_cast<DeclRefExpr>(Arg);
if (!ArgRef)
return;
auto *VD = dyn_cast_or_null<VarDecl>(ArgRef->getDecl());
if (!VD)
return;
auto *Init = VD->getInit()->IgnoreParenCasts();
auto *L = dyn_cast_or_null<LambdaExpr>(Init);
if (!L)
return;
DeclRefExprsToIgnore.insert(ArgRef);
Checker->visitLambdaExpr(L, shouldCheckThis(),
/* ignoreParamVarDecl */ true);
}
};

LocalVisitor visitor(this);
visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD));
}

void visitLambdaExpr(LambdaExpr *L) const {
void visitLambdaExpr(LambdaExpr *L, bool shouldCheckThis,
bool ignoreParamVarDecl = false) const {
if (TFA.isTrivial(L->getBody()))
return;
for (const LambdaCapture &C : L->captures()) {
if (C.capturesVariable()) {
ValueDecl *CapturedVar = C.getCapturedVar();
if (ignoreParamVarDecl && isa<ParmVarDecl>(CapturedVar))
continue;
QualType CapturedVarQualType = CapturedVar->getType();
if (auto *CapturedVarType = CapturedVarQualType.getTypePtrOrNull()) {
auto IsUncountedPtr = isUncountedPtr(CapturedVarQualType);
if (IsUncountedPtr && *IsUncountedPtr)
reportBug(C, CapturedVar, CapturedVarType);
}
auto IsUncountedPtr = isUnsafePtr(CapturedVar->getType());
if (IsUncountedPtr && *IsUncountedPtr)
reportBug(C, CapturedVar, CapturedVarQualType);
} else if (C.capturesThis() && shouldCheckThis) {
if (ignoreParamVarDecl) // this is always a parameter to this function.
continue;
reportBugOnThisPtr(C);
}
}
}

void reportBug(const LambdaCapture &Capture, ValueDecl *CapturedVar,
const Type *T) const {
const QualType T) const {
assert(CapturedVar);

SmallString<100> Buf;
Expand All @@ -89,7 +183,25 @@ class UncountedLambdaCapturesChecker
}

printQuotedQualifiedName(Os, Capture.getCapturedVar());
Os << " to uncounted type is unsafe.";
Os << " to ref-counted / CheckedPtr capable type is unsafe.";

PathDiagnosticLocation BSLoc(Capture.getLocation(), BR->getSourceManager());
auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
BR->emitReport(std::move(Report));
}

void reportBugOnThisPtr(const LambdaCapture &Capture) const {
SmallString<100> Buf;
llvm::raw_svector_ostream Os(Buf);

if (Capture.isExplicit()) {
Os << "Captured ";
} else {
Os << "Implicitly captured ";
}

Os << "raw-pointer 'this' to ref-counted / CheckedPtr capable type is "
"unsafe.";

PathDiagnosticLocation BSLoc(Capture.getLocation(), BR->getSourceManager());
auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
Expand Down
2 changes: 2 additions & 0 deletions clang/test/Analysis/Checkers/WebKit/mock-types.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,9 @@ struct RefCountable {
void ref() {}
void deref() {}
void method();
void constMethod() const;
int trivial() { return 123; }
RefCountable* next();
};

template <typename T> T *downcast(T *t) { return t; }
Expand Down
Loading
Loading