mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 07:48:11 +00:00

When doing the last unref() on a listed-ref-counted object, we keep the list locked while mutating the ref count. The destructor itself is invoked after unlocking the list. This was racy with weakable classes, since their weak pointer factory still pointed to the object after we'd decided to destroy it. That opened a small time window where someone could try to strong-ref a weak pointer to an object after it was removed from the list, but just before the destructor got invoked. This patch closes the race window by explicitly revoking all weak pointers while the list is locked.
58 lines
1.7 KiB
C++
58 lines
1.7 KiB
C++
/*
|
|
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <AK/RefCounted.h>
|
|
|
|
namespace Kernel {
|
|
|
|
// ListedRefCounted<T> is a slot-in replacement for RefCounted<T> to use in classes
|
|
// that add themselves to a {Spinlock, Mutex}Protected<IntrusiveList> when constructed.
|
|
// The custom unref() implementation here ensures that the list is locked during
|
|
// unref(), and that the T is removed from the list before ~T() is invoked.
|
|
|
|
enum class LockType {
|
|
Spinlock,
|
|
Mutex,
|
|
};
|
|
|
|
template<typename T, LockType Lock>
|
|
class ListedRefCounted : public RefCountedBase {
|
|
public:
|
|
bool unref() const
|
|
{
|
|
auto const* that = static_cast<T const*>(this);
|
|
|
|
auto callback = [&](auto& list) {
|
|
auto new_ref_count = deref_base();
|
|
if (new_ref_count == 0) {
|
|
list.remove(const_cast<T&>(*that));
|
|
if constexpr (requires { that->revoke_weak_ptrs(); }) {
|
|
that->revoke_weak_ptrs();
|
|
}
|
|
}
|
|
return new_ref_count;
|
|
};
|
|
|
|
RefCountType new_ref_count;
|
|
if constexpr (Lock == LockType::Spinlock)
|
|
new_ref_count = T::all_instances().with(callback);
|
|
else if constexpr (Lock == LockType::Mutex)
|
|
new_ref_count = T::all_instances().with_exclusive(callback);
|
|
if (new_ref_count == 0) {
|
|
if constexpr (requires { that->will_be_destroyed(); })
|
|
that->will_be_destroyed();
|
|
delete that;
|
|
} else if (new_ref_count == 1) {
|
|
if constexpr (requires { that->one_ref_left(); })
|
|
that->one_ref_left();
|
|
}
|
|
return new_ref_count == 0;
|
|
}
|
|
};
|
|
|
|
}
|