PythonDebugger.cpp

Go to the documentation of this file.
00001 /***************************************************************************
00002  *   Copyright (c) 2010 Werner Mayer <wmayer[at]users.sourceforge.net>     *
00003  *                                                                         *
00004  *   This file is part of the FreeCAD CAx development system.              *
00005  *                                                                         *
00006  *   This library is free software; you can redistribute it and/or         *
00007  *   modify it under the terms of the GNU Library General Public           *
00008  *   License as published by the Free Software Foundation; either          *
00009  *   version 2 of the License, or (at your option) any later version.      *
00010  *                                                                         *
00011  *   This library  is distributed in the hope that it will be useful,      *
00012  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
00013  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
00014  *   GNU Library General Public License for more details.                  *
00015  *                                                                         *
00016  *   You should have received a copy of the GNU Library General Public     *
00017  *   License along with this library; see the file COPYING.LIB. If not,    *
00018  *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
00019  *   Suite 330, Boston, MA  02111-1307, USA                                *
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     // you must have overwritten the virtual functions
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     //args contains a single parameter which is the string to write.
00189     if (!PyArg_ParseTuple(args.ptr(), "Os:OutputString", &pObj, &msg))
00190         throw Py::Exception();
00191 
00192     if (strlen(msg) > 0)
00193     {
00194         //send it to our stdout
00195         printf("%s\n",msg);
00196 
00197         //send it to the debugger as well
00198         //g_DebugSocket.SendMessage(eMSG_OUTPUT, msg);
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     // you must have overwritten the virtual functions
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     //args contains a single parameter which is the string to write.
00240     if (!PyArg_ParseTuple(args.ptr(), "Os:OutputDebugString", &pObj, &msg))
00241         throw Py::Exception();
00242 
00243     if (strlen(msg) > 0)
00244     {
00245         //send the message to our own stderr
00246         //dprintf(msg);
00247 
00248         //send it to the debugger as well
00249         //g_DebugSocket.SendMessage(eMSG_TRACE, msg);
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     // you must have overwritten the virtual functions
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     if (eEXCEPTMODE_IGNORE != g_eExceptionMode)
00293     {
00294         assert(tb);
00295 
00296         if (tb && (tb != Py_None))
00297         {
00298             //get the pointer to the frame held by the bottom traceback object - this
00299             //should be where the exception occurred.
00300             tracebackobject* pTb = (tracebackobject*)tb;
00301             while (pTb->tb_next != NULL) 
00302             {
00303                 pTb = pTb->tb_next;
00304             }
00305             PyFrameObject* frame = (PyFrameObject*)PyObject_GetAttr((PyObject*)pTb, PyString_FromString("tb_frame"));
00306             EnterBreakState(frame, (PyObject*)pTb);
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 // http://www.koders.com/cpp/fidBA6CD8A0FE5F41F1464D74733D9A711DA257D20B.aspx?s=PyEval_SetTrace
00511 // http://code.google.com/p/idapython/source/browse/trunk/python.cpp
00512 // http://www.koders.com/cpp/fid191F7B13CF73133935A7A2E18B7BF43ACC6D1784.aspx?s=PyEval_SetTrace
00513 // http://stuff.mit.edu/afs/sipb/project/python/src/python2.2-2.2.2/Modules/_hotshot.c
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     //int no;
00522 
00523     //no = frame->f_tstate->recursion_depth;
00524     //char* name = PyString_AsString(frame->f_code->co_name);
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             //PyObject *str;
00537             //str = PyObject_Str(frame->f_code->co_filename);
00538             //no = frame->f_lineno;
00539             int line = PyCode_Addr2Line(frame->f_code, frame->f_lasti);
00540             //if (str) {
00541             //    Base::Console().Message("PROFILING: %s:%d\n", PyString_AsString(str), frame->f_lineno);
00542             //    Py_DECREF(str);
00543             //}
00544     // For testing only
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         /* ignore PyTrace_EXCEPTION */
00567         break;
00568     }
00569     return 0;
00570 }
00571 
00572 #include "moc_PythonDebugger.cpp"

Generated on Wed Nov 23 19:00:32 2011 for FreeCAD by  doxygen 1.6.1