[llcpp] Don't change source value in std::move of VectorView and tracking_ptr
tracking_ptr is intended to match the behavior of raw pointers for
unowned pointers where possible (and the behavior of unique_ptr for
owned pointers).
This wasn't the case for std::move. Example:
int* x = &something;
int* y = std::move(x);
After these statements, x == y rather than x == nullptr.
The DecodedMessage class (used in ResultOf) has a destructor that
walks the object to close handles. The previous behavior of zeroing
pointers after std::move prevented this walk from traversing the
object and led to handle leaks. This change restores the previous
behavior and allows the object to be walked.
Fixed: 49226
Test: fx test llcpp_fidl_types_test
Test: fx test llcpp_fidl_builder_test
Tests originate from fxr/377022
Change-Id: I35b89f575bfef021c1e4ba48fe549c9fd446439f
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/377654
Commit-Queue: Benjamin Prosnitz <bprosnitz@google.com>
Reviewed-by: Chris Tam <godtamit@google.com>
Reviewed-by: Yifei Teng <yifeit@google.com>
Testability-Review: Yifei Teng <yifeit@google.com>
diff --git a/src/lib/fidl/llcpp/tests/protocol.test.fidl b/src/lib/fidl/llcpp/tests/protocol.test.fidl
index 12e97dd..ac4b625 100644
--- a/src/lib/fidl/llcpp/tests/protocol.test.fidl
+++ b/src/lib/fidl/llcpp/tests/protocol.test.fidl
@@ -9,6 +9,22 @@
REALLY_BAD_ERROR = 2;
};
+struct HandleStruct {
+ handle<event> h;
+};
+
+struct VectorStruct {
+ vector<HandleStruct> v;
+};
+
+union HandleUnion {
+ 1: handle<event> h;
+};
+
+struct HandleUnionStruct {
+ HandleUnion u;
+};
+
/// Protocol for testing methods with error types.
/// In the implementation, each method is hardcoded to return either the
/// success or the error case. This should follow the naming of the method,
@@ -23,3 +39,9 @@
Grob(string value) -> (string value);
-> Hrob(string value);
};
+
+protocol HandleProvider {
+ GetHandle() -> (HandleStruct value);
+ GetHandleVector(uint32 count) -> (vector<HandleStruct> value);
+ GetHandleUnion() -> (HandleUnionStruct value);
+};
diff --git a/src/lib/fidl/llcpp/tests/protocol_test.cc b/src/lib/fidl/llcpp/tests/protocol_test.cc
index 7fabc2a..fb01c74 100644
--- a/src/lib/fidl/llcpp/tests/protocol_test.cc
+++ b/src/lib/fidl/llcpp/tests/protocol_test.cc
@@ -6,9 +6,15 @@
#include <lib/async-loop/default.h>
#include <lib/async/wait.h>
#include <lib/fidl-async/cpp/bind.h>
+#include <lib/fidl/llcpp/memory.h>
+#include <lib/fidl/llcpp/vector_view.h>
+#include <lib/zx/object.h>
#include <zircon/errors.h>
#include <zircon/fidl.h>
#include <zircon/status.h>
+#include <zircon/syscalls/object.h>
+
+#include <cstdint>
#include <llcpptest/protocol/test/llcpp/fidl.h>
@@ -18,6 +24,15 @@
namespace {
zx_status_t kErrorStatus = 271;
+
+template <typename T>
+uint32_t GetHandleCount(zx::unowned<T> h) {
+ zx_info_handle_count_t info = {};
+ auto status = h->get_info(ZX_INFO_HANDLE_COUNT, &info, sizeof(info), nullptr, nullptr);
+ ZX_ASSERT(status == ZX_OK);
+ return info.handle_count;
+}
+
} // namespace
class ErrorServer : public test::ErrorMethods::Interface {
@@ -220,3 +235,142 @@
auto resp = client.NoArgsPrimitiveError(false);
ASSERT_EQ(ZX_ERR_BAD_HANDLE, resp.status());
}
+
+class HandleProviderServer : public test::HandleProvider::Interface {
+ public:
+ void GetHandle(GetHandleCompleter::Sync completer) override {
+ test::HandleStruct s;
+ zx::event::create(0, &s.h);
+ completer.Reply(std::move(s));
+ }
+
+ void GetHandleVector(uint32_t count, GetHandleVectorCompleter::Sync completer) override {
+ std::vector<test::HandleStruct> v(count);
+ for (auto& s : v) {
+ zx::event::create(0, &s.h);
+ }
+ completer.Reply(fidl::unowned_vec(v));
+ }
+
+ void GetHandleUnion(GetHandleUnionCompleter::Sync completer) override {
+ zx::event h;
+ zx::event::create(0, &h);
+ test::HandleUnionStruct s = {.u = test::HandleUnion::WithH(fidl::unowned_ptr(&h))};
+ completer.Reply(std::move(s));
+ }
+};
+
+class HandleTest : public ::testing::Test {
+ protected:
+ virtual void SetUp() {
+ loop_ = std::make_unique<async::Loop>(&kAsyncLoopConfigAttachToCurrentThread);
+ ASSERT_EQ(loop_->StartThread("test_llcpp_handle_server"), ZX_OK);
+
+ zx::channel server_end;
+ ASSERT_EQ(zx::channel::create(0, &client_end_, &server_end), ZX_OK);
+ server_ = std::make_unique<HandleProviderServer>();
+ fidl::Bind(loop_->dispatcher(), std::move(server_end), server_.get());
+ }
+
+ test::HandleProvider::SyncClient TakeClient() {
+ EXPECT_TRUE(client_end_.is_valid());
+ return test::HandleProvider::SyncClient(std::move(client_end_));
+ }
+
+ private:
+ std::unique_ptr<async::Loop> loop_;
+ std::unique_ptr<HandleProviderServer> server_;
+ zx::channel client_end_;
+};
+
+TEST_F(HandleTest, HandleClosedAfterResultOfMove) {
+ auto client = TakeClient();
+ auto result = client.GetHandle();
+
+ ASSERT_TRUE(result.ok()) << result.error();
+ ASSERT_TRUE(result->value.h.is_valid());
+
+ // Dupe the event so we can get the handle count after move.
+ zx::event dupe;
+ ASSERT_EQ(result->value.h.duplicate(ZX_RIGHT_SAME_RIGHTS, &dupe), ZX_OK);
+
+ // Moving the ResultOf::GetHandle should move the handle.
+ { auto release = std::move(result); } // ~ResultOf::GetHandle
+
+ // Only remaining handle should be the dupe.
+ ASSERT_EQ(GetHandleCount(dupe.borrow()), 1u);
+}
+
+TEST_F(HandleTest, HandleClosedAfterHandleStructMove) {
+ auto client = TakeClient();
+ auto result = client.GetHandle();
+
+ ASSERT_TRUE(result.ok()) << result.error();
+ ASSERT_TRUE(result->value.h.is_valid());
+
+ // Dupe the event so we can get the handle count after move.
+ zx::event dupe;
+ ASSERT_EQ(result->value.h.duplicate(ZX_RIGHT_SAME_RIGHTS, &dupe), ZX_OK);
+
+ // A move of a struct holding a handle will move the handle from the result, resulting in a close
+ { auto release = std::move(result->value); } // ~HandleStruct
+
+ // Only remaining handle should be the dupe.
+ ASSERT_EQ(GetHandleCount(dupe.borrow()), 1u);
+}
+
+TEST_F(HandleTest, HandleClosedOnResultOfDestructorAfterVectorMove) {
+ constexpr uint32_t kNumHandles = 2;
+
+ auto client = TakeClient();
+ std::vector<zx::event> dupes(kNumHandles);
+
+ {
+ auto result = client.GetHandleVector(kNumHandles);
+
+ ASSERT_TRUE(result.ok()) << result.error();
+ ASSERT_EQ(result->value.count(), kNumHandles);
+
+ for (uint32_t i = 0; i < kNumHandles; i++) {
+ ASSERT_TRUE(result->value[i].h.is_valid());
+ ASSERT_EQ(result->value[i].h.duplicate(ZX_RIGHT_SAME_RIGHTS, &dupes[i]), ZX_OK);
+ }
+
+ { auto release = std::move(result->value); } // ~VectorView<HandleStruct>
+
+ // std::move of VectorView only moves pointers, not handles.
+ // 1 handle in ResultOf + 1 handle in dupe = 2.
+ for (auto& e : dupes) {
+ ASSERT_EQ(GetHandleCount(e.borrow()), 2u);
+ }
+ }
+
+ // Handle cleaned up after ResultOf destructor is called.
+ // Remaining handle is the dupe.
+ for (auto& e : dupes) {
+ ASSERT_EQ(GetHandleCount(e.borrow()), 1u);
+ }
+}
+
+TEST_F(HandleTest, HandleClosedOnResultOfDestructorAfterTrackingPtrMove) {
+ auto client = TakeClient();
+ zx::event dupe;
+
+ {
+ auto result = client.GetHandleUnion();
+
+ ASSERT_TRUE(result.ok()) << result.error();
+ ASSERT_TRUE(result->value.u.h().is_valid());
+ ASSERT_EQ(result->value.u.h().duplicate(ZX_RIGHT_SAME_RIGHTS, &dupe), ZX_OK);
+
+ { auto release = std::move(result->value); } // ~HandleUnion
+
+ // std::move of tracking_ptr in union only moves pointers, not handles.
+ // 1 handle in ResultOf + 1 handle in dupe = 2.
+ ASSERT_EQ(GetHandleCount(dupe.borrow()), 2u);
+ }
+
+ // Handle cleaned up after ResultOf destructor is called.
+ // Remaining handle is the dupe.
+ ASSERT_EQ(GetHandleCount(dupe.borrow()), 1u);
+}
diff --git a/src/lib/fidl/llcpp/tests/tracking_ptr_test.cc b/src/lib/fidl/llcpp/tests/tracking_ptr_test.cc
index e6f2aa7..715e61a 100644
--- a/src/lib/fidl/llcpp/tests/tracking_ptr_test.cc
+++ b/src/lib/fidl/llcpp/tests/tracking_ptr_test.cc
@@ -42,6 +42,8 @@
fidl::tracking_ptr<DestructableObject> ptr1 = fidl::unowned_ptr_t<DestructableObject>(&obj1);
fidl::tracking_ptr<DestructableObject> ptr2 = fidl::unowned_ptr_t<DestructableObject>(&obj2);
ptr2 = std::move(ptr1);
+ EXPECT_EQ(ptr1.get(), &obj1);
+ EXPECT_EQ(ptr2.get(), &obj1);
}
EXPECT_FALSE(ds1.destructor_called);
EXPECT_FALSE(ds2.destructor_called);
@@ -49,14 +51,19 @@
TEST(TrackingPtr, OwnedSingleValueLifecycle) {
DestructionState ds1, ds2;
+ auto obj1 = std::make_unique<DestructableObject>(&ds1);
+ auto obj2 = std::make_unique<DestructableObject>(&ds2);
+ DestructableObject* obj1_raw = obj1.get();
{
- fidl::tracking_ptr<DestructableObject> ptr1 = std::make_unique<DestructableObject>(&ds1);
- fidl::tracking_ptr<DestructableObject> ptr2 = std::make_unique<DestructableObject>(&ds2);
+ fidl::tracking_ptr<DestructableObject> ptr1 = std::move(obj1);
+ fidl::tracking_ptr<DestructableObject> ptr2 = std::move(obj2);
EXPECT_FALSE(ds1.destructor_called);
EXPECT_FALSE(ds2.destructor_called);
ptr2 = std::move(ptr1);
EXPECT_FALSE(ds1.destructor_called);
EXPECT_TRUE(ds2.destructor_called);
+ EXPECT_EQ(ptr1.get(), nullptr);
+ EXPECT_EQ(ptr2.get(), obj1_raw);
}
EXPECT_TRUE(ds1.destructor_called);
}
@@ -70,6 +77,8 @@
fidl::tracking_ptr<DestructableObject[]> ptr1 = fidl::unowned_ptr_t<DestructableObject>(arr1);
fidl::tracking_ptr<DestructableObject[]> ptr2 = fidl::unowned_ptr_t<DestructableObject>(arr2);
ptr2 = std::move(ptr1);
+ EXPECT_EQ(ptr1.get(), arr1);
+ EXPECT_EQ(ptr2.get(), arr1);
}
EXPECT_FALSE(ds1[0].destructor_called);
EXPECT_FALSE(ds1[1].destructor_called);
@@ -80,15 +89,15 @@
TEST(TrackingPtr, OwnedArrayLifecycle) {
DestructionState ds1[2] = {};
DestructionState ds2[2] = {};
-
+ auto arr1 = std::make_unique<DestructableObject[]>(2);
+ arr1[0].ds = &ds1[0];
+ arr1[1].ds = &ds1[1];
+ auto arr2 = std::make_unique<DestructableObject[]>(2);
+ arr2[0].ds = &ds2[0];
+ arr2[1].ds = &ds2[1];
+ DestructableObject* arr1_raw = arr1.get();
{
- auto arr1 = std::make_unique<DestructableObject[]>(2);
- arr1[0].ds = &ds1[0];
- arr1[1].ds = &ds1[1];
fidl::tracking_ptr<DestructableObject[]> ptr1(std::move(arr1));
- auto arr2 = std::make_unique<DestructableObject[]>(2);
- arr2[0].ds = &ds2[0];
- arr2[1].ds = &ds2[1];
fidl::tracking_ptr<DestructableObject[]> ptr2(std::move(arr2));
EXPECT_FALSE(ds1[0].destructor_called);
EXPECT_FALSE(ds1[1].destructor_called);
@@ -99,6 +108,8 @@
EXPECT_FALSE(ds1[1].destructor_called);
EXPECT_TRUE(ds2[0].destructor_called);
EXPECT_TRUE(ds2[1].destructor_called);
+ EXPECT_EQ(ptr1.get(), nullptr);
+ EXPECT_EQ(ptr2.get(), arr1_raw);
}
EXPECT_TRUE(ds1[0].destructor_called);
EXPECT_TRUE(ds1[1].destructor_called);
diff --git a/src/lib/fidl/llcpp/tests/vector_view_test.cc b/src/lib/fidl/llcpp/tests/vector_view_test.cc
index 4c9c787..97f599b 100644
--- a/src/lib/fidl/llcpp/tests/vector_view_test.cc
+++ b/src/lib/fidl/llcpp/tests/vector_view_test.cc
@@ -39,25 +39,56 @@
EXPECT_FALSE(ds[1].destructor_called);
}
-TEST(VectorView, MoveConstructor) {
+TEST(VectorView, MoveConstructorUnowned) {
std::vector<int32_t> vec{1, 2, 3};
fidl::VectorView<int32_t> vv(fidl::unowned_ptr_t<int32_t>(vec.data()), vec.size());
fidl::VectorView<int32_t> moved_vv(std::move(vv));
- EXPECT_EQ(vv.count(), 0ULL);
- EXPECT_EQ(vv.data(), nullptr);
+ EXPECT_EQ(vv.count(), 3ULL);
+ EXPECT_EQ(vv.data(), vec.data());
EXPECT_EQ(moved_vv.count(), 3ULL);
EXPECT_EQ(moved_vv.data(), vec.data());
}
-TEST(VectorView, MoveAssigment) {
+TEST(VectorView, MoveConstructorOwned) {
+ constexpr size_t size = 3;
+ auto arr = std::make_unique<int32_t[]>(3);
+ arr[0] = 1;
+ arr[1] = 2;
+ arr[2] = 3;
+ int32_t* arr_raw = arr.get();
+ fidl::VectorView<int32_t> vv(std::move(arr), size);
+ fidl::VectorView<int32_t> moved_vv(std::move(vv));
+ EXPECT_EQ(vv.count(), 0ULL);
+ EXPECT_EQ(vv.data(), nullptr);
+ EXPECT_EQ(moved_vv.count(), 3ULL);
+ EXPECT_EQ(moved_vv.data(), arr_raw);
+}
+
+TEST(VectorView, MoveAssigmentUnowned) {
std::vector<int32_t> vec{1, 2, 3};
fidl::VectorView<int32_t> vv(fidl::unowned_ptr_t<int32_t>(vec.data()), vec.size());
fidl::VectorView<int32_t> moved_vv;
moved_vv = std::move(vv);
+ EXPECT_EQ(vv.count(), 3ULL);
+ EXPECT_EQ(vv.data(), vec.data());
+ EXPECT_EQ(moved_vv.count(), 3ULL);
+ EXPECT_EQ(moved_vv.data(), vec.data());
+}
+
+TEST(VectorView, MoveAssigmentOwned) {
+ constexpr size_t size = 3;
+ auto arr = std::make_unique<int32_t[]>(3);
+ arr[0] = 1;
+ arr[1] = 2;
+ arr[2] = 3;
+ int32_t* arr_raw = arr.get();
+ fidl::VectorView<int32_t> vv(std::move(arr), size);
+ fidl::VectorView<int32_t> moved_vv;
+ moved_vv = std::move(vv);
EXPECT_EQ(vv.count(), 0ULL);
EXPECT_EQ(vv.data(), nullptr);
EXPECT_EQ(moved_vv.count(), 3ULL);
- EXPECT_EQ(moved_vv.data(), vec.data());
+ EXPECT_EQ(moved_vv.data(), arr_raw);
}
TEST(VectorView, Iteration) {
diff --git a/zircon/system/ulib/fidl/include/lib/fidl/llcpp/tracking_ptr.h b/zircon/system/ulib/fidl/include/lib/fidl/llcpp/tracking_ptr.h
index 0c983a3..4e77b758 100644
--- a/zircon/system/ulib/fidl/include/lib/fidl/llcpp/tracking_ptr.h
+++ b/zircon/system/ulib/fidl/include/lib/fidl/llcpp/tracking_ptr.h
@@ -179,7 +179,10 @@
marked_ptr release_marked_ptr() noexcept {
marked_ptr temp = mptr_;
- mptr_ = kNullMarkedPtr;
+ if (is_owned()) {
+ // Unowned pointers keep their value like raw pointers, but owned pointers should zero.
+ mptr_ = kNullMarkedPtr;
+ }
return temp;
}
@@ -209,14 +212,12 @@
tracking_ptr(tracking_ptr&& other) noexcept {
reset(other.is_owned_, other.ptr_);
- other.is_owned_ = false;
- other.ptr_ = nullptr;
+ other.release();
}
template <typename U = T, typename = std::enable_if_t<std::is_const<U>::value>>
tracking_ptr(tracking_ptr<std::remove_const_t<U>[]>&& other) noexcept {
reset(other.is_owned_, other.ptr_);
- other.is_owned_ = false;
- other.ptr_ = nullptr;
+ other.release();
}
template <typename U = T, typename = std::enable_if_t<!std::is_void<U>::value>>
tracking_ptr(std::unique_ptr<U>&& other) {
@@ -233,8 +234,7 @@
tracking_ptr& operator=(tracking_ptr&& other) noexcept {
reset(other.is_owned_, other.ptr_);
- other.is_owned_ = false;
- other.ptr_ = nullptr;
+ other.release();
return *this;
}
tracking_ptr& operator=(const tracking_ptr&) = delete;
@@ -248,8 +248,11 @@
// Hand off responsibility of ownership to the caller.
// The internal data can be retrieved through get() and is_owned() before calling release().
void release() {
- ptr_ = nullptr;
- is_owned_ = false;
+ if (is_owned()) {
+ // Unowned pointers keep their value like raw pointers, but owned pointers should zero.
+ ptr_ = nullptr;
+ is_owned_ = false;
+ }
}
private:
diff --git a/zircon/system/ulib/fidl/include/lib/fidl/llcpp/vector_view.h b/zircon/system/ulib/fidl/include/lib/fidl/llcpp/vector_view.h
index abcae87..d578c7c 100644
--- a/zircon/system/ulib/fidl/include/lib/fidl/llcpp/vector_view.h
+++ b/zircon/system/ulib/fidl/include/lib/fidl/llcpp/vector_view.h
@@ -72,8 +72,7 @@
"VectorView<T> can only be move-constructed from VectorView<T> or VectorView<const T>");
count_ = other.count_;
data_ = other.data_;
- other.count_ = 0;
- other.data_ = nullptr;
+ other.release();
}
~VectorView() {
@@ -91,8 +90,7 @@
}
count_ = other.count_;
data_ = other.data_;
- other.count_ = 0;
- other.data_ = nullptr;
+ other.release();
return *this;
}
@@ -146,6 +144,15 @@
bool is_owned() const noexcept { return count_ & kOwnershipMask; }
+ void release() {
+ if (is_owned()) {
+ // Match unique_ptr std::move behavior in owned and unowned case
+ // (unowned case doesn't reset source).
+ count_ = 0;
+ data_ = nullptr;
+ }
+ }
+
friend ::LayoutChecker;
// The lower 63 bits of count_ are reserved to store the number of elements.