Skip to content

Commit

Permalink
Implement base::FunctionRef<R(Args...) wrapper with stricter typing.
Browse files Browse the repository at this point in the history
This provides equivalent functionality to absl::FunctionRef<R(Args...)>,
but disallows implicit conversions between function types, e.g.:
- a base::FunctionRef<void()> cannot bind to functors with a non-void
  return value
- a base::FunctionRef<void(Derived*)> cannot bind to a functor with a
  void(Base*) signature.

As a side effect, this also improves the diagnostic message when trying
to use `base::BindOnce()/base::BindRepeating()` with a capturing lambda.

Change-Id: Ibf341e3b15dba5a8bc0d8999e2ab1d905d8bc0e8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3768723
Reviewed-by: danakj <danakj@chromium.org>
Reviewed-by: K. Moon <kmoon@chromium.org>
Commit-Queue: Daniel Cheng <dcheng@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1028595}
  • Loading branch information
zetafunction authored and Chromium LUCI CQ committed Jul 27, 2022
1 parent e43533a commit 2248b33
Show file tree
Hide file tree
Showing 8 changed files with 333 additions and 42 deletions.
18 changes: 18 additions & 0 deletions PRESUBMIT.py
Original file line number Diff line number Diff line change
Expand Up @@ -1049,6 +1049,24 @@ class BanRule:
False,
(),
),
BanRule(
r'/\babsl::FunctionRef\b',
(
'absl::FunctionRef is banned. Use base::FunctionRef instead.',
),
# Temporarily a warning due to pre-existing usage in the tree.
# TODO(https://crbug.com/1347676): Migrate usage to base::FunctionRef.
False,
[
# base::Bind{Once,Repeating} references absl::FunctionRef to disallow
# interoperability.
r'^base[\\/]bind_internal\.h',
# base::FunctionRef is implemented on top of absl::FunctionRef.
r'^base[\\/]functional[\\/]function_ref.*\..+',
# Not an error in third_party folders.
_THIRD_PARTY_EXCEPT_BLINK,
],
),
)

_BANNED_MOJOM_PATTERNS : Sequence[BanRule] = (
Expand Down
3 changes: 3 additions & 0 deletions base/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,7 @@ mixed_component("base") {
"files/scoped_temp_dir.cc",
"files/scoped_temp_dir.h",
"format_macros.h",
"functional/function_ref.h",
"functional/identity.h",
"functional/invoke.h",
"functional/not_fn.h",
Expand Down Expand Up @@ -3095,6 +3096,7 @@ test("base_unittests") {
"files/memory_mapped_file_unittest.cc",
"files/safe_base_name_unittest.cc",
"files/scoped_temp_dir_unittest.cc",
"functional/function_ref_unittest.cc",
"functional/identity_unittest.cc",
"functional/invoke_unittest.cc",
"functional/not_fn_unittest.cc",
Expand Down Expand Up @@ -3947,6 +3949,7 @@ if (enable_nocompile_tests) {
"containers/enum_set_unittest.nc",
"containers/span_unittest.nc",
"debug/crash_logging_unittest.nc",
"functional/function_ref_unittest.nc",
"memory/raw_ptr_unittest.nc",
"memory/ref_counted_unittest.nc",
"memory/weak_ptr_unittest.nc",
Expand Down
25 changes: 25 additions & 0 deletions base/bind_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
#include "base/memory/raw_scoped_refptr_mismatch_checker.h"
#include "base/memory/weak_ptr.h"
#include "base/notreached.h"
#include "base/types/always_false.h"
#include "build/build_config.h"
#include "third_party/abseil-cpp/absl/functional/function_ref.h"

#if BUILDFLAG(IS_APPLE) && !HAS_FEATURE(objc_arc)
#include "base/mac/scoped_block.h"
Expand Down Expand Up @@ -75,6 +77,9 @@ struct BindUnwrapTraits;
template <typename Functor, typename BoundArgsTuple, typename SFINAE = void>
struct CallbackCancellationTraits;

template <typename Signature>
class FunctionRef;

namespace internal {

template <typename Functor, typename SFINAE = void>
Expand Down Expand Up @@ -1356,6 +1361,26 @@ RepeatingCallback<Signature> BindImpl(RepeatingCallback<Signature> callback) {
return callback;
}

template <template <typename> class CallbackT, typename Signature>
auto BindImpl(absl::FunctionRef<Signature>, ...) {
static_assert(
AlwaysFalse<Signature>,
"base::Bind{Once,Repeating} require strong ownership: non-owning "
"function references may not bound as the functor due to potential "
"lifetime issues.");
return nullptr;
}

template <template <typename> class CallbackT, typename Signature>
auto BindImpl(FunctionRef<Signature>, ...) {
static_assert(
AlwaysFalse<Signature>,
"base::Bind{Once,Repeating} require strong ownership: non-owning "
"function references may not bound as the functor due to potential "
"lifetime issues.");
return nullptr;
}

} // namespace internal

// An injection point to control |this| pointer behavior on a method invocation.
Expand Down
18 changes: 9 additions & 9 deletions base/callback.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
#include "base/callback_forward.h"
#include "base/callback_internal.h"
#include "base/check.h"
#include "base/functional/function_ref.h"
#include "base/notreached.h"
#include "base/types/always_false.h"
#include "third_party/abseil-cpp/absl/functional/function_ref.h"

// -----------------------------------------------------------------------------
// Usage documentation
Expand Down Expand Up @@ -180,19 +180,19 @@ class OnceCallback<R(Args...)> : public internal::CallbackBase {

template <typename Signature>
// NOLINTNEXTLINE(google-explicit-constructor)
operator absl::FunctionRef<Signature>() & {
operator FunctionRef<Signature>() & {
static_assert(
AlwaysFalse<Signature>,
"need to convert a base::OnceCallback to absl::FunctionRef? "
"need to convert a base::OnceCallback to base::FunctionRef? "
"Please bring up this use case on #cxx (Slack) or cxx@chromium.org.");
}

template <typename Signature>
// NOLINTNEXTLINE(google-explicit-constructor)
operator absl::FunctionRef<Signature>() && {
operator FunctionRef<Signature>() && {
static_assert(
AlwaysFalse<Signature>,
"using base::BindOnce() is not necessary with absl::FunctionRef; is it "
"using base::BindOnce() is not necessary with base::FunctionRef; is it "
"possible to use a capturing lambda directly? If not, please bring up "
"this use case on #cxx (Slack) or cxx@chromium.org.");
}
Expand Down Expand Up @@ -310,19 +310,19 @@ class RepeatingCallback<R(Args...)> : public internal::CallbackBaseCopyable {

template <typename Signature>
// NOLINTNEXTLINE(google-explicit-constructor)
operator absl::FunctionRef<Signature>() & {
operator FunctionRef<Signature>() & {
static_assert(
AlwaysFalse<Signature>,
"need to convert a base::RepeatingCallback to absl::FunctionRef? "
"need to convert a base::RepeatingCallback to base::FunctionRef? "
"Please bring up this use case on #cxx (Slack) or cxx@chromium.org.");
}

template <typename Signature>
// NOLINTNEXTLINE(google-explicit-constructor)
operator absl::FunctionRef<Signature>() && {
operator FunctionRef<Signature>() && {
static_assert(
AlwaysFalse<Signature>,
"using base::BindRepeating() is not necessary with absl::FunctionRef; "
"using base::BindRepeating() is not necessary with base::FunctionRef; "
"is it possible to use a capturing lambda directly? If not, please "
"bring up this use case on #cxx (Slack) or cxx@chromium.org.");
}
Expand Down
102 changes: 102 additions & 0 deletions base/functional/function_ref.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright 2022 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef BASE_FUNCTIONAL_FUNCTION_REF_H_
#define BASE_FUNCTIONAL_FUNCTION_REF_H_

#include <type_traits>
#include <utility>

#include "base/bind_internal.h"
#include "third_party/abseil-cpp/absl/base/attributes.h"
#include "third_party/abseil-cpp/absl/functional/function_ref.h"

namespace base {

template <typename Signature>
class FunctionRef;

// A non-owning reference to any invocable object (e.g. function pointer, method
// pointer, functor, lambda, et cetera) suitable for use as a type-erased
// argument to ForEach-style functions or other visitor patterns that:
//
// - do not need to copy or take ownership of the argument
// - synchronously call the invocable that was passed as an argument
//
// `base::FunctionRef` makes no heap allocations: it is trivially copyable and
// should be passed by value.
//
// `base::FunctionRef` has no null/empty state: a `base::FunctionRef` is always
// valid to invoke.
//
// The usual lifetime precautions for other non-owning references types (e.g.
// `base::StringPiece`, `base::span`) also apply to `base::FunctionRef`.
// `base::FunctionRef` should typically be used as an argument; returning a
// `base::FunctionRef` or storing a `base::FunctionRef` as a field is dangerous
// and likely to result in lifetime bugs.
//
// `base::RepeatingCallback` and `base::BindRepeating()` is another common way
// to represent type-erased invocable objects. In contrast, it requires a heap
// allocation and is not trivially copyable. It should be used when there are
// ownership requirements (e.g. partial application of arguments to a function
// stored for asynchronous execution).
//
// Note: this class is very similar to `absl::FunctionRef<R(Args...)>`, but
// disallows implicit conversions between function types, e.g. functors that
// return non-void values may bind to `absl::FunctionRef<void(...)>`:
//
// ```
// void F(absl::FunctionRef<void()>);
// ...
// F([] { return 42; });
// ```
//
// This compiles and silently discards the return value, but with the base
// version, the equivalent snippet:
//
// ```
// void F(base::FunctionRef<void()>);
// ...
// F([] { return 42;});
// ```
//
// will not compile at all.
template <typename R, typename... Args>
class FunctionRef<R(Args...)> {
public:
// `ABSL_ATTRIBUTE_LIFETIME_BOUND` is important since `FunctionRef` retains
// only a reference to `functor`, `functor` must outlive `this`.
template <typename Functor,
typename = std::enable_if_t<std::is_same_v<
R(Args...),
typename internal::MakeFunctorTraits<Functor>::RunType>>>
// NOLINTNEXTLINE(google-explicit-constructor)
FunctionRef(const Functor& functor ABSL_ATTRIBUTE_LIFETIME_BOUND)
: wrapped_func_ref_(functor) {}

// Null FunctionRefs are not allowed.
FunctionRef() = delete;

FunctionRef(const FunctionRef&) = default;
// Reduce the likelihood of lifetime bugs by disallowing assignment.
FunctionRef& operator=(const FunctionRef&) = delete;

R operator()(Args... args) const {
return wrapped_func_ref_(std::forward<Args>(args)...);
}

absl::FunctionRef<R(Args...)> ToAbsl() const { return wrapped_func_ref_; }

// In Chrome, converting to `absl::FunctionRef` should be explicitly done
// through `ToAbsl()`.
template <typename Signature>
operator absl::FunctionRef<Signature>() = delete;

private:
absl::FunctionRef<R(Args...)> wrapped_func_ref_;
};

} // namespace base

#endif // BASE_FUNCTIONAL_FUNCTION_REF_H_
71 changes: 71 additions & 0 deletions base/functional/function_ref_unittest.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright 2022 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/functional/function_ref.h"

#include "testing/gtest/include/gtest/gtest.h"

namespace base {

namespace {

char Moo(float) {
return 'a';
}

struct C {
long Method() { return value; }
long value;
};

} // namespace

TEST(FunctionRefTest, FreeFunction) {
[](FunctionRef<char(float)> ref) { EXPECT_EQ('a', ref(1.0)); }(&Moo);
}

TEST(FunctionRefTest, Method) {
[](FunctionRef<long(C*)> ref) {
C c = {.value = 25L};
EXPECT_EQ(25L, ref(&c));
}(&C::Method);
}

TEST(FunctionRefTest, Lambda) {
int x = 3;
auto lambda = [&x]() { return x; };
[](FunctionRef<int()> ref) { EXPECT_EQ(3, ref()); }(lambda);
}

TEST(FunctionRefTest, AbslConversion) {
// Matching signatures should work.
{
bool called = false;
auto lambda = [&called](float) {
called = true;
return 'a';
};
FunctionRef<char(float)> ref(lambda);
[](absl::FunctionRef<char(float)> absl_ref) {
absl_ref(1.0);
}(ref.ToAbsl());
EXPECT_TRUE(called);
}

// `absl::FunctionRef` should be able to adapt "similar enough" signatures.
{
bool called = false;
auto lambda = [&called](float) {
called = true;
return 'a';
};
FunctionRef<char(float)> ref(lambda);
[](absl::FunctionRef<void(float)> absl_ref) {
absl_ref(1.0);
}(ref.ToAbsl());
EXPECT_TRUE(called);
}
}

} // namespace base
66 changes: 66 additions & 0 deletions base/functional/function_ref_unittest.nc
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright 2022 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// This is a "No Compile Test" suite.
// http://dev.chromium.org/developers/testing/no-compile-tests

#include "base/bind.h"
#include "base/callback.h"
#include "base/functional/function_ref.h"
#include "third_party/abseil-cpp/absl/functional/function_ref.h"

namespace base {

#if defined(NCTEST_NO_IMPLICIT_RETURN_TYPE_CONVERSIONS) // [r"note: candidate template ignored: requirement 'std::is_same_v<void \(\), int \(\)>' was not satisfied \[with Functor = \(lambda at [^)]+\)\]"]

void WontCompile() {
auto returns_int = [] () { return 42; };
FunctionRef<void()> ref(returns_int);
}

#elif defined(NCTEST_NO_IMPLICIT_DERIVED_TO_BASE_CONVERSIONS) // [r"note: candidate template ignored: requirement 'std::is_same_v<void \(base::Derived \*\), void \(base::Base \*\)>' was not satisfied \[with Functor = \(lambda at [^)]+\)\]"]

class Base { };
class Derived : public Base { };

void WontCompile() {
auto takes_base = [] (Base*) { };
FunctionRef<void(Derived*)> ref(takes_base);
}

#elif defined(NCTEST_BIND_ONCE_TO_ABSL_FUNCTION_REF) // [r"fatal error: static_assert failed due to requirement 'AlwaysFalse<void \(\)>': base::Bind{Once,Repeating} require strong ownership: non-owning function references may not bound as the functor due to potential lifetime issues\."]

void WontCompile() {
[] (absl::FunctionRef<void()> ref) {
BindOnce(ref).Run();
}([] {});
}

#elif defined(NCTEST_BIND_REPEATING_TO_ABSL_FUNCTION_REF) // [r"fatal error: static_assert failed due to requirement 'AlwaysFalse<void \(\)>': base::Bind{Once,Repeating} require strong ownership: non-owning function references may not bound as the functor due to potential lifetime issues\."]

void WontCompile() {
[] (FunctionRef<void()> ref) {
BindRepeating(ref).Run();
}([] {});
}

#elif defined(NCTEST_BIND_ONCE_TO_BASE_FUNCTION_REF) // [r"fatal error: static_assert failed due to requirement 'AlwaysFalse<void \(\)>': base::Bind{Once,Repeating} require strong ownership: non-owning function references may not bound as the functor due to potential lifetime issues\."]

void WontCompile() {
[] (FunctionRef<void()> ref) {
BindOnce(ref).Run();
}([] {});
}

#elif defined(NCTEST_BIND_REPEATING_TO_BASE_FUNCTION_REF) // [r"fatal error: static_assert failed due to requirement 'AlwaysFalse<void \(\)>': base::Bind{Once,Repeating} require strong ownership: non-owning function references may not bound as the functor due to potential lifetime issues\."]

void WontCompile() {
[] (FunctionRef<void()> ref) {
BindRepeating(ref).Run();
}([] {});
}

#endif

} // namespace base

0 comments on commit 2248b33

Please sign in to comment.