Fix #109024: Off-by-1 in rna_access for non-array props without raw access

The `a + array_len > in.len` check was off-by-1 whenever accessing a
non-array property without raw access. This was because `array_len` was
actually the array length of the property, which is `0` for non-array
properties.

Given an array which was too short, this would cause the slower loop to
overrun the end of the array by one item. When getting items this would
cause a crash on a debug build with `Fatal Python error:
_PyMem_DebugRawFree: bad trailing pad byte`.

So use `item_len` instead, wichi is always set to `1` for non-array
properties.

Also do not assume that an `array_len` of `0` means that the property is
an array. While this may be true currently, it is cleaner and safer to
use the dedicated RNA API to check that.

This PR also adds some basic checks for expected failure of `foreach_set`
/`foreach_get` API when the provided array is too small.

Co-authored-by: Bastien Montagne <bastien@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/115967
This commit is contained in:
Thomas Barlow
2025-06-18 11:03:27 +02:00
committed by Bastien Montagne
parent 2f8fb76313
commit dc8e2c09d9
2 changed files with 70 additions and 2 deletions

View File

@@ -317,6 +317,69 @@ class TestPropArrayDynamicArg(unittest.TestCase):
self.assertEqual(tuple([1.0] * self.dims), tuple([vg.weight(i) for i in range(self.dims)]))
class TestPropArrayInvalidForeachGetSet(unittest.TestCase):
"""
Test proper detection of invalid usages of foreach_get/foreach_set.
"""
dims = 8
def setUp(self):
self.me = bpy.data.meshes.new("")
self.me.vertices.add(self.dims)
self.ob = bpy.data.objects.new("", self.me)
def tearDown(self):
bpy.data.objects.remove(self.ob)
bpy.data.meshes.remove(self.me)
self.me = None
self.ob = None
def test_foreach_valid(self):
me = self.me
# Non-array (scalar) data access.
valid_1b_list = [False] * len(me.vertices)
me.vertices.foreach_get("select", valid_1b_list)
self.assertEqual(tuple([True] * self.dims), tuple(valid_1b_list))
valid_1b_list = [False] * len(me.vertices)
me.vertices.foreach_set("select", valid_1b_list)
for v in me.vertices:
self.assertFalse(v.select)
# Array (vector) data access.
valid_3f_list = [1.0] * (len(me.vertices) * 3)
me.vertices.foreach_get("co", valid_3f_list)
self.assertEqual(tuple([0.0] * self.dims * 3), tuple(valid_3f_list))
valid_3f_list = [1.0] * (len(me.vertices) * 3)
me.vertices.foreach_set("co", valid_3f_list)
for v in me.vertices:
self.assertEqual(tuple(v.co), (1.0, 1.0, 1.0))
def test_foreach_invalid_smaller_array(self):
me = self.me
# Non-array (scalar) data access.
invalid_1b_list = [False] * (len(me.vertices) - 1)
with self.assertRaises(RuntimeError):
me.vertices.foreach_get("select", invalid_1b_list)
invalid_1b_list = [False] * (len(me.vertices) - 1)
with self.assertRaises(RuntimeError):
me.vertices.foreach_set("select", invalid_1b_list)
# Array (vector) data access.
invalid_3f_list = [1.0] * (len(me.vertices) * 3 - 1)
with self.assertRaises(RuntimeError):
me.vertices.foreach_get("co", invalid_3f_list)
invalid_3f_list = [1.0] * (len(me.vertices) * 3 - 1)
with self.assertRaises(RuntimeError):
me.vertices.foreach_set("co", invalid_3f_list)
if __name__ == '__main__':
import sys
sys.argv = [__file__] + (sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [])