HAL  v4.5.0-151-g9638bf6b9
The Hardware Analyzer - a comprehensive reverse engineering and manipulation framework for gate-level netlists.
python_thread.cpp
Go to the documentation of this file.
3 #include <QDebug>
4 
5 #include <pyerrors.h>
6 #include <ceval.h>
7 #include <pystate.h>
10 #include <QApplication>
11 #include <QDebug>
12 #include <QRegularExpression>
13 
14 namespace hal {
16  {
17  mState = PyGILState_Ensure();
18  }
19 
21  {
22  PyGILState_Release((PyGILState_STATE)mState);
23  }
24 
25  PythonThread::PythonThread(const QString& script, bool singleStatement, QObject* parent)
26  : QThread(parent), mScript(script), mSingleStatement(singleStatement),
27  mAbortRequested(false), mSpamCount(0)
28  {
29  // qDebug() << "+++PythonThread" << QString::number((quintptr) this, 16);
30  }
31 
33  {
34  // qDebug() << "---PythonThread" << QString::number((quintptr) this, 16);
35  }
36 
38  {
39  // decides it's our turn.
40  PythonMutex pmutex;
41  py::dict tmp_context(py::globals());
42  PythonContext::initializeScript(&tmp_context);
43 
44  // Capture the Python-internal thread ID, which is needed if we want to
45  // interrupt the thread later
46  pybind11::object rc = py::eval("threading.get_ident()", tmp_context, tmp_context);
47  mPythonThreadID = rc.cast<unsigned long>();
48 
49  mElapsedTimer.start();
50  try
51  {
52  py::object rc;
53  if (mSingleStatement)
54  rc = py::eval<py::eval_single_statement>(mScript.toStdString(), tmp_context, tmp_context);
55  else
56  rc = py::eval<py::eval_statements>(mScript.toStdString(), tmp_context, tmp_context);
57 
58  if (!rc.is_none())
59  mResult = QString::fromStdString(py::str(rc).cast<std::string>());
60  }
61  catch (py::error_already_set& e)
62  {
63  qDebug() << "AlreadySet";
64  mErrorMessage = QString::fromStdString(std::string(e.what()));
65 
66  // pybind11 prepends a coding line to the script, so reported line numbers are
67  // one too high. Rewrite "At:\n <string>(6):" to "At line 5:" if it matches,
68  // otherwise leave the message unchanged.
69  QRegularExpression re("At:\\s*\\n\\s*<string>\\((\\d+)\\):");
70  QRegularExpressionMatch match = re.match(mErrorMessage);
71  if (match.hasMatch())
72  {
73  int line = match.captured(1).toInt() - 1;
74  if (line >= 1)
75  mErrorMessage.replace(match.capturedStart(0), match.capturedLength(0),
76  QString("At line %1:").arg(line));
77  }
78  mErrorMessage += "\n";
79  e.restore();
80  PyErr_Clear();
81  }
82  catch (std::exception& e)
83  {
84  qDebug() << "Exception";
85  mErrorMessage = QString::fromStdString(std::string(e.what()) + "#\n");
86  }
87 
88  // running out of scope calls PyGILState_Release(state);
89  }
90 
91  void PythonThread::handleStdout(const QString& output)
92  {
93  if (mElapsedTimer.elapsed() > 100)
94  {
95  if (!mStdoutBuffer.isEmpty())
96  ++mSpamCount;
97  else
98  mSpamCount = 0;
99  if (mSpamCount > 1000)
100  mStdoutBuffer += output;
101  else if (mSpamCount < 10 || mElapsedTimer.elapsed() > 1000)
102  {
103  Q_EMIT stdOutput(mStdoutBuffer + output);
104  mStdoutBuffer.clear();
105  mElapsedTimer.restart();
106  }
107  else
108  mStdoutBuffer += output;
109  }
110  else
111  mStdoutBuffer += output;
112  }
113 
115  {
116  QString retval = mStdoutBuffer;
117  mStdoutBuffer.clear();
118  mElapsedTimer.restart();
119  return retval;
120  }
121 
122  void PythonThread::handleError(const QString& output)
123  {
125  }
126 
128  {;}
129 
131  if (!mInputMutex.tryLock())
132  {
133  mAbortRequested = true;
134  mInputMutex.unlock();
135  return;
136  }
137  else
138  {
139  mInputMutex.unlock();
140  }
141  qDebug() << "about to terminate thread..." << mPythonThreadID;
142  PythonMutex pmutex;
143  // We interrupt the thread by forcibly injecting an exception
144  // (namely the KeyboardInterrupt exception, but any other one works as well)
145  int nThreads = PyThreadState_SetAsyncExc(mPythonThreadID, PyExc_KeyboardInterrupt);
146  if (nThreads == 0)
147  {
148  qDebug() << "Oh no! The Python interpreter doesn't know that thread.";
149  }
150  else if (nThreads > 1)
151  {
152  // apparently this can actually happen if you mess up the C<->Python bindings
153  qDebug() << "Oh no! There seem to be multiple threads with the same ID!";
154  }
155  qDebug() << "thread terminated";
156  // running out of scope calls PyGILState_Release(state);
157  }
158 
160  {
161  if (!mInputMutex.tryLock())
162  {
163  qDebug() << "Oh no! Function already locked waiting for input.";
164  return false;
165  }
166  if (type != WaitForMenuSelection)
167  Q_EMIT requireInput(type,prompt,defaultValue);
168  mInputMutex.lock(); // wait for set Input
169  mInputMutex.unlock();
170  if (mAbortRequested)
171  {
172  throw std::runtime_error(std::string("Python script aborted by user"));
173  return false;
174  }
175  return true;
176  }
177 
178  std::string PythonThread::handleConsoleInput(const QString& prompt)
179  {
180  if (!getInput(ConsoleInput, prompt, QString())) return std::string();
181  return mInput.toString().toStdString();
182  }
183 
184  std::string PythonThread::handleStringInput(const QString& prompt, const QString& defval)
185  {
186  if (!getInput(StringInput, prompt, defval)) return std::string();
187  return mInput.toString().toStdString();
188  }
189 
190  int PythonThread::handleNumberInput(const QString& prompt, int defval)
191  {
192  if (!getInput(NumberInput, prompt, defval)) return 0;
193  return mInput.toInt();
194  }
195 
197  {
198  if (!getInput(ModuleInput, prompt, QString())) return 0;
199  return static_cast<Module*>(mInput.value<void*>());
200  }
201 
203  {
204  if (!getInput(GateInput, prompt, QString())) return 0;
205  return static_cast<Gate*>(mInput.value<void*>());
206  }
207 
208  std::string PythonThread::handleFilenameInput(const QString& prompt, const QString& filetype)
209  {
210  if (!getInput(FilenameInput, prompt, filetype)) return std::string();
211  return mInput.toString().toStdString();
212  }
213 
215  {
216  mInput = inp;
217  mInputMutex.unlock();
218  }
219 
221  {
222  // unlocking if not locked might cause undefined behavior
223  mInputMutex.tryLock();
224  mInputMutex.unlock();
225  }
226 }
Definition: gate.h:58
static void initializeScript(py::dict *context)
PythonMutex()
Aquire GIL.
~PythonMutex()
Release GIL.
void clear() override
void stdError(QString txt)
Gate * handleGateInput(const QString &prompt)
void handleStdout(const QString &output) override
void setInput(const QVariant &inp)
std::string handleStringInput(const QString &prompt, const QString &defval)
int handleNumberInput(const QString &prompt, int defval)
bool getInput(InputType type, QString prompt, QVariant defaultValue)
std::string handleConsoleInput(const QString &prompt)
Module * handleModuleInput(const QString &prompt)
void handleError(const QString &output) override
void stdOutput(QString txt)
void requireInput(int type, QString prompt, QVariant defaultValue)
void run() override
PythonThread(const QString &script, bool singleStatement, QObject *parent=nullptr)
std::string handleFilenameInput(const QString &prompt, const QString &filetype)
Definition: defines.h:45
PinType type
qint64 elapsed() const const
qint64 restart()
void lock()
bool tryLock(int timeout)
void unlock()
Q_EMITQ_EMIT
QRegularExpressionMatch match(const QString &subject, int offset, QRegularExpression::MatchType matchType, QRegularExpression::MatchOptions matchOptions) const const
QString captured(int nth) const const
int capturedLength(int nth) const const
int capturedStart(int nth) const const
bool hasMatch() const const
void clear()
QString fromStdString(const std::string &str)
bool isEmpty() const const
QString & replace(int position, int n, QChar after)
int toInt(bool *ok, int base) const const
std::string toStdString() const const
int toInt(bool *ok) const const
QString toString() const const
T value() const const