Repository: godotengine/godot
Pull request #105537: Fix cases where `_get` returned true erroneously
Author: login: RedMser
Labels: name: bug
name: topic:core
Created: 2025-04-18T19:04:27Z | Merged: 2025-04-28T00:27:05Z

If you for example create a `BoneAttachment3D` and query `"albasldasbla" in bone_attachment` it would always return true.
This is because its `_get` always reported that a property exists.

I went through all classes that override `_get` and fixed this problem everywhere I could find.
The same still has to be done for `_set` in the future (I only updated those `_set`'s on classes that had erroneous `_get`'s)!

Ideally, this could be automatically detected and reported in debug builds, by checking whether the second parameter was modified when true is returned. But that is outside of the scope of this PR.


Diff:
diff --git a/modules/openxr/scene/openxr_composition_layer.cpp b/modules/openxr/scene/openxr_composition_layer.cpp
index 3c26edcbe42c..c71455358c52 100644
--- a/modules/openxr/scene/openxr_composition_layer.cpp
+++ b/modules/openxr/scene/openxr_composition_layer.cpp
@@ -662,9 +662,10 @@ void OpenXRCompositionLayer::_get_property_list(List<PropertyInfo> *p_property_l
 bool OpenXRCompositionLayer::_get(const StringName &p_property, Variant &r_value) const {
 	if (extension_property_values.has(p_property)) {
 		r_value = extension_property_values[p_property];
+		return true;
 	}
 
-	return true;
+	return false;
 }
 
 bool OpenXRCompositionLayer::_set(const StringName &p_property, const Variant &p_value) {
diff --git a/scene/3d/bone_attachment_3d.cpp b/scene/3d/bone_attachment_3d.cpp
index 59647457d831..e47882efacc7 100644
--- a/scene/3d/bone_attachment_3d.cpp
+++ b/scene/3d/bone_attachment_3d.cpp
@@ -56,21 +56,25 @@ void BoneAttachment3D::_validate_property(PropertyInfo &p_property) const {
 bool BoneAttachment3D::_set(const StringName &p_path, const Variant &p_value) {
 	if (p_path == SNAME("use_external_skeleton")) {
 		set_use_external_skeleton(p_value);
+		return true;
 	} else if (p_path == SNAME("external_skeleton")) {
 		set_external_skeleton(p_value);
+		return true;
 	}
 
-	return true;
+	return false;
 }
 
 bool BoneAttachment3D::_get(const StringName &p_path, Variant &r_ret) const {
 	if (p_path == SNAME("use_external_skeleton")) {
 		r_ret = get_use_external_skeleton();
+		return true;
 	} else if (p_path == SNAME("external_skeleton")) {
 		r_ret = get_external_skeleton();
+		return true;
 	}
 
-	return true;
+	return false;
 }
 
 void BoneAttachment3D::_get_property_list(List<PropertyInfo> *p_list) const {
diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.cpp b/scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.cpp
index 0dcba66c5e61..9aee817450c1 100644
--- a/scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.cpp
+++ b/scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.cpp
@@ -67,7 +67,8 @@ bool SkeletonModification2DPhysicalBones::_get(const StringName &p_path, Variant
 #ifdef TOOLS_ENABLED
 	if (Engine::get_singleton()->is_editor_hint()) {
 		if (path.begins_with("fetch_bones")) {
-			return true; // Do nothing!
+			// Do nothing!
+			return false;
 		}
 	}
 #endif //TOOLS_ENABLED
diff --git a/scene/resources/skeleton_profile.cpp b/scene/resources/skeleton_profile.cpp
index 2a1b64078a4c..0d3fbd35115d 100644
--- a/scene/resources/skeleton_profile.cpp
+++ b/scene/resources/skeleton_profile.cpp
@@ -41,10 +41,10 @@ bool SkeletonProfile::_set(const StringName &p_path, const Variant &p_value) {
 
 		if (what == "group_name") {
 			set_group_name(which, p_value);
+			return true;
 		} else if (what == "texture") {
 			set_texture(which, p_value);
-		} else {
-			return false;
+			return true;
 		}
 	}
 
@@ -55,25 +55,31 @@ bool SkeletonProfile::_set(const StringName &p_path, const Variant &p_value) {
 
 		if (what == "bone_name") {
 			set_bone_name(which, p_value);
+			return true;
 		} else if (what == "bone_parent") {
 			set_bone_parent(which, p_value);
+			return true;
 		} else if (what == "tail_direction") {
 			set_tail_direction(which, static_cast<TailDirection>((int)p_value));
+			return true;
 		} else if (what == "bone_tail") {
 			set_bone_tail(which, p_value);
+			return true;
 		} else if (what == "reference_pose") {
 			set_reference_pose(which, p_value);
+			return true;
 		} else if (what == "handle_offset") {
 			set_handle_offset(which, p_value);
+			return true;
 		} else if (what == "group") {
 			set_group(which, p_value);
+			return true;
 		} else if (what == "require") {
 			set_required(which, p_value);
-		} else {
-			return false;
+			return true;
 		}
 	}
-	return true;
+	return false;
 }
 
 bool SkeletonProfile::_get(const StringName &p_path, Variant &r_ret) const {
@@ -86,39 +92,44 @@ bool SkeletonProfile::_get(const StringName &p_path, Variant &r_ret) const {
 
 		if (what == "group_name") {
 			r_ret = get_group_name(which);
+			return true;
 		} else if (what == "texture") {
 			r_ret = get_texture(which);
-		} else {
-			return false;
+			return true;
 		}
-	}
-
-	if (path.begins_with("bones/")) {
+	} else if (path.begins_with("bones/")) {
 		int which = path.get_slicec('/', 1).to_int();
 		String what = path.get_slicec('/', 2);
 		ERR_FAIL_INDEX_V(which, bones.size(), false);
 
 		if (what == "bone_name") {
 			r_ret = get_bone_name(which);
+			return true;
 		} else if (what == "bone_parent") {
 			r_ret = get_bone_parent(which);
+			return true;
 		} else if (what == "tail_direction") {
 			r_ret = get_tail_direction(which);
+			return true;
 		} else if (what == "bone_tail") {
 			r_ret = get_bone_tail(which);
+			return true;
 		} else if (what == "reference_pose") {
 			r_ret = get_reference_pose(which);
+			return true;
 		} else if (what == "handle_offset") {
 			r_ret = get_handle_offset(which);
+			return true;
 		} else if (what == "group") {
 			r_ret = get_group(which);
+			return true;
 		} else if (what == "require") {
 			r_ret = is_required(which);
-		} else {
-			return false;
+			return true;
 		}
 	}
-	return true;
+
+	return false;
 }
 
 void SkeletonProfile::_validate_property(PropertyInfo &p_property) const {


Reviews:
user: login: RedMser
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T19:07:07Z
user: login: RedMser
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T19:09:46Z
user: login: KoBeWi
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-25T08:54:13Z
user: login: KoBeWi
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-25T08:58:57Z
user: login: akien-mga
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-25T09:01:59Z
user: login: akien-mga
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-25T09:07:11Z
user: login: akien-mga
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-25T09:08:41Z
user: login: RedMser
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T10:35:36Z
user: login: RedMser
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T10:38:30Z
user: login: KoBeWi
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-25T10:56:28Z
user: login: RedMser
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T16:55:44Z
user: login: RedMser
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T17:01:27Z
user: login: RedMser
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T17:02:56Z
user: login: RedMser
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T17:08:57Z
user: login: KoBeWi
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-26T21:03:30Z

Review comments:
diff_hunk: @@ -67,7 +67,7 @@ bool SkeletonModification2DPhysicalBones::_get(const StringName &p_path, Variant
 #ifdef TOOLS_ENABLED
 	if (Engine::get_singleton()->is_editor_hint()) {
 		if (path.begins_with("fetch_bones")) {
-			return true; // Do nothing!
+			return false; // Do nothing!
path: scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.cpp
user: login: RedMser
body: this change seems wrong, will investigate
created_at: 2025-04-18T19:07:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -67,7 +67,7 @@ bool SkeletonModification2DPhysicalBones::_get(const StringName &p_path, Variant
 #ifdef TOOLS_ENABLED
 	if (Engine::get_singleton()->is_editor_hint()) {
 		if (path.begins_with("fetch_bones")) {
-			return true; // Do nothing!
+			return false; // Do nothing!
path: scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.cpp
user: login: RedMser
body: OK this bool is basically a button in the inspector. Kinda hacky, but that means it should exist as a property (`_get` returns true) but always have a value of "false" I think.
created_at: 2025-04-18T19:09:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -662,6 +662,8 @@ void OpenXRCompositionLayer::_get_property_list(List<PropertyInfo> *p_property_l
 bool OpenXRCompositionLayer::_get(const StringName &p_property, Variant &r_value) const {
 	if (extension_property_values.has(p_property)) {
 		r_value = extension_property_values[p_property];
+	} else {
+		return false;
 	}
 
 	return true;
path: modules/openxr/scene/openxr_composition_layer.cpp
user: login: KoBeWi
body: This could be changed to
```suggestion
		return true;
	}
	return false;
```
created_at: 2025-04-25T08:54:12Z
author_association: MEMBER
diff_hunk: @@ -67,7 +67,9 @@ bool SkeletonModification2DPhysicalBones::_get(const StringName &p_path, Variant
 #ifdef TOOLS_ENABLED
 	if (Engine::get_singleton()->is_editor_hint()) {
 		if (path.begins_with("fetch_bones")) {
-			return true; // Do nothing!
+			// Do nothing!
+			r_ret = false;
+			return true;
path: scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.cpp
user: login: KoBeWi
body: Not sure about this one. Why `r_ret = false`? Isn't it supposed to simply return false (as if the property didn't exist)?
created_at: 2025-04-25T08:58:57Z
author_association: MEMBER
diff_hunk: @@ -68,6 +68,8 @@ bool BoneAttachment3D::_get(const StringName &p_path, Variant &r_ret) const {
 		r_ret = get_use_external_skeleton();
 	} else if (p_path == SNAME("external_skeleton")) {
 		r_ret = get_external_skeleton();
+	} else {
+		return false;
path: scene/3d/bone_attachment_3d.cpp
user: login: akien-mga
body: Isn't there the same problem with `_set` here?
created_at: 2025-04-25T09:01:59Z
author_association: MEMBER
diff_hunk: @@ -91,9 +91,7 @@ bool SkeletonProfile::_get(const StringName &p_path, Variant &r_ret) const {
 		} else {
 			return false;
 		}
-	}
-
-	if (path.begins_with("bones/")) {
+	} else if (path.begins_with("bones/")) {
path: scene/resources/skeleton_profile.cpp
user: login: akien-mga
body: I think it might be a bit easier to parse and less error prone to have each top-level `if` scope end with `return true` when it has done some successful handling of the property, instead of just having `return false` and then hoping that through trickling down the chain of `else if`s we won't fail to land on the final return.
created_at: 2025-04-25T09:07:11Z
author_association: MEMBER
diff_hunk: @@ -662,6 +662,8 @@ void OpenXRCompositionLayer::_get_property_list(List<PropertyInfo> *p_property_l
 bool OpenXRCompositionLayer::_get(const StringName &p_property, Variant &r_value) const {
 	if (extension_property_values.has(p_property)) {
 		r_value = extension_property_values[p_property];
+	} else {
+		return false;
 	}
 
 	return true;
path: modules/openxr/scene/openxr_composition_layer.cpp
user: login: akien-mga
body: Yeah, in this specific case it doesn't make much difference but in more complex cases like in `skeleton_profile.cpp` the top-level pattern
```cpp
if (some cond) {
  // maybe return true or false
} else if (some mutually exclusive cond) {
  // maybe return true or false
} else {
  return false;
}

return true;
```
is quite error prone IMO (as we see with the bugs this fixes).

I think it's easier to reason about if the code is like this:
```cpp
if (some cond) {
  // ensure this always returns true or false
}

if (some mutually exclusive cond) {
  // ensure this always returns true or false
}

return true; // Nothing was matched.
```
created_at: 2025-04-25T09:08:41Z
author_association: MEMBER
diff_hunk: @@ -67,7 +67,9 @@ bool SkeletonModification2DPhysicalBones::_get(const StringName &p_path, Variant
 #ifdef TOOLS_ENABLED
 	if (Engine::get_singleton()->is_editor_hint()) {
 		if (path.begins_with("fetch_bones")) {
-			return true; // Do nothing!
+			// Do nothing!
+			r_ret = false;
+			return true;
path: scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.cpp
user: login: RedMser
body: Is it ok to override _set but not _get? I don't really know what the behavior is there.

But this boolean property is used as a button (you click it to trigger a function), so it should never have a true as a value. So this enforces it.
created_at: 2025-04-25T10:35:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -68,6 +68,8 @@ bool BoneAttachment3D::_get(const StringName &p_path, Variant &r_ret) const {
 		r_ret = get_use_external_skeleton();
 	} else if (p_path == SNAME("external_skeleton")) {
 		r_ret = get_external_skeleton();
+	} else {
+		return false;
path: scene/3d/bone_attachment_3d.cpp
user: login: RedMser
body: You're right! I only really looked through _get for this PR. Someone with the time should go through all _set to find more problematic ones. I'll double check the ones from this PR.
created_at: 2025-04-25T10:38:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -67,7 +67,9 @@ bool SkeletonModification2DPhysicalBones::_get(const StringName &p_path, Variant
 #ifdef TOOLS_ENABLED
 	if (Engine::get_singleton()->is_editor_hint()) {
 		if (path.begins_with("fetch_bones")) {
-			return true; // Do nothing!
+			// Do nothing!
+			r_ret = false;
+			return true;
path: scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.cpp
user: login: KoBeWi
body: > Is it ok to override _set but not _get?

Yes, trying to get property will just return null.
I'm not familiar with this code either, but the intention seems to have a property that can only be set.
created_at: 2025-04-25T10:56:28Z
author_association: MEMBER
diff_hunk: @@ -662,6 +662,8 @@ void OpenXRCompositionLayer::_get_property_list(List<PropertyInfo> *p_property_l
 bool OpenXRCompositionLayer::_get(const StringName &p_property, Variant &r_value) const {
 	if (extension_property_values.has(p_property)) {
 		r_value = extension_property_values[p_property];
+	} else {
+		return false;
 	}
 
 	return true;
path: modules/openxr/scene/openxr_composition_layer.cpp
user: login: RedMser
body: Changing as suggested.
created_at: 2025-04-25T16:55:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -91,9 +91,7 @@ bool SkeletonProfile::_get(const StringName &p_path, Variant &r_ret) const {
 		} else {
 			return false;
 		}
-	}
-
-	if (path.begins_with("bones/")) {
+	} else if (path.begins_with("bones/")) {
path: scene/resources/skeleton_profile.cpp
user: login: RedMser
body: I agree, in my check I found [animation.cpp](https://github.com/godotengine/godot/blob/master/scene/resources/animation.cpp#L463) to be quite tough to read for example, since it mixes styles for each branch it felt like...

I don't have the time to go through all `_get`'s and fix them up, but I'll clean up the buggy ones of this PR to aid readability.
created_at: 2025-04-25T17:01:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -67,7 +67,9 @@ bool SkeletonModification2DPhysicalBones::_get(const StringName &p_path, Variant
 #ifdef TOOLS_ENABLED
 	if (Engine::get_singleton()->is_editor_hint()) {
 		if (path.begins_with("fetch_bones")) {
-			return true; // Do nothing!
+			// Do nothing!
+			r_ret = false;
+			return true;
path: scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.cpp
user: login: RedMser
body: Right I guess there's not much difference between "retrieve the current value (which is always going to be `false`, due to setter doing nothing)" vs "always return false". I'll change it to return false.
created_at: 2025-04-25T17:02:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -67,7 +67,9 @@ bool SkeletonModification2DPhysicalBones::_get(const StringName &p_path, Variant
 #ifdef TOOLS_ENABLED
 	if (Engine::get_singleton()->is_editor_hint()) {
 		if (path.begins_with("fetch_bones")) {
-			return true; // Do nothing!
+			// Do nothing!
+			r_ret = false;
+			return true;
path: scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.cpp
user: login: RedMser
body: Or the whole block could be left out, since there's a `return false` further down as well... I guess it's better to be more explicit and leave it in?
created_at: 2025-04-25T17:08:57Z
author_association: CONTRIBUTOR

PR comments:
user: login: AThousandShips
created_at: 2025-04-18T19:18:53Z
body: Related:
* https://github.com/godotengine/godot/pull/84054
* https://github.com/godotengine/godot/pull/84060
* https://github.com/godotengine/godot/pull/95124
author_association: MEMBER
user: login: Repiteo
created_at: 2025-04-28T00:27:39Z
body: Thanks!
author_association: CONTRIBUTOR