Fix: frozen mathutils Vector & Matrix types could be resized

It's important that frozen types are immutable, add a generic
check that mathutils types can be resized and check the frozen flag.

Also correct the exception types when Vector's cant be resized,
using a ValueError instead of a TypeError as the type is correct.
This commit is contained in:
Campbell Barton
2025-08-14 12:26:05 +10:00
parent 3198b336e1
commit 25c69382fc
5 changed files with 54 additions and 50 deletions

View File

@@ -612,6 +612,24 @@ void _BaseMathObject_RaiseNotFrozenExc(const BaseMathObject *self)
PyExc_TypeError, "%s is not frozen (mutable), call freeze first", Py_TYPE(self)->tp_name);
}
int _BaseMathObject_ResizeOkOrRaiseExc(BaseMathObject *self, const char *error_prefix)
{
if (UNLIKELY(self->flag & BASE_MATH_FLAG_IS_FROZEN)) {
PyErr_Format(PyExc_ValueError, "%s: cannot resize frozen data", error_prefix);
return -1;
}
if (UNLIKELY(self->flag & BASE_MATH_FLAG_IS_WRAP)) {
PyErr_Format(
PyExc_TypeError, "%s: cannot resize wrapped data - only Python vectors", error_prefix);
return -1;
}
if (UNLIKELY(self->cb_user)) {
PyErr_Format(PyExc_TypeError, "%s: cannot resize a vector that has an owner", error_prefix);
return -1;
}
return 0;
}
/* #BaseMathObject generic functions for all mathutils types. */
char BaseMathObject_owner_doc[] = "The item this is wrapping or None (read-only).";

View File

@@ -118,6 +118,9 @@ struct Mathutils_Callback {
[[nodiscard]] int _BaseMathObject_WriteCallback(BaseMathObject *self);
[[nodiscard]] int _BaseMathObject_ReadIndexCallback(BaseMathObject *self, int index);
[[nodiscard]] int _BaseMathObject_WriteIndexCallback(BaseMathObject *self, int index);
/** To implement #BaseMath_Prepare_ForResize. */
[[nodiscard]] int _BaseMathObject_ResizeOkOrRaiseExc(BaseMathObject *self,
const char *error_prefix);
void _BaseMathObject_RaiseFrozenExc(const BaseMathObject *self);
void _BaseMathObject_RaiseNotFrozenExc(const BaseMathObject *self);
@@ -154,6 +157,12 @@ void _BaseMathObject_RaiseNotFrozenExc(const BaseMathObject *self);
(UNLIKELY(((_self)->flag & BASE_MATH_FLAG_IS_FROZEN) == 0) ? \
(_BaseMathObject_RaiseNotFrozenExc((BaseMathObject *)_self), -1) : \
0)
/**
* Helper to de-duplicate checks for in-place resizing.
* \return -1 and set an exception if the vector `_self` cannot be resized.
*/
#define BaseMathObject_Prepare_ForResize(_self, error_prefix) \
_BaseMathObject_ResizeOkOrRaiseExc((BaseMathObject *)_self, error_prefix)
/* utility func */
/**

View File

@@ -1391,16 +1391,8 @@ static PyObject *Matrix_resize_4x4(MatrixObject *self)
float mat[4][4];
int col;
if (self->flag & BASE_MATH_FLAG_IS_WRAP) {
PyErr_SetString(PyExc_ValueError,
"Matrix.resize_4x4(): "
"cannot resize wrapped data - make a copy and resize that");
return nullptr;
}
if (self->cb_user) {
PyErr_SetString(PyExc_ValueError,
"Matrix.resize_4x4(): "
"cannot resize owned data - make a copy and resize that");
if (UNLIKELY(BaseMathObject_Prepare_ForResize(self, "Matrix.resize_4x4()") == -1)) {
/* An exception has been raised. */
return nullptr;
}

View File

@@ -497,16 +497,8 @@ static PyObject *Vector_resize(VectorObject *self, PyObject *value)
{
int vec_num;
if (self->flag & BASE_MATH_FLAG_IS_WRAP) {
PyErr_SetString(PyExc_TypeError,
"Vector.resize(): "
"cannot resize wrapped data - only Python vectors");
return nullptr;
}
if (self->cb_user) {
PyErr_SetString(PyExc_TypeError,
"Vector.resize(): "
"cannot resize a vector that has an owner");
if (UNLIKELY(BaseMathObject_Prepare_ForResize(self, "Vector.resize()") == -1)) {
/* An exception has been raised. */
return nullptr;
}
@@ -585,16 +577,8 @@ PyDoc_STRVAR(
" Resize the vector to 2D (x, y).\n");
static PyObject *Vector_resize_2d(VectorObject *self)
{
if (self->flag & BASE_MATH_FLAG_IS_WRAP) {
PyErr_SetString(PyExc_TypeError,
"Vector.resize_2d(): "
"cannot resize wrapped data - only Python vectors");
return nullptr;
}
if (self->cb_user) {
PyErr_SetString(PyExc_TypeError,
"Vector.resize_2d(): "
"cannot resize a vector that has an owner");
if (UNLIKELY(BaseMathObject_Prepare_ForResize(self, "Vector.resize_2d()") == -1)) {
/* An exception has been raised. */
return nullptr;
}
@@ -618,16 +602,8 @@ PyDoc_STRVAR(
" Resize the vector to 3D (x, y, z).\n");
static PyObject *Vector_resize_3d(VectorObject *self)
{
if (self->flag & BASE_MATH_FLAG_IS_WRAP) {
PyErr_SetString(PyExc_TypeError,
"Vector.resize_3d(): "
"cannot resize wrapped data - only Python vectors");
return nullptr;
}
if (self->cb_user) {
PyErr_SetString(PyExc_TypeError,
"Vector.resize_3d(): "
"cannot resize a vector that has an owner");
if (UNLIKELY(BaseMathObject_Prepare_ForResize(self, "Vector.resize_3d()") == -1)) {
/* An exception has been raised. */
return nullptr;
}
@@ -655,16 +631,8 @@ PyDoc_STRVAR(
" Resize the vector to 4D (x, y, z, w).\n");
static PyObject *Vector_resize_4d(VectorObject *self)
{
if (self->flag & BASE_MATH_FLAG_IS_WRAP) {
PyErr_SetString(PyExc_TypeError,
"Vector.resize_4d(): "
"cannot resize wrapped data - only Python vectors");
return nullptr;
}
if (self->cb_user) {
PyErr_SetString(PyExc_TypeError,
"Vector.resize_4d(): "
"cannot resize a vector that has an owner");
if (UNLIKELY(BaseMathObject_Prepare_ForResize(self, "Vector.resize_4d()") == -1)) {
/* An exception has been raised. */
return nullptr;
}

View File

@@ -249,6 +249,14 @@ class MatrixTesting(unittest.TestCase):
result = Matrix.LocRotScale((1, 2, 3), euler.to_matrix(), (4, 5, 6))
self.assertAlmostEqualMatrix(result, expected, 4)
def test_matrix_freeze_is_read_only(self):
mat = Matrix(((1, 1, 1),) * 3)
mat.freeze()
with self.assertRaises(ValueError):
mat.resize_4x4()
with self.assertRaises(TypeError):
mat[0][0] = 0.0
def assertAlmostEqualMatrix(self, first, second, size, *, places=6, msg=None, delta=None):
for i in range(size):
for j in range(size):
@@ -306,6 +314,15 @@ class VectorTesting(unittest.TestCase):
vec *= 2
self.assertEqual(vec, prod2)
def test_vector_freeze_is_read_only(self):
vec = Vector((1, 3, 5))
vec.freeze()
with self.assertRaises(ValueError):
vec.resize(2)
with self.assertRaises(TypeError):
vec[0] = 0.0
class QuaternionTesting(unittest.TestCase):