Skip to content

Commit

Permalink
Fixes empty tracebacks for user Python code; fix #34370, #31235
Browse files Browse the repository at this point in the history
Moving python traceback collection to within runStringUnsafe() so other
threads don't clear the global error status before we have a chance to
display it to users.
  • Loading branch information
espinafre authored and nyalldawson committed Feb 12, 2020
1 parent 213e3b6 commit f03eea9
Show file tree
Hide file tree
Showing 3 changed files with 18 additions and 18 deletions.
6 changes: 3 additions & 3 deletions src/python/qgspythonutils.h
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -92,10 +92,10 @@ class PYTHON_EXPORT QgsPythonUtils
virtual bool runString( const QString &command, QString msgOnError = QString(), bool single = true ) = 0; virtual bool runString( const QString &command, QString msgOnError = QString(), bool single = true ) = 0;


/** /**
* Runs a Python \a command. No error reporting is not performed. * Runs a Python \a command.
* \returns TRUE if no error occurred * \returns empty QString if no error occurred, or Python traceback as a QString on error.
*/ */
virtual bool runStringUnsafe( const QString &command, bool single = true ) = 0; virtual QString runStringUnsafe( const QString &command, bool single = true ) = 0;


/** /**
* Evaluates a Python \a command and stores the result in a the \a result string. * Evaluates a Python \a command and stores the result in a the \a result string.
Expand Down
28 changes: 14 additions & 14 deletions src/python/qgspythonutilsimpl.cpp
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -294,30 +294,38 @@ void QgsPythonUtilsImpl::uninstallErrorHook()
runString( QStringLiteral( "qgis.utils.uninstallErrorHook()" ) ); runString( QStringLiteral( "qgis.utils.uninstallErrorHook()" ) );
} }


bool QgsPythonUtilsImpl::runStringUnsafe( const QString &command, bool single ) QString QgsPythonUtilsImpl::runStringUnsafe( const QString &command, bool single )
{ {
// acquire global interpreter lock to ensure we are in a consistent state // acquire global interpreter lock to ensure we are in a consistent state
PyGILState_STATE gstate; PyGILState_STATE gstate;
gstate = PyGILState_Ensure(); gstate = PyGILState_Ensure();
QString ret;


// TODO: convert special characters from unicode strings u"…" to \uXXXX // TODO: convert special characters from unicode strings u"…" to \uXXXX
// so that they're not mangled to utf-8 // so that they're not mangled to utf-8
// (non-unicode strings can be mangled) // (non-unicode strings can be mangled)
PyObject *obj = PyRun_String( command.toUtf8().constData(), single ? Py_single_input : Py_file_input, mMainDict, mMainDict ); PyObject *obj = PyRun_String( command.toUtf8().constData(), single ? Py_single_input : Py_file_input, mMainDict, mMainDict );
bool res = nullptr == PyErr_Occurred(); PyObject *errobj = PyErr_Occurred();
if(nullptr != errobj)
{
ret = getTraceback();
}
Py_XDECREF( obj ); Py_XDECREF( obj );


// we are done calling python API, release global interpreter lock // we are done calling python API, release global interpreter lock
PyGILState_Release( gstate ); PyGILState_Release( gstate );


return res; return ret;
} }


bool QgsPythonUtilsImpl::runString( const QString &command, QString msgOnError, bool single ) bool QgsPythonUtilsImpl::runString( const QString &command, QString msgOnError, bool single )
{ {
bool res = runStringUnsafe( command, single ); bool res = true;
if ( res ) QString traceback = runStringUnsafe( command, single );
if ( traceback.isEmpty() )
return true; return true;
else
res = false;


if ( msgOnError.isEmpty() ) if ( msgOnError.isEmpty() )
{ {
Expand All @@ -327,7 +335,6 @@ bool QgsPythonUtilsImpl::runString( const QString &command, QString msgOnError,


// TODO: use python implementation // TODO: use python implementation


QString traceback = getTraceback();
QString path, version; QString path, version;
evalString( QStringLiteral( "str(sys.path)" ), path ); evalString( QStringLiteral( "str(sys.path)" ), path );
evalString( QStringLiteral( "sys.version" ), version ); evalString( QStringLiteral( "sys.version" ), version );
Expand All @@ -352,10 +359,6 @@ QString QgsPythonUtilsImpl::getTraceback()
{ {
#define TRACEBACK_FETCH_ERROR(what) {errMsg = what; goto done;} #define TRACEBACK_FETCH_ERROR(what) {errMsg = what; goto done;}


// acquire global interpreter lock to ensure we are in a consistent state
PyGILState_STATE gstate;
gstate = PyGILState_Ensure();

QString errMsg; QString errMsg;
QString result; QString result;


Expand All @@ -364,7 +367,7 @@ QString QgsPythonUtilsImpl::getTraceback()
PyObject *obStringIO = nullptr; PyObject *obStringIO = nullptr;
PyObject *obResult = nullptr; PyObject *obResult = nullptr;


PyObject *type, *value, *traceback; PyObject *type = nullptr, *value = nullptr, *traceback = nullptr;


PyErr_Fetch( &type, &value, &traceback ); PyErr_Fetch( &type, &value, &traceback );
PyErr_NormalizeException( &type, &value, &traceback ); PyErr_NormalizeException( &type, &value, &traceback );
Expand Down Expand Up @@ -423,9 +426,6 @@ QString QgsPythonUtilsImpl::getTraceback()
Py_XDECREF( traceback ); Py_XDECREF( traceback );
Py_XDECREF( type ); Py_XDECREF( type );


// we are done calling python API, release global interpreter lock
PyGILState_Release( gstate );

return result; return result;
} }


Expand Down
2 changes: 1 addition & 1 deletion src/python/qgspythonutilsimpl.h
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class QgsPythonUtilsImpl : public QgsPythonUtils
void exitPython() override; void exitPython() override;
bool isEnabled() override; bool isEnabled() override;
bool runString( const QString &command, QString msgOnError = QString(), bool single = true ) override; bool runString( const QString &command, QString msgOnError = QString(), bool single = true ) override;
bool runStringUnsafe( const QString &command, bool single = true ) override; QString runStringUnsafe( const QString &command, bool single = true ) override; // returns error traceback on failure, empty QString on success
bool evalString( const QString &command, QString &result ) override; bool evalString( const QString &command, QString &result ) override;
bool getError( QString &errorClassName, QString &errorText ) override; bool getError( QString &errorClassName, QString &errorText ) override;


Expand Down

0 comments on commit f03eea9

Please sign in to comment.