Skip to content

Commit f484aaa

Browse files
committed
Fixed bug causing ConnectHandle::is_connected() to sometimes panic.
A ConnectHandle created from a connection from A to B would panic if `is_connected()` was called after A was freed. Fixed by `is_connected()` checking if the contained object is valid through `is_instance_valid()`.
1 parent e1073bc commit f484aaa

File tree

2 files changed

+59
-10
lines changed

2 files changed

+59
-10
lines changed

godot-core/src/registry/signal/connect_handle.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,15 @@ impl ConnectHandle {
5050

5151
/// Whether the handle represents a valid connection.
5252
///
53-
/// Returns false if the signals and callables managed by this handle have been disconnected in any other way than by using
54-
/// [`disconnect()`][Self::disconnect] -- e.g. through [`Signal::disconnect()`][crate::builtin::Signal::disconnect] or
55-
/// [`Object::disconnect()`][crate::classes::Object::disconnect].
53+
/// Returns false if:
54+
/// - ... the signals and callables managed by this handle have been disconnected in any other way than by using
55+
/// [`disconnect()`][Self::disconnect] -- e.g. through [`Signal::disconnect()`][crate::builtin::Signal::disconnect] or
56+
/// [`Object::disconnect()`][crate::classes::Object::disconnect].
57+
/// - ... the broadcasting object managed by this handle is not valid -- e.g. if the object has been freed.
5658
pub fn is_connected(&self) -> bool {
57-
self.receiver_object
58-
.is_connected(&*self.signal_name, &self.callable)
59+
self.receiver_object.is_instance_valid()
60+
&& self
61+
.receiver_object
62+
.is_connected(&*self.signal_name, &self.callable)
5963
}
6064
}

itest/rust/src/builtin_tests/containers/signal_disconnect_test.rs

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
#![cfg(since_api = "4.2")]
99

10-
use crate::framework::{expect_debug_panic_or_release_ok, itest};
10+
use crate::framework::{expect_debug_panic_or_release_ok, expect_panic, itest};
1111
use godot::{
1212
builtin::{Callable, Signal},
1313
classes::Object,
@@ -152,10 +152,14 @@ fn handle_recognizes_direct_object_disconnect() {
152152
})
153153
}
154154

155-
#[itest(skip)]
156-
fn handle_recognizes_freed_object() {
157-
// TODO
158-
// We should define what happens when either the broadcasting object or (potential) receiving object is freed.
155+
#[itest]
156+
fn test_handle_after_freeing_broadcaster() {
157+
test_freed_nodes(true);
158+
}
159+
160+
#[itest]
161+
fn test_handle_after_freeing_receiver() {
162+
test_freed_nodes(false);
159163
}
160164

161165
// Helper functions:
@@ -229,6 +233,47 @@ fn test_handle_recognizes_non_valid_state(disconnect_function: impl FnOnce(&mut
229233
obj.free();
230234
}
231235

236+
fn test_freed_nodes(free_broadcaster_first: bool) {
237+
let broadcaster = SignalDisc::new_alloc();
238+
let receiver = SignalDisc::new_alloc();
239+
240+
let handle = broadcaster
241+
.signals()
242+
.my_signal()
243+
.connect_other(&receiver, |r| {
244+
r.increment_self();
245+
});
246+
247+
let (to_free, other) = if free_broadcaster_first {
248+
(broadcaster, receiver)
249+
} else {
250+
(receiver, broadcaster)
251+
};
252+
253+
// Free one of the nodes, and check if the handle thinks the objects are connected.
254+
// If the broadcaster is freed, its signals to other objects is implicitly freed as well. Thus, the handle should not be connected.
255+
// If the receiver is freed, the connection between the valid broadcaster and the non-valid freed object still exists.
256+
// In the latter case, the connection can - and probably should - be freed with disconnect().
257+
to_free.free();
258+
let is_connected = handle.is_connected();
259+
assert_ne!(
260+
free_broadcaster_first, is_connected,
261+
"Handle should only return false if broadcasting is freed!"
262+
);
263+
264+
// It should be possible to disconnect a connection between a valid broadcaster and a non-valid receiver.
265+
// If the connection is not valid (e.g. because of freed broadcaster), calling disconnect() should panic.
266+
if is_connected {
267+
handle.disconnect();
268+
} else {
269+
expect_panic("Disconnected invalid handle!", || {
270+
handle.disconnect();
271+
});
272+
}
273+
274+
other.free();
275+
}
276+
232277
fn has_connections(obj: &Gd<SignalDisc>) -> bool {
233278
!obj.get_signal_connection_list("my_signal").is_empty()
234279
}

0 commit comments

Comments
 (0)