Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/python/intern')
-rw-r--r--source/blender/python/intern/CMakeLists.txt4
-rw-r--r--source/blender/python/intern/bpy.c169
-rw-r--r--source/blender/python/intern/bpy_app_build_options.c4
-rw-r--r--source/blender/python/intern/bpy_app_handlers.c70
-rw-r--r--source/blender/python/intern/bpy_driver.c254
-rw-r--r--source/blender/python/intern/bpy_driver.h10
-rw-r--r--source/blender/python/intern/bpy_interface.c27
-rw-r--r--source/blender/python/intern/bpy_interface_atexit.c2
-rw-r--r--source/blender/python/intern/bpy_rna.c99
-rw-r--r--source/blender/python/intern/bpy_rna_anim.c4
-rw-r--r--source/blender/python/intern/bpy_rna_gizmo.c2
-rw-r--r--source/blender/python/intern/bpy_traceback.c3
-rw-r--r--source/blender/python/intern/stubs.c2
13 files changed, 451 insertions, 199 deletions
diff --git a/source/blender/python/intern/CMakeLists.txt b/source/blender/python/intern/CMakeLists.txt
index 71138134370..9d2516969cf 100644
--- a/source/blender/python/intern/CMakeLists.txt
+++ b/source/blender/python/intern/CMakeLists.txt
@@ -174,8 +174,8 @@ if(WITH_CODEC_SNDFILE)
add_definitions(-DWITH_SNDFILE)
endif()
-if(WITH_COMPOSITOR)
- add_definitions(-DWITH_COMPOSITOR)
+if(WITH_COMPOSITOR_CPU)
+ add_definitions(-DWITH_COMPOSITOR_CPU)
endif()
if(WITH_CYCLES)
diff --git a/source/blender/python/intern/bpy.c b/source/blender/python/intern/bpy.c
index 2e97ae0fc1d..7fe0b9455e6 100644
--- a/source/blender/python/intern/bpy.c
+++ b/source/blender/python/intern/bpy.c
@@ -32,6 +32,7 @@
#include "bpy.h"
#include "bpy_app.h"
#include "bpy_capi_utils.h"
+#include "bpy_driver.h"
#include "bpy_library.h"
#include "bpy_operator.h"
#include "bpy_props.h"
@@ -326,6 +327,49 @@ static PyObject *bpy_resource_path(PyObject *UNUSED(self), PyObject *args, PyObj
return PyC_UnicodeFromByte(path ? path : "");
}
+/* This is only exposed for tests, see: `tests/python/bl_pyapi_bpy_driver_secure_eval.py`. */
+PyDoc_STRVAR(bpy_driver_secure_code_test_doc,
+ ".. function:: _driver_secure_code_test(code)\n"
+ "\n"
+ " Test if the script should be considered trusted.\n"
+ "\n"
+ " :arg code: The code to test.\n"
+ " :type code: code\n"
+ " :arg namespace: The namespace of values which are allowed.\n"
+ " :type namespace: dict\n"
+ " :arg verbose: Print the reason for considering insecure to the ``stderr``.\n"
+ " :type verbose: bool\n"
+ " :return: True when the script is considered trusted.\n"
+ " :rtype: bool\n");
+static PyObject *bpy_driver_secure_code_test(PyObject *UNUSED(self), PyObject *args, PyObject *kw)
+{
+ PyObject *py_code;
+ PyObject *py_namespace = NULL;
+ const bool verbose = false;
+ static const char *_keywords[] = {"code", "namespace", "verbose", NULL};
+ static _PyArg_Parser _parser = {
+ "O!" /* `expression` */
+ "|$" /* Optional keyword only arguments. */
+ "O!" /* `namespace` */
+ "O&" /* `verbose` */
+ ":driver_secure_code_test",
+ _keywords,
+ 0,
+ };
+ if (!_PyArg_ParseTupleAndKeywordsFast(args,
+ kw,
+ &_parser,
+ &PyCode_Type,
+ &py_code,
+ &PyDict_Type,
+ &py_namespace,
+ PyC_ParseBool,
+ &verbose)) {
+ return NULL;
+ }
+ return PyBool_FromLong(BPY_driver_secure_bytecode_test(py_code, py_namespace, verbose));
+}
+
PyDoc_STRVAR(bpy_escape_identifier_doc,
".. function:: escape_identifier(string)\n"
"\n"
@@ -492,65 +536,37 @@ static PyObject *bpy_rna_enum_items_static(PyObject *UNUSED(self))
return result;
}
-static PyMethodDef meth_bpy_script_paths = {
- "script_paths",
- (PyCFunction)bpy_script_paths,
- METH_NOARGS,
- bpy_script_paths_doc,
-};
-static PyMethodDef meth_bpy_blend_paths = {
- "blend_paths",
- (PyCFunction)bpy_blend_paths,
- METH_VARARGS | METH_KEYWORDS,
- bpy_blend_paths_doc,
-};
-static PyMethodDef meth_bpy_flip_name = {
- "flip_name",
- (PyCFunction)bpy_flip_name,
- METH_VARARGS | METH_KEYWORDS,
- bpy_flip_name_doc,
-};
-static PyMethodDef meth_bpy_user_resource = {
- "user_resource",
- (PyCFunction)bpy_user_resource,
- METH_VARARGS | METH_KEYWORDS,
- NULL,
-};
-static PyMethodDef meth_bpy_system_resource = {
- "system_resource",
- (PyCFunction)bpy_system_resource,
- METH_VARARGS | METH_KEYWORDS,
- bpy_system_resource_doc,
-};
-static PyMethodDef meth_bpy_resource_path = {
- "resource_path",
- (PyCFunction)bpy_resource_path,
- METH_VARARGS | METH_KEYWORDS,
- bpy_resource_path_doc,
-};
-static PyMethodDef meth_bpy_escape_identifier = {
- "escape_identifier",
- (PyCFunction)bpy_escape_identifier,
- METH_O,
- bpy_escape_identifier_doc,
-};
-static PyMethodDef meth_bpy_unescape_identifier = {
- "unescape_identifier",
- (PyCFunction)bpy_unescape_identifier,
- METH_O,
- bpy_unescape_identifier_doc,
-};
-static PyMethodDef meth_bpy_context_members = {
- "context_members",
- (PyCFunction)bpy_context_members,
- METH_NOARGS,
- bpy_context_members_doc,
-};
-static PyMethodDef meth_bpy_rna_enum_items_static = {
- "rna_enum_items_static",
- (PyCFunction)bpy_rna_enum_items_static,
- METH_NOARGS,
- bpy_rna_enum_items_static_doc,
+static PyMethodDef bpy_methods[] = {
+ {"script_paths", (PyCFunction)bpy_script_paths, METH_NOARGS, bpy_script_paths_doc},
+ {"blend_paths",
+ (PyCFunction)bpy_blend_paths,
+ METH_VARARGS | METH_KEYWORDS,
+ bpy_blend_paths_doc},
+ {"flip_name", (PyCFunction)bpy_flip_name, METH_VARARGS | METH_KEYWORDS, bpy_flip_name_doc},
+ {"user_resource", (PyCFunction)bpy_user_resource, METH_VARARGS | METH_KEYWORDS, NULL},
+ {"system_resource",
+ (PyCFunction)bpy_system_resource,
+ METH_VARARGS | METH_KEYWORDS,
+ bpy_system_resource_doc},
+ {"resource_path",
+ (PyCFunction)bpy_resource_path,
+ METH_VARARGS | METH_KEYWORDS,
+ bpy_resource_path_doc},
+ {"_driver_secure_code_test",
+ (PyCFunction)bpy_driver_secure_code_test,
+ METH_VARARGS | METH_KEYWORDS,
+ bpy_driver_secure_code_test_doc},
+ {"escape_identifier", (PyCFunction)bpy_escape_identifier, METH_O, bpy_escape_identifier_doc},
+ {"unescape_identifier",
+ (PyCFunction)bpy_unescape_identifier,
+ METH_O,
+ bpy_unescape_identifier_doc},
+ {"context_members", (PyCFunction)bpy_context_members, METH_NOARGS, bpy_context_members_doc},
+ {"rna_enum_items_static",
+ (PyCFunction)bpy_rna_enum_items_static,
+ METH_NOARGS,
+ bpy_rna_enum_items_static_doc},
+ {NULL, NULL, 0, NULL},
};
static PyObject *bpy_import_test(const char *modname)
@@ -632,35 +648,12 @@ void BPy_init_modules(struct bContext *C)
/* Register methods and property get/set for RNA types. */
BPY_rna_types_extend_capi();
- /* utility func's that have nowhere else to go */
- PyModule_AddObject(mod,
- meth_bpy_script_paths.ml_name,
- (PyObject *)PyCFunction_New(&meth_bpy_script_paths, NULL));
- PyModule_AddObject(
- mod, meth_bpy_blend_paths.ml_name, (PyObject *)PyCFunction_New(&meth_bpy_blend_paths, NULL));
- PyModule_AddObject(mod,
- meth_bpy_user_resource.ml_name,
- (PyObject *)PyCFunction_New(&meth_bpy_user_resource, NULL));
- PyModule_AddObject(mod,
- meth_bpy_system_resource.ml_name,
- (PyObject *)PyCFunction_New(&meth_bpy_system_resource, NULL));
- PyModule_AddObject(mod,
- meth_bpy_resource_path.ml_name,
- (PyObject *)PyCFunction_New(&meth_bpy_resource_path, NULL));
- PyModule_AddObject(mod,
- meth_bpy_escape_identifier.ml_name,
- (PyObject *)PyCFunction_New(&meth_bpy_escape_identifier, NULL));
- PyModule_AddObject(mod,
- meth_bpy_unescape_identifier.ml_name,
- (PyObject *)PyCFunction_New(&meth_bpy_unescape_identifier, NULL));
- PyModule_AddObject(
- mod, meth_bpy_flip_name.ml_name, (PyObject *)PyCFunction_New(&meth_bpy_flip_name, NULL));
- PyModule_AddObject(mod,
- meth_bpy_context_members.ml_name,
- (PyObject *)PyCFunction_New(&meth_bpy_context_members, NULL));
- PyModule_AddObject(mod,
- meth_bpy_rna_enum_items_static.ml_name,
- (PyObject *)PyCFunction_New(&meth_bpy_rna_enum_items_static, NULL));
+ for (int i = 0; bpy_methods[i].ml_name; i++) {
+ PyMethodDef *m = &bpy_methods[i];
+ /* Currently there is no need to support these. */
+ BLI_assert((m->ml_flags & (METH_CLASS | METH_STATIC)) == 0);
+ PyModule_AddObject(mod, m->ml_name, (PyObject *)PyCFunction_New(m, NULL));
+ }
/* register funcs (bpy_rna.c) */
PyModule_AddObject(mod,
diff --git a/source/blender/python/intern/bpy_app_build_options.c b/source/blender/python/intern/bpy_app_build_options.c
index fe5111c37f2..a744f3fd4fa 100644
--- a/source/blender/python/intern/bpy_app_build_options.c
+++ b/source/blender/python/intern/bpy_app_build_options.c
@@ -18,7 +18,7 @@ static PyStructSequence_Field app_builtopts_info_fields[] = {
{"codec_avi", NULL},
{"codec_ffmpeg", NULL},
{"codec_sndfile", NULL},
- {"compositor", NULL},
+ {"compositor_cpu", NULL},
{"cycles", NULL},
{"cycles_osl", NULL},
{"freestyle", NULL},
@@ -104,7 +104,7 @@ static PyObject *make_builtopts_info(void)
SetObjIncref(Py_False);
#endif
-#ifdef WITH_COMPOSITOR
+#ifdef WITH_COMPOSITOR_CPU
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
diff --git a/source/blender/python/intern/bpy_app_handlers.c b/source/blender/python/intern/bpy_app_handlers.c
index 641727927ec..8c5fb22eab1 100644
--- a/source/blender/python/intern/bpy_app_handlers.c
+++ b/source/blender/python/intern/bpy_app_handlers.c
@@ -145,41 +145,41 @@ static PyTypeObject BPyPersistent_Type = {
0, /* tp_basicsize */
0, /* tp_itemsize */
/* methods */
- 0, /* tp_dealloc */
- 0, /* tp_print */
- 0, /* tp_getattr */
- 0, /* tp_setattr */
- 0, /* tp_reserved */
- 0, /* tp_repr */
- 0, /* tp_as_number */
- 0, /* tp_as_sequence */
- 0, /* tp_as_mapping */
- 0, /* tp_hash */
- 0, /* tp_call */
- 0, /* tp_str */
- 0, /* tp_getattro */
- 0, /* tp_setattro */
- 0, /* tp_as_buffer */
- Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE, /* tp_flags */
- 0, /* tp_doc */
- 0, /* tp_traverse */
- 0, /* tp_clear */
- 0, /* tp_richcompare */
- 0, /* tp_weaklistoffset */
- 0, /* tp_iter */
- 0, /* tp_iternext */
- 0, /* tp_methods */
- 0, /* tp_members */
- 0, /* tp_getset */
- 0, /* tp_base */
- 0, /* tp_dict */
- 0, /* tp_descr_get */
- 0, /* tp_descr_set */
- 0, /* tp_dictoffset */
- 0, /* tp_init */
- 0, /* tp_alloc */
- bpy_app_handlers_persistent_new, /* tp_new */
- 0, /* tp_free */
+ 0, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_reserved */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ 0, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ bpy_app_handlers_persistent_new, /* tp_new */
+ 0, /* tp_free */
};
static PyObject *py_cb_array[BKE_CB_EVT_TOT] = {NULL};
diff --git a/source/blender/python/intern/bpy_driver.c b/source/blender/python/intern/bpy_driver.c
index a9cc0019783..04aa203d198 100644
--- a/source/blender/python/intern/bpy_driver.c
+++ b/source/blender/python/intern/bpy_driver.c
@@ -19,6 +19,7 @@
#include "BKE_animsys.h"
#include "BKE_fcurve_driver.h"
#include "BKE_global.h"
+#include "BKE_idtype.h"
#include "RNA_access.h"
#include "RNA_prototypes.h"
@@ -233,15 +234,8 @@ static void bpy_pydriver_namespace_update_depsgraph(struct Depsgraph *depsgraph)
}
}
-void BPY_driver_reset(void)
+void BPY_driver_exit(void)
{
- PyGILState_STATE gilstate;
- const bool use_gil = true; /* !PyC_IsInterpreterActive(); */
-
- if (use_gil) {
- gilstate = PyGILState_Ensure();
- }
-
if (bpy_pydriver_Dict) { /* Free the global dict used by python-drivers. */
PyDict_Clear(bpy_pydriver_Dict);
Py_DECREF(bpy_pydriver_Dict);
@@ -261,19 +255,46 @@ void BPY_driver_reset(void)
/* Freed when clearing driver dictionary. */
g_pydriver_state_prev.self = NULL;
g_pydriver_state_prev.depsgraph = NULL;
+}
+
+void BPY_driver_reset(void)
+{
+ PyGILState_STATE gilstate;
+ const bool use_gil = true; /* !PyC_IsInterpreterActive(); */
+
+ if (use_gil) {
+ gilstate = PyGILState_Ensure();
+ }
+
+ /* Currently exit/reset are practically the same besides the GIL check. */
+ BPY_driver_exit();
if (use_gil) {
PyGILState_Release(gilstate);
}
}
-/** Error return function for #BPY_eval_pydriver. */
-static void pydriver_error(ChannelDriver *driver)
+/**
+ * Error return function for #BPY_driver_exec.
+ *
+ * \param anim_rna: Used to show the target when printing the error to give additional context.
+ */
+static void pydriver_error(ChannelDriver *driver, const struct PathResolvedRNA *anim_rna)
{
driver->flag |= DRIVER_FLAG_INVALID; /* Python expression failed. */
+
+ const char *null_str = "<null>";
+ const ID *id = anim_rna->ptr.owner_id;
fprintf(stderr,
- "\nError in Driver: The following Python expression failed:\n\t'%s'\n\n",
- driver->expression);
+ "\n"
+ "Error in PyDriver: expression failed: %s\n"
+ "For target: (type=%s, name=\"%s\", property=%s, property_index=%d)\n"
+ "\n",
+ driver->expression,
+ id ? BKE_idtype_idcode_to_name(GS(id->name)) : null_str,
+ id ? id->name + 2 : null_str,
+ anim_rna->prop ? RNA_property_identifier(anim_rna->prop) : null_str,
+ anim_rna->prop_index);
// BPy_errors_to_report(NULL); /* TODO: reports. */
PyErr_Print();
@@ -282,14 +303,75 @@ static void pydriver_error(ChannelDriver *driver)
#ifdef USE_BYTECODE_WHITELIST
-# define OK_OP(op) [op] = 1
+# define OK_OP(op) [op] = true
+
+static const bool secure_opcodes[255] = {
+# if PY_VERSION_HEX >= 0x030b0000 /* Python 3.11 & newer. */
+
+ OK_OP(CACHE),
+ OK_OP(POP_TOP),
+ OK_OP(PUSH_NULL),
+ OK_OP(NOP),
+ OK_OP(UNARY_POSITIVE),
+ OK_OP(UNARY_NEGATIVE),
+ OK_OP(UNARY_NOT),
+ OK_OP(UNARY_INVERT),
+ OK_OP(BINARY_SUBSCR),
+ OK_OP(GET_LEN),
+ OK_OP(LIST_TO_TUPLE),
+ OK_OP(RETURN_VALUE),
+ OK_OP(SWAP),
+ OK_OP(BUILD_TUPLE),
+ OK_OP(BUILD_LIST),
+ OK_OP(BUILD_SET),
+ OK_OP(BUILD_MAP),
+ OK_OP(COMPARE_OP),
+ OK_OP(JUMP_FORWARD),
+ OK_OP(JUMP_IF_FALSE_OR_POP),
+ OK_OP(JUMP_IF_TRUE_OR_POP),
+ OK_OP(POP_JUMP_FORWARD_IF_FALSE),
+ OK_OP(POP_JUMP_FORWARD_IF_TRUE),
+ OK_OP(LOAD_GLOBAL),
+ OK_OP(IS_OP),
+ OK_OP(CONTAINS_OP),
+ OK_OP(BINARY_OP),
+ OK_OP(LOAD_FAST),
+ OK_OP(STORE_FAST),
+ OK_OP(DELETE_FAST),
+ OK_OP(POP_JUMP_FORWARD_IF_NOT_NONE),
+ OK_OP(POP_JUMP_FORWARD_IF_NONE),
+ OK_OP(BUILD_SLICE),
+ OK_OP(LOAD_DEREF),
+ OK_OP(STORE_DEREF),
+ OK_OP(RESUME),
+ OK_OP(LIST_EXTEND),
+ OK_OP(SET_UPDATE),
+/* NOTE(@campbellbarton): Don't enable dict manipulation, unless we can prove there is not way it
+ * can be used to manipulate the name-space (potentially allowing malicious code). */
+# if 0
+ OK_OP(DICT_MERGE),
+ OK_OP(DICT_UPDATE),
+# endif
+ OK_OP(POP_JUMP_BACKWARD_IF_NOT_NONE),
+ OK_OP(POP_JUMP_BACKWARD_IF_NONE),
+ OK_OP(POP_JUMP_BACKWARD_IF_FALSE),
+ OK_OP(POP_JUMP_BACKWARD_IF_TRUE),
+
+ /* Special cases. */
+ OK_OP(LOAD_CONST), /* Ok because constants are accepted. */
+ OK_OP(LOAD_NAME), /* Ok, because `PyCodeObject.names` is checked. */
+ OK_OP(CALL), /* Ok, because we check its "name" before calling. */
+ OK_OP(KW_NAMES), /* Ok, because it's used for calling functions with keyword arguments. */
+ OK_OP(PRECALL), /* Ok, because it's used for calling. */
+
+# else /* Python 3.10 and older. */
-static const char secure_opcodes[255] = {
OK_OP(POP_TOP),
OK_OP(ROT_TWO),
OK_OP(ROT_THREE),
OK_OP(DUP_TOP),
OK_OP(DUP_TOP_TWO),
+ OK_OP(ROT_FOUR),
OK_OP(NOP),
OK_OP(UNARY_POSITIVE),
OK_OP(UNARY_NEGATIVE),
@@ -307,6 +389,7 @@ static const char secure_opcodes[255] = {
OK_OP(BINARY_TRUE_DIVIDE),
OK_OP(INPLACE_FLOOR_DIVIDE),
OK_OP(INPLACE_TRUE_DIVIDE),
+ OK_OP(GET_LEN),
OK_OP(INPLACE_ADD),
OK_OP(INPLACE_SUBTRACT),
OK_OP(INPLACE_MULTIPLY),
@@ -322,7 +405,9 @@ static const char secure_opcodes[255] = {
OK_OP(INPLACE_AND),
OK_OP(INPLACE_XOR),
OK_OP(INPLACE_OR),
+ OK_OP(LIST_TO_TUPLE),
OK_OP(RETURN_VALUE),
+ OK_OP(ROT_N),
OK_OP(BUILD_TUPLE),
OK_OP(BUILD_LIST),
OK_OP(BUILD_SET),
@@ -335,11 +420,22 @@ static const char secure_opcodes[255] = {
OK_OP(POP_JUMP_IF_FALSE),
OK_OP(POP_JUMP_IF_TRUE),
OK_OP(LOAD_GLOBAL),
+ OK_OP(IS_OP),
+ OK_OP(CONTAINS_OP),
OK_OP(LOAD_FAST),
OK_OP(STORE_FAST),
OK_OP(DELETE_FAST),
+ OK_OP(BUILD_SLICE),
OK_OP(LOAD_DEREF),
OK_OP(STORE_DEREF),
+ OK_OP(LIST_EXTEND),
+ OK_OP(SET_UPDATE),
+/* NOTE(@campbellbarton): Don't enable dict manipulation, unless we can prove there is not way it
+ * can be used to manipulate the name-space (potentially allowing malicious code). */
+# if 0
+ OK_OP(DICT_MERGE),
+ OK_OP(DICT_UPDATE),
+# endif
/* Special cases. */
OK_OP(LOAD_CONST), /* Ok because constants are accepted. */
@@ -347,11 +443,16 @@ static const char secure_opcodes[255] = {
OK_OP(CALL_FUNCTION), /* Ok, because we check its "name" before calling. */
OK_OP(CALL_FUNCTION_KW),
OK_OP(CALL_FUNCTION_EX),
+
+# endif /* Python 3.10 and older. */
};
# undef OK_OP
-static bool bpy_driver_secure_bytecode_validate(PyObject *expr_code, PyObject *dict_arr[])
+bool BPY_driver_secure_bytecode_test_ex(PyObject *expr_code,
+ PyObject *namespace_array[],
+ const bool verbose,
+ const char *error_prefix)
{
PyCodeObject *py_code = (PyCodeObject *)expr_code;
@@ -359,20 +460,23 @@ static bool bpy_driver_secure_bytecode_validate(PyObject *expr_code, PyObject *d
{
for (int i = 0; i < PyTuple_GET_SIZE(py_code->co_names); i++) {
PyObject *name = PyTuple_GET_ITEM(py_code->co_names, i);
-
+ const char *name_str = PyUnicode_AsUTF8(name);
bool contains_name = false;
- for (int j = 0; dict_arr[j]; j++) {
- if (PyDict_Contains(dict_arr[j], name)) {
+ for (int j = 0; namespace_array[j]; j++) {
+ if (PyDict_Contains(namespace_array[j], name)) {
contains_name = true;
break;
}
}
- if (contains_name == false) {
- fprintf(stderr,
- "\tBPY_driver_eval() - restricted access disallows name '%s', "
- "enable auto-execution to support\n",
- PyUnicode_AsUTF8(name));
+ if ((contains_name == false) || (name_str[0] == '_')) {
+ if (verbose) {
+ fprintf(stderr,
+ "\t%s: restricted access disallows name '%s', "
+ "enable auto-execution to support\n",
+ error_prefix,
+ name_str);
+ }
return false;
}
}
@@ -383,26 +487,70 @@ static bool bpy_driver_secure_bytecode_validate(PyObject *expr_code, PyObject *d
const _Py_CODEUNIT *codestr;
Py_ssize_t code_len;
- PyBytes_AsStringAndSize(py_code->co_code, (char **)&codestr, &code_len);
+ PyObject *co_code;
+
+# if PY_VERSION_HEX >= 0x030b0000 /* Python 3.11 & newer. */
+ co_code = PyCode_GetCode(py_code);
+ if (UNLIKELY(!co_code)) {
+ PyErr_Print();
+ PyErr_Clear();
+ return false;
+ }
+# else
+ co_code = py_code->co_code;
+# endif
+
+ PyBytes_AsStringAndSize(co_code, (char **)&codestr, &code_len);
code_len /= sizeof(*codestr);
+ bool ok = true;
+ /* Loop over op-code's, the op-code arguments are ignored. */
for (Py_ssize_t i = 0; i < code_len; i++) {
const int opcode = _Py_OPCODE(codestr[i]);
- if (secure_opcodes[opcode] == 0) {
- fprintf(stderr,
- "\tBPY_driver_eval() - restricted access disallows opcode '%d', "
- "enable auto-execution to support\n",
- opcode);
- return false;
+ if (secure_opcodes[opcode] == false) {
+ if (verbose) {
+ fprintf(stderr,
+ "\t%s: restricted access disallows opcode '%d', "
+ "enable auto-execution to support\n",
+ error_prefix,
+ opcode);
+ }
+ ok = false;
+ break;
}
}
-# undef CODESIZE
+# if PY_VERSION_HEX >= 0x030b0000 /* Python 3.11 & newer. */
+ Py_DECREF(co_code);
+# endif
+ if (!ok) {
+ return false;
+ }
}
return true;
}
+bool BPY_driver_secure_bytecode_test(PyObject *expr_code, PyObject *namespace, const bool verbose)
+{
+
+ if (!bpy_pydriver_Dict) {
+ if (bpy_pydriver_create_dict() != 0) {
+ fprintf(stderr, "%s: couldn't create Python dictionary\n", __func__);
+ return false;
+ }
+ }
+ return BPY_driver_secure_bytecode_test_ex(expr_code,
+ (PyObject *[]){
+ bpy_pydriver_Dict,
+ bpy_pydriver_Dict__whitelist,
+ namespace,
+ NULL,
+ },
+ verbose,
+ __func__);
+}
+
#endif /* USE_BYTECODE_WHITELIST */
float BPY_driver_exec(struct PathResolvedRNA *anim_rna,
ChannelDriver *driver,
@@ -435,7 +583,7 @@ float BPY_driver_exec(struct PathResolvedRNA *anim_rna,
DriverVar *dvar;
double result = 0.0; /* Default return. */
const char *expr;
- short targets_ok = 1;
+ bool targets_ok = true;
int i;
/* Get the python expression to be evaluated. */
@@ -470,7 +618,7 @@ float BPY_driver_exec(struct PathResolvedRNA *anim_rna,
/* Initialize global dictionary for Python driver evaluation settings. */
if (!bpy_pydriver_Dict) {
if (bpy_pydriver_create_dict() != 0) {
- fprintf(stderr, "PyDriver error: couldn't create Python dictionary\n");
+ fprintf(stderr, "%s: couldn't create Python dictionary\n", __func__);
if (use_gil) {
PyGILState_Release(gilstate);
}
@@ -579,12 +727,11 @@ float BPY_driver_exec(struct PathResolvedRNA *anim_rna,
/* This target failed - bad name. */
if (targets_ok) {
/* First one, print some extra info for easier identification. */
- fprintf(stderr, "\nBPY_driver_eval() - Error while evaluating PyDriver:\n");
- targets_ok = 0;
+ fprintf(stderr, "\n%s: Error while evaluating PyDriver:\n", __func__);
+ targets_ok = false;
}
- fprintf(
- stderr, "\tBPY_driver_eval() - couldn't add variable '%s' to namespace\n", dvar->name);
+ fprintf(stderr, "\t%s: couldn't add variable '%s' to namespace\n", __func__, dvar->name);
// BPy_errors_to_report(NULL); /* TODO: reports. */
PyErr_Print();
PyErr_Clear();
@@ -595,13 +742,17 @@ float BPY_driver_exec(struct PathResolvedRNA *anim_rna,
#ifdef USE_BYTECODE_WHITELIST
if (is_recompile && expr_code) {
if (!(G.f & G_FLAG_SCRIPT_AUTOEXEC)) {
- if (!bpy_driver_secure_bytecode_validate(expr_code,
- (PyObject *[]){
- bpy_pydriver_Dict,
- bpy_pydriver_Dict__whitelist,
- driver_vars,
- NULL,
- })) {
+ if (!BPY_driver_secure_bytecode_test_ex(
+ expr_code,
+ (PyObject *[]){
+ bpy_pydriver_Dict,
+ bpy_pydriver_Dict__whitelist,
+ driver_vars,
+ NULL,
+ },
+ /* Always be verbose since this can give hints to why evaluation fails. */
+ true,
+ __func__)) {
if (!(G.f & G_FLAG_SCRIPT_AUTOEXEC_FAIL_QUIET)) {
G.f |= G_FLAG_SCRIPT_AUTOEXEC_FAIL;
BLI_snprintf(G.autoexec_fail, sizeof(G.autoexec_fail), "Driver '%s'", expr);
@@ -630,11 +781,11 @@ float BPY_driver_exec(struct PathResolvedRNA *anim_rna,
/* Process the result. */
if (retval == NULL) {
- pydriver_error(driver);
+ pydriver_error(driver, anim_rna);
}
else {
- if ((result = PyFloat_AsDouble(retval)) == -1.0 && PyErr_Occurred()) {
- pydriver_error(driver);
+ if (UNLIKELY((result = PyFloat_AsDouble(retval)) == -1.0 && PyErr_Occurred())) {
+ pydriver_error(driver, anim_rna);
result = 0.0;
}
else {
@@ -648,11 +799,10 @@ float BPY_driver_exec(struct PathResolvedRNA *anim_rna,
PyGILState_Release(gilstate);
}
- if (isfinite(result)) {
- return (float)result;
+ if (UNLIKELY(!isfinite(result))) {
+ fprintf(stderr, "\t%s: driver '%s' evaluates to '%f'\n", __func__, driver->expression, result);
+ return 0.0f;
}
- fprintf(
- stderr, "\tBPY_driver_eval() - driver '%s' evaluates to '%f'\n", driver->expression, result);
- return 0.0f;
+ return (float)result;
}
diff --git a/source/blender/python/intern/bpy_driver.h b/source/blender/python/intern/bpy_driver.h
index 301c6b3662e..f0d9717dbbd 100644
--- a/source/blender/python/intern/bpy_driver.h
+++ b/source/blender/python/intern/bpy_driver.h
@@ -10,6 +10,8 @@
extern "C" {
#endif
+#include <stdbool.h>
+
/**
* For faster execution we keep a special dictionary for py-drivers, with
* the needed modules and aliases.
@@ -21,6 +23,14 @@ int bpy_pydriver_create_dict(void);
*/
extern PyObject *bpy_pydriver_Dict;
+extern bool BPY_driver_secure_bytecode_test_ex(PyObject *expr_code,
+ PyObject *namespace_array[],
+ const bool verbose,
+ const char *error_prefix);
+extern bool BPY_driver_secure_bytecode_test(PyObject *expr_code,
+ PyObject *namespace,
+ const bool verbose);
+
#ifdef __cplusplus
}
#endif
diff --git a/source/blender/python/intern/bpy_interface.c b/source/blender/python/intern/bpy_interface.c
index 0ab8b4385e5..23fc0bcaeda 100644
--- a/source/blender/python/intern/bpy_interface.c
+++ b/source/blender/python/intern/bpy_interface.c
@@ -512,6 +512,9 @@ void BPY_python_end(void)
/* finalizing, no need to grab the state, except when we are a module */
gilstate = PyGILState_Ensure();
+ /* Frees the python-driver name-space & cached data. */
+ BPY_driver_exit();
+
/* Clear Python values in the context so freeing the context after Python exits doesn't crash. */
bpy_context_end(BPY_context_get());
@@ -582,16 +585,22 @@ void BPY_python_use_system_env(void)
void BPY_python_backtrace(FILE *fp)
{
fputs("\n# Python backtrace\n", fp);
- PyThreadState *tstate = PyGILState_GetThisThreadState();
- if (tstate != NULL && tstate->frame != NULL) {
- PyFrameObject *frame = tstate->frame;
- do {
- const int line = PyCode_Addr2Line(frame->f_code, frame->f_lasti);
- const char *filepath = PyUnicode_AsUTF8(frame->f_code->co_filename);
- const char *funcname = PyUnicode_AsUTF8(frame->f_code->co_name);
- fprintf(fp, " File \"%s\", line %d in %s\n", filepath, line, funcname);
- } while ((frame = frame->f_back));
+
+ /* Can happen in rare cases. */
+ if (!_PyThreadState_UncheckedGet()) {
+ return;
+ }
+ PyFrameObject *frame;
+ if (!(frame = PyEval_GetFrame())) {
+ return;
}
+ do {
+ PyCodeObject *code = PyFrame_GetCode(frame);
+ const int line = PyFrame_GetLineNumber(frame);
+ const char *filepath = PyUnicode_AsUTF8(code->co_filename);
+ const char *funcname = PyUnicode_AsUTF8(code->co_name);
+ fprintf(fp, " File \"%s\", line %d in %s\n", filepath, line, funcname);
+ } while ((frame = PyFrame_GetBack(frame)));
}
void BPY_DECREF(void *pyob_ptr)
diff --git a/source/blender/python/intern/bpy_interface_atexit.c b/source/blender/python/intern/bpy_interface_atexit.c
index 1ba3ab6a40b..cba9bd59abf 100644
--- a/source/blender/python/intern/bpy_interface_atexit.c
+++ b/source/blender/python/intern/bpy_interface_atexit.c
@@ -32,7 +32,7 @@ static PyObject *func_bpy_atregister = NULL; /* borrowed reference, `atexit` hol
static void atexit_func_call(const char *func_name, PyObject *atexit_func_arg)
{
- /* NOTE(campbell): no error checking, if any of these fail we'll get a crash
+ /* NOTE(@campbellbarton): no error checking, if any of these fail we'll get a crash
* this is intended, but if its problematic it could be changed. */
PyObject *atexit_mod = PyImport_ImportModuleLevel("atexit", NULL, NULL, NULL, 0);
diff --git a/source/blender/python/intern/bpy_rna.c b/source/blender/python/intern/bpy_rna.c
index 14be9ceda94..56de0bfc18e 100644
--- a/source/blender/python/intern/bpy_rna.c
+++ b/source/blender/python/intern/bpy_rna.c
@@ -15,6 +15,7 @@
#include <float.h> /* FLT_MIN/MAX */
#include <stddef.h>
+#include "RNA_path.h"
#include "RNA_types.h"
#include "BLI_bitmap.h"
@@ -779,7 +780,7 @@ PyObject *pyrna_math_object_from_array(PointerRNA *ptr, PropertyRNA *prop)
return ret;
}
-/* NOTE(campbell): Regarding comparison `__cmp__`:
+/* NOTE(@campbellbarton): Regarding comparison `__cmp__`:
* checking the 'ptr->data' matches works in almost all cases,
* however there are a few RNA properties that are fake sub-structs and
* share the pointer with the parent, in those cases this happens 'a.b == a'
@@ -2212,6 +2213,26 @@ static int pyrna_prop_collection_bool(BPy_PropertyRNA *self)
} \
(void)0
+/**
+ * \param result: The result of calling a subscription operation on a collection (never NULL).
+ */
+static int pyrna_prop_collection_subscript_is_valid_or_error(const PyObject *value)
+{
+ if (value != Py_None) {
+ BLI_assert(BPy_StructRNA_Check(value));
+ const BPy_StructRNA *value_pyrna = (const BPy_StructRNA *)value;
+ if (UNLIKELY(value_pyrna->ptr.type == NULL)) {
+ /* It's important to use a `TypeError` as that is what's returned when `__getitem__` is
+ * called on an object that doesn't support item access. */
+ PyErr_Format(PyExc_TypeError,
+ "'%.200s' object is not subscriptable (only iteration is supported)",
+ Py_TYPE(value)->tp_name);
+ return -1;
+ }
+ }
+ return 0;
+}
+
/* Internal use only. */
static PyObject *pyrna_prop_collection_subscript_int(BPy_PropertyRNA *self, Py_ssize_t keynum)
{
@@ -2222,8 +2243,35 @@ static PyObject *pyrna_prop_collection_subscript_int(BPy_PropertyRNA *self, Py_s
PYRNA_PROP_COLLECTION_ABS_INDEX(NULL);
- if (RNA_property_collection_lookup_int(&self->ptr, self->prop, keynum_abs, &newptr)) {
- return pyrna_struct_CreatePyObject(&newptr);
+ if (RNA_property_collection_lookup_int_has_fn(self->prop)) {
+ if (RNA_property_collection_lookup_int(&self->ptr, self->prop, keynum_abs, &newptr)) {
+ return pyrna_struct_CreatePyObject(&newptr);
+ }
+ }
+ else {
+ /* No callback defined, just iterate and find the nth item. */
+ const int key = (int)keynum_abs;
+ PyObject *result = NULL;
+ bool found = false;
+ CollectionPropertyIterator iter;
+ RNA_property_collection_begin(&self->ptr, self->prop, &iter);
+ for (int i = 0; iter.valid; RNA_property_collection_next(&iter), i++) {
+ if (i == key) {
+ result = pyrna_struct_CreatePyObject(&iter.ptr);
+ found = true;
+ break;
+ }
+ }
+ /* It's important to end the iterator after `result` has been created
+ * so iterators may optionally invalidate items that were iterated over, see: T100286. */
+ RNA_property_collection_end(&iter);
+ if (found) {
+ if (result && (pyrna_prop_collection_subscript_is_valid_or_error(result) == -1)) {
+ Py_DECREF(result);
+ result = NULL; /* The exception has been set. */
+ }
+ return result;
+ }
}
const int len = RNA_property_collection_length(&self->ptr, self->prop);
@@ -2305,8 +2353,45 @@ static PyObject *pyrna_prop_collection_subscript_str(BPy_PropertyRNA *self, cons
PYRNA_PROP_CHECK_OBJ(self);
- if (RNA_property_collection_lookup_string(&self->ptr, self->prop, keyname, &newptr)) {
- return pyrna_struct_CreatePyObject(&newptr);
+ if (RNA_property_collection_lookup_string_has_fn(self->prop)) {
+ if (RNA_property_collection_lookup_string(&self->ptr, self->prop, keyname, &newptr)) {
+ return pyrna_struct_CreatePyObject(&newptr);
+ }
+ }
+ else {
+ /* No callback defined, just iterate and find the nth item. */
+ const int keylen = strlen(keyname);
+ char name[256];
+ int namelen;
+ PyObject *result = NULL;
+ bool found = false;
+ CollectionPropertyIterator iter;
+ RNA_property_collection_begin(&self->ptr, self->prop, &iter);
+ for (int i = 0; iter.valid; RNA_property_collection_next(&iter), i++) {
+ PropertyRNA *nameprop = RNA_struct_name_property(iter.ptr.type);
+ char *nameptr = RNA_property_string_get_alloc(
+ &iter.ptr, nameprop, name, sizeof(name), &namelen);
+ if ((keylen == namelen) && STREQ(nameptr, keyname)) {
+ found = true;
+ }
+ if ((char *)&name != nameptr) {
+ MEM_freeN(nameptr);
+ }
+ if (found) {
+ result = pyrna_struct_CreatePyObject(&iter.ptr);
+ break;
+ }
+ }
+ /* It's important to end the iterator after `result` has been created
+ * so iterators may optionally invalidate items that were iterated over, see: T100286. */
+ RNA_property_collection_end(&iter);
+ if (found) {
+ if (result && (pyrna_prop_collection_subscript_is_valid_or_error(result) == -1)) {
+ Py_DECREF(result);
+ result = NULL; /* The exception has been set. */
+ }
+ return result;
+ }
}
PyErr_Format(PyExc_KeyError, "bpy_prop_collection[key]: key \"%.200s\" not found", keyname);
@@ -8253,7 +8338,7 @@ static int bpy_class_validate_recursive(PointerRNA *dummyptr,
continue;
}
- /* TODO(campbell): this is used for classmethod's too,
+ /* TODO(@campbellbarton): this is used for classmethod's too,
* even though class methods should have 'FUNC_USE_SELF_TYPE' set, see Operator.poll for eg.
* Keep this as-is since it's working, but we should be using
* 'FUNC_USE_SELF_TYPE' for many functions. */
@@ -8344,7 +8429,7 @@ static int bpy_class_validate_recursive(PointerRNA *dummyptr,
continue;
}
- /* TODO(campbell): Use Python3.7x _PyObject_LookupAttr(), also in the macro below. */
+ /* TODO(@campbellbarton): Use Python3.7x _PyObject_LookupAttr(), also in the macro below. */
identifier = RNA_property_identifier(prop);
item = PyObject_GetAttrString(py_class, identifier);
diff --git a/source/blender/python/intern/bpy_rna_anim.c b/source/blender/python/intern/bpy_rna_anim.c
index fb744432d4b..d4a164d9482 100644
--- a/source/blender/python/intern/bpy_rna_anim.c
+++ b/source/blender/python/intern/bpy_rna_anim.c
@@ -32,6 +32,7 @@
#include "RNA_access.h"
#include "RNA_enum_types.h"
+#include "RNA_path.h"
#include "RNA_prototypes.h"
#include "WM_api.h"
@@ -487,7 +488,8 @@ PyObject *pyrna_struct_keyframe_delete(BPy_StructRNA *self, PyObject *args, PyOb
i = BKE_fcurve_bezt_binarysearch_index(fcu->bezt, cfra, fcu->totvert, &found);
if (found) {
/* delete the key at the index (will sanity check + do recalc afterwards) */
- delete_fcurve_key(fcu, i, 1);
+ BKE_fcurve_delete_key(fcu, i);
+ BKE_fcurve_handles_recalc(fcu);
result = true;
}
}
diff --git a/source/blender/python/intern/bpy_rna_gizmo.c b/source/blender/python/intern/bpy_rna_gizmo.c
index 61f439e5152..32ef7865e84 100644
--- a/source/blender/python/intern/bpy_rna_gizmo.c
+++ b/source/blender/python/intern/bpy_rna_gizmo.c
@@ -314,6 +314,8 @@ PyDoc_STRVAR(
"\n"
" Assigns callbacks to a gizmos property.\n"
"\n"
+ " :arg target: Target property name.\n"
+ " :type target: string\n"
" :arg get: Function that returns the value for this property (single value or sequence).\n"
" :type get: callable\n"
" :arg set: Function that takes a single value argument and applies it.\n"
diff --git a/source/blender/python/intern/bpy_traceback.c b/source/blender/python/intern/bpy_traceback.c
index cb93843a6de..1f2458c752f 100644
--- a/source/blender/python/intern/bpy_traceback.c
+++ b/source/blender/python/intern/bpy_traceback.c
@@ -20,7 +20,8 @@
static const char *traceback_filepath(PyTracebackObject *tb, PyObject **coerce)
{
- *coerce = PyUnicode_EncodeFSDefault(tb->tb_frame->f_code->co_filename);
+ PyCodeObject *code = PyFrame_GetCode(tb->tb_frame);
+ *coerce = PyUnicode_EncodeFSDefault(code->co_filename);
return PyBytes_AS_STRING(*coerce);
}
diff --git a/source/blender/python/intern/stubs.c b/source/blender/python/intern/stubs.c
index c29f9188eea..f860bdc36ee 100644
--- a/source/blender/python/intern/stubs.c
+++ b/source/blender/python/intern/stubs.c
@@ -25,7 +25,7 @@ void BPY_pyconstraint_exec(struct bPythonConstraint *con,
void BPY_pyconstraint_target(struct bPythonConstraint *con, struct bConstraintTarget *ct)
{
}
-int BPY_is_pyconstraint(struct Text *text)
+bool BPY_is_pyconstraint(struct Text *text)
{
return 0;
}