00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024 #include "PreCompiled.h"
00025 #ifndef _PreComp_
00026 # include <QEventLoop>
00027 # include <QCoreApplication>
00028 # include <QFileInfo>
00029 # include <QTimer>
00030 #endif
00031
00032 #include "PythonDebugger.h"
00033 #include "MainWindow.h"
00034 #include "EditorView.h"
00035 #include "PythonEditor.h"
00036 #include "BitmapFactory.h"
00037 #include <Base/Interpreter.h>
00038 #include <Base/Console.h>
00039
00040 using namespace Gui;
00041
00042 Breakpoint::Breakpoint()
00043 {
00044 }
00045
00046 Breakpoint::Breakpoint(const Breakpoint& rBp)
00047 {
00048 setFilename(rBp.filename());
00049 for (std::set<int>::const_iterator it = rBp._linenums.begin(); it != rBp._linenums.end(); ++it)
00050 _linenums.insert(*it);
00051 }
00052
00053 Breakpoint& Breakpoint::operator= (const Breakpoint& rBp)
00054 {
00055 if (this == &rBp)
00056 return *this;
00057 setFilename(rBp.filename());
00058 _linenums.clear();
00059 for (std::set<int>::const_iterator it = rBp._linenums.begin(); it != rBp._linenums.end(); ++it)
00060 _linenums.insert(*it);
00061 return *this;
00062 }
00063
00064 Breakpoint::~Breakpoint()
00065 {
00066
00067 }
00068
00069 void Breakpoint::setFilename(const QString& fn)
00070 {
00071 _filename = fn;
00072 }
00073
00074 void Breakpoint::addLine(int line)
00075 {
00076 _linenums.insert(line);
00077 }
00078
00079 void Breakpoint::removeLine(int line)
00080 {
00081 _linenums.erase(line);
00082 }
00083
00084 bool Breakpoint::checkLine(int line)
00085 {
00086 return (_linenums.find(line) != _linenums.end());
00087 }
00088
00089 int Breakpoint::lineIndex(int ind)const
00090 {
00091 int i = 0;
00092 for (std::set<int>::const_iterator it = _linenums.begin(); it != _linenums.end(); ++it)
00093 {
00094 if (ind == i++)
00095 return *it;
00096 }
00097 return -1;
00098 }
00099
00100
00101
00102 void PythonDebugModule::init_module(void)
00103 {
00104 PythonDebugStdout::init_type();
00105 PythonDebugStderr::init_type();
00106 PythonDebugExcept::init_type();
00107 static PythonDebugModule* mod = new PythonDebugModule();
00108 Q_UNUSED(mod);
00109 }
00110
00111 PythonDebugModule::PythonDebugModule()
00112 : Py::ExtensionModule<PythonDebugModule>("FreeCADDbg")
00113 {
00114 add_varargs_method("getFunctionCallCount", &PythonDebugModule::getFunctionCallCount,
00115 "Get the total number of function calls executed and the number executed since the last call to this function.");
00116 add_varargs_method("getExceptionCount", &PythonDebugModule::getExceptionCount,
00117 "Get the total number of exceptions and the number executed since the last call to this function.");
00118 add_varargs_method("getLineCount", &PythonDebugModule::getLineCount,
00119 "Get the total number of lines executed and the number executed since the last call to this function.");
00120 add_varargs_method("getFunctionReturnCount", &PythonDebugModule::getFunctionReturnCount,
00121 "Get the total number of function returns executed and the number executed since the last call to this function.");
00122
00123 initialize( "The FreeCAD Python debug module" );
00124
00125 Py::Dict d(moduleDictionary());
00126 Py::Object out(Py::asObject(new PythonDebugStdout()));
00127 d["StdOut"] = out;
00128 Py::Object err(Py::asObject(new PythonDebugStderr()));
00129 d["StdErr"] = err;
00130 }
00131
00132 PythonDebugModule::~PythonDebugModule()
00133 {
00134 }
00135
00136 Py::Object PythonDebugModule::getFunctionCallCount(const Py::Tuple &a)
00137 {
00138 return Py::None();
00139 }
00140
00141 Py::Object PythonDebugModule::getExceptionCount(const Py::Tuple &a)
00142 {
00143 return Py::None();
00144 }
00145
00146 Py::Object PythonDebugModule::getLineCount(const Py::Tuple &a)
00147 {
00148 return Py::None();
00149 }
00150
00151 Py::Object PythonDebugModule::getFunctionReturnCount(const Py::Tuple &a)
00152 {
00153 return Py::None();
00154 }
00155
00156
00157
00158 void PythonDebugStdout::init_type()
00159 {
00160 behaviors().name("PythonDebugStdout");
00161 behaviors().doc("Redirection of stdout to FreeCAD's Python debugger window");
00162
00163 behaviors().supportRepr();
00164 add_varargs_method("write",&PythonDebugStdout::write,"write to stdout");
00165 add_varargs_method("flush",&PythonDebugStdout::flush,"flush the output");
00166 }
00167
00168 PythonDebugStdout::PythonDebugStdout()
00169 {
00170 }
00171
00172 PythonDebugStdout::~PythonDebugStdout()
00173 {
00174 }
00175
00176 Py::Object PythonDebugStdout::repr()
00177 {
00178 std::string s;
00179 std::ostringstream s_out;
00180 s_out << "PythonDebugStdout";
00181 return Py::String(s_out.str());
00182 }
00183
00184 Py::Object PythonDebugStdout::write(const Py::Tuple& args)
00185 {
00186 char *msg;
00187 PyObject* pObj;
00188
00189 if (!PyArg_ParseTuple(args.ptr(), "Os:OutputString", &pObj, &msg))
00190 throw Py::Exception();
00191
00192 if (strlen(msg) > 0)
00193 {
00194
00195 printf("%s\n",msg);
00196
00197
00198
00199 }
00200 return Py::None();
00201 }
00202
00203 Py::Object PythonDebugStdout::flush(const Py::Tuple&)
00204 {
00205 return Py::None();
00206 }
00207
00208
00209
00210 void PythonDebugStderr::init_type()
00211 {
00212 behaviors().name("PythonDebugStderr");
00213 behaviors().doc("Redirection of stderr to FreeCAD's Python debugger window");
00214
00215 behaviors().supportRepr();
00216 add_varargs_method("write",&PythonDebugStderr::write,"write to stderr");
00217 }
00218
00219 PythonDebugStderr::PythonDebugStderr()
00220 {
00221 }
00222
00223 PythonDebugStderr::~PythonDebugStderr()
00224 {
00225 }
00226
00227 Py::Object PythonDebugStderr::repr()
00228 {
00229 std::string s;
00230 std::ostringstream s_out;
00231 s_out << "PythonDebugStderr";
00232 return Py::String(s_out.str());
00233 }
00234
00235 Py::Object PythonDebugStderr::write(const Py::Tuple& args)
00236 {
00237 char *msg;
00238 PyObject* pObj;
00239
00240 if (!PyArg_ParseTuple(args.ptr(), "Os:OutputDebugString", &pObj, &msg))
00241 throw Py::Exception();
00242
00243 if (strlen(msg) > 0)
00244 {
00245
00246
00247
00248
00249
00250 }
00251
00252 return Py::None();
00253 }
00254
00255
00256
00257 void PythonDebugExcept::init_type()
00258 {
00259 behaviors().name("PythonDebugExcept");
00260 behaviors().doc("Custom exception handler");
00261
00262 behaviors().supportRepr();
00263 add_varargs_method("fc_excepthook",&PythonDebugExcept::excepthook,"Custom exception handler");
00264 }
00265
00266 PythonDebugExcept::PythonDebugExcept()
00267 {
00268 }
00269
00270 PythonDebugExcept::~PythonDebugExcept()
00271 {
00272 }
00273
00274 Py::Object PythonDebugExcept::repr()
00275 {
00276 std::string s;
00277 std::ostringstream s_out;
00278 s_out << "PythonDebugExcept";
00279 return Py::String(s_out.str());
00280 }
00281
00282 Py::Object PythonDebugExcept::excepthook(const Py::Tuple& args)
00283 {
00284 PyObject *exc, *value, *tb;
00285 if (!PyArg_UnpackTuple(args.ptr(), "excepthook", 3, 3, &exc, &value, &tb))
00286 throw Py::Exception();
00287
00288 PyErr_NormalizeException(&exc, &value, &tb);
00289
00290 PyErr_Display(exc, value, tb);
00291
00292
00293
00294
00295
00296
00297
00298
00299
00300
00301
00302
00303
00304
00305
00306
00307
00308
00309
00310 return Py::None();
00311 }
00312
00313
00314
00315 namespace Gui {
00316 class PythonDebuggerPy : public Py::PythonExtension<PythonDebuggerPy>
00317 {
00318 public:
00319 PythonDebuggerPy(PythonDebugger* d) : dbg(d), depth(0) { }
00320 ~PythonDebuggerPy() {}
00321 PythonDebugger* dbg;
00322 int depth;
00323 };
00324
00325 class RunningState
00326 {
00327 public:
00328 RunningState(bool& s) : state(s)
00329 { state = true; }
00330 ~RunningState()
00331 { state = false; }
00332 private:
00333 bool& state;
00334 };
00335
00336 struct PythonDebuggerP {
00337 PyObject* out_o;
00338 PyObject* err_o;
00339 PyObject* exc_o;
00340 PyObject* out_n;
00341 PyObject* err_n;
00342 PyObject* exc_n;
00343 bool init, trystop, running;
00344 QEventLoop loop;
00345 PyObject* pydbg;
00346 std::vector<Breakpoint> bps;
00347
00348 PythonDebuggerP(PythonDebugger* that) :
00349 init(false), trystop(false), running(false)
00350 {
00351 Base::PyGILStateLocker lock;
00352 out_n = new PythonDebugStdout();
00353 err_n = new PythonDebugStderr();
00354 PythonDebugExcept* err = new PythonDebugExcept();
00355 Py::Object func = err->getattr("fc_excepthook");
00356 exc_n = Py::new_reference_to(func);
00357 Py_DECREF(err);
00358 pydbg = new PythonDebuggerPy(that);
00359 }
00360 ~PythonDebuggerP()
00361 {
00362 Py_DECREF(out_n);
00363 Py_DECREF(err_n);
00364 Py_DECREF(exc_n);
00365 Py_DECREF(pydbg);
00366 }
00367 };
00368 }
00369
00370 PythonDebugger::PythonDebugger()
00371 : d(new PythonDebuggerP(this))
00372 {
00373 }
00374
00375 PythonDebugger::~PythonDebugger()
00376 {
00377 delete d;
00378 }
00379
00380 Breakpoint PythonDebugger::getBreakpoint(const QString& fn) const
00381 {
00382 for (std::vector<Breakpoint>::const_iterator it = d->bps.begin(); it != d->bps.end(); ++it) {
00383 if (fn == it->filename()) {
00384 return *it;
00385 }
00386 }
00387
00388 return Breakpoint();
00389 }
00390
00391 bool PythonDebugger::toggleBreakpoint(int line, const QString& fn)
00392 {
00393 for (std::vector<Breakpoint>::iterator it = d->bps.begin(); it != d->bps.end(); ++it) {
00394 if (fn == it->filename()) {
00395 if (it->checkLine(line)) {
00396 it->removeLine(line);
00397 return false;
00398 }
00399 else {
00400 it->addLine(line);
00401 return true;
00402 }
00403 }
00404 }
00405
00406 Breakpoint bp;
00407 bp.setFilename(fn);
00408 bp.addLine(line);
00409 d->bps.push_back(bp);
00410 return true;
00411 }
00412
00413 void PythonDebugger::runFile(const QString& fn)
00414 {
00415 try {
00416 RunningState state(d->running);
00417 Base::Interpreter().runFile((const char*)fn.toUtf8());
00418 }
00419 catch (const Base::PyException&) {
00420 }
00421 catch (...) {
00422 Base::Console().Warning("Unknown exception thrown during macro debugging\n");
00423 }
00424 }
00425
00426 bool PythonDebugger::isRunning() const
00427 {
00428 return d->running;
00429 }
00430
00431 bool PythonDebugger::start()
00432 {
00433 if (d->init)
00434 return false;
00435 d->init = true;
00436 d->trystop = false;
00437 Base::PyGILStateLocker lock;
00438 d->out_o = PySys_GetObject("stdout");
00439 d->err_o = PySys_GetObject("stderr");
00440 d->exc_o = PySys_GetObject("excepthook");
00441
00442 PySys_SetObject("stdout", d->out_n);
00443 PySys_SetObject("stderr", d->err_n);
00444 PySys_SetObject("excepthook", d->exc_o);
00445
00446 PyEval_SetTrace(tracer_callback, d->pydbg);
00447 return true;
00448 }
00449
00450 bool PythonDebugger::stop()
00451 {
00452 if (!d->init)
00453 return false;
00454 Base::PyGILStateLocker lock;
00455 PyEval_SetTrace(NULL, NULL);
00456 PySys_SetObject("stdout", d->out_o);
00457 PySys_SetObject("stderr", d->err_o);
00458 PySys_SetObject("excepthook", d->exc_o);
00459 d->init = false;
00460 return true;
00461 }
00462
00463 void PythonDebugger::tryStop()
00464 {
00465 d->trystop = true;
00466 signalNextStep();
00467 }
00468
00469 void PythonDebugger::stepOver()
00470 {
00471 signalNextStep();
00472 }
00473
00474 void PythonDebugger::showDebugMarker(const QString& fn, int line)
00475 {
00476 PythonEditorView* edit = 0;
00477 QList<QWidget*> mdis = getMainWindow()->windows();
00478 for (QList<QWidget*>::iterator it = mdis.begin(); it != mdis.end(); ++it) {
00479 edit = qobject_cast<PythonEditorView*>(*it);
00480 if (edit && edit->fileName() == fn)
00481 break;
00482 }
00483
00484 if (!edit) {
00485 PythonEditor* editor = new PythonEditor();
00486 editor->setWindowIcon(Gui::BitmapFactory().pixmap("python_small"));
00487 edit = new PythonEditorView(editor, getMainWindow());
00488 edit->open(fn);
00489 edit->resize(400, 300);
00490 getMainWindow()->addWindow(edit);
00491 }
00492
00493 getMainWindow()->setActiveWindow(edit);
00494 edit->showDebugMarker(line);
00495 }
00496
00497 void PythonDebugger::hideDebugMarker(const QString& fn)
00498 {
00499 PythonEditorView* edit = 0;
00500 QList<QWidget*> mdis = getMainWindow()->windows();
00501 for (QList<QWidget*>::iterator it = mdis.begin(); it != mdis.end(); ++it) {
00502 edit = qobject_cast<PythonEditorView*>(*it);
00503 if (edit && edit->fileName() == fn) {
00504 edit->hideDebugMarker();
00505 break;
00506 }
00507 }
00508 }
00509
00510
00511
00512
00513
00514 int PythonDebugger::tracer_callback(PyObject *obj, PyFrameObject *frame, int what, PyObject *arg)
00515 {
00516 PythonDebuggerPy* self = static_cast<PythonDebuggerPy*>(obj);
00517 PythonDebugger* dbg = self->dbg;
00518 if (dbg->d->trystop)
00519 PyErr_SetInterrupt();
00520 QCoreApplication::processEvents();
00521
00522
00523
00524
00525 QString file = QString::fromUtf8(PyString_AsString(frame->f_code->co_filename));
00526 switch (what) {
00527 case PyTrace_CALL:
00528 self->depth++;
00529 return 0;
00530 case PyTrace_RETURN:
00531 if (self->depth > 0)
00532 self->depth--;
00533 return 0;
00534 case PyTrace_LINE:
00535 {
00536
00537
00538
00539 int line = PyCode_Addr2Line(frame->f_code, frame->f_lasti);
00540
00541
00542
00543
00544
00545 if (!dbg->d->trystop) {
00546 Breakpoint bp = dbg->getBreakpoint(file);
00547 if (bp.checkLine(line)) {
00548 dbg->showDebugMarker(file, line);
00549 QEventLoop loop;
00550 QObject::connect(dbg, SIGNAL(signalNextStep()), &loop, SLOT(quit()));
00551 loop.exec();
00552 dbg->hideDebugMarker(file);
00553 }
00554 }
00555 return 0;
00556 }
00557 case PyTrace_EXCEPTION:
00558 return 0;
00559 case PyTrace_C_CALL:
00560 return 0;
00561 case PyTrace_C_EXCEPTION:
00562 return 0;
00563 case PyTrace_C_RETURN:
00564 return 0;
00565 default:
00566
00567 break;
00568 }
00569 return 0;
00570 }
00571
00572 #include "moc_PythonDebugger.cpp"