PyAPI: call 'sys.excepthook' for text editor exceptions
Resolves #115090, where exceptions from the text editor called
`sys.excepthook` in v3.6 but not in v4.0.
The error was caused by PyC_ExceptionBuffer calling PyErr_Display
instead of PyErr_Print.
While technically a regression in [0], the behavior in 3.6x wasn't
working properly either since a user-defined `sys.excepthook` could
prevent PyC_ExceptionBuffer from accessing the exception text.
Resolve with the following changes:
- BPy_errors_to_report has been updated not to print errors.
Previously BPy_errors_to_report would *sometimes* print the error,
which complicated situations where the caller wanted predictable
behavior (always printing or never printing).
- `PyErr_Print()` must be used to display errors to the output,
this will call `sys.excepthook` as it used to.
It's better not to rely on side effects of BPy_errors_to_report()
for error handling.
[0]: 6a0f98aeef
This commit is contained in:
parent
1e84a028b3
commit
2e1486da60
|
@ -92,6 +92,7 @@ class PythonInterpreter : public Interpreter {
|
|||
|
||||
if (!BPY_run_string_eval(_context, nullptr, str.c_str())) {
|
||||
BPy_errors_to_report(reports);
|
||||
PyErr_Print();
|
||||
PyErr_Clear();
|
||||
cerr << "\nError executing Python script from PythonInterpreter::interpretString" << endl;
|
||||
cerr << "Name: " << name << endl;
|
||||
|
|
|
@ -87,6 +87,17 @@ bool BPy_errors_to_report_ex(ReportList *reports,
|
|||
PyC_FileAndNum(&location_filepath, &location_line_number);
|
||||
}
|
||||
|
||||
/* Create a temporary report list so none of the reports are printed (only stored).
|
||||
* In practically all cases printing should be handled by #PyErr_Print since this invokes
|
||||
* `sys.excepthook` as expected. */
|
||||
ReportList _reports_buf = {{0}};
|
||||
ReportList *reports_orig = reports;
|
||||
if ((reports->flag & RPT_PRINT_HANDLED_BY_OWNER) == 0) {
|
||||
reports = &_reports_buf;
|
||||
BKE_reports_init(reports, reports_orig->flag | RPT_PRINT_HANDLED_BY_OWNER);
|
||||
reports->storelevel = reports_orig->storelevel;
|
||||
}
|
||||
|
||||
if (location_filepath) {
|
||||
BKE_reportf(reports,
|
||||
RPT_ERROR,
|
||||
|
@ -103,6 +114,11 @@ bool BPy_errors_to_report_ex(ReportList *reports,
|
|||
BKE_reportf(reports, RPT_ERROR, "%s: %.*s", err_prefix, int(err_str_len), err_str);
|
||||
}
|
||||
|
||||
if (reports != reports_orig) {
|
||||
BKE_reports_move_to_reports(reports_orig, reports);
|
||||
BKE_reports_free(reports);
|
||||
}
|
||||
|
||||
/* Ensure this is _always_ printed to the output so developers don't miss exceptions. */
|
||||
Py_DECREF(err_str_py);
|
||||
return true;
|
||||
|
|
|
@ -32,16 +32,11 @@ bool BPy_errors_to_report_ex(struct ReportList *reports,
|
|||
bool use_full,
|
||||
bool use_location);
|
||||
/**
|
||||
* \param reports: When set, an error will be added to this report, when NULL, print the error.
|
||||
* \param reports: Any errors will be added to the report list.
|
||||
*
|
||||
* \note Unless the caller handles printing the reports (or reports is NULL) it's best to ensure
|
||||
* the output is printed to the `stdout/stderr`:
|
||||
* \code{.cc}
|
||||
* BPy_errors_to_report(reports);
|
||||
* if (!BKE_reports_print_test(reports)) {
|
||||
* BKE_reports_print(reports);
|
||||
* }
|
||||
* \endcode
|
||||
* \note The reports are never printed to the `stdout/stderr`,
|
||||
* so you may wish to call either `BKE_reports_print(reports)` or `PyErr_Print()` afterwards.
|
||||
* Typically `PyErr_Print()` is preferable as `sys.excepthook` is called.
|
||||
*
|
||||
* \note The caller is responsible for clearing the error (see #PyErr_Clear).
|
||||
*/
|
||||
|
|
|
@ -180,7 +180,9 @@ static bool python_script_exec(
|
|||
}
|
||||
|
||||
if (!py_result) {
|
||||
BPy_errors_to_report(reports);
|
||||
if (reports) {
|
||||
BPy_errors_to_report(reports);
|
||||
}
|
||||
if (text) {
|
||||
if (do_jump) {
|
||||
/* ensure text is valid before use, the script may have freed itself */
|
||||
|
@ -190,6 +192,7 @@ static bool python_script_exec(
|
|||
}
|
||||
}
|
||||
}
|
||||
PyErr_Print();
|
||||
PyErr_Clear();
|
||||
}
|
||||
else {
|
||||
|
@ -272,12 +275,9 @@ static bool bpy_run_string_impl(bContext *C,
|
|||
ReportList reports;
|
||||
BKE_reports_init(&reports, RPT_STORE);
|
||||
BPy_errors_to_report(&reports);
|
||||
PyErr_Clear();
|
||||
|
||||
/* Ensure the reports are printed. */
|
||||
if (!BKE_reports_print_test(&reports, RPT_ERROR)) {
|
||||
BKE_reports_print(&reports, RPT_ERROR);
|
||||
}
|
||||
PyErr_Print();
|
||||
PyErr_Clear();
|
||||
|
||||
ReportList *wm_reports = CTX_wm_reports(C);
|
||||
if (wm_reports) {
|
||||
|
|
|
@ -8781,19 +8781,8 @@ static int bpy_class_call(bContext *C, PointerRNA *ptr, FunctionRNA *func, Param
|
|||
reports = CTX_wm_reports(C);
|
||||
}
|
||||
|
||||
/* Typically null reports are sent to the output,
|
||||
* in this case however PyErr_Print is responsible for that,
|
||||
* so only run this if reports are non-null. */
|
||||
if (reports) {
|
||||
/* Create a temporary report list so none of the reports are printed (only stored).
|
||||
* Only do this when reports is non-null because the error is printed to the `stderr`
|
||||
* #PyErr_Print below. */
|
||||
ReportList reports_temp = {{0}};
|
||||
BKE_reports_init(&reports_temp, reports->flag | RPT_PRINT_HANDLED_BY_OWNER);
|
||||
reports_temp.storelevel = reports->storelevel;
|
||||
BPy_errors_to_report(&reports_temp);
|
||||
BKE_reports_move_to_reports(reports, &reports_temp);
|
||||
BKE_reports_free(&reports_temp);
|
||||
BPy_errors_to_report(reports);
|
||||
}
|
||||
|
||||
/* Also print in the console for Python. */
|
||||
|
|
Loading…
Reference in New Issue