diff --git a/clang/docs/analyzer/checkers.rst b/clang/docs/analyzer/checkers.rst --- a/clang/docs/analyzer/checkers.rst +++ b/clang/docs/analyzer/checkers.rst @@ -1423,6 +1423,25 @@ // ... }; +.. _webkit-UncountedLambdaCapturesChecker: + +webkit.UncountedLambdaCapturesChecker +""""""""""""""""""""""""""""""""""""" +Raw pointers and references to uncounted types can't be captured in lambdas. Only ref-counted types are allowed. + +.. code-block:: cpp + + struct RefCntbl { + void ref() {} + void deref() {} + }; + + void foo(RefCntbl* a, RefCntbl& b) { + [&, a](){ // warn about 'a' + do_something(b); // warn about 'b' + }; + }; + .. _alpha-checkers: Experimental Checkers diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -1654,6 +1654,10 @@ HelpText<"Check for no uncounted member variables.">, Documentation; +def UncountedLambdaCapturesChecker : Checker<"UncountedLambdaCapturesChecker">, + HelpText<"Check uncounted lambda captures.">, + Documentation; + } // end webkit let ParentPackage = WebKitAlpha in { diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -127,6 +127,7 @@ WebKit/PtrTypesSemantics.cpp WebKit/RefCntblBaseVirtualDtorChecker.cpp WebKit/UncountedCallArgsChecker.cpp + WebKit/UncountedLambdaCapturesChecker.cpp LINK_LIBS clangAST diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLambdaCapturesChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLambdaCapturesChecker.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLambdaCapturesChecker.cpp @@ -0,0 +1,106 @@ +//=======- UncountedLambdaCapturesChecker.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 "DiagOutputUtils.h" +#include "PtrTypesSemantics.h" +#include "clang/AST/CXXInheritance.h" +#include "clang/AST/RecursiveASTVisitor.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" + +using namespace clang; +using namespace ento; + +namespace { +class UncountedLambdaCapturesChecker + : public Checker> { +private: + BugType Bug{this, "Lambda capture of uncounted 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 { + const UncountedLambdaCapturesChecker *Checker; + explicit LocalVisitor(const UncountedLambdaCapturesChecker *Checker) + : Checker(Checker) { + assert(Checker); + } + + bool shouldVisitTemplateInstantiations() const { return true; } + bool shouldVisitImplicitCode() const { return false; } + + bool VisitLambdaExpr(LambdaExpr *L) { + Checker->visitLambdaExpr(L); + return true; + } + }; + + LocalVisitor visitor(this); + visitor.TraverseDecl(const_cast(TUD)); + } + + void visitLambdaExpr(LambdaExpr *L) const { + for (const LambdaCapture &C : L->captures()) { + if (C.capturesVariable()) { + VarDecl *CapturedVar = C.getCapturedVar(); + if (auto *CapturedVarType = CapturedVar->getType().getTypePtrOrNull()) { + if (isUncountedPtr(CapturedVarType)) { + reportBug(C, CapturedVar, CapturedVarType); + } + } + } + } + } + + void reportBug(const LambdaCapture &Capture, VarDecl *CapturedVar, + const Type *T) const { + assert(CapturedVar); + + SmallString<100> Buf; + llvm::raw_svector_ostream Os(Buf); + + if (Capture.isExplicit()) { + Os << "Captured "; + } else { + Os << "Implicitly captured "; + } + if (T->isPointerType()) { + Os << "raw-pointer "; + } else { + assert(T->isReferenceType()); + Os << "reference "; + } + + printQuotedQualifiedName(Os, Capture.getCapturedVar()); + Os << " to uncounted type is unsafe."; + + PathDiagnosticLocation BSLoc(Capture.getLocation(), BR->getSourceManager()); + auto Report = std::make_unique(Bug, Os.str(), BSLoc); + BR->emitReport(std::move(Report)); + } +}; +} // namespace + +void ento::registerUncountedLambdaCapturesChecker(CheckerManager &Mgr) { + Mgr.registerChecker(); +} + +bool ento::shouldRegisterUncountedLambdaCapturesChecker( + const CheckerManager &mgr) { + return true; +} diff --git a/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp b/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp @@ -0,0 +1,44 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=webkit.UncountedLambdaCapturesChecker %s 2>&1 | FileCheck %s --strict-whitespace +#include "mock-types.h" + +void raw_ptr() { + RefCountable* ref_countable = nullptr; + auto foo1 = [ref_countable](){}; + // CHECK: warning: Captured raw-pointer 'ref_countable' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker] + // CHECK-NEXT:{{^}} auto foo1 = [ref_countable](){}; + // CHECK-NEXT:{{^}} ^ + auto foo2 = [&ref_countable](){}; + // CHECK: warning: Captured raw-pointer 'ref_countable' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker] + auto foo3 = [&](){ ref_countable = nullptr; }; + // CHECK: warning: Implicitly captured raw-pointer 'ref_countable' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker] + // CHECK-NEXT:{{^}} auto foo3 = [&](){ ref_countable = nullptr; }; + // CHECK-NEXT:{{^}} ^ + auto foo4 = [=](){ (void) ref_countable; }; + // CHECK: warning: Implicitly captured raw-pointer 'ref_countable' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker] +} + +void references() { + RefCountable automatic; + RefCountable& ref_countable_ref = automatic; + + auto foo1 = [ref_countable_ref](){}; + // CHECK: warning: Captured reference 'ref_countable_ref' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker] + auto foo2 = [&ref_countable_ref](){}; + // CHECK: warning: Captured reference 'ref_countable_ref' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker] + auto foo3 = [&](){ (void) ref_countable_ref; }; + // CHECK: warning: Implicitly captured reference 'ref_countable_ref' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker] + auto foo4 = [=](){ (void) ref_countable_ref; }; + // CHECK: warning: Implicitly captured reference 'ref_countable_ref' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker] +} + +void quiet() { +// This code is not expected to trigger any warnings. + { + RefCountable automatic; + RefCountable &ref_countable_ref = automatic; + } + + auto foo3 = [&]() {}; + auto foo4 = [=]() {}; + RefCountable *ref_countable = nullptr; +}