godotengine / godotengine/godot

Anonymous lambdas can be randomly assigned a new context object if the original context object is missing at the time of invocation

Open
#98,673 1 comment 0 reactions 0 assignees View on GitHub
bug topic:gdscript
Dominant language
C++
Stars
117k
Forks
26.8k
PR merge metrics
PR metrics pending

Description

### Tested versions

Reproducible in 4.3.stable

### System information

Godot v4.3.stable - Windows 10.0.22631 - Vulkan (Forward+) - dedicated NVIDIA GeForce GTX 1660 SUPER (NVIDIA; 32.0.15.6590) - Intel(R) Core(TM) i7-10700F CPU @ 2.90GHz (16 Threads)

### Issue description

When you make an anonymous lambda that contains a reference to a local variable or function in the current script where the lambda is defined, and then this lambda is later called when the current node is missing, Godot can (unpredictably) bind this lambda to a completely different node.

In the case that the new node does not have the same members, it will throw an error like in the screenshot below, which is super confusing. But an even more dangerous case I have encountered is if the new node *does* share the same members being referenced in the lambda (i.e. `position` or `name`). In this case, the lambda will silently proceed with the wrong target node as though nothing is wrong, leading to a silent bug that can be almost impossible to diagnose.

In the screenshot below, the `story.gd` script attached to the `story` node is where the error occurs. The lambda being constructed contains a call to a function `_perform_place_transition` which is local to `story.gd`. However, at runtime, the lambda construction fails. The error message and the `self` field in the debugger window both say that the error is occuring on a completely different node in the scene - the `tf_mirror` node under the right eye of a penguin.

Godot will randomly re-bind the lambda to one of a few other nodes, or it will not re-bind at all. There appears to be no pattern as to how it chooses how/whether to re-bind.

![38cc0f5f-5187-4ee0-a291-1177b9d19055](https://github.com/user-attachments/assets/38cc0f5f-5187-4ee0-a291-1177b9d19055)

The problem line of code:
```Game.loader.queue_load_op(func(): await _perform_place_transition(place_name, spawn_point_name))```
The rebinding bug *still occurs* if the lambda does not contain an `await`:
```Game.loader.queue_load_op(func(): _perform_place_transition(place_name, spawn_point_name))```
The rebinding bug *does NOT occur* if an anonymous lambda is not used:
```Game.loader.queue_load_op(_perform_place_transition.bind(place_name, spawn_point_name))```

Just to be clear:
It is a user bug to allow an anonymous lambda to be invoked on a no-longer-valid object. The bug being reported here is the random re-binding being done by Godot.

### Steps to reproduce

I can't share my entire commercial project, and I have attempted but not succeeded in making a reduced or fresh repro project.

### Minimal reproduction project (MRP)

N/A

Contributor guide

Open the contributing guide

Research direction

Start with the reported lambda forms in story.gd, comparing the anonymous calls with _perform_place_transition.bind(place_name, spawn_point_name). A reproducible project or reduced test is needed before implementation; done should mean invoking a lambda after its original node is missing never rebinds it to another node.

Written by the indexing model from the issue text.

Assessment

Domain
game-dev
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.