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:
committed by
Bastien Montagne
parent
2f8fb76313
commit
dc8e2c09d9
@@ -4948,6 +4948,8 @@ static int rna_raw_access(ReportList *reports,
|
||||
int array_len = 0;
|
||||
/* Item length. Will always be `1` for non-array properties. */
|
||||
int item_len = 0;
|
||||
/* Whether the accessed property is an array or not. */
|
||||
bool is_array;
|
||||
|
||||
/* initialize in array, stride assumed 0 in following code */
|
||||
in.array = inarray;
|
||||
@@ -4967,6 +4969,7 @@ static int rna_raw_access(ReportList *reports,
|
||||
|
||||
/* check type */
|
||||
itemtype = RNA_property_type(itemprop);
|
||||
is_array = RNA_property_array_check(itemprop);
|
||||
|
||||
if (!ELEM(itemtype, PROP_BOOLEAN, PROP_INT, PROP_FLOAT, PROP_ENUM)) {
|
||||
BKE_report(reports, RPT_ERROR, "Only boolean, int, float, and enum properties supported");
|
||||
@@ -4975,6 +4978,7 @@ static int rna_raw_access(ReportList *reports,
|
||||
|
||||
/* check item array */
|
||||
array_len = RNA_property_array_length(&itemptr_base, itemprop);
|
||||
item_len = is_array ? array_len : 1;
|
||||
|
||||
/* dynamic array? need to get length per item */
|
||||
if (itemprop->getlength) {
|
||||
@@ -4982,7 +4986,6 @@ static int rna_raw_access(ReportList *reports,
|
||||
}
|
||||
/* try to access as raw array */
|
||||
else if (RNA_property_collection_raw_array(ptr, prop, itemprop, set, &out)) {
|
||||
item_len = (array_len == 0) ? 1 : array_len;
|
||||
if (in.len != item_len * out.len) {
|
||||
BKE_reportf(reports,
|
||||
RPT_ERROR,
|
||||
@@ -5059,7 +5062,9 @@ static int rna_raw_access(ReportList *reports,
|
||||
iprop = RNA_struct_find_property(&itemptr, propname);
|
||||
|
||||
if (iprop) {
|
||||
is_array = RNA_property_array_check(itemprop);
|
||||
array_len = rna_property_array_length_all_dimensions(&itemptr, iprop);
|
||||
item_len = is_array ? array_len : 1;
|
||||
itemtype = RNA_property_type(iprop);
|
||||
}
|
||||
else {
|
||||
@@ -5080,7 +5085,7 @@ static int rna_raw_access(ReportList *reports,
|
||||
|
||||
/* editable check */
|
||||
if (!set || RNA_property_editable(&itemptr, iprop)) {
|
||||
if (a + array_len > in.len) {
|
||||
if (a + item_len > in.len) {
|
||||
BKE_reportf(
|
||||
reports, RPT_ERROR, "Array length mismatch (got %d, expected more)", in.len);
|
||||
err = 1;
|
||||
|
||||
@@ -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 [])
|
||||
|
||||
Reference in New Issue
Block a user