Skip to content

Auto-remove custom callables also when connected in untyped APIs#1612

Merged
Bromeon merged 3 commits into
masterfrom
bugfix/custom-callable-hot-reload
May 24, 2026
Merged

Auto-remove custom callables also when connected in untyped APIs#1612
Bromeon merged 3 commits into
masterfrom
bugfix/custom-callable-hot-reload

Conversation

@Bromeon
Copy link
Copy Markdown
Member

@Bromeon Bromeon commented May 22, 2026

Fixes #984.

@Yarwin already laid out the groundwork in #1223. This extends the auto-disconnection to untyped signal connections via Object::connect* and Signal::connect* APIs.

Custom callables can be Rust-based closures, but also just any callables that were wrapped with bind()/bindv(), and also GDScript lambdas. As such, it's inevitable that this approach has some false positives. Concretely, some callables may not survive hot-reload even though they would be safe -- on the other hand, we prevent UB for Rust callables.

Now I'm not 100% sure if this is the right trade-off, some considerations:

  • Untyped APIs are rarely used -- but we still advertise them on the signals page
  • Object::connect then works slightly differently than Object.connect in GDScript/reflection calls (although it's just an extra best-effort check)
  • We do emit a warning in such cases, so it's not just silent
  • The original issue was reported before we had typed signals, so maybe it's not happening in practice anymore?

Maybe there's a better way to recognize Rust callables, heuristic based on their string representation, or trying to interpret the user_data (might also be UB though 🤔 )

@Bromeon Bromeon added bug c: core Core components labels May 22, 2026
@GodotRust
Copy link
Copy Markdown

API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-1612

@Bromeon Bromeon force-pushed the bugfix/custom-callable-hot-reload branch from 3834f1d to a2f4340 Compare May 22, 2026 20:01
@Bromeon Bromeon marked this pull request as ready for review May 22, 2026 21:25
@Bromeon Bromeon force-pushed the bugfix/custom-callable-hot-reload branch from c17596f to 6a7903c Compare May 22, 2026 21:30
@Bromeon
Copy link
Copy Markdown
Member Author

Bromeon commented May 22, 2026

I found a way to recognize Rust callables, which gives us a relatively good heuristic: we auto-disconnect common cases (Rust::from_fn etc.), but we do not handle Rust callables referenced in Godot callables -- either through bind(), or GDScript lambdas etc. These are very rare and impossible to detect (to my knowledge).

On the bright side, we don't touch legitimate Godot callables during hot-reload.

@TitanNano
Copy link
Copy Markdown
Contributor

is_rust_callable will probably be useful for my thread-safety work, so this is a great byproduct. 👍

Comment thread godot-core/src/signal/signal_connections_registry.rs Outdated
// Rust callable | yes | yes | yes <- registered, disconnected before reload
// Rust callable + bind/bindv | yes | no | yes <- MISSED, see below
// Rust callable, other extension | yes | no | no <- other extension takes care of it
// Engine method | no | no | no
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, it also applies to user-defined #[func].

@Bromeon Bromeon force-pushed the bugfix/custom-callable-hot-reload branch from 6a7903c to 2851e37 Compare May 24, 2026 14:33
@Bromeon Bromeon enabled auto-merge May 24, 2026 14:37
@Bromeon Bromeon added this pull request to the merge queue May 24, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks May 24, 2026
@Bromeon Bromeon force-pushed the bugfix/custom-callable-hot-reload branch from 2851e37 to 063daa0 Compare May 24, 2026 15:14
Bromeon added 2 commits May 24, 2026 18:24
A custom Rust callable connected to a signal survives hot-reload, but Godot
runs into UAF the next time it accesses it.

There is already `Callable::from_linked_fn` to mitigate this problem. This change
extends the signal connection registry to also track custom callables registered in
untyped `Object::connect*`/`Signal::connect*` calls, auto-disconnecting them before
hot-reload. Engine-side (non-custom) callables are not affected by this.
@Bromeon Bromeon force-pushed the bugfix/custom-callable-hot-reload branch from 063daa0 to 44bc5cd Compare May 24, 2026 16:25
Hot-reload registry for signal-connection: instead of broad `is_custom()`,
we can narrow down the affected Callables to reduce some false positives
(while also adding a few false negatives).
@Bromeon Bromeon force-pushed the bugfix/custom-callable-hot-reload branch from 44bc5cd to 7109b3f Compare May 24, 2026 16:53
@Bromeon Bromeon added this pull request to the merge queue May 24, 2026
Merged via the queue into master with commit 1a9e41f May 24, 2026
23 checks passed
@Bromeon Bromeon deleted the bugfix/custom-callable-hot-reload branch May 24, 2026 18:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug c: core Core components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Crash on hot reload when binding gd pointers

4 participants