1
/***************************************************************************
2
* Copyright (c) 2010 Werner Mayer <wmayer[at]users.sourceforge.net> *
4
* This file is part of the FreeCAD CAx development system. *
6
* This library is free software; you can redistribute it and/or *
7
* modify it under the terms of the GNU Library General Public *
8
* License as published by the Free Software Foundation; either *
9
* version 2 of the License, or (at your option) any later version. *
11
* This library is distributed in the hope that it will be useful, *
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14
* GNU Library General Public License for more details. *
16
* You should have received a copy of the GNU Library General Public *
17
* License along with this library; see the file COPYING.LIB. If not, *
18
* write to the Free Software Foundation, Inc., 59 Temple Place, *
19
* Suite 330, Boston, MA 02111-1307, USA *
21
***************************************************************************/
23
#include "PreCompiled.h"
25
# include <QCoreApplication>
29
#include <Base/Console.h>
30
#include <Base/Interpreter.h>
32
#include "PythonDebugger.h"
33
#include "BitmapFactory.h"
34
#include "EditorView.h"
35
#include "MainWindow.h"
36
#include "PythonEditor.h"
41
Breakpoint::Breakpoint() = default;
43
Breakpoint::Breakpoint(const Breakpoint& rBp)
45
setFilename(rBp.filename());
46
for (int it : rBp._linenums)
50
Breakpoint& Breakpoint::operator= (const Breakpoint& rBp)
54
setFilename(rBp.filename());
56
for (int it : rBp._linenums)
61
Breakpoint::~Breakpoint() = default;
63
void Breakpoint::setFilename(const QString& fn)
68
void Breakpoint::addLine(int line)
70
_linenums.insert(line);
73
void Breakpoint::removeLine(int line)
75
_linenums.erase(line);
78
bool Breakpoint::checkLine(int line)
80
return (_linenums.find(line) != _linenums.end());
83
int Breakpoint::lineIndex(int ind)const
86
for (int it : _linenums)
94
// -----------------------------------------------------
96
void PythonDebugModule::init_module()
98
PythonDebugStdout::init_type();
99
PythonDebugStderr::init_type();
100
PythonDebugExcept::init_type();
101
Base::Interpreter().addModule(new PythonDebugModule);
104
PythonDebugModule::PythonDebugModule()
105
: Py::ExtensionModule<PythonDebugModule>("FreeCADDbg")
107
add_varargs_method("getFunctionCallCount", &PythonDebugModule::getFunctionCallCount,
108
"Get the total number of function calls executed and the number executed since the last call to this function.");
109
add_varargs_method("getExceptionCount", &PythonDebugModule::getExceptionCount,
110
"Get the total number of exceptions and the number executed since the last call to this function.");
111
add_varargs_method("getLineCount", &PythonDebugModule::getLineCount,
112
"Get the total number of lines executed and the number executed since the last call to this function.");
113
add_varargs_method("getFunctionReturnCount", &PythonDebugModule::getFunctionReturnCount,
114
"Get the total number of function returns executed and the number executed since the last call to this function.");
116
initialize( "The FreeCAD Python debug module" );
118
Py::Dict d(moduleDictionary());
119
Py::Object out(Py::asObject(new PythonDebugStdout()));
121
Py::Object err(Py::asObject(new PythonDebugStderr()));
125
PythonDebugModule::~PythonDebugModule()
127
Py::Dict d(moduleDictionary());
128
d["StdOut"] = Py::None();
129
d["StdErr"] = Py::None();
132
Py::Object PythonDebugModule::getFunctionCallCount(const Py::Tuple &)
137
Py::Object PythonDebugModule::getExceptionCount(const Py::Tuple &)
142
Py::Object PythonDebugModule::getLineCount(const Py::Tuple &)
147
Py::Object PythonDebugModule::getFunctionReturnCount(const Py::Tuple &)
152
// -----------------------------------------------------
154
void PythonDebugStdout::init_type()
156
behaviors().name("PythonDebugStdout");
157
behaviors().doc("Redirection of stdout to FreeCAD's Python debugger window");
158
// you must have overwritten the virtual functions
159
behaviors().supportRepr();
160
add_varargs_method("write",&PythonDebugStdout::write,"write to stdout");
161
add_varargs_method("flush",&PythonDebugStdout::flush,"flush the output");
164
PythonDebugStdout::PythonDebugStdout() = default;
166
PythonDebugStdout::~PythonDebugStdout() = default;
168
Py::Object PythonDebugStdout::repr()
171
std::ostringstream s_out;
172
s_out << "PythonDebugStdout";
173
return Py::String(s_out.str());
176
Py::Object PythonDebugStdout::write(const Py::Tuple& args)
180
////args contains a single parameter which is the string to write.
181
//if (!PyArg_ParseTuple(args.ptr(), "Os:OutputString", &pObj, &msg))
182
if (!PyArg_ParseTuple(args.ptr(), "s:OutputString", &msg))
183
throw Py::Exception();
187
//send it to our stdout
190
//send it to the debugger as well
191
//g_DebugSocket.SendMessage(eMSG_OUTPUT, msg);
196
Py::Object PythonDebugStdout::flush(const Py::Tuple&)
201
// -----------------------------------------------------
203
void PythonDebugStderr::init_type()
205
behaviors().name("PythonDebugStderr");
206
behaviors().doc("Redirection of stderr to FreeCAD's Python debugger window");
207
// you must have overwritten the virtual functions
208
behaviors().supportRepr();
209
add_varargs_method("write",&PythonDebugStderr::write,"write to stderr");
212
PythonDebugStderr::PythonDebugStderr() = default;
214
PythonDebugStderr::~PythonDebugStderr() = default;
216
Py::Object PythonDebugStderr::repr()
219
std::ostringstream s_out;
220
s_out << "PythonDebugStderr";
221
return Py::String(s_out.str());
224
Py::Object PythonDebugStderr::write(const Py::Tuple& args)
228
//args contains a single parameter which is the string to write.
229
//if (!PyArg_ParseTuple(args.ptr(), "Os:OutputDebugString", &pObj, &msg))
230
if (!PyArg_ParseTuple(args.ptr(), "s:OutputDebugString", &msg))
231
throw Py::Exception();
235
//send the message to our own stderr
238
//send it to the debugger as well
239
//g_DebugSocket.SendMessage(eMSG_TRACE, msg);
240
Base::Console().Error("%s", msg);
246
// -----------------------------------------------------
248
void PythonDebugExcept::init_type()
250
behaviors().name("PythonDebugExcept");
251
behaviors().doc("Custom exception handler");
252
// you must have overwritten the virtual functions
253
behaviors().supportRepr();
254
add_varargs_method("fc_excepthook",&PythonDebugExcept::excepthook,"Custom exception handler");
257
PythonDebugExcept::PythonDebugExcept() = default;
259
PythonDebugExcept::~PythonDebugExcept() = default;
261
Py::Object PythonDebugExcept::repr()
264
std::ostringstream s_out;
265
s_out << "PythonDebugExcept";
266
return Py::String(s_out.str());
269
Py::Object PythonDebugExcept::excepthook(const Py::Tuple& args)
271
PyObject *exc, *value, *tb;
272
if (!PyArg_UnpackTuple(args.ptr(), "excepthook", 3, 3, &exc, &value, &tb))
273
throw Py::Exception();
275
PyErr_NormalizeException(&exc, &value, &tb);
277
PyErr_Display(exc, value, tb);
279
if (eEXCEPTMODE_IGNORE != g_eExceptionMode)
283
if (tb && (tb != Py_None))
285
//get the pointer to the frame held by the bottom traceback object - this
286
//should be where the exception occurred.
287
tracebackobject* pTb = (tracebackobject*)tb;
288
while (pTb->tb_next != NULL)
292
PyFrameObject* frame = (PyFrameObject*)PyObject_GetAttr((PyObject*)pTb, PyString_FromString("tb_frame"));
293
EnterBreakState(frame, (PyObject*)pTb);
300
// -----------------------------------------------------
303
class PythonDebuggerPy : public Py::PythonExtension<PythonDebuggerPy>
306
explicit PythonDebuggerPy(PythonDebugger* d) : dbg(d), depth(0) { }
307
~PythonDebuggerPy() override = default;
315
explicit RunningState(bool& s) : state(s)
323
struct PythonDebuggerP {
324
PyObject* out_o{nullptr};
325
PyObject* err_o{nullptr};
326
PyObject* exc_o{nullptr};
327
PyObject* out_n{nullptr};
328
PyObject* err_n{nullptr};
329
PyObject* exc_n{nullptr};
330
PythonDebugExcept* pypde;
331
bool init{false}, trystop{false}, running{false};
333
PyObject* pydbg{nullptr};
334
std::vector<Breakpoint> bps;
336
explicit PythonDebuggerP(PythonDebugger* that)
338
Base::PyGILStateLocker lock;
339
out_n = new PythonDebugStdout();
340
err_n = new PythonDebugStderr();
341
pypde = new PythonDebugExcept();
342
Py::Object func = pypde->getattr("fc_excepthook");
343
exc_n = Py::new_reference_to(func);
344
pydbg = new PythonDebuggerPy(that);
348
Base::PyGILStateLocker lock;
358
PythonDebugger::PythonDebugger()
359
: d(new PythonDebuggerP(this))
363
PythonDebugger::~PythonDebugger()
368
Breakpoint PythonDebugger::getBreakpoint(const QString& fn) const
370
for (const Breakpoint& it : d->bps) {
371
if (fn == it.filename()) {
379
bool PythonDebugger::toggleBreakpoint(int line, const QString& fn)
381
for (Breakpoint& it : d->bps) {
382
if (fn == it.filename()) {
383
if (it.checkLine(line)) {
397
d->bps.push_back(bp);
401
void PythonDebugger::runFile(const QString& fn)
404
RunningState state(d->running);
405
QByteArray pxFileName = fn.toUtf8();
407
Base::FileInfo fi((const char*)pxFileName);
408
FILE *fp = _wfopen(fi.toStdWString().c_str(),L"r");
410
FILE *fp = fopen((const char*)pxFileName,"r");
415
Base::PyGILStateLocker locker;
416
PyObject *module, *dict;
417
module = PyImport_AddModule("__main__");
418
dict = PyModule_GetDict(module);
419
dict = PyDict_Copy(dict);
420
if (!PyDict_GetItemString(dict, "__file__")) {
421
PyObject *pyObj = PyUnicode_FromString((const char*)pxFileName);
426
if (PyDict_SetItemString(dict, "__file__", pyObj) < 0) {
434
PyObject *result = PyRun_File(fp, (const char*)pxFileName, Py_file_input, dict, dict);
443
catch (const Base::PyException&/* e*/) {
444
//PySys_WriteStderr("Exception: %s\n", e.what());
447
Base::Console().Warning("Unknown exception thrown during macro debugging\n");
451
bool PythonDebugger::isRunning() const
456
bool PythonDebugger::start()
462
Base::PyGILStateLocker lock;
463
d->out_o = PySys_GetObject("stdout");
464
d->err_o = PySys_GetObject("stderr");
465
d->exc_o = PySys_GetObject("excepthook");
467
PySys_SetObject("stdout", d->out_n);
468
PySys_SetObject("stderr", d->err_n);
469
PySys_SetObject("excepthook", d->exc_o);
471
PyEval_SetTrace(tracer_callback, d->pydbg);
475
bool PythonDebugger::stop()
479
Base::PyGILStateLocker lock;
480
PyEval_SetTrace(nullptr, nullptr);
481
PySys_SetObject("stdout", d->out_o);
482
PySys_SetObject("stderr", d->err_o);
483
PySys_SetObject("excepthook", d->exc_o);
488
void PythonDebugger::tryStop()
491
Q_EMIT signalNextStep();
494
void PythonDebugger::stepOver()
496
Q_EMIT signalNextStep();
499
void PythonDebugger::stepInto()
501
Q_EMIT signalNextStep();
504
void PythonDebugger::stepRun()
506
Q_EMIT signalNextStep();
509
void PythonDebugger::showDebugMarker(const QString& fn, int line)
511
PythonEditorView* edit = nullptr;
512
QList<QWidget*> mdis = getMainWindow()->windows();
513
for (const auto & mdi : mdis) {
514
edit = qobject_cast<PythonEditorView*>(mdi);
515
if (edit && edit->fileName() == fn)
520
auto editor = new PythonEditor();
521
editor->setWindowIcon(Gui::BitmapFactory().iconFromTheme("applications-python"));
522
edit = new PythonEditorView(editor, getMainWindow());
524
edit->resize(400, 300);
525
getMainWindow()->addWindow(edit);
528
getMainWindow()->setActiveWindow(edit);
529
edit->showDebugMarker(line);
532
void PythonDebugger::hideDebugMarker(const QString& fn)
534
PythonEditorView* edit = nullptr;
535
QList<QWidget*> mdis = getMainWindow()->windows();
536
for (const auto & mdi : mdis) {
537
edit = qobject_cast<PythonEditorView*>(mdi);
538
if (edit && edit->fileName() == fn) {
539
edit->hideDebugMarker();
545
#if PY_VERSION_HEX < 0x030900B1
546
static PyCodeObject* PyFrame_GetCode(PyFrameObject *frame)
548
Py_INCREF(frame->f_code);
549
return frame->f_code;
553
// http://www.koders.com/cpp/fidBA6CD8A0FE5F41F1464D74733D9A711DA257D20B.aspx?s=PyEval_SetTrace
554
// http://code.google.com/p/idapython/source/browse/trunk/python.cpp
555
// http://www.koders.com/cpp/fid191F7B13CF73133935A7A2E18B7BF43ACC6D1784.aspx?s=PyEval_SetTrace
557
int PythonDebugger::tracer_callback(PyObject *obj, PyFrameObject *frame, int what, PyObject * /*arg*/)
559
auto self = static_cast<PythonDebuggerPy*>(obj);
560
PythonDebugger* dbg = self->dbg;
562
PyErr_SetInterrupt();
563
QCoreApplication::processEvents();
564
PyCodeObject* code = PyFrame_GetCode(frame);
565
QString file = QString::fromUtf8(PyUnicode_AsUTF8(code->co_filename));
577
PyCodeObject* f_code = PyFrame_GetCode(frame);
578
int f_lasti = PyFrame_GetLineNumber(frame);
579
int line = PyCode_Addr2Line(f_code, f_lasti);
582
if (!dbg->d->trystop) {
583
Breakpoint bp = dbg->getBreakpoint(file);
584
if (bp.checkLine(line)) {
585
dbg->showDebugMarker(file, line);
587
QObject::connect(dbg, &PythonDebugger::signalNextStep, &loop, &QEventLoop::quit);
589
dbg->hideDebugMarker(file);
594
case PyTrace_EXCEPTION:
598
case PyTrace_C_EXCEPTION:
600
case PyTrace_C_RETURN:
603
/* ignore PyTrace_EXCEPTION */
609
#include "moc_PythonDebugger.cpp"