[GDScript] Correctly report invalid read-only access

This commit is contained in:
A Thousand Ships 2024-03-18 14:42:42 +01:00
parent 26738ea20d
commit c4e24d2b3b
No known key found for this signature in database
GPG key ID: 2033189A662F8BD7
6 changed files with 64 additions and 36 deletions

View file

@ -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: {

View file

@ -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;

View file

@ -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(); if (dst->is_read_only()) {
String v = index->operator String(); err_text = "Invalid assignment on read-only value (on base: '" + _get_var_type(dst) + "').";
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));
} else { } else {
if (!v.is_empty()) { Object *obj = dst->get_validated_object();
v = "'" + v + "'"; String v = index->operator String();
} else { bool read_only_property = false;
v = "of type '" + _get_var_type(index) + "'"; 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 (read_only_property) {
if (err_code == Variant::VariantSetError::SET_INDEXED_ERR) { err_text = vformat(R"(Cannot set value into property "%s" (on base "%s") because it is read-only.)", v, _get_var_type(dst));
err_text = "Invalid assignment of index " + v + " (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'."; } 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 (dst->is_read_only()) {
if (!v.is_empty()) { err_text = "Invalid assignment on read-only value (on base: '" + _get_var_type(dst) + "').";
v = "'" + v + "'";
} 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 (dst->is_read_only()) {
if (!v.is_empty()) { err_text = "Invalid assignment on read-only value (on base: '" + _get_var_type(dst) + "').";
v = "'" + v + "'";
} 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(); if (dst->is_read_only()) {
bool read_only_property = false; err_text = "Invalid assignment on read-only value (on base: '" + _get_var_type(dst) + "').";
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 { } 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;
} }

View file

@ -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').

View file

@ -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').

View file

@ -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').