Repository: godot
Issue: Calling unreference on one object appears to temporarily cause different object to be <Object#null>
Labels: bug,topic:core,needs testing

### Godot version

4.0.beta5.official.89a33d28f

### System information

OSX 10.15.7

### Issue description

**EDIT** Changed branch from `godot_4` to `godot_4_with_69411` in `clone` commands since a workaround will soon be merged into the original branch.

**TL;DR**:  Calling `unreference` on a function's local variable appears to cause a value in a dictionary, in a completely different part of the project, to be `<Object#null>`.  This happens less often if I `print` more.

I believe I have found an obscure race condition when calling `unreference`?  I have tried to simplify the steps to reproduce with no luck.   This occurs in [GUT](https://github.com/bitwes/Gut). 

When running the tests for GUT through GUT (using the stones to destroy the stones) I occasionally get a null object in a dictionary after calling `unreference` on a completely unrelated object.  This happens less frequently when there are more calls to `print` between the call to `unreference` and accessing the dictionary.

In the code below I have added debug `print` calls.  When these calls are present the error is less frequent.  Without the `print` calls and the logging for GUT set to 0, the error happens regularly.

## The call to `unreference`
This test function is making a local variable and then freeing it (it tests the implementation of `func unreference()` in `script_parser.gd`).  The scope of the unreferenced variable should be this function only.
``` gdscript
# res://test/unit/test_script_parser.gd
	func test_parsing_native_does_not_generate_orphans():
		var parsed = ParsedScript.new(Node2D)
		print('parsed = ', parsed)
		parsed.unreference()
		parsed = null
		assert_no_new_orphans()
```
This function is run dynamically through `gut.gd`.  `gut.gd` gets the names of methods and then does a ton of stuff to eventually call this method.

## The dictionary that gets a null object sometimes
`gut.gd` has an instance of `summary.gd`.  `summary.gd` has a dictionary that contains information about each test function that has been executed.  The dictionary is keyed with the name of the test function.  The value is an instance of an inner-class named `Test`.  `Test` class is defined in `summary.gd`.  I could explain more, but the call stacks in the output do a better job.

The below function handles adding and retrieval of values in the dictionary.  Occasionally, the value in the dictionary in `summary.gd` will be null after the call to `parsed.unreference` is made.

``` gdscript

# res://addons/gut/summary.gd.TestScript
class TestScript:
	var _tests = {}
	var _test_order = []

	...

	func get_test_obj(obj_name):
		print('--- get_test_obj stack ---')
		print_stack()
		print('---')
		if(!_tests.has(obj_name)):
			print('_tests does not have "', obj_name, '".')
			var to_add = Test.new()
			_tests[obj_name] = to_add
			_test_order.append(obj_name)
			print('    added "', obj_name, '" to _tests as ', to_add, '.  _tests[', obj_name, '] = ', _tests[obj_name])
		else:
			pass
			print('_tests has "', obj_name, '" key.  _tests[', obj_name, '] = ', _tests[obj_name])

		var to_return = _tests[obj_name]
		if(to_return == null):
			print('Return value is null.  _tests[', obj_name, '] = ', _tests[obj_name])
```

Commented and simplified output for when error occurs.  Here you can see
* the instance of `ParsedScript` is not the same instance added to the dictionary.
* `get_test_obj` adds a new instance of `Test` to the `_tests` dictionary for `"test_parsing_native_does_not_generate_orphans"` 
* Later `get_test_obj` sees that the key exists but the value is `<Object#null>`.
```
# the value of parsed in the test test_parsing_native_does_not_generate_orphans
parsed = <RefCounted#-9223371999273810438>

# The first call to get_test_obj 
# This call is triggered by assert_no_new_orphans
_tests does not have "test_parsing_native_does_not_generate_orphans".  
    added "test_parsing_native_does_not_generate_orphans" to _tests as <RefCounted#-9223371994525858310>.  _tests[test_parsing_native_does_not_generate_orphans] = <RefCounted#-9223371994525858310>

# The second call to get_test_obj.
# This call triggered when test_parsing_native_does_not_generate_orphans
# finishes.
_tests has "test_parsing_native_does_not_generate_orphans" key.  _tests[test_parsing_native_does_not_generate_orphans] = <Object#null>
Return value is null.  _tests[test_parsing_native_does_not_generate_orphans] = <Object#null>
```

Here is the output with the output of the `print_stack` call inside `get_test_obj`:
```
res://test/unit/test_script_parser.gd.TestParsedScript
parsed = <RefCounted#-9223371999273810438>
--- get_test_obj stack ---
Frame 0 - :113 in function 'get_test_obj'
Frame 1 - :132 in function 'add_pass'
Frame 2 - res://addons/gut/summary.gd:170 in function 'add_pass'
Frame 3 - res://addons/gut/gut.gd:1060 in function '_pass'
Frame 4 - res://addons/gut/test.gd:113 in function '_pass'
Frame 5 - res://addons/gut/test.gd:970 in function 'assert_no_new_orphans'
Frame 6 - :206 in function 'test_parsing_native_does_not_generate_orphans'
Frame 7 - res://addons/gut/gut.gd:794 in function '_run_test'
Frame 8 - res://addons/gut/gut.gd:999 in function '_test_the_scripts'
Frame 9 - res://addons/gut/gut.gd:1200 in function 'test_scripts'
Frame 10 - res://addons/gut/gui/GutRunner.gd:55 in function 'run_tests'
Frame 11 - res://addons/gut/gut_cmdln.gd:280 in function '_run_gut'
Frame 12 - res://addons/gut/gut_cmdln.gd:319 in function '_init'
---
_tests does not have "test_parsing_native_does_not_generate_orphans".
    added "test_parsing_native_does_not_generate_orphans" to _tests as <RefCounted#-9223371994525858310>.  _tests[test_parsing_native_does_not_generate_orphans] = <RefCounted#-9223371994525858310>
--- get_test_obj stack ---
Frame 0 - :113 in function 'get_test_obj'
Frame 1 - res://addons/gut/summary.gd:167 in function 'add_test'
Frame 2 - res://addons/gut/gut.gd:800 in function '_run_test'
Frame 3 - res://addons/gut/gut.gd:999 in function '_test_the_scripts'
Frame 4 - res://addons/gut/gut.gd:1200 in function 'test_scripts'
Frame 5 - res://addons/gut/gui/GutRunner.gd:55 in function 'run_tests'
Frame 6 - res://addons/gut/gut_cmdln.gd:280 in function '_run_gut'
Frame 7 - res://addons/gut/gut_cmdln.gd:319 in function '_init'
---
_tests has "test_parsing_native_does_not_generate_orphans" key.  _tests[test_parsing_native_does_not_generate_orphans] = <Object#null>
Return value is null.  _tests[test_parsing_native_does_not_generate_orphans] = <Object#null>
```

Let me know what other information you need.  This felt like a lot of information and also not enough.

### Steps to reproduce

## Get GUT
To get GUT and all of its tests you have to clone the Godot 4 branch (the zip only contains addons)
```
git clone -b godot_4_with_69411 https://github.com/bitwes/Gut
```

## Run test script to get error
Running GUT with the following command should cause the error below to happen after a couple runs (occured 1st and 3rd time I ran with these steps).

```
<path to godot> -s res://addons/gut/gut_cmdln.gd  -d  --headless  -gselect=test_script_parser.gd -glog=0
```
The error:
```
Debugger Break, Reason: 'Invalid set index 'orphans' (on base: 'null instance') with value of type 'float'.'
*Frame 0 - res://addons/gut/gut.gd:827 in function '_run_test'
Enter "help" for assistance.
debug> bt
*Frame 0 - res://addons/gut/gut.gd:827 in function '_run_test'
 Frame 1 - res://addons/gut/gut.gd:991 in function '_test_the_scripts'
debug>
```

## More output = less often
Running GUT with a higher log level makes the error occur less frequently.
```
<path to godot> -s res://addons/gut/gut_cmdln.gd  -d  --headless  -gselect=test_script_parser.gd -glog=3
```

### Minimal reproduction project

As I said earlier, I was unable to simplify the steps to reproduce.  To get GUT and all of its tests you have to clone the Godot 4 branch (the zip only contains addons)
```
git clone -b godot_4_with_69411 https://github.com/bitwes/Gut
```