Repository: godotengine/godot (language: cpp)

Issue:
Crash when calling get_argument_count() on a Callable after its target object is freed
### Tested versions

Reproducible in: 
- 4.4 RC1 (on macOS and Linux, Standard build),
- 4.4 RC3 (on Linux, Standard build)

### System information

Godot v4.4.rc3 - Linux Mint 21.3 (Virginia) on X11 - X11 display driver, Multi-window, 1 monitor - Vulkan (Forward+) - dedicated NVIDIA GeForce RTX 3060 (nvidia; 535.171.04) - Intel(R) Core(TM) i5-9600K CPU @ 3.70GHz (6 threads)

### Issue description

When calling `get_argument_count()` on a Callable after its target object has been freed, the engine crashes without any error message or warning. This happens silently, making it difficult to debug.
Other methods in the Callable handle freed objects more gracefully:

- `get_bound_arguments_count()` - returns 0, no crash
- `get_method()` - returns "test_method", no crash
- `get_object()` - returns <Object#null>, no crash
- `get_object_id()` - returns the ID, no crash
- `call()` - produces error: "Attempt to call function 'null::test_method (Callable)' on a null instance."

Only `get_argument_count()` causes a hard crash without warning.

### Steps to reproduce

- Create a new Godot project
- Add the following script to a Node2D:
```gdscript
extends Node2D

class TestObject extends Node2D:
	func test_method(arg1, arg2 = null, arg3 = null) -> void:
		print("test_method called")

func _ready() -> void:
	var test_object := TestObject.new()
	add_child(test_object)
	
	var test_callable := Callable(test_object.test_method)
	var arg_count = test_callable.get_argument_count()
	print("init_check: ", arg_count)
	
	print("is_valid before queue_free: ", is_instance_valid(test_object))
	test_object.queue_free()
	
	await get_tree().create_timer(0.1).timeout
	print("is_valid after queue_free: ", is_instance_valid(test_object))
	
	# The crash occurs when trying to get argument count from a callable with freed object
	print("Testing get_argument_count() - game crash")
	var freed_arg_count = test_callable.get_argument_count()
	print("This line won't be executed if get_argument_count() crashes")
```

```
================================================================
handle_crash: Program crashed with signal 11
Engine version: Godot Engine v4.4.rc3.official (15ff450680a40391aabbffde0a57ead2cd84db56)
Dumping the backtrace. Please include this when reporting the bug to the project developer.
[1] /lib/x86_64-linux-gnu/libc.so.6(+0x42520) [0x7e69b7642520] (??:0)
[2] ./Godot_v4.4-rc3_linux.x86_64() [0x4750813] (??:0)
[3] ./Godot_v4.4-rc3_linux.x86_64() [0x45819a9] (??:0)
[4] ./Godot_v4.4-rc3_linux.x86_64() [0xb824d0] (??:0)
[5] ./Godot_v4.4-rc3_linux.x86_64() [0xa9d00c] (??:0)
[6] ./Godot_v4.4-rc3_linux.x86_64() [0xa9dd0a] (??:0)
[7] ./Godot_v4.4-rc3_linux.x86_64() [0xabf8f3] (??:0)
[8] ./Godot_v4.4-rc3_linux.x86_64() [0x4753399] (??:0)
[9] ./Godot_v4.4-rc3_linux.x86_64() [0x450dbad] (??:0)
[10] ./Godot_v4.4-rc3_linux.x86_64() [0x450de24] (??:0)
[11] ./Godot_v4.4-rc3_linux.x86_64() [0x4808f2a] (??:0)
[12] ./Godot_v4.4-rc3_linux.x86_64() [0x28c05c9] (??:0)
[13] ./Godot_v4.4-rc3_linux.x86_64() [0x290fd7e] (??:0)
[14] ./Godot_v4.4-rc3_linux.x86_64() [0x51c32d] (??:0)
[15] ./Godot_v4.4-rc3_linux.x86_64() [0x41e6a2] (??:0)
[16] /lib/x86_64-linux-gnu/libc.so.6(+0x29d90) [0x7e69b7629d90] (??:0)
[17] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0x80) [0x7e69b7629e40] (??:0)
[18] ./Godot_v4.4-rc3_linux.x86_64() [0x44280a] (??:0)
-- END OF BACKTRACE --
================================================================
```

### Minimal reproduction project (MRP)

[test_get_argument_count.zip](https://github.com/user-attachments/files/19039986/test_get_argument_count.zip)


Discussion / hints:
I tested around a bit and this seemed to fix it: 

```c++
int Callable::get_argument_count(bool *r_is_valid) const {
	if (is_valid()) {
		if (is_custom()) {
			bool valid = false;
			return custom->get_argument_count(r_is_valid ? *r_is_valid : valid);
		} else if (!is_null()) {
			return get_object()->get_method_argument_count(method, r_is_valid);
		} else {
			if (r_is_valid) {
				*r_is_valid = false;
			}
			return 0;
		}
	}
	else {
		ERR_PRINT("Callable is not valid");
		return 0;
	}
}
``` 
I added the is_valid() check. I don't know what a good error message would so I just went with that. 
I am new to contributing to the source code and can't open a pull request because I already have an open one (and unfortunately did it on the main branch).
@TrueJole Thanks for the solution! Is it okay if I make a PR with this fix?
Yes, go ahead!

Fix patch:
diff --git a/core/variant/callable.cpp b/core/variant/callable.cpp
index 402dc6823c0a..0603ea273253 100644
--- a/core/variant/callable.cpp
+++ b/core/variant/callable.cpp
@@ -188,7 +188,7 @@ int Callable::get_argument_count(bool *r_is_valid) const {
 	if (is_custom()) {
 		bool valid = false;
 		return custom->get_argument_count(r_is_valid ? *r_is_valid : valid);
-	} else if (!is_null()) {
+	} else if (is_valid()) {
 		return get_object()->get_method_argument_count(method, r_is_valid);
 	} else {
 		if (r_is_valid) {


Test patch: