llvm-project/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp
Ryosuke Niwa 2c6424e691
[webkit.UncountedLambdaCapturesChecker] Ignore trivial functions and [[clang::noescape]]. (#114897)
This PR makes webkit.UncountedLambdaCapturesChecker ignore trivial
functions as well as the one being passed to an argument with
[[clang::noescape]] attribute. This dramatically reduces the false
positive rate for this checker.

To do this, this PR replaces VisitLambdaExpr in favor of checking
lambdas via VisitDeclRefExpr and VisitCallExpr. The idea is that if a
lambda is defined but never called or stored somewhere, then capturing
whatever variable in such a lambda is harmless.

VisitCallExpr explicitly looks for direct invocation of lambdas and
registers its DeclRefExpr to be ignored in VisitDeclRefExpr. If a lambda
is being passed to a function, it checks whether its argument is
annotated with [[clang::noescape]]. If it's not annotated such, it
checks captures for their safety.

Because WTF::switchOn could not be annotated with [[clang::noescape]] as
function type parameters are variadic template function so we hard-code
this function into the checker.

In order to check whether "this" pointer is ref-counted type or not, we
override TraverseDecl and record the most recent method's declaration.

In addition, this PR fixes a bug in isUnsafePtr that it was erroneously
checking whether std::nullopt was returned by isUncounted and
isUnchecked as opposed to the actual boolean value.

Finally, this PR also converts the accompanying test to use -verify and
adds a bunch of tests.
2024-11-12 09:46:28 -08:00

177 lines
5.0 KiB
C++

// RUN: %clang_analyze_cc1 -analyzer-checker=webkit.UncountedLambdaCapturesChecker -verify %s
struct RefCountable {
void ref() {}
void deref() {}
void method();
void constMethod() const;
int trivial() { return 123; }
RefCountable* next();
};
RefCountable* make_obj();
void someFunction();
template <typename Callback> void call(Callback callback) {
someFunction();
callback();
}
void raw_ptr() {
RefCountable* ref_countable = make_obj();
auto foo1 = [ref_countable](){
// expected-warning@-1{{Captured raw-pointer 'ref_countable' to ref-counted type or CheckedPtr-capable type is unsafe [webkit.UncountedLambdaCapturesChecker]}}
ref_countable->method();
};
auto foo2 = [&ref_countable](){
// expected-warning@-1{{Captured raw-pointer 'ref_countable' to ref-counted type or CheckedPtr-capable type is unsafe [webkit.UncountedLambdaCapturesChecker]}}
ref_countable->method();
};
auto foo3 = [&](){
ref_countable->method();
// expected-warning@-1{{Implicitly captured raw-pointer 'ref_countable' to ref-counted type or CheckedPtr-capable type is unsafe [webkit.UncountedLambdaCapturesChecker]}}
ref_countable = nullptr;
};
auto foo4 = [=](){
ref_countable->method();
// expected-warning@-1{{Implicitly captured raw-pointer 'ref_countable' to ref-counted type or CheckedPtr-capable type is unsafe [webkit.UncountedLambdaCapturesChecker]}}
};
call(foo1);
call(foo2);
call(foo3);
call(foo4);
// Confirm that the checker respects [[clang::suppress]].
RefCountable* suppressed_ref_countable = nullptr;
[[clang::suppress]] auto foo5 = [suppressed_ref_countable](){};
// no warning.
call(foo5);
}
void references() {
RefCountable automatic;
RefCountable& ref_countable_ref = automatic;
auto foo1 = [ref_countable_ref](){ ref_countable_ref.constMethod(); };
// expected-warning@-1{{Captured reference 'ref_countable_ref' to ref-counted type or CheckedPtr-capable type is unsafe [webkit.UncountedLambdaCapturesChecker]}}
auto foo2 = [&ref_countable_ref](){ ref_countable_ref.method(); };
// expected-warning@-1{{Captured reference 'ref_countable_ref' to ref-counted type or CheckedPtr-capable type is unsafe [webkit.UncountedLambdaCapturesChecker]}}
auto foo3 = [&](){ ref_countable_ref.method(); };
// expected-warning@-1{{Implicitly captured reference 'ref_countable_ref' to ref-counted type or CheckedPtr-capable type is unsafe [webkit.UncountedLambdaCapturesChecker]}}
auto foo4 = [=](){ ref_countable_ref.constMethod(); };
// expected-warning@-1{{Implicitly captured reference 'ref_countable_ref' to ref-counted type or CheckedPtr-capable type is unsafe [webkit.UncountedLambdaCapturesChecker]}}
call(foo1);
call(foo2);
call(foo3);
call(foo4);
}
void quiet() {
// This code is not expected to trigger any warnings.
{
RefCountable automatic;
RefCountable &ref_countable_ref = automatic;
}
auto foo3 = [&]() {};
auto foo4 = [=]() {};
call(foo3);
call(foo4);
RefCountable *ref_countable = nullptr;
}
template <typename Callback>
void map(RefCountable* start, [[clang::noescape]] Callback&& callback)
{
while (start) {
callback(*start);
start = start->next();
}
}
template <typename Callback1, typename Callback2>
void doubleMap(RefCountable* start, [[clang::noescape]] Callback1&& callback1, Callback2&& callback2)
{
while (start) {
callback1(*start);
callback2(*start);
start = start->next();
}
}
void noescape_lambda() {
RefCountable* someObj = make_obj();
RefCountable* otherObj = make_obj();
map(make_obj(), [&](RefCountable& obj) {
otherObj->method();
});
doubleMap(make_obj(), [&](RefCountable& obj) {
otherObj->method();
}, [&](RefCountable& obj) {
otherObj->method();
// expected-warning@-1{{Implicitly captured raw-pointer 'otherObj' to ref-counted type or CheckedPtr-capable type is unsafe [webkit.UncountedLambdaCapturesChecker]}}
});
([&] {
someObj->method();
})();
}
void lambda_capture_param(RefCountable* obj) {
auto someLambda = [&] {
obj->method();
};
someLambda();
someLambda();
}
struct RefCountableWithLambdaCapturingThis {
void ref() const;
void deref() const;
void nonTrivial();
void method_captures_this_safe() {
auto lambda = [&]() {
nonTrivial();
};
lambda();
}
void method_captures_this_unsafe() {
auto lambda = [&]() {
nonTrivial();
// expected-warning@-1{{Implicitly captured raw-pointer 'this' to ref-counted type or CheckedPtr-capable type is unsafe [webkit.UncountedLambdaCapturesChecker]}}
};
call(lambda);
}
};
struct NonRefCountableWithLambdaCapturingThis {
void nonTrivial();
void method_captures_this_safe() {
auto lambda = [&]() {
nonTrivial();
};
lambda();
}
void method_captures_this_unsafe() {
auto lambda = [&]() {
nonTrivial();
};
call(lambda);
}
};
void trivial_lambda() {
RefCountable* ref_countable = make_obj();
auto trivial_lambda = [&]() {
return ref_countable->trivial();
};
trivial_lambda();
}