diff options
Diffstat (limited to 'source/blender/python/intern')
-rw-r--r-- | source/blender/python/intern/CMakeLists.txt | 4 | ||||
-rw-r--r-- | source/blender/python/intern/bpy.c | 169 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_app_build_options.c | 4 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_app_handlers.c | 70 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_driver.c | 254 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_driver.h | 10 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_interface.c | 27 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_interface_atexit.c | 2 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_rna.c | 99 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_rna_anim.c | 4 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_rna_gizmo.c | 2 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_traceback.c | 3 | ||||
-rw-r--r-- | source/blender/python/intern/stubs.c | 2 |
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; } |