Fix T66256: Context overrides crash when operators change context
Using context overrides in Python caused problems for any operator that changed the context and require these changes to be read back. CTX_wm_area_set() for e.g. would set the struct member but future calls to CTX_wm_area() would still return the value defined by Python callers context overrides. This also resolves a mismatch between polling and calling operators from Python, where poll would override the Python context where calling only overrode the context when a new context was passed in.
This commit is contained in:
@@ -88,6 +88,16 @@ int BPY_context_member_get(struct bContext *C,
|
||||
void BPY_context_set(struct bContext *C);
|
||||
void BPY_context_update(struct bContext *C);
|
||||
|
||||
#define BPY_context_dict_clear_members(C, ...) \
|
||||
BPY_context_dict_clear_members_array(&((C)->data.py_context), \
|
||||
(C)->data.py_context_orig, \
|
||||
((const char *[]){__VA_ARGS__}), \
|
||||
VA_NARGS_COUNT(__VA_ARGS__))
|
||||
void BPY_context_dict_clear_members_array(void **dict_p,
|
||||
void *dict_orig,
|
||||
const char *context_members[],
|
||||
uint context_members_len);
|
||||
|
||||
void BPY_id_release(struct ID *id);
|
||||
|
||||
bool BPY_string_is_keyword(const char *str);
|
||||
|
@@ -165,6 +165,44 @@ void bpy_context_clear(bContext *UNUSED(C), const PyGILState_STATE *gilstate)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use for `CTX_*_set(..)` funcitons need to set values which are later read back as expected.
|
||||
* In this case we don't want the Python context to override the values as it causes problems
|
||||
* see T66256.
|
||||
*
|
||||
* \param dict_p: A pointer to #bContext.data.py_context so we can assign a new value.
|
||||
* \param dict_orig: The value of #bContext.data.py_context_orig to check if we need to copy.
|
||||
*
|
||||
* \note Typically accessed via #BPY_context_dict_clear_members macro.
|
||||
*/
|
||||
void BPY_context_dict_clear_members_array(void **dict_p,
|
||||
void *dict_orig,
|
||||
const char *context_members[],
|
||||
uint context_members_len)
|
||||
{
|
||||
PyGILState_STATE gilstate;
|
||||
const bool use_gil = !PyC_IsInterpreterActive();
|
||||
|
||||
if (use_gil) {
|
||||
gilstate = PyGILState_Ensure();
|
||||
}
|
||||
|
||||
/* Copy on write. */
|
||||
if (*dict_p == dict_orig) {
|
||||
*dict_p = PyDict_Copy(dict_orig);
|
||||
}
|
||||
|
||||
PyObject *dict = *dict_p;
|
||||
BLI_assert(PyDict_Check(dict));
|
||||
for (uint i = 0; i < context_members_len; i++) {
|
||||
PyDict_DelItemString(dict, context_members[i]);
|
||||
}
|
||||
|
||||
if (use_gil) {
|
||||
PyGILState_Release(gilstate);
|
||||
}
|
||||
}
|
||||
|
||||
void BPY_text_free_code(Text *text)
|
||||
{
|
||||
if (text->compiled) {
|
||||
|
@@ -77,7 +77,6 @@ static PyObject *pyop_poll(PyObject *UNUSED(self), PyObject *args)
|
||||
wmOperatorType *ot;
|
||||
const char *opname;
|
||||
PyObject *context_dict = NULL; /* optional args */
|
||||
PyObject *context_dict_back;
|
||||
const char *context_str = NULL;
|
||||
PyObject *ret;
|
||||
|
||||
@@ -131,16 +130,25 @@ static PyObject *pyop_poll(PyObject *UNUSED(self), PyObject *args)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
context_dict_back = CTX_py_dict_get(C);
|
||||
CTX_py_dict_set(C, (void *)context_dict);
|
||||
Py_XINCREF(context_dict); /* so we done loose it */
|
||||
struct bContext_PyState context_py_state;
|
||||
if (context_dict != NULL) {
|
||||
CTX_py_state_push(C, &context_py_state, (void *)context_dict);
|
||||
Py_INCREF(context_dict); /* so we done loose it */
|
||||
}
|
||||
|
||||
/* main purpose of this function */
|
||||
ret = WM_operator_poll_context((bContext *)C, ot, context) ? Py_True : Py_False;
|
||||
|
||||
/* restore with original context dict, probably NULL but need this for nested operator calls */
|
||||
Py_XDECREF(context_dict);
|
||||
CTX_py_dict_set(C, (void *)context_dict_back);
|
||||
if (context_dict != NULL) {
|
||||
PyObject *context_dict_test = CTX_py_dict_get(C);
|
||||
if (context_dict_test != context_dict) {
|
||||
Py_DECREF(context_dict_test);
|
||||
}
|
||||
/* Restore with original context dict,
|
||||
* probably NULL but need this for nested operator calls. */
|
||||
Py_DECREF(context_dict);
|
||||
CTX_py_state_pop(C, &context_py_state);
|
||||
}
|
||||
|
||||
return Py_INCREF_RET(ret);
|
||||
}
|
||||
@@ -156,7 +164,6 @@ static PyObject *pyop_call(PyObject *UNUSED(self), PyObject *args)
|
||||
const char *context_str = NULL;
|
||||
PyObject *kw = NULL; /* optional args */
|
||||
PyObject *context_dict = NULL; /* optional args */
|
||||
PyObject *context_dict_back;
|
||||
|
||||
/* note that context is an int, python does the conversion in this case */
|
||||
int context = WM_OP_EXEC_DEFAULT;
|
||||
@@ -225,17 +232,16 @@ static PyObject *pyop_call(PyObject *UNUSED(self), PyObject *args)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
context_dict_back = CTX_py_dict_get(C);
|
||||
|
||||
/**
|
||||
* It might be that there is already a Python context override. We don't want to remove that
|
||||
* except when this operator call sets a new override explicitly. This is necessary so that
|
||||
* called operator runs in the same context as the calling code by default.
|
||||
*/
|
||||
struct bContext_PyState context_py_state;
|
||||
if (context_dict != NULL) {
|
||||
CTX_py_dict_set(C, (void *)context_dict);
|
||||
CTX_py_state_push(C, &context_py_state, (void *)context_dict);
|
||||
Py_INCREF(context_dict); /* so we done loose it */
|
||||
}
|
||||
Py_XINCREF(context_dict); /* so we done loose it */
|
||||
|
||||
if (WM_operator_poll_context((bContext *)C, ot, context) == false) {
|
||||
const char *msg = CTX_wm_operator_poll_msg_get(C);
|
||||
@@ -314,9 +320,16 @@ static PyObject *pyop_call(PyObject *UNUSED(self), PyObject *args)
|
||||
#endif
|
||||
}
|
||||
|
||||
/* restore with original context dict, probably NULL but need this for nested operator calls */
|
||||
Py_XDECREF(context_dict);
|
||||
CTX_py_dict_set(C, (void *)context_dict_back);
|
||||
if (context_dict != NULL) {
|
||||
PyObject *context_dict_test = CTX_py_dict_get(C);
|
||||
if (context_dict_test != context_dict) {
|
||||
Py_DECREF(context_dict_test);
|
||||
}
|
||||
/* Restore with original context dict,
|
||||
* probably NULL but need this for nested operator calls. */
|
||||
Py_DECREF(context_dict);
|
||||
CTX_py_state_pop(C, &context_py_state);
|
||||
}
|
||||
|
||||
if (error_val == -1) {
|
||||
return NULL;
|
||||
|
Reference in New Issue
Block a user