Changeset View
Changeset View
Standalone View
Standalone View
source/blender/python/intern/bpy_driver.c
| Show First 20 Lines • Show All 46 Lines • ▼ Show 20 Lines | |||||
| #include "bpy_intern_string.h" | #include "bpy_intern_string.h" | ||||
| #include "bpy_driver.h" | #include "bpy_driver.h" | ||||
| extern void BPY_update_rna_module(void); | extern void BPY_update_rna_module(void); | ||||
| #define USE_RNA_AS_PYOBJECT | #define USE_RNA_AS_PYOBJECT | ||||
| #define USE_BYTECODE_WHITELIST | |||||
| #ifdef USE_BYTECODE_WHITELIST | |||||
| #include <opcode.h> | |||||
sergey: Indent nested preprocessor. | |||||
| #endif | |||||
| /* for pydrivers (drivers using one-line Python expressions to express relationships between targets) */ | /* for pydrivers (drivers using one-line Python expressions to express relationships between targets) */ | ||||
| PyObject *bpy_pydriver_Dict = NULL; | PyObject *bpy_pydriver_Dict = NULL; | ||||
| #ifdef USE_BYTECODE_WHITELIST | |||||
| static PyObject *bpy_pydriver_Dict__whitelist = NULL; | |||||
| #endif | |||||
| /* For faster execution we keep a special dictionary for pydrivers, with | /* For faster execution we keep a special dictionary for pydrivers, with | ||||
| * the needed modules and aliases. | * the needed modules and aliases. | ||||
| */ | */ | ||||
| int bpy_pydriver_create_dict(void) | int bpy_pydriver_create_dict(void) | ||||
| { | { | ||||
| PyObject *d, *mod; | PyObject *d, *mod; | ||||
| /* validate namespace for driver evaluation */ | /* validate namespace for driver evaluation */ | ||||
| if (bpy_pydriver_Dict) return -1; | if (bpy_pydriver_Dict) return -1; | ||||
| d = PyDict_New(); | d = PyDict_New(); | ||||
| if (d == NULL) | if (d == NULL) | ||||
| return -1; | return -1; | ||||
| else | else | ||||
| bpy_pydriver_Dict = d; | bpy_pydriver_Dict = d; | ||||
| /* import some modules: builtins, bpy, math, (Blender.noise)*/ | /* import some modules: builtins, bpy, math, (Blender.noise)*/ | ||||
| PyDict_SetItemString(d, "__builtins__", PyEval_GetBuiltins()); | PyDict_SetItemString(d, "__builtins__", PyEval_GetBuiltins()); | ||||
| mod = PyImport_ImportModule("math"); | mod = PyImport_ImportModule("math"); | ||||
| if (mod) { | if (mod) { | ||||
| PyDict_Merge(d, PyModule_GetDict(mod), 0); /* 0 - don't overwrite existing values */ | PyDict_Merge(d, PyModule_GetDict(mod), 0); /* 0 - don't overwrite existing values */ | ||||
| Py_DECREF(mod); | Py_DECREF(mod); | ||||
| } | } | ||||
| #ifdef USE_BYTECODE_WHITELIST | |||||
| PyObject *mod_math = mod; | |||||
| #endif | |||||
| /* add bpy to global namespace */ | /* add bpy to global namespace */ | ||||
| mod = PyImport_ImportModuleLevel("bpy", NULL, NULL, NULL, 0); | mod = PyImport_ImportModuleLevel("bpy", NULL, NULL, NULL, 0); | ||||
| if (mod) { | if (mod) { | ||||
| PyDict_SetItemString(bpy_pydriver_Dict, "bpy", mod); | PyDict_SetItemString(bpy_pydriver_Dict, "bpy", mod); | ||||
| Py_DECREF(mod); | Py_DECREF(mod); | ||||
| } | } | ||||
| /* add noise to global namespace */ | /* add noise to global namespace */ | ||||
| mod = PyImport_ImportModuleLevel("mathutils", NULL, NULL, NULL, 0); | mod = PyImport_ImportModuleLevel("mathutils", NULL, NULL, NULL, 0); | ||||
| if (mod) { | if (mod) { | ||||
| PyObject *modsub = PyDict_GetItemString(PyModule_GetDict(mod), "noise"); | PyObject *modsub = PyDict_GetItemString(PyModule_GetDict(mod), "noise"); | ||||
| PyDict_SetItemString(bpy_pydriver_Dict, "noise", modsub); | PyDict_SetItemString(bpy_pydriver_Dict, "noise", modsub); | ||||
| Py_DECREF(mod); | Py_DECREF(mod); | ||||
| } | } | ||||
| #ifdef USE_BYTECODE_WHITELIST | |||||
| /* setup the whitelist */ | |||||
| { | |||||
| bpy_pydriver_Dict__whitelist = PyDict_New(); | |||||
| const char *whitelist[] = { | |||||
| /* builtins (basic) */ | |||||
| "all", | |||||
| "any", | |||||
| "len", | |||||
| /* builtins (numeric) */ | |||||
| "max", | |||||
| "min", | |||||
| "pow", | |||||
| "round", | |||||
| "sum", | |||||
| /* types */ | |||||
| "bool", | |||||
| "float", | |||||
| "int", | |||||
| NULL, | |||||
| }; | |||||
| for (int i = 0; whitelist[i]; i++) { | |||||
| PyDict_SetItemString(bpy_pydriver_Dict__whitelist, whitelist[i], Py_None); | |||||
| } | |||||
| /* Add all of 'math' functions. */ | |||||
| if (mod_math != NULL) { | |||||
| PyObject *mod_math_dict = PyModule_GetDict(mod_math); | |||||
| PyObject *arg_key, *arg_value; | |||||
| Py_ssize_t arg_pos = 0; | |||||
| while (PyDict_Next(mod_math_dict, &arg_pos, &arg_key, &arg_value)) { | |||||
| const char *arg_str = _PyUnicode_AsString(arg_key); | |||||
| if (arg_str[0] && arg_str[1] != '_') { | |||||
| PyDict_SetItem(bpy_pydriver_Dict__whitelist, arg_key, Py_None); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| #endif /* USE_BYTECODE_WHITELIST */ | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| /* note, this function should do nothing most runs, only when changing frame */ | /* note, this function should do nothing most runs, only when changing frame */ | ||||
| /* not thread safe but neither is python */ | /* not thread safe but neither is python */ | ||||
| static struct { | static struct { | ||||
| float evaltime; | float evaltime; | ||||
| ▲ Show 20 Lines • Show All 52 Lines • ▼ Show 20 Lines | if (use_gil) | ||||
| gilstate = PyGILState_Ensure(); | gilstate = PyGILState_Ensure(); | ||||
| if (bpy_pydriver_Dict) { /* free the global dict used by pydrivers */ | if (bpy_pydriver_Dict) { /* free the global dict used by pydrivers */ | ||||
| PyDict_Clear(bpy_pydriver_Dict); | PyDict_Clear(bpy_pydriver_Dict); | ||||
| Py_DECREF(bpy_pydriver_Dict); | Py_DECREF(bpy_pydriver_Dict); | ||||
| bpy_pydriver_Dict = NULL; | bpy_pydriver_Dict = NULL; | ||||
| } | } | ||||
| #ifdef USE_BYTECODE_WHITELIST | |||||
| if (bpy_pydriver_Dict__whitelist) { | |||||
| PyDict_Clear(bpy_pydriver_Dict__whitelist); | |||||
| Py_DECREF(bpy_pydriver_Dict__whitelist); | |||||
| bpy_pydriver_Dict__whitelist = NULL; | |||||
| } | |||||
| #endif | |||||
| g_pydriver_state_prev.evaltime = FLT_MAX; | g_pydriver_state_prev.evaltime = FLT_MAX; | ||||
| /* freed when clearing driver dict */ | /* freed when clearing driver dict */ | ||||
| g_pydriver_state_prev.self = NULL; | g_pydriver_state_prev.self = NULL; | ||||
| if (use_gil) | if (use_gil) | ||||
| PyGILState_Release(gilstate); | PyGILState_Release(gilstate); | ||||
| return; | return; | ||||
| } | } | ||||
| /* error return function for BPY_eval_pydriver */ | /* error return function for BPY_eval_pydriver */ | ||||
| static void pydriver_error(ChannelDriver *driver) | static void pydriver_error(ChannelDriver *driver) | ||||
| { | { | ||||
| driver->flag |= DRIVER_FLAG_INVALID; /* py expression failed */ | driver->flag |= DRIVER_FLAG_INVALID; /* py expression failed */ | ||||
| fprintf(stderr, "\nError in Driver: The following Python expression failed:\n\t'%s'\n\n", driver->expression); | fprintf(stderr, "\nError in Driver: The following Python expression failed:\n\t'%s'\n\n", driver->expression); | ||||
| // BPy_errors_to_report(NULL); // TODO - reports | // BPy_errors_to_report(NULL); // TODO - reports | ||||
| PyErr_Print(); | PyErr_Print(); | ||||
| PyErr_Clear(); | PyErr_Clear(); | ||||
| } | } | ||||
| #ifdef USE_BYTECODE_WHITELIST | |||||
| #define OK_OP(op) [op] = 1 | |||||
| const char secure_opcodes[255] = { | |||||
| OK_OP(NOP), | |||||
| OK_OP(UNARY_POSITIVE), | |||||
| OK_OP(UNARY_NEGATIVE), | |||||
| OK_OP(UNARY_NOT), | |||||
| OK_OP(UNARY_INVERT), | |||||
| OK_OP(BINARY_MATRIX_MULTIPLY), | |||||
| OK_OP(INPLACE_MATRIX_MULTIPLY), | |||||
| OK_OP(BINARY_POWER), | |||||
| OK_OP(BINARY_MULTIPLY), | |||||
| OK_OP(BINARY_MODULO), | |||||
| OK_OP(BINARY_ADD), | |||||
| OK_OP(BINARY_SUBTRACT), | |||||
| OK_OP(BINARY_SUBSCR), | |||||
| OK_OP(BINARY_FLOOR_DIVIDE), | |||||
| OK_OP(BINARY_TRUE_DIVIDE), | |||||
| OK_OP(INPLACE_FLOOR_DIVIDE), | |||||
| OK_OP(INPLACE_TRUE_DIVIDE), | |||||
| OK_OP(INPLACE_ADD), | |||||
| OK_OP(INPLACE_SUBTRACT), | |||||
| OK_OP(INPLACE_MULTIPLY), | |||||
| OK_OP(INPLACE_MODULO), | |||||
| OK_OP(BINARY_LSHIFT), | |||||
| OK_OP(BINARY_RSHIFT), | |||||
| OK_OP(BINARY_AND), | |||||
| OK_OP(BINARY_XOR), | |||||
| OK_OP(BINARY_OR), | |||||
| OK_OP(INPLACE_POWER), | |||||
| OK_OP(INPLACE_LSHIFT), | |||||
| OK_OP(INPLACE_RSHIFT), | |||||
| OK_OP(INPLACE_AND), | |||||
| OK_OP(INPLACE_XOR), | |||||
| OK_OP(INPLACE_OR), | |||||
| OK_OP(RETURN_VALUE), | |||||
| 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(JUMP_ABSOLUTE), | |||||
| OK_OP(POP_JUMP_IF_FALSE), | |||||
| OK_OP(POP_JUMP_IF_TRUE), | |||||
| OK_OP(LOAD_GLOBAL), | |||||
| OK_OP(LOAD_FAST), | |||||
| OK_OP(STORE_FAST), | |||||
| OK_OP(DELETE_FAST), | |||||
| OK_OP(LOAD_DEREF), | |||||
| OK_OP(STORE_DEREF), | |||||
| /* special cases */ | |||||
| OK_OP(LOAD_CONST), /* ok because constants are accepted */ | |||||
| OK_OP(LOAD_NAME), /* ok, because PyCodeObject.names is checked */ | |||||
| OK_OP(CALL_FUNCTION), /* ok, because we check its 'name' before calling */ | |||||
| OK_OP(CALL_FUNCTION_KW), | |||||
| OK_OP(CALL_FUNCTION_EX), | |||||
| }; | |||||
| #undef OK_OP | |||||
| static bool bpy_driver_secure_bytecode_validate(PyObject *expr_code, PyObject *dict_arr[]) | |||||
| { | |||||
| PyCodeObject *py_code = (PyCodeObject *)expr_code; | |||||
| /* check names */ | |||||
| { | |||||
| for (int i = 0; i < PyTuple_GET_SIZE(py_code->co_names); i++) { | |||||
| PyObject *name = PyTuple_GET_ITEM(py_code->co_names, i); | |||||
| bool contains_name = false; | |||||
| for (int j = 0; dict_arr[j]; j++) { | |||||
| if (PyDict_Contains(dict_arr[j], name)) { | |||||
| contains_name = true; | |||||
| break; | |||||
| } | |||||
| } | |||||
| if (contains_name == false) { | |||||
| fprintf(stderr, "\tBPY_driver_eval() - restructed access disallows name '%s', " | |||||
| "enable auto-execution to support\n", _PyUnicode_AsString(name)); | |||||
| return false; | |||||
| } | |||||
| } | |||||
| } | |||||
| { | |||||
| const char *codestr; | |||||
| Py_ssize_t code_len; | |||||
| PyBytes_AsStringAndSize(py_code->co_code, (char **)&codestr, &code_len); | |||||
| #define CODESIZE(op) (HAS_ARG(op) ? 3 : 1) | |||||
| for (Py_ssize_t i = 0; i < code_len; i += CODESIZE(codestr[i])) { | |||||
| const int opcode = codestr[i]; | |||||
| if (secure_opcodes[opcode] == 0) { | |||||
| fprintf(stderr, "\tBPY_driver_eval() - restructed access disallows opcode '%d', " | |||||
| "enable auto-execution to support\n", opcode); | |||||
| return false; | |||||
| } | |||||
| } | |||||
| #undef CODESIZE | |||||
| } | |||||
| return true; | |||||
| } | |||||
| #endif /* USE_BYTECODE_WHITELIST */ | |||||
| /* This evals py driver expressions, 'expr' is a Python expression that | /* This evals py driver expressions, 'expr' is a Python expression that | ||||
| * should evaluate to a float number, which is returned. | * should evaluate to a float number, which is returned. | ||||
| * | * | ||||
| * (old)note: PyGILState_Ensure() isn't always called because python can call | * (old)note: PyGILState_Ensure() isn't always called because python can call | ||||
| * the bake operator which intern starts a thread which calls scene update | * the bake operator which intern starts a thread which calls scene update | ||||
| * which does a driver update. to avoid a deadlock check PyC_IsInterpreterActive() | * which does a driver update. to avoid a deadlock check PyC_IsInterpreterActive() | ||||
| * if PyGILState_Ensure() is needed - see [#27683] | * if PyGILState_Ensure() is needed - see [#27683] | ||||
| * | * | ||||
| Show All 16 Lines | float BPY_driver_exec(struct PathResolvedRNA *anim_rna, ChannelDriver *driver, const float evaltime) | ||||
| short targets_ok = 1; | short targets_ok = 1; | ||||
| int i; | int i; | ||||
| /* get the py expression to be evaluated */ | /* get the py expression to be evaluated */ | ||||
| expr = driver->expression; | expr = driver->expression; | ||||
| if (expr[0] == '\0') | if (expr[0] == '\0') | ||||
| return 0.0f; | return 0.0f; | ||||
| #ifndef USE_BYTECODE_WHITELIST | |||||
| if (!(G.f & G_SCRIPT_AUTOEXEC)) { | if (!(G.f & G_SCRIPT_AUTOEXEC)) { | ||||
| if (!(G.f & G_SCRIPT_AUTOEXEC_FAIL_QUIET)) { | if (!(G.f & G_SCRIPT_AUTOEXEC_FAIL_QUIET)) { | ||||
| G.f |= G_SCRIPT_AUTOEXEC_FAIL; | G.f |= G_SCRIPT_AUTOEXEC_FAIL; | ||||
| BLI_snprintf(G.autoexec_fail, sizeof(G.autoexec_fail), "Driver '%s'", expr); | BLI_snprintf(G.autoexec_fail, sizeof(G.autoexec_fail), "Driver '%s'", expr); | ||||
| printf("skipping driver '%s', automatic scripts are disabled\n", expr); | printf("skipping driver '%s', automatic scripts are disabled\n", expr); | ||||
| } | } | ||||
| return 0.0f; | return 0.0f; | ||||
| } | } | ||||
| #else | |||||
| bool is_recompile = false; | |||||
| #endif | |||||
| use_gil = true; /* !PyC_IsInterpreterActive(); */ | use_gil = true; /* !PyC_IsInterpreterActive(); */ | ||||
| if (use_gil) | if (use_gil) | ||||
| gilstate = PyGILState_Ensure(); | gilstate = PyGILState_Ensure(); | ||||
| /* needed since drivers are updated directly after undo where 'main' is | /* needed since drivers are updated directly after undo where 'main' is | ||||
| * re-allocated [#28807] */ | * re-allocated [#28807] */ | ||||
| Show All 27 Lines | if (driver->flag & DRIVER_FLAG_RECOMPILE) { | ||||
| Py_XDECREF(driver->expr_comp); | Py_XDECREF(driver->expr_comp); | ||||
| driver->expr_comp = PyTuple_New(2); | driver->expr_comp = PyTuple_New(2); | ||||
| expr_code = Py_CompileString(expr, "<bpy driver>", Py_eval_input); | expr_code = Py_CompileString(expr, "<bpy driver>", Py_eval_input); | ||||
| PyTuple_SET_ITEM(((PyObject *)driver->expr_comp), 0, expr_code); | PyTuple_SET_ITEM(((PyObject *)driver->expr_comp), 0, expr_code); | ||||
| driver->flag &= ~DRIVER_FLAG_RECOMPILE; | driver->flag &= ~DRIVER_FLAG_RECOMPILE; | ||||
| driver->flag |= DRIVER_FLAG_RENAMEVAR; /* maybe this can be removed but for now best keep until were sure */ | driver->flag |= DRIVER_FLAG_RENAMEVAR; /* maybe this can be removed but for now best keep until were sure */ | ||||
| #ifdef USE_BYTECODE_WHITELIST | |||||
| is_recompile = true; | |||||
| #endif | |||||
| } | } | ||||
| else { | else { | ||||
| expr_code = PyTuple_GET_ITEM(((PyObject *)driver->expr_comp), 0); | expr_code = PyTuple_GET_ITEM(((PyObject *)driver->expr_comp), 0); | ||||
| } | } | ||||
| if (driver->flag & DRIVER_FLAG_RENAMEVAR) { | if (driver->flag & DRIVER_FLAG_RENAMEVAR) { | ||||
| /* may not be set */ | /* may not be set */ | ||||
| expr_vars = PyTuple_GET_ITEM(((PyObject *)driver->expr_comp), 1); | expr_vars = PyTuple_GET_ITEM(((PyObject *)driver->expr_comp), 1); | ||||
| ▲ Show 20 Lines • Show All 65 Lines • ▼ Show 20 Lines | else { | ||||
| fprintf(stderr, "\tBPY_driver_eval() - couldn't add variable '%s' to namespace\n", dvar->name); | fprintf(stderr, "\tBPY_driver_eval() - couldn't add variable '%s' to namespace\n", dvar->name); | ||||
| // BPy_errors_to_report(NULL); // TODO - reports | // BPy_errors_to_report(NULL); // TODO - reports | ||||
| PyErr_Print(); | PyErr_Print(); | ||||
| PyErr_Clear(); | PyErr_Clear(); | ||||
| } | } | ||||
| } | } | ||||
| #ifdef USE_BYTECODE_WHITELIST | |||||
| if (is_recompile) { | |||||
| if (!(G.f & G_SCRIPT_AUTOEXEC)) { | |||||
| if (!bpy_driver_secure_bytecode_validate( | |||||
| expr_code, (PyObject *[]){ | |||||
| bpy_pydriver_Dict, | |||||
| bpy_pydriver_Dict__whitelist, | |||||
| driver_vars, | |||||
| NULL,} | |||||
| )) | |||||
| { | |||||
| Py_DECREF(expr_code); | |||||
| expr_code = NULL; | |||||
| PyTuple_SET_ITEM(((PyObject *)driver->expr_comp), 1, NULL); | |||||
| } | |||||
| } | |||||
| } | |||||
| #endif /* USE_BYTECODE_WHITELIST */ | |||||
| #if 0 /* slow, with this can avoid all Py_CompileString above. */ | #if 0 /* slow, with this can avoid all Py_CompileString above. */ | ||||
| /* execute expression to get a value */ | /* execute expression to get a value */ | ||||
| retval = PyRun_String(expr, Py_eval_input, bpy_pydriver_Dict, driver_vars); | retval = PyRun_String(expr, Py_eval_input, bpy_pydriver_Dict, driver_vars); | ||||
| #else | #else | ||||
| /* evaluate the compiled expression */ | /* evaluate the compiled expression */ | ||||
| if (expr_code) | if (expr_code) | ||||
| retval = PyEval_EvalCode((void *)expr_code, bpy_pydriver_Dict, driver_vars); | retval = PyEval_EvalCode((void *)expr_code, bpy_pydriver_Dict, driver_vars); | ||||
| Show All 31 Lines | |||||
Indent nested preprocessor.