From 6e996a597fca1181436816a82cced6b8cf34f280 Mon Sep 17 00:00:00 2001 From: Danil Alexeev Date: Fri, 27 Oct 2023 11:08:54 +0300 Subject: [PATCH] GDScript: Fix `UNSAFE_CAST` warning --- doc/classes/ProjectSettings.xml | 2 +- modules/gdscript/gdscript_analyzer.cpp | 4 +-- modules/gdscript/gdscript_warning.cpp | 2 +- modules/gdscript/gdscript_warning.h | 2 +- .../analyzer/errors/cast_int_to_array.gd | 3 ++ .../analyzer/errors/cast_int_to_array.out | 2 ++ .../analyzer/errors/cast_int_to_object.gd | 3 ++ .../analyzer/errors/cast_int_to_object.out | 2 ++ .../analyzer/errors/cast_object_to_int.gd | 3 ++ .../analyzer/errors/cast_object_to_int.out | 2 ++ .../scripts/analyzer/warnings/unsafe_cast.gd | 24 ++++++++++++++ .../scripts/analyzer/warnings/unsafe_cast.out | 33 +++++++++++++++++++ .../runtime/errors/cast_freed_object.gd | 4 +++ .../runtime/errors/cast_freed_object.out | 6 ++++ .../runtime/errors/cast_int_to_array.gd | 4 +++ .../runtime/errors/cast_int_to_array.out | 6 ++++ .../runtime/errors/cast_int_to_object.gd | 4 +++ .../runtime/errors/cast_int_to_object.out | 6 ++++ .../runtime/errors/cast_object_to_int.gd | 4 +++ .../runtime/errors/cast_object_to_int.out | 6 ++++ .../scripts/runtime/features/type_casting.gd | 24 ++++++++++++++ .../scripts/runtime/features/type_casting.out | 10 ++++++ 22 files changed, 150 insertions(+), 6 deletions(-) create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_array.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_array.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_object.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_object.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/cast_object_to_int.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/cast_object_to_int.out create mode 100644 modules/gdscript/tests/scripts/analyzer/warnings/unsafe_cast.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/warnings/unsafe_cast.out create mode 100644 modules/gdscript/tests/scripts/runtime/errors/cast_freed_object.gd create mode 100644 modules/gdscript/tests/scripts/runtime/errors/cast_freed_object.out create mode 100644 modules/gdscript/tests/scripts/runtime/errors/cast_int_to_array.gd create mode 100644 modules/gdscript/tests/scripts/runtime/errors/cast_int_to_array.out create mode 100644 modules/gdscript/tests/scripts/runtime/errors/cast_int_to_object.gd create mode 100644 modules/gdscript/tests/scripts/runtime/errors/cast_int_to_object.out create mode 100644 modules/gdscript/tests/scripts/runtime/errors/cast_object_to_int.gd create mode 100644 modules/gdscript/tests/scripts/runtime/errors/cast_object_to_int.out create mode 100644 modules/gdscript/tests/scripts/runtime/features/type_casting.gd create mode 100644 modules/gdscript/tests/scripts/runtime/features/type_casting.out diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 69a374fd587..6abcd9d72cd 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -556,7 +556,7 @@ When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when using an expression whose type may not be compatible with the function parameter expected. - When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when performing an unsafe cast. + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a [Variant] value is cast to a non-Variant. When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when calling a method whose presence is not guaranteed at compile-time in the class. diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 983a19470ad..a5728374cf2 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -3453,9 +3453,7 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) { if (op_type.is_variant() || !op_type.is_hard_type()) { mark_node_unsafe(p_cast); #ifdef DEBUG_ENABLED - if (op_type.is_variant() && !op_type.is_hard_type()) { - parser->push_warning(p_cast, GDScriptWarning::UNSAFE_CAST, cast_type.to_string()); - } + parser->push_warning(p_cast, GDScriptWarning::UNSAFE_CAST, cast_type.to_string()); #endif } else { bool valid = false; diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp index 1fe9b0425c8..f2ba75b032c 100644 --- a/modules/gdscript/gdscript_warning.cpp +++ b/modules/gdscript/gdscript_warning.cpp @@ -105,7 +105,7 @@ String GDScriptWarning::get_message() const { return vformat(R"*(The method "%s()" is not present on the inferred type "%s" (but may be present on a subtype).)*", symbols[0], symbols[1]); case UNSAFE_CAST: CHECK_SYMBOLS(1); - return vformat(R"(The value is cast to "%s" but has an unknown type.)", symbols[0]); + return vformat(R"(Casting "Variant" to "%s" is unsafe.)", symbols[0]); case UNSAFE_CALL_ARGUMENT: CHECK_SYMBOLS(5); return vformat(R"*(The argument %s of the %s "%s()" requires the subtype "%s" but the supertype "%s" was provided.)*", symbols[0], symbols[1], symbols[2], symbols[3], symbols[4]); diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h index 1aef6fa81bb..6a3d84c0c78 100644 --- a/modules/gdscript/gdscript_warning.h +++ b/modules/gdscript/gdscript_warning.h @@ -68,7 +68,7 @@ public: INFERRED_DECLARATION, // Variable/constant/parameter has an implicitly inferred static type. UNSAFE_PROPERTY_ACCESS, // Property not found in the detected type (but can be in subtypes). UNSAFE_METHOD_ACCESS, // Function not found in the detected type (but can be in subtypes). - UNSAFE_CAST, // Cast used in an unknown type. + UNSAFE_CAST, // Casting a `Variant` value to non-`Variant`. UNSAFE_CALL_ARGUMENT, // Function call argument is of a supertype of the required type. UNSAFE_VOID_RETURN, // Function returns void but returned a call to a function that can't be type checked. RETURN_VALUE_DISCARDED, // Function call returns something but the value isn't used. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_array.gd b/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_array.gd new file mode 100644 index 00000000000..b53e814eea7 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_array.gd @@ -0,0 +1,3 @@ +func test(): + var integer := 1 + print(integer as Array) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_array.out b/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_array.out new file mode 100644 index 00000000000..e3e82c2b7e1 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_array.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid cast. Cannot convert from "int" to "Array". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_object.gd b/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_object.gd new file mode 100644 index 00000000000..323e367f8e9 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_object.gd @@ -0,0 +1,3 @@ +func test(): + var integer := 1 + print(integer as Node) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_object.out b/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_object.out new file mode 100644 index 00000000000..7de40418bf0 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_object.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid cast. Cannot convert from "int" to "Node". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cast_object_to_int.gd b/modules/gdscript/tests/scripts/analyzer/errors/cast_object_to_int.gd new file mode 100644 index 00000000000..f6cd5e217e4 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cast_object_to_int.gd @@ -0,0 +1,3 @@ +func test(): + var object := RefCounted.new() + print(object as int) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cast_object_to_int.out b/modules/gdscript/tests/scripts/analyzer/errors/cast_object_to_int.out new file mode 100644 index 00000000000..8af0847577a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cast_object_to_int.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid cast. Cannot convert from "RefCounted" to "int". diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_cast.gd b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_cast.gd new file mode 100644 index 00000000000..1a6d10f8f75 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_cast.gd @@ -0,0 +1,24 @@ +# We don't want to execute it because of errors, just analyze. +func no_exec_test(): + var weak_int = 1 + print(weak_int as Variant) # No warning. + print(weak_int as int) + print(weak_int as Node) + + var weak_node = Node.new() + print(weak_node as Variant) # No warning. + print(weak_node as int) + print(weak_node as Node) + + var weak_variant = null + print(weak_variant as Variant) # No warning. + print(weak_variant as int) + print(weak_variant as Node) + + var hard_variant: Variant = null + print(hard_variant as Variant) # No warning. + print(hard_variant as int) + print(hard_variant as Node) + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_cast.out b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_cast.out new file mode 100644 index 00000000000..c1e683d942d --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_cast.out @@ -0,0 +1,33 @@ +GDTEST_OK +>> WARNING +>> Line: 5 +>> UNSAFE_CAST +>> Casting "Variant" to "int" is unsafe. +>> WARNING +>> Line: 6 +>> UNSAFE_CAST +>> Casting "Variant" to "Node" is unsafe. +>> WARNING +>> Line: 10 +>> UNSAFE_CAST +>> Casting "Variant" to "int" is unsafe. +>> WARNING +>> Line: 11 +>> UNSAFE_CAST +>> Casting "Variant" to "Node" is unsafe. +>> WARNING +>> Line: 15 +>> UNSAFE_CAST +>> Casting "Variant" to "int" is unsafe. +>> WARNING +>> Line: 16 +>> UNSAFE_CAST +>> Casting "Variant" to "Node" is unsafe. +>> WARNING +>> Line: 20 +>> UNSAFE_CAST +>> Casting "Variant" to "int" is unsafe. +>> WARNING +>> Line: 21 +>> UNSAFE_CAST +>> Casting "Variant" to "Node" is unsafe. diff --git a/modules/gdscript/tests/scripts/runtime/errors/cast_freed_object.gd b/modules/gdscript/tests/scripts/runtime/errors/cast_freed_object.gd new file mode 100644 index 00000000000..6b766f4d3d0 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/cast_freed_object.gd @@ -0,0 +1,4 @@ +func test(): + var node := Node.new() + node.free() + print(node as Node2D) diff --git a/modules/gdscript/tests/scripts/runtime/errors/cast_freed_object.out b/modules/gdscript/tests/scripts/runtime/errors/cast_freed_object.out new file mode 100644 index 00000000000..90d81dd9a1a --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/cast_freed_object.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/cast_freed_object.gd +>> 4 +>> Trying to cast a freed object. diff --git a/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_array.gd b/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_array.gd new file mode 100644 index 00000000000..00817c588fa --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_array.gd @@ -0,0 +1,4 @@ +func test(): + var integer: Variant = 1 + @warning_ignore("unsafe_cast") + print(integer as Array) diff --git a/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_array.out b/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_array.out new file mode 100644 index 00000000000..545d7a4906e --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_array.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/cast_int_to_array.gd +>> 4 +>> Invalid cast: could not convert value to 'Array'. diff --git a/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_object.gd b/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_object.gd new file mode 100644 index 00000000000..44673a45137 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_object.gd @@ -0,0 +1,4 @@ +func test(): + var integer: Variant = 1 + @warning_ignore("unsafe_cast") + print(integer as Node) diff --git a/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_object.out b/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_object.out new file mode 100644 index 00000000000..7c39b463963 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_object.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/cast_int_to_object.gd +>> 4 +>> Invalid cast: can't convert a non-object value to an object type. diff --git a/modules/gdscript/tests/scripts/runtime/errors/cast_object_to_int.gd b/modules/gdscript/tests/scripts/runtime/errors/cast_object_to_int.gd new file mode 100644 index 00000000000..830d0c0c4ac --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/cast_object_to_int.gd @@ -0,0 +1,4 @@ +func test(): + var object: Variant = RefCounted.new() + @warning_ignore("unsafe_cast") + print(object as int) diff --git a/modules/gdscript/tests/scripts/runtime/errors/cast_object_to_int.out b/modules/gdscript/tests/scripts/runtime/errors/cast_object_to_int.out new file mode 100644 index 00000000000..f922199fb30 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/cast_object_to_int.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/cast_object_to_int.gd +>> 4 +>> Invalid cast: could not convert value to 'int'. diff --git a/modules/gdscript/tests/scripts/runtime/features/type_casting.gd b/modules/gdscript/tests/scripts/runtime/features/type_casting.gd new file mode 100644 index 00000000000..c63ea16c32c --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/type_casting.gd @@ -0,0 +1,24 @@ +func print_value(value: Variant) -> void: + if value is Object: + @warning_ignore("unsafe_method_access") + print("<%s>" % value.get_class()) + else: + print(var_to_str(value)) + +func test(): + var int_value := 1 + print_value(int_value as Variant) + print_value(int_value as int) + print_value(int_value as float) + + var node_value := Node.new() + print_value(node_value as Variant) + print_value(node_value as Object) + print_value(node_value as Node) + print_value(node_value as Node2D) + node_value.free() + + var null_value = null + print_value(null_value as Variant) + @warning_ignore("unsafe_cast") + print_value(null_value as Node) diff --git a/modules/gdscript/tests/scripts/runtime/features/type_casting.out b/modules/gdscript/tests/scripts/runtime/features/type_casting.out new file mode 100644 index 00000000000..7da5a4c0a4c --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/type_casting.out @@ -0,0 +1,10 @@ +GDTEST_OK +1 +1 +1.0 + + + +null +null +null