Skip to content

Commit 21be818

Browse files
authored
[analyzer] Support determining origins in a conditional operator in WebKit checkers. (#91143)
This PR adds the support for determining the origin of a pointer in a conditional operator. Because such an expression can have two distinct origins each of which needs to be visited, this PR refactors tryToFindPtrOrigin to take a callback instead of returning a pair. The callback is called for the second operand and the third operand of the conditioanl operator (i.e. E2 and E3 in E1 ? E2 : E3). Also treat nullptr and integer literal as safe pointer origins in the local variable checker.
1 parent 181e2e8 commit 21be818

File tree

6 files changed

+113
-62
lines changed

6 files changed

+113
-62
lines changed

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

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616

1717
namespace clang {
1818

19-
std::pair<const Expr *, bool>
20-
tryToFindPtrOrigin(const Expr *E, bool StopAtFirstRefCountedObj) {
19+
bool tryToFindPtrOrigin(
20+
const Expr *E, bool StopAtFirstRefCountedObj,
21+
std::function<bool(const clang::Expr *, bool)> callback) {
2122
while (E) {
2223
if (auto *tempExpr = dyn_cast<MaterializeTemporaryExpr>(E)) {
2324
E = tempExpr->getSubExpr();
@@ -31,12 +32,18 @@ tryToFindPtrOrigin(const Expr *E, bool StopAtFirstRefCountedObj) {
3132
E = tempExpr->getSubExpr();
3233
continue;
3334
}
35+
if (auto *Expr = dyn_cast<ConditionalOperator>(E)) {
36+
return tryToFindPtrOrigin(Expr->getTrueExpr(), StopAtFirstRefCountedObj,
37+
callback) &&
38+
tryToFindPtrOrigin(Expr->getFalseExpr(), StopAtFirstRefCountedObj,
39+
callback);
40+
}
3441
if (auto *cast = dyn_cast<CastExpr>(E)) {
3542
if (StopAtFirstRefCountedObj) {
3643
if (auto *ConversionFunc =
3744
dyn_cast_or_null<FunctionDecl>(cast->getConversionFunction())) {
3845
if (isCtorOfRefCounted(ConversionFunc))
39-
return {E, true};
46+
return callback(E, true);
4047
}
4148
}
4249
// FIXME: This can give false "origin" that would lead to false negatives
@@ -51,7 +58,7 @@ tryToFindPtrOrigin(const Expr *E, bool StopAtFirstRefCountedObj) {
5158
if (IsGetterOfRefCt && *IsGetterOfRefCt) {
5259
E = memberCall->getImplicitObjectArgument();
5360
if (StopAtFirstRefCountedObj) {
54-
return {E, true};
61+
return callback(E, true);
5562
}
5663
continue;
5764
}
@@ -68,17 +75,17 @@ tryToFindPtrOrigin(const Expr *E, bool StopAtFirstRefCountedObj) {
6875
if (auto *callee = call->getDirectCallee()) {
6976
if (isCtorOfRefCounted(callee)) {
7077
if (StopAtFirstRefCountedObj)
71-
return {E, true};
78+
return callback(E, true);
7279

7380
E = call->getArg(0);
7481
continue;
7582
}
7683

7784
if (isReturnValueRefCounted(callee))
78-
return {E, true};
85+
return callback(E, true);
7986

8087
if (isSingleton(callee))
81-
return {E, true};
88+
return callback(E, true);
8289

8390
if (isPtrConversion(callee)) {
8491
E = call->getArg(0);
@@ -95,7 +102,7 @@ tryToFindPtrOrigin(const Expr *E, bool StopAtFirstRefCountedObj) {
95102
break;
96103
}
97104
// Some other expression.
98-
return {E, false};
105+
return callback(E, false);
99106
}
100107

101108
bool isASafeCallArg(const Expr *E) {

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "llvm/ADT/APInt.h"
1414
#include "llvm/Support/Casting.h"
1515

16+
#include <functional>
1617
#include <string>
1718
#include <utility>
1819

@@ -48,10 +49,12 @@ class Expr;
4849
/// represents ref-counted object during the traversal we return relevant
4950
/// sub-expression and true.
5051
///
51-
/// \returns subexpression that we traversed to and if \p
52-
/// StopAtFirstRefCountedObj is true we also return whether we stopped early.
53-
std::pair<const clang::Expr *, bool>
54-
tryToFindPtrOrigin(const clang::Expr *E, bool StopAtFirstRefCountedObj);
52+
/// Calls \p callback with the subexpression that we traversed to and if \p
53+
/// StopAtFirstRefCountedObj is true we also specify whether we stopped early.
54+
/// Returns false if any of calls to callbacks returned false. Otherwise true.
55+
bool tryToFindPtrOrigin(
56+
const clang::Expr *E, bool StopAtFirstRefCountedObj,
57+
std::function<bool(const clang::Expr *, bool)> callback);
5558

5659
/// For \p E referring to a ref-countable/-counted pointer/reference we return
5760
/// whether it's a safe call argument. Examples: function parameter or

clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedCallArgsChecker.cpp

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -126,25 +126,23 @@ class UncountedCallArgsChecker
126126
}
127127

128128
bool isPtrOriginSafe(const Expr *Arg) const {
129-
std::pair<const clang::Expr *, bool> ArgOrigin =
130-
tryToFindPtrOrigin(Arg, true);
131-
132-
// Temporary ref-counted object created as part of the call argument
133-
// would outlive the call.
134-
if (ArgOrigin.second)
135-
return true;
136-
137-
if (isa<CXXNullPtrLiteralExpr>(ArgOrigin.first)) {
138-
// foo(nullptr)
139-
return true;
140-
}
141-
if (isa<IntegerLiteral>(ArgOrigin.first)) {
142-
// FIXME: Check the value.
143-
// foo(NULL)
144-
return true;
145-
}
146-
147-
return isASafeCallArg(ArgOrigin.first);
129+
return tryToFindPtrOrigin(Arg, /*StopAtFirstRefCountedObj=*/true,
130+
[](const clang::Expr *ArgOrigin, bool IsSafe) {
131+
if (IsSafe)
132+
return true;
133+
if (isa<CXXNullPtrLiteralExpr>(ArgOrigin)) {
134+
// foo(nullptr)
135+
return true;
136+
}
137+
if (isa<IntegerLiteral>(ArgOrigin)) {
138+
// FIXME: Check the value.
139+
// foo(NULL)
140+
return true;
141+
}
142+
if (isASafeCallArg(ArgOrigin))
143+
return true;
144+
return false;
145+
});
148146
}
149147

150148
bool shouldSkipCall(const CallExpr *CE) const {

clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLocalVarsChecker.cpp

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -188,39 +188,50 @@ class UncountedLocalVarsChecker
188188
if (!InitExpr)
189189
return; // FIXME: later on we might warn on uninitialized vars too
190190

191-
const clang::Expr *const InitArgOrigin =
192-
tryToFindPtrOrigin(InitExpr, /*StopAtFirstRefCountedObj=*/false)
193-
.first;
194-
if (!InitArgOrigin)
191+
if (tryToFindPtrOrigin(
192+
InitExpr, /*StopAtFirstRefCountedObj=*/false,
193+
[&](const clang::Expr *InitArgOrigin, bool IsSafe) {
194+
if (!InitArgOrigin)
195+
return true;
196+
197+
if (isa<CXXThisExpr>(InitArgOrigin))
198+
return true;
199+
200+
if (isa<CXXNullPtrLiteralExpr>(InitArgOrigin))
201+
return true;
202+
203+
if (isa<IntegerLiteral>(InitArgOrigin))
204+
return true;
205+
206+
if (auto *Ref = llvm::dyn_cast<DeclRefExpr>(InitArgOrigin)) {
207+
if (auto *MaybeGuardian =
208+
dyn_cast_or_null<VarDecl>(Ref->getFoundDecl())) {
209+
const auto *MaybeGuardianArgType =
210+
MaybeGuardian->getType().getTypePtr();
211+
if (MaybeGuardianArgType) {
212+
const CXXRecordDecl *const MaybeGuardianArgCXXRecord =
213+
MaybeGuardianArgType->getAsCXXRecordDecl();
214+
if (MaybeGuardianArgCXXRecord) {
215+
if (MaybeGuardian->isLocalVarDecl() &&
216+
(isRefCounted(MaybeGuardianArgCXXRecord) ||
217+
isRefcountedStringsHack(MaybeGuardian)) &&
218+
isGuardedScopeEmbeddedInGuardianScope(
219+
V, MaybeGuardian))
220+
return true;
221+
}
222+
}
223+
224+
// Parameters are guaranteed to be safe for the duration of
225+
// the call by another checker.
226+
if (isa<ParmVarDecl>(MaybeGuardian))
227+
return true;
228+
}
229+
}
230+
231+
return false;
232+
}))
195233
return;
196234

197-
if (isa<CXXThisExpr>(InitArgOrigin))
198-
return;
199-
200-
if (auto *Ref = llvm::dyn_cast<DeclRefExpr>(InitArgOrigin)) {
201-
if (auto *MaybeGuardian =
202-
dyn_cast_or_null<VarDecl>(Ref->getFoundDecl())) {
203-
const auto *MaybeGuardianArgType =
204-
MaybeGuardian->getType().getTypePtr();
205-
if (MaybeGuardianArgType) {
206-
const CXXRecordDecl *const MaybeGuardianArgCXXRecord =
207-
MaybeGuardianArgType->getAsCXXRecordDecl();
208-
if (MaybeGuardianArgCXXRecord) {
209-
if (MaybeGuardian->isLocalVarDecl() &&
210-
(isRefCounted(MaybeGuardianArgCXXRecord) ||
211-
isRefcountedStringsHack(MaybeGuardian)) &&
212-
isGuardedScopeEmbeddedInGuardianScope(V, MaybeGuardian))
213-
return;
214-
}
215-
}
216-
217-
// Parameters are guaranteed to be safe for the duration of the call
218-
// by another checker.
219-
if (isa<ParmVarDecl>(MaybeGuardian))
220-
return;
221-
}
222-
}
223-
224235
reportBug(V);
225236
}
226237
}

clang/test/Analysis/Checkers/WebKit/call-args.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,3 +344,17 @@ namespace cxx_member_operator_call {
344344
// expected-warning@-1{{Call argument for parameter 'bad' is uncounted and unsafe}}
345345
}
346346
}
347+
348+
namespace call_with_ptr_on_ref {
349+
Ref<RefCountable> provideProtected();
350+
void bar(RefCountable* bad);
351+
bool baz();
352+
void foo(bool v) {
353+
bar(v ? nullptr : provideProtected().ptr());
354+
bar(baz() ? provideProtected().ptr() : nullptr);
355+
bar(v ? provide() : provideProtected().ptr());
356+
// expected-warning@-1{{Call argument for parameter 'bad' is uncounted and unsafe}}
357+
bar(v ? provideProtected().ptr() : provide());
358+
// expected-warning@-1{{Call argument for parameter 'bad' is uncounted and unsafe}}
359+
}
360+
}

clang/test/Analysis/Checkers/WebKit/uncounted-local-vars.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,3 +198,21 @@ void system_header() {
198198
}
199199

200200
} // ignore_system_headers
201+
202+
namespace conditional_op {
203+
RefCountable *provide_ref_ctnbl();
204+
bool bar();
205+
206+
void foo() {
207+
RefCountable *a = bar() ? nullptr : provide_ref_ctnbl();
208+
// expected-warning@-1{{Local variable 'a' is uncounted and unsafe [alpha.webkit.UncountedLocalVarsChecker]}}
209+
RefPtr<RefCountable> b = provide_ref_ctnbl();
210+
{
211+
RefCountable* c = bar() ? nullptr : b.get();
212+
c->method();
213+
RefCountable* d = bar() ? b.get() : nullptr;
214+
d->method();
215+
}
216+
}
217+
218+
} // namespace conditional_op

0 commit comments

Comments
 (0)