Skip to content

Commit a7eb369

Browse files
committed
[Analyzer][WebKit] UncountedCallArgsChecker
Differential Revision: https://reviews.llvm.org/D77179
1 parent 3a726bc commit a7eb369

File tree

7 files changed

+753
-3
lines changed

7 files changed

+753
-3
lines changed

clang/docs/analyzer/checkers.rst

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1407,7 +1407,7 @@ Ref-counted types hold their ref-countable data by a raw pointer and allow impli
14071407
.. _webkit-WebKitNoUncountedMemberChecker:
14081408
14091409
webkit.WebKitNoUncountedMemberChecker
1410-
""""""""""""""""""""""""""""""""""""
1410+
"""""""""""""""""""""""""""""""""""""
14111411
Raw pointers and references to uncounted types can't be used as class members. Only ref-counted types are allowed.
14121412
14131413
.. code-block:: cpp
@@ -2377,6 +2377,99 @@ Check for non-determinism caused by sorting of pointers.
23772377
}
23782378
23792379
2380+
alpha.WebKit
2381+
^^^^^^^^^^^^
2382+
2383+
.. _alpha-webkit-UncountedCallArgsChecker:
2384+
2385+
alpha.webkit.UncountedCallArgsChecker
2386+
"""""""""""""""""""""""""""""""""""""
2387+
The goal of this rule is to make sure that lifetime of any dynamically allocated ref-countable object passed as a call argument spans past the end of the call. This applies to call to any function, method, lambda, function pointer or functor. Ref-countable types aren't supposed to be allocated on stack so we check arguments for parameters of raw pointers and references to uncounted types.
2388+
2389+
Here are some examples of situations that we warn about as they *might* be potentially unsafe. The logic is that either we're able to guarantee that an argument is safe or it's considered if not a bug then bug-prone.
2390+
2391+
.. code-block:: cpp
2392+
2393+
RefCountable* provide_uncounted();
2394+
void consume(RefCountable*);
2395+
2396+
// In these cases we can't make sure callee won't directly or indirectly call `deref()` on the argument which could make it unsafe from such point until the end of the call.
2397+
2398+
void foo1() {
2399+
consume(provide_uncounted()); // warn
2400+
}
2401+
2402+
void foo2() {
2403+
RefCountable* uncounted = provide_uncounted();
2404+
consume(uncounted); // warn
2405+
}
2406+
2407+
Although we are enforcing member variables to be ref-counted by `webkit.WebKitNoUncountedMemberChecker` any method of the same class still has unrestricted access to these. Since from a caller's perspective we can't guarantee a particular member won't get modified by callee (directly or indirectly) we don't consider values obtained from members safe.
2408+
2409+
Note: It's likely this heuristic could be made more precise with fewer false positives - for example calls to free functions that don't have any parameter other than the pointer should be safe as the callee won't be able to tamper with the member unless it's a global variable.
2410+
2411+
.. code-block:: cpp
2412+
2413+
struct Foo {
2414+
RefPtr<RefCountable> member;
2415+
void consume(RefCountable*) { /* ... */ }
2416+
void bugprone() {
2417+
consume(member.get()); // warn
2418+
}
2419+
};
2420+
2421+
The implementation of this rule is a heuristic - we define a whitelist of kinds of values that are considered safe to be passed as arguments. If we can't prove an argument is safe it's considered an error.
2422+
2423+
Allowed kinds of arguments:
2424+
2425+
- values obtained from ref-counted objects (including temporaries as those survive the call too)
2426+
2427+
.. code-block:: cpp
2428+
2429+
RefCountable* provide_uncounted();
2430+
void consume(RefCountable*);
2431+
2432+
void foo() {
2433+
RefPtr<RefCountable> rc = makeRef(provide_uncounted());
2434+
consume(rc.get()); // ok
2435+
consume(makeRef(provide_uncounted()).get()); // ok
2436+
}
2437+
2438+
- forwarding uncounted arguments from caller to callee
2439+
2440+
.. code-block:: cpp
2441+
2442+
void foo(RefCountable& a) {
2443+
bar(a); // ok
2444+
}
2445+
2446+
Caller of ``foo()`` is responsible for ``a``'s lifetime.
2447+
2448+
- ``this`` pointer
2449+
2450+
.. code-block:: cpp
2451+
2452+
void Foo::foo() {
2453+
baz(this); // ok
2454+
}
2455+
2456+
Caller of ``foo()`` is responsible for keeping the memory pointed to by ``this`` pointer safe.
2457+
2458+
- constants
2459+
2460+
.. code-block:: cpp
2461+
2462+
foo(nullptr, NULL, 0); // ok
2463+
2464+
We also define a set of safe transformations which if passed a safe value as an input provide (usually it's the return value) a safe value (or an object that provides safe values). This is also a heuristic.
2465+
2466+
- constructors of ref-counted types (including factory methods)
2467+
- getters of ref-counted types
2468+
- member overloaded operators
2469+
- casts
2470+
- unary operators like ``&`` or ``*``
2471+
2472+
23802473
Debug Checkers
23812474
---------------
23822475

clang/include/clang/StaticAnalyzer/Checkers/Checkers.td

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1628,4 +1628,13 @@ def RefCntblBaseVirtualDtorChecker : Checker<"RefCntblBaseVirtualDtor">,
16281628
def WebKitNoUncountedMemberChecker : Checker<"WebKitNoUncountedMemberChecker">,
16291629
HelpText<"Check for no uncounted member variables.">,
16301630
Documentation<HasDocumentation>;
1631+
16311632
} // end webkit
1633+
1634+
let ParentPackage = WebKitAlpha in {
1635+
1636+
def UncountedCallArgsChecker : Checker<"UncountedCallArgsChecker">,
1637+
HelpText<"Check uncounted call arguments.">,
1638+
Documentation<HasDocumentation>;
1639+
1640+
} // end alpha.webkit

clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,10 @@ add_clang_library(clangStaticAnalyzerCheckers
122122
ValistChecker.cpp
123123
VirtualCallChecker.cpp
124124
WebKit/NoUncountedMembersChecker.cpp
125+
WebKit/ASTUtils.cpp
125126
WebKit/PtrTypesSemantics.cpp
126127
WebKit/RefCntblBaseVirtualDtorChecker.cpp
128+
WebKit/UncountedCallArgsChecker.cpp
127129

128130
LINK_LIBS
129131
clangAST
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
//=======- ASTUtils.cpp ------------------------------------------*- C++ -*-==//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "ASTUtils.h"
10+
#include "PtrTypesSemantics.h"
11+
#include "clang/AST/CXXInheritance.h"
12+
#include "clang/AST/Decl.h"
13+
#include "clang/AST/DeclCXX.h"
14+
#include "clang/AST/ExprCXX.h"
15+
16+
using llvm::Optional;
17+
namespace clang {
18+
19+
std::pair<const Expr *, bool>
20+
tryToFindPtrOrigin(const Expr *E, bool StopAtFirstRefCountedObj) {
21+
while (E) {
22+
if (auto *cast = dyn_cast<CastExpr>(E)) {
23+
if (StopAtFirstRefCountedObj) {
24+
if (auto *ConversionFunc =
25+
dyn_cast_or_null<FunctionDecl>(cast->getConversionFunction())) {
26+
if (isCtorOfRefCounted(ConversionFunc))
27+
return {E, true};
28+
}
29+
}
30+
// FIXME: This can give false "origin" that would lead to false negatives
31+
// in checkers. See https://reviews.llvm.org/D37023 for reference.
32+
E = cast->getSubExpr();
33+
continue;
34+
}
35+
if (auto *call = dyn_cast<CallExpr>(E)) {
36+
if (auto *memberCall = dyn_cast<CXXMemberCallExpr>(call)) {
37+
if (isGetterOfRefCounted(memberCall->getMethodDecl())) {
38+
E = memberCall->getImplicitObjectArgument();
39+
if (StopAtFirstRefCountedObj) {
40+
return {E, true};
41+
}
42+
continue;
43+
}
44+
}
45+
46+
if (auto *operatorCall = dyn_cast<CXXOperatorCallExpr>(E)) {
47+
if (operatorCall->getNumArgs() == 1) {
48+
E = operatorCall->getArg(0);
49+
continue;
50+
}
51+
}
52+
53+
if (auto *callee = call->getDirectCallee()) {
54+
if (isCtorOfRefCounted(callee)) {
55+
if (StopAtFirstRefCountedObj)
56+
return {E, true};
57+
58+
E = call->getArg(0);
59+
continue;
60+
}
61+
62+
if (isPtrConversion(callee)) {
63+
E = call->getArg(0);
64+
continue;
65+
}
66+
}
67+
}
68+
if (auto *unaryOp = dyn_cast<UnaryOperator>(E)) {
69+
// FIXME: Currently accepts ANY unary operator. Is it OK?
70+
E = unaryOp->getSubExpr();
71+
continue;
72+
}
73+
74+
break;
75+
}
76+
// Some other expression.
77+
return {E, false};
78+
}
79+
80+
bool isASafeCallArg(const Expr *E) {
81+
assert(E);
82+
if (auto *Ref = dyn_cast<DeclRefExpr>(E)) {
83+
if (auto *D = dyn_cast_or_null<VarDecl>(Ref->getFoundDecl())) {
84+
if (isa<ParmVarDecl>(D) || D->isLocalVarDecl())
85+
return true;
86+
}
87+
}
88+
89+
// TODO: checker for method calls on non-refcounted objects
90+
return isa<CXXThisExpr>(E);
91+
}
92+
93+
} // namespace clang

clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,23 @@ class FunctionDecl;
2323
class CXXMethodDecl;
2424
class Expr;
2525

26+
/// This function de-facto defines a set of transformations that we consider
27+
/// safe (in heuristical sense). These transformation if passed a safe value as
28+
/// an input should provide a safe value (or an object that provides safe
29+
/// values).
30+
///
31+
/// For more context see Static Analyzer checkers documentation - specifically
32+
/// webkit.UncountedCallArgsChecker checker. Whitelist of transformations:
33+
/// - constructors of ref-counted types (including factory methods)
34+
/// - getters of ref-counted types
35+
/// - member overloaded operators
36+
/// - casts
37+
/// - unary operators like ``&`` or ``*``
38+
///
2639
/// If passed expression is of type uncounted pointer/reference we try to find
27-
/// the origin of this pointer. Example: Origin can be a local variable, nullptr
28-
/// constant or this-pointer.
40+
/// the "origin" of the pointer value.
41+
/// Origin can be for example a local variable, nullptr, constant or
42+
/// this-pointer.
2943
///
3044
/// Certain subexpression nodes represent transformations that don't affect
3145
/// where the memory address originates from. We try to traverse such

0 commit comments

Comments
 (0)