https://github.com/rniwa created https://github.com/llvm/llvm-project/pull/114522
This PR introduces alpha.webkit.UncheckedCallArgsChecker which detects a function argument which is a raw reference or a raw pointer to a CheckedPtr capable object. >From 0f32df43d91145c43331b9afb705449bae729523 Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa <rn...@webkit.org> Date: Fri, 1 Nov 2024 01:08:35 -0700 Subject: [PATCH] Introduce a new WebKit checker for a unchecked call arguments (#113708) This PR introduces alpha.webkit.UncheckedCallArgsChecker which detects a function argument which is a raw reference or a raw pointer to a CheckedPtr capable object. --- .../clang/StaticAnalyzer/Checkers/Checkers.td | 4 + .../StaticAnalyzer/Checkers/CMakeLists.txt | 2 +- .../Checkers/WebKit/PtrTypesSemantics.cpp | 12 +- .../Checkers/WebKit/PtrTypesSemantics.h | 8 + ...ecker.cpp => RawPtrRefCallArgsChecker.cpp} | 78 +++- .../Checkers/WebKit/call-args-checked-ptr.cpp | 367 ++++++++++++++++++ .../WebKit/call-args-checked-return-value.cpp | 25 ++ .../lib/StaticAnalyzer/Checkers/BUILD.gn | 2 +- 8 files changed, 481 insertions(+), 17 deletions(-) rename clang/lib/StaticAnalyzer/Checkers/WebKit/{UncountedCallArgsChecker.cpp => RawPtrRefCallArgsChecker.cpp} (82%) create mode 100644 clang/test/Analysis/Checkers/WebKit/call-args-checked-ptr.cpp create mode 100644 clang/test/Analysis/Checkers/WebKit/call-args-checked-return-value.cpp diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index 86d62b58cac0fb..bc172c7d2fe348 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -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<NotDocumented>; + def UncountedLocalVarsChecker : Checker<"UncountedLocalVarsChecker">, HelpText<"Check uncounted local variables.">, Documentation<HasDocumentation>; diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt index c6e5afdc42424c..f40318f46dea1a 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -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 diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp index 46819d5ca12058..2c80bd86375431 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp @@ -173,6 +173,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. @@ -187,7 +197,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); } diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h index 30bdaed706bb53..54c4872ad57d8c 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h @@ -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); + /// \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); diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedCallArgsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp similarity index 82% rename from clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedCallArgsChecker.cpp rename to clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp index 1a5a7309a54f16..f1fbe61c421f88 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedCallArgsChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp @@ -1,4 +1,4 @@ -//=======- UncountedCallArgsChecker.cpp --------------------------*- 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. @@ -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 { @@ -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); } @@ -101,8 +105,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); } @@ -118,7 +122,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; @@ -264,7 +268,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() @@ -282,15 +286,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) { @@ -300,3 +342,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; +} diff --git a/clang/test/Analysis/Checkers/WebKit/call-args-checked-ptr.cpp b/clang/test/Analysis/Checkers/WebKit/call-args-checked-ptr.cpp new file mode 100644 index 00000000000000..34ff0c70e230ea --- /dev/null +++ b/clang/test/Analysis/Checkers/WebKit/call-args-checked-ptr.cpp @@ -0,0 +1,367 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.UncheckedCallArgsChecker -verify %s + +#include "mock-types.h" + +CheckedObj* provide(); +void consume_refcntbl(CheckedObj*); +void some_function(); + +namespace simple { + void foo() { + consume_refcntbl(provide()); + // expected-warning@-1{{Call argument is unchecked and unsafe}} + } + + // Test that the checker works with [[clang::suppress]]. + void foo_suppressed() { + [[clang::suppress]] + consume_refcntbl(provide()); // no-warning + } +} + +namespace multi_arg { + void consume_refcntbl(int, CheckedObj* foo, bool); + void foo() { + consume_refcntbl(42, provide(), true); + // expected-warning@-1{{Call argument for parameter 'foo' is unchecked and unsafe}} + } +} + +namespace ref_counted { + CheckedRef<CheckedObj> provide_ref_counted() { return CheckedRef<CheckedObj>{}; } + void consume_ref_counted(CheckedRef<CheckedObj>) {} + + void foo() { + consume_refcntbl(provide_ref_counted().ptr()); + // no warning + } +} + +namespace methods { + struct Consumer { + void consume_ptr(CheckedObj* ptr); + void consume_ref(const CheckedObj& ref); + }; + + void foo() { + Consumer c; + + c.consume_ptr(provide()); + // expected-warning@-1{{Call argument for parameter 'ptr' is unchecked and unsafe}} + c.consume_ref(*provide()); + // expected-warning@-1{{Call argument for parameter 'ref' is unchecked and unsafe}} + } + + void foo2() { + struct Consumer { + void consume(CheckedObj*) { some_function(); } + void whatever() { + consume(provide()); + // expected-warning@-1{{Call argument is unchecked and unsafe}} + } + }; + } + + void foo3() { + struct Consumer { + void consume(CheckedObj*) { some_function(); } + void whatever() { + this->consume(provide()); + // expected-warning@-1{{Call argument is unchecked and unsafe}} + } + }; + } +} + +namespace casts { + CheckedObj* downcast(CheckedObj*); + + void foo() { + consume_refcntbl(provide()); + // expected-warning@-1{{Call argument is unchecked and unsafe}} + + consume_refcntbl(static_cast<CheckedObj*>(provide())); + // expected-warning@-1{{Call argument is unchecked and unsafe}} + + consume_refcntbl(dynamic_cast<CheckedObj*>(provide())); + // expected-warning@-1{{Call argument is unchecked and unsafe}} + + consume_refcntbl(const_cast<CheckedObj*>(provide())); + // expected-warning@-1{{Call argument is unchecked and unsafe}} + + consume_refcntbl(reinterpret_cast<CheckedObj*>(provide())); + // expected-warning@-1{{Call argument is unchecked and unsafe}} + + consume_refcntbl(downcast(provide())); + // expected-warning@-1{{Call argument is unchecked and unsafe}} + + consume_refcntbl( + static_cast<CheckedObj*>( + downcast( + static_cast<CheckedObj*>( + provide() + ) + ) + ) + ); + // expected-warning@-8{{Call argument is unchecked and unsafe}} + } +} + +namespace null_ptr { + void foo_ref() { + consume_refcntbl(nullptr); + consume_refcntbl(0); + } +} + +namespace ref_counted_lookalike { + struct Decoy { + CheckedObj* get() { return nullptr; } + }; + + void foo() { + Decoy D; + + consume_refcntbl(D.get()); + // expected-warning@-1{{Call argument is unchecked and unsafe}} + } +} + +namespace Ref_to_reference_conversion_operator { + template<typename T> struct Ref { + Ref() = default; + Ref(T*) { } + T* get() { return nullptr; } + operator T& () { return t; } + T t; + }; + + void consume_ref(CheckedObj&) {} + + void foo() { + CheckedRef<CheckedObj> bar; + consume_ref(bar); + } +} + +namespace param_formarding_function { + void consume_ref_countable_ref(CheckedObj&); + void consume_ref_countable_ptr(CheckedObj*); + + namespace ptr { + void foo(CheckedObj* param) { + consume_ref_countable_ptr(param); + } + } + + namespace ref { + void foo(CheckedObj& param) { + consume_ref_countable_ref(param); + } + } + + namespace ref_deref_operators { + void foo_ref(CheckedObj& param) { + consume_ref_countable_ptr(¶m); + } + + void foo_ptr(CheckedObj* param) { + consume_ref_countable_ref(*param); + } + } + + namespace casts { + + CheckedObj* downcast(CheckedObj*) { return nullptr; } + + template<class T> + T* bitwise_cast(T*) { return nullptr; } + + void foo(CheckedObj* param) { + consume_ref_countable_ptr(downcast(param)); + consume_ref_countable_ptr(bitwise_cast(param)); + } + } +} + +namespace param_formarding_lambda { + auto consume_ref_countable_ref = [](CheckedObj&) { some_function(); }; + auto consume_ref_countable_ptr = [](CheckedObj*) { some_function(); }; + + namespace ptr { + void foo(CheckedObj* param) { + consume_ref_countable_ptr(param); + } + } + + namespace ref { + void foo(CheckedObj& param) { + consume_ref_countable_ref(param); + } + } + + namespace ref_deref_operators { + void foo_ref(CheckedObj& param) { + consume_ref_countable_ptr(¶m); + } + + void foo_ptr(CheckedObj* param) { + consume_ref_countable_ref(*param); + } + } + + namespace casts { + + CheckedObj* downcast(CheckedObj*) { return nullptr; } + + template<class T> + T* bitwise_cast(T*) { return nullptr; } + + void foo(CheckedObj* param) { + consume_ref_countable_ptr(downcast(param)); + consume_ref_countable_ptr(bitwise_cast(param)); + } + } +} + +namespace param_forwarding_method { + struct methodclass { + void consume_ref_countable_ref(CheckedObj&) {}; + static void consume_ref_countable_ptr(CheckedObj*) {}; + }; + + namespace ptr { + void foo(CheckedObj* param) { + methodclass::consume_ref_countable_ptr(param); + } + } + + namespace ref { + void foo(CheckedObj& param) { + methodclass mc; + mc.consume_ref_countable_ref(param); + } + } + + namespace ref_deref_operators { + void foo_ref(CheckedObj& param) { + methodclass::consume_ref_countable_ptr(¶m); + } + + void foo_ptr(CheckedObj* param) { + methodclass mc; + mc.consume_ref_countable_ref(*param); + } + } + + namespace casts { + + CheckedObj* downcast(CheckedObj*) { return nullptr; } + + template<class T> + T* bitwise_cast(T*) { return nullptr; } + + void foo(CheckedObj* param) { + methodclass::consume_ref_countable_ptr(downcast(param)); + methodclass::consume_ref_countable_ptr(bitwise_cast(param)); + } + } +} + +namespace downcast { + void consume_ref_countable(CheckedObj*) {} + CheckedObj* downcast(CheckedObj*) { return nullptr; } + + void foo() { + CheckedPtr<CheckedObj> bar; + consume_ref_countable( downcast(bar.get()) ); + } +} + +namespace string_impl { + struct String { + CheckedObj* impl() { return nullptr; } + }; + + struct AtomString { + CheckedObj rc; + CheckedObj& impl() { return rc; } + }; + + void consume_ptr(CheckedObj*) {} + void consume_ref(CheckedObj&) {} + + namespace simple { + void foo() { + String s; + AtomString as; + consume_ptr(s.impl()); + consume_ref(as.impl()); + } + } +} + +namespace default_arg { + CheckedObj* global; + + void function_with_default_arg(CheckedObj* param = global); + // expected-warning@-1{{Call argument for parameter 'param' is unchecked and unsafe}} + + void foo() { + function_with_default_arg(); + } +} + +namespace cxx_member_func { + CheckedRef<CheckedObj> provideProtected(); + void foo() { + provide()->trivial(); + provide()->method(); + // expected-warning@-1{{Call argument for 'this' parameter is unchecked and unsafe}} + provideProtected()->method(); + (provideProtected())->method(); + }; +} + +namespace cxx_member_operator_call { + // The hidden this-pointer argument without a corresponding parameter caused couple bugs in parameter <-> argument attribution. + struct Foo { + Foo& operator+(CheckedObj* bad); + friend Foo& operator-(Foo& lhs, CheckedObj* bad); + void operator()(CheckedObj* bad); + }; + + CheckedObj* global; + + void foo() { + Foo f; + f + global; + // expected-warning@-1{{Call argument for parameter 'bad' is unchecked and unsafe}} + f - global; + // expected-warning@-1{{Call argument for parameter 'bad' is unchecked and unsafe}} + f(global); + // expected-warning@-1{{Call argument for parameter 'bad' is unchecked and unsafe}} + } +} + +namespace call_with_ptr_on_ref { + CheckedRef<CheckedObj> provideProtected(); + void bar(CheckedObj* bad); + bool baz(); + void foo(bool v) { + bar(v ? nullptr : provideProtected().ptr()); + bar(baz() ? provideProtected().ptr() : nullptr); + bar(v ? provide() : provideProtected().ptr()); + // expected-warning@-1{{Call argument for parameter 'bad' is unchecked and unsafe}} + bar(v ? provideProtected().ptr() : provide()); + // expected-warning@-1{{Call argument for parameter 'bad' is unchecked and unsafe}} + } +} + +namespace call_with_explicit_temporary_obj { + void foo() { + CheckedRef { *provide() }->method(); + CheckedPtr { provide() }->method(); + } +} diff --git a/clang/test/Analysis/Checkers/WebKit/call-args-checked-return-value.cpp b/clang/test/Analysis/Checkers/WebKit/call-args-checked-return-value.cpp new file mode 100644 index 00000000000000..33f7f8272a2560 --- /dev/null +++ b/clang/test/Analysis/Checkers/WebKit/call-args-checked-return-value.cpp @@ -0,0 +1,25 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.UncheckedCallArgsChecker -verify %s +// expected-no-diagnostics + +#include "mock-types.h" + +class Checkable { +public: + void ref() const; + void deref() const; +}; + +class Object { +public: + void ref() const; + void deref() const; + void someFunction(Checkable&); +}; + +RefPtr<Object> object(); +RefPtr<Checkable> protectedTargetObject(); + +void testFunction() { + if (RefPtr obj = object()) + obj->someFunction(*protectedTargetObject()); +} diff --git a/llvm/utils/gn/secondary/clang/lib/StaticAnalyzer/Checkers/BUILD.gn b/llvm/utils/gn/secondary/clang/lib/StaticAnalyzer/Checkers/BUILD.gn index 6a4c92390d773b..8579141aa7f99c 100644 --- a/llvm/utils/gn/secondary/clang/lib/StaticAnalyzer/Checkers/BUILD.gn +++ b/llvm/utils/gn/secondary/clang/lib/StaticAnalyzer/Checkers/BUILD.gn @@ -142,7 +142,7 @@ static_library("Checkers") { "WebKit/PtrTypesSemantics.cpp", "WebKit/RawPtrRefMemberChecker.cpp", "WebKit/RefCntblBaseVirtualDtorChecker.cpp", - "WebKit/UncountedCallArgsChecker.cpp", + "WebKit/RawPtrRefCallArgsChecker.cpp", "WebKit/UncountedLambdaCapturesChecker.cpp", "WebKit/RawPtrRefLocalVarsChecker.cpp", "cert/InvalidPtrChecker.cpp", _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits