From 77e5e195f580c2d0bde25265653eb7f2461a7cdf Mon Sep 17 00:00:00 2001 From: Raul Santos Date: Sun, 9 Jul 2023 19:42:55 +0200 Subject: [PATCH] C#: Print error when MethodBind/Callable call fails --- modules/mono/editor/bindings_generator.cpp | 17 ++- .../GodotSharp/GodotSharp/Core/Callable.cs | 3 +- .../Core/NativeInterop/ExceptionUtils.cs | 104 ++++++++++++++++++ .../Core/NativeInterop/InteropStructs.cs | 1 + 4 files changed, 123 insertions(+), 2 deletions(-) diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 15bb574f4d4..1e59b71bc31 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -2087,6 +2087,11 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf cs_in_expr_is_unsafe |= arg_type->cs_in_expr_is_unsafe; } + // Collect caller name for MethodBind + if (p_imethod.is_vararg) { + icall_params += ", (godot_string_name)MethodName." + p_imethod.proxy_name + ".NativeValue"; + } + // Generate method { if (!p_imethod.is_virtual && !p_imethod.requires_object_call) { @@ -2460,6 +2465,11 @@ Error BindingsGenerator::_generate_cs_native_calls(const InternalCall &p_icall, i++; } + // Collect caller name for MethodBind + if (p_icall.is_vararg) { + c_func_sig << ", godot_string_name caller"; + } + String icall_method = p_icall.name; // Generate icall function @@ -2525,7 +2535,12 @@ Error BindingsGenerator::_generate_cs_native_calls(const InternalCall &p_icall, r_output << C_CLASS_NATIVE_FUNCS ".godotsharp_method_bind_call(" << CS_PARAM_METHODBIND ", " << (p_icall.is_static ? "IntPtr.Zero" : CS_PARAM_INSTANCE) << ", " << (p_icall.get_arguments_count() ? "(godot_variant**)" C_LOCAL_PTRCALL_ARGS : "null") - << ", total_length, out _);\n"; + << ", total_length, out godot_variant_call_error vcall_error);\n"; + + r_output << base_indent << "ExceptionUtils.DebugCheckCallError(caller" + << ", " << (p_icall.is_static ? "IntPtr.Zero" : CS_PARAM_INSTANCE) + << ", " << (p_icall.get_arguments_count() ? "(godot_variant**)" C_LOCAL_PTRCALL_ARGS : "null") + << ", total_length, vcall_error);\n"; if (!ret_void) { if (return_type->cname != name_cache.type_Variant) { diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs index 219a9a8c158..1239533a01c 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs @@ -109,7 +109,8 @@ namespace Godot } godot_variant ret = NativeFuncs.godotsharp_callable_call(callable, - (godot_variant**)argsPtr, argc, out _); + (godot_variant**)argsPtr, argc, out godot_variant_call_error vcall_error); + ExceptionUtils.DebugCheckCallError(callable, (godot_variant**)argsPtr, argc, vcall_error); return Variant.CreateTakingOwnershipOfDisposableValue(ret); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs index 2d8067d3006..ba2c232580f 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs @@ -135,5 +135,109 @@ namespace Godot.NativeInterop OnExceptionLoggerException(unexpected, e); } } + + [Conditional("DEBUG")] + public unsafe static void DebugCheckCallError(godot_string_name method, IntPtr instance, godot_variant** args, int argCount, godot_variant_call_error error) + { + if (error.Error != godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_OK) + { + using godot_variant instanceVariant = VariantUtils.CreateFromGodotObjectPtr(instance); + string where = GetCallErrorWhere(method, &instanceVariant, args, argCount); + string errorText = GetCallErrorMessage(error, where, args); + GD.PushError(errorText); + } + } + + [Conditional("DEBUG")] + public unsafe static void DebugCheckCallError(in godot_callable callable, godot_variant** args, int argCount, godot_variant_call_error error) + { + if (error.Error != godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_OK) + { + using godot_variant callableVariant = VariantUtils.CreateFromCallableTakingOwnershipOfDisposableValue(callable); + string where = $"callable '{VariantUtils.ConvertToString(callableVariant)}'"; + string errorText = GetCallErrorMessage(error, where, args); + GD.PushError(errorText); + } + } + + private unsafe static string GetCallErrorWhere(godot_string_name method, godot_variant* instance, godot_variant** args, int argCount) + { + string? methodstr = null; + string basestr = GetVariantTypeName(instance); + + if (method == GodotObject.MethodName.Call || (basestr == "Godot.TreeItem" && method == TreeItem.MethodName.CallRecursive)) + { + if (argCount >= 1) + { + methodstr = VariantUtils.ConvertToString(*args[0]); + } + } + + if (string.IsNullOrEmpty(methodstr)) + { + methodstr = StringName.CreateTakingOwnershipOfDisposableValue(method); + } + + return $"function '{methodstr}' in base '{basestr}'"; + } + + private unsafe static string GetCallErrorMessage(godot_variant_call_error error, string where, godot_variant** args) + { + switch (error.Error) + { + case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INVALID_ARGUMENT: + { + int errorarg = error.Argument; + // Handle the Object to Object case separately as we don't have further class details. +#if DEBUG + if (error.Expected == Variant.Type.Object && args[errorarg]->Type == error.Expected) + { + return $"Invalid type in {where}. The Object-derived class of argument {errorarg + 1} (" + GetVariantTypeName(args[errorarg]) + ") is not a subclass of the expected argument class."; + } + else if (error.Expected == Variant.Type.Array && args[errorarg]->Type == error.Expected) + { + return $"Invalid type in {where}. The array of argument {errorarg + 1} (" + GetVariantTypeName(args[errorarg]) + ") does not have the same element type as the expected typed array argument."; + } + else +#endif + { + return $"Invalid type in {where}. Cannot convert argument {errorarg + 1} from {args[errorarg]->Type} to {error.Expected}."; + } + } + case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_TOO_MANY_ARGUMENTS: + case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_TOO_FEW_ARGUMENTS: + return $"Invalid call to {where}. Expected {error.Argument} arguments."; + case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INVALID_METHOD: + return $"Invalid call. Nonexistent {where}."; + case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INSTANCE_IS_NULL: + return $"Attempt to call {where} on a null instance."; + case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_METHOD_NOT_CONST: + return $"Attempt to call {where} on a const instance."; + default: + return $"Bug, call error: #{error.Error}"; + } + } + + private unsafe static string GetVariantTypeName(godot_variant* variant) + { + if (variant->Type == Variant.Type.Object) + { + GodotObject obj = VariantUtils.ConvertToGodotObject(*variant); + if (obj == null) + { + return "null instance"; + } + else if (!GodotObject.IsInstanceValid(obj)) + { + return "previously freed"; + } + else + { + return obj.GetType().ToString(); + } + } + + return variant->Type.ToString(); + } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs index 43e7c7eb9ac..1dddc82e857 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs @@ -71,6 +71,7 @@ namespace Godot.NativeInterop GODOT_CALL_ERROR_CALL_ERROR_TOO_MANY_ARGUMENTS, GODOT_CALL_ERROR_CALL_ERROR_TOO_FEW_ARGUMENTS, GODOT_CALL_ERROR_CALL_ERROR_INSTANCE_IS_NULL, + GODOT_CALL_ERROR_CALL_ERROR_METHOD_NOT_CONST, } [StructLayout(LayoutKind.Sequential)]