Browse Source

Merge pull request #89648 from AThousandShips/read_only_check_2

[GDScript] Correctly report invalid read-only access
Rémi Verschelde 1 year ago
parent
commit
c27d7a9ae9

+ 11 - 0
core/variant/variant.cpp

@@ -3515,6 +3515,17 @@ bool Variant::is_shared() const {
 	return is_type_shared(type);
 	return is_type_shared(type);
 }
 }
 
 
+bool Variant::is_read_only() const {
+	switch (type) {
+		case ARRAY:
+			return reinterpret_cast<const Array *>(_data._mem)->is_read_only();
+		case DICTIONARY:
+			return reinterpret_cast<const Dictionary *>(_data._mem)->is_read_only();
+		default:
+			return false;
+	}
+}
+
 void Variant::_variant_call_error(const String &p_method, Callable::CallError &error) {
 void Variant::_variant_call_error(const String &p_method, Callable::CallError &error) {
 	switch (error.error) {
 	switch (error.error) {
 		case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: {
 		case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: {

+ 1 - 0
core/variant/variant.h

@@ -349,6 +349,7 @@ public:
 	bool is_zero() const;
 	bool is_zero() const;
 	bool is_one() const;
 	bool is_one() const;
 	bool is_null() const;
 	bool is_null() const;
+	bool is_read_only() const;
 
 
 	// Make sure Variant is not implicitly cast when accessing it with bracket notation (GH-49469).
 	// Make sure Variant is not implicitly cast when accessing it with bracket notation (GH-49469).
 	Variant &operator[](const Variant &p_key) = delete;
 	Variant &operator[](const Variant &p_key) = delete;

+ 49 - 33
modules/gdscript/gdscript_vm.cpp

@@ -884,23 +884,27 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
 #endif
 #endif
 #ifdef DEBUG_ENABLED
 #ifdef DEBUG_ENABLED
 				if (!valid) {
 				if (!valid) {
-					Object *obj = dst->get_validated_object();
-					String v = index->operator String();
-					bool read_only_property = false;
-					if (obj) {
-						read_only_property = ClassDB::has_property(obj->get_class_name(), v) && (ClassDB::get_property_setter(obj->get_class_name(), v) == StringName());
-					}
-					if (read_only_property) {
-						err_text = vformat(R"(Cannot set value into property "%s" (on base "%s") because it is read-only.)", v, _get_var_type(dst));
+					if (dst->is_read_only()) {
+						err_text = "Invalid assignment on read-only value (on base: '" + _get_var_type(dst) + "').";
 					} else {
 					} else {
-						if (!v.is_empty()) {
-							v = "'" + v + "'";
-						} else {
-							v = "of type '" + _get_var_type(index) + "'";
+						Object *obj = dst->get_validated_object();
+						String v = index->operator String();
+						bool read_only_property = false;
+						if (obj) {
+							read_only_property = ClassDB::has_property(obj->get_class_name(), v) && (ClassDB::get_property_setter(obj->get_class_name(), v) == StringName());
 						}
 						}
-						err_text = "Invalid assignment of property or key " + v + " with value of type '" + _get_var_type(value) + "' on a base object of type '" + _get_var_type(dst) + "'.";
-						if (err_code == Variant::VariantSetError::SET_INDEXED_ERR) {
-							err_text = "Invalid assignment of index " + v + " (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'.";
+						if (read_only_property) {
+							err_text = vformat(R"(Cannot set value into property "%s" (on base "%s") because it is read-only.)", v, _get_var_type(dst));
+						} else {
+							if (!v.is_empty()) {
+								v = "'" + v + "'";
+							} else {
+								v = "of type '" + _get_var_type(index) + "'";
+							}
+							err_text = "Invalid assignment of property or key " + v + " with value of type '" + _get_var_type(value) + "' on a base object of type '" + _get_var_type(dst) + "'.";
+							if (err_code == Variant::VariantSetError::SET_INDEXED_ERR) {
+								err_text = "Invalid assignment of index " + v + " (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'.";
+							}
 						}
 						}
 					}
 					}
 					OPCODE_BREAK;
 					OPCODE_BREAK;
@@ -926,13 +930,17 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
 
 
 #ifdef DEBUG_ENABLED
 #ifdef DEBUG_ENABLED
 				if (!valid) {
 				if (!valid) {
-					String v = index->operator String();
-					if (!v.is_empty()) {
-						v = "'" + v + "'";
+					if (dst->is_read_only()) {
+						err_text = "Invalid assignment on read-only value (on base: '" + _get_var_type(dst) + "').";
 					} else {
 					} else {
-						v = "of type '" + _get_var_type(index) + "'";
+						String v = index->operator String();
+						if (!v.is_empty()) {
+							v = "'" + v + "'";
+						} else {
+							v = "of type '" + _get_var_type(index) + "'";
+						}
+						err_text = "Invalid assignment of property or key " + v + " with value of type '" + _get_var_type(value) + "' on a base object of type '" + _get_var_type(dst) + "'.";
 					}
 					}
-					err_text = "Invalid assignment of property or key " + v + " with value of type '" + _get_var_type(value) + "' on a base object of type '" + _get_var_type(dst) + "'.";
 					OPCODE_BREAK;
 					OPCODE_BREAK;
 				}
 				}
 #endif
 #endif
@@ -958,13 +966,17 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
 
 
 #ifdef DEBUG_ENABLED
 #ifdef DEBUG_ENABLED
 				if (oob) {
 				if (oob) {
-					String v = index->operator String();
-					if (!v.is_empty()) {
-						v = "'" + v + "'";
+					if (dst->is_read_only()) {
+						err_text = "Invalid assignment on read-only value (on base: '" + _get_var_type(dst) + "').";
 					} else {
 					} else {
-						v = "of type '" + _get_var_type(index) + "'";
+						String v = index->operator String();
+						if (!v.is_empty()) {
+							v = "'" + v + "'";
+						} else {
+							v = "of type '" + _get_var_type(index) + "'";
+						}
+						err_text = "Out of bounds set index " + v + " (on base: '" + _get_var_type(dst) + "')";
 					}
 					}
-					err_text = "Out of bounds set index " + v + " (on base: '" + _get_var_type(dst) + "')";
 					OPCODE_BREAK;
 					OPCODE_BREAK;
 				}
 				}
 #endif
 #endif
@@ -1092,15 +1104,19 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
 
 
 #ifdef DEBUG_ENABLED
 #ifdef DEBUG_ENABLED
 				if (!valid) {
 				if (!valid) {
-					Object *obj = dst->get_validated_object();
-					bool read_only_property = false;
-					if (obj) {
-						read_only_property = ClassDB::has_property(obj->get_class_name(), *index) && (ClassDB::get_property_setter(obj->get_class_name(), *index) == StringName());
-					}
-					if (read_only_property) {
-						err_text = vformat(R"(Cannot set value into property "%s" (on base "%s") because it is read-only.)", String(*index), _get_var_type(dst));
+					if (dst->is_read_only()) {
+						err_text = "Invalid assignment on read-only value (on base: '" + _get_var_type(dst) + "').";
 					} else {
 					} else {
-						err_text = "Invalid assignment of property or key '" + String(*index) + "' with value of type '" + _get_var_type(value) + "' on a base object of type '" + _get_var_type(dst) + "'.";
+						Object *obj = dst->get_validated_object();
+						bool read_only_property = false;
+						if (obj) {
+							read_only_property = ClassDB::has_property(obj->get_class_name(), *index) && (ClassDB::get_property_setter(obj->get_class_name(), *index) == StringName());
+						}
+						if (read_only_property) {
+							err_text = vformat(R"(Cannot set value into property "%s" (on base "%s") because it is read-only.)", String(*index), _get_var_type(dst));
+						} else {
+							err_text = "Invalid assignment of property or key '" + String(*index) + "' with value of type '" + _get_var_type(value) + "' on a base object of type '" + _get_var_type(dst) + "'.";
+						}
 					}
 					}
 					OPCODE_BREAK;
 					OPCODE_BREAK;
 				}
 				}

+ 1 - 1
modules/gdscript/tests/scripts/runtime/errors/constant_array_is_deep.out

@@ -3,4 +3,4 @@ GDTEST_RUNTIME_ERROR
 >> on function: test()
 >> on function: test()
 >> runtime/errors/constant_array_is_deep.gd
 >> runtime/errors/constant_array_is_deep.gd
 >> 6
 >> 6
->> Invalid assignment of property or key '0' with value of type 'int' on a base object of type 'Dictionary'.
+>> Invalid assignment on read-only value (on base: 'Dictionary').

+ 1 - 1
modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_is_deep.out

@@ -3,4 +3,4 @@ GDTEST_RUNTIME_ERROR
 >> on function: test()
 >> on function: test()
 >> runtime/errors/constant_dictionary_is_deep.gd
 >> runtime/errors/constant_dictionary_is_deep.gd
 >> 6
 >> 6
->> Invalid assignment of index '0' (on base: 'Array') with value of type 'int'.
+>> Invalid assignment on read-only value (on base: 'Array').

+ 1 - 1
modules/gdscript/tests/scripts/runtime/errors/read_only_dictionary.out

@@ -3,4 +3,4 @@ GDTEST_RUNTIME_ERROR
 >> on function: test()
 >> on function: test()
 >> runtime/errors/read_only_dictionary.gd
 >> runtime/errors/read_only_dictionary.gd
 >> 4
 >> 4
->> Invalid assignment of property or key 'a' with value of type 'int' on a base object of type 'Dictionary'.
+>> Invalid assignment on read-only value (on base: 'Dictionary').