FreeCAD

Форк
0
/
PythonDebugger.cpp 
609 строк · 17.2 Кб
1
/***************************************************************************
2
 *   Copyright (c) 2010 Werner Mayer <wmayer[at]users.sourceforge.net>     *
3
 *                                                                         *
4
 *   This file is part of the FreeCAD CAx development system.              *
5
 *                                                                         *
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.      *
10
 *                                                                         *
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.                  *
15
 *                                                                         *
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                                *
20
 *                                                                         *
21
 ***************************************************************************/
22

23
#include "PreCompiled.h"
24
#ifndef _PreComp_
25
# include <QCoreApplication>
26
# include <QEventLoop>
27
#endif
28

29
#include <Base/Console.h>
30
#include <Base/Interpreter.h>
31

32
#include "PythonDebugger.h"
33
#include "BitmapFactory.h"
34
#include "EditorView.h"
35
#include "MainWindow.h"
36
#include "PythonEditor.h"
37

38

39
using namespace Gui;
40

41
Breakpoint::Breakpoint() = default;
42

43
Breakpoint::Breakpoint(const Breakpoint& rBp)
44
{
45
    setFilename(rBp.filename());
46
    for (int it : rBp._linenums)
47
        _linenums.insert(it);
48
}
49

50
Breakpoint& Breakpoint::operator= (const Breakpoint& rBp)
51
{
52
    if (this == &rBp)
53
        return *this;
54
    setFilename(rBp.filename());
55
    _linenums.clear();
56
    for (int it : rBp._linenums)
57
        _linenums.insert(it);
58
    return *this;
59
}
60

61
Breakpoint::~Breakpoint() = default;
62

63
void Breakpoint::setFilename(const QString& fn)
64
{
65
    _filename = fn;
66
}
67

68
void Breakpoint::addLine(int line)
69
{
70
    _linenums.insert(line);
71
}
72

73
void Breakpoint::removeLine(int line)
74
{
75
    _linenums.erase(line);
76
}
77

78
bool Breakpoint::checkLine(int line)
79
{
80
    return (_linenums.find(line) != _linenums.end());
81
}
82

83
int Breakpoint::lineIndex(int ind)const
84
{
85
    int i = 0;
86
    for (int it : _linenums)
87
    {
88
        if (ind == i++)
89
            return it;
90
    }
91
    return -1;
92
}
93

94
// -----------------------------------------------------
95

96
void PythonDebugModule::init_module()
97
{
98
    PythonDebugStdout::init_type();
99
    PythonDebugStderr::init_type();
100
    PythonDebugExcept::init_type();
101
    Base::Interpreter().addModule(new PythonDebugModule);
102
}
103

104
PythonDebugModule::PythonDebugModule()
105
  : Py::ExtensionModule<PythonDebugModule>("FreeCADDbg")
106
{
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.");
115

116
    initialize( "The FreeCAD Python debug module" );
117

118
    Py::Dict d(moduleDictionary());
119
    Py::Object out(Py::asObject(new PythonDebugStdout()));
120
    d["StdOut"] = out;
121
    Py::Object err(Py::asObject(new PythonDebugStderr()));
122
    d["StdErr"] = err;
123
}
124

125
PythonDebugModule::~PythonDebugModule()
126
{
127
    Py::Dict d(moduleDictionary());
128
    d["StdOut"] = Py::None();
129
    d["StdErr"] = Py::None();
130
}
131

132
Py::Object PythonDebugModule::getFunctionCallCount(const Py::Tuple &)
133
{
134
    return Py::None();
135
}
136

137
Py::Object PythonDebugModule::getExceptionCount(const Py::Tuple &)
138
{
139
    return Py::None();
140
}
141

142
Py::Object PythonDebugModule::getLineCount(const Py::Tuple &)
143
{
144
    return Py::None();
145
}
146

147
Py::Object PythonDebugModule::getFunctionReturnCount(const Py::Tuple &)
148
{
149
    return Py::None();
150
}
151

152
// -----------------------------------------------------
153

154
void PythonDebugStdout::init_type()
155
{
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");
162
}
163

164
PythonDebugStdout::PythonDebugStdout() = default;
165

166
PythonDebugStdout::~PythonDebugStdout() = default;
167

168
Py::Object PythonDebugStdout::repr()
169
{
170
    std::string s;
171
    std::ostringstream s_out;
172
    s_out << "PythonDebugStdout";
173
    return Py::String(s_out.str());
174
}
175

176
Py::Object PythonDebugStdout::write(const Py::Tuple& args)
177
{
178
    char *msg;
179
    //PyObject* pObj;
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();
184

185
    if (strlen(msg) > 0)
186
    {
187
        //send it to our stdout
188
        printf("%s\n",msg);
189

190
        //send it to the debugger as well
191
        //g_DebugSocket.SendMessage(eMSG_OUTPUT, msg);
192
    }
193
    return Py::None();
194
}
195

196
Py::Object PythonDebugStdout::flush(const Py::Tuple&)
197
{
198
    return Py::None();
199
}
200

201
// -----------------------------------------------------
202

203
void PythonDebugStderr::init_type()
204
{
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");
210
}
211

212
PythonDebugStderr::PythonDebugStderr() = default;
213

214
PythonDebugStderr::~PythonDebugStderr() = default;
215

216
Py::Object PythonDebugStderr::repr()
217
{
218
    std::string s;
219
    std::ostringstream s_out;
220
    s_out << "PythonDebugStderr";
221
    return Py::String(s_out.str());
222
}
223

224
Py::Object PythonDebugStderr::write(const Py::Tuple& args)
225
{
226
    char *msg;
227
    //PyObject* pObj;
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();
232

233
    if (strlen(msg) > 0)
234
    {
235
        //send the message to our own stderr
236
        //dprintf(msg);
237

238
        //send it to the debugger as well
239
        //g_DebugSocket.SendMessage(eMSG_TRACE, msg);
240
        Base::Console().Error("%s", msg);
241
    }
242

243
    return Py::None();
244
}
245

246
// -----------------------------------------------------
247

248
void PythonDebugExcept::init_type()
249
{
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");
255
}
256

257
PythonDebugExcept::PythonDebugExcept() = default;
258

259
PythonDebugExcept::~PythonDebugExcept() = default;
260

261
Py::Object PythonDebugExcept::repr()
262
{
263
    std::string s;
264
    std::ostringstream s_out;
265
    s_out << "PythonDebugExcept";
266
    return Py::String(s_out.str());
267
}
268

269
Py::Object PythonDebugExcept::excepthook(const Py::Tuple& args)
270
{
271
    PyObject *exc, *value, *tb;
272
    if (!PyArg_UnpackTuple(args.ptr(), "excepthook", 3, 3, &exc, &value, &tb))
273
        throw Py::Exception();
274

275
    PyErr_NormalizeException(&exc, &value, &tb);
276

277
    PyErr_Display(exc, value, tb);
278
/*
279
    if (eEXCEPTMODE_IGNORE != g_eExceptionMode)
280
    {
281
        assert(tb);
282

283
        if (tb && (tb != Py_None))
284
        {
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)
289
            {
290
                pTb = pTb->tb_next;
291
            }
292
            PyFrameObject* frame = (PyFrameObject*)PyObject_GetAttr((PyObject*)pTb, PyString_FromString("tb_frame"));
293
            EnterBreakState(frame, (PyObject*)pTb);
294
        }
295
    }*/
296

297
    return Py::None();
298
}
299

300
// -----------------------------------------------------
301

302
namespace Gui {
303
class PythonDebuggerPy : public Py::PythonExtension<PythonDebuggerPy>
304
{
305
public:
306
    explicit PythonDebuggerPy(PythonDebugger* d) : dbg(d), depth(0) { }
307
    ~PythonDebuggerPy() override = default;
308
    PythonDebugger* dbg;
309
    int depth;
310
};
311

312
class RunningState
313
{
314
public:
315
    explicit RunningState(bool& s) : state(s)
316
    { state = true; }
317
    ~RunningState()
318
    { state = false; }
319
private:
320
    bool& state;
321
};
322

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};
332
    QEventLoop loop;
333
    PyObject* pydbg{nullptr};
334
    std::vector<Breakpoint> bps;
335

336
    explicit PythonDebuggerP(PythonDebugger* that)
337
    {
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);
345
    }
346
    ~PythonDebuggerP()
347
    {
348
        Base::PyGILStateLocker lock;
349
        Py_DECREF(out_n);
350
        Py_DECREF(err_n);
351
        Py_DECREF(exc_n);
352
        Py_DECREF(pypde);
353
        Py_DECREF(pydbg);
354
    }
355
};
356
}
357

358
PythonDebugger::PythonDebugger()
359
  : d(new PythonDebuggerP(this))
360
{
361
}
362

363
PythonDebugger::~PythonDebugger()
364
{
365
    delete d;
366
}
367

368
Breakpoint PythonDebugger::getBreakpoint(const QString& fn) const
369
{
370
    for (const Breakpoint& it : d->bps) {
371
        if (fn == it.filename()) {
372
            return it;
373
        }
374
    }
375

376
    return {};
377
}
378

379
bool PythonDebugger::toggleBreakpoint(int line, const QString& fn)
380
{
381
    for (Breakpoint& it : d->bps) {
382
        if (fn == it.filename()) {
383
            if (it.checkLine(line)) {
384
                it.removeLine(line);
385
                return false;
386
            }
387
            else {
388
                it.addLine(line);
389
                return true;
390
            }
391
        }
392
    }
393

394
    Breakpoint bp;
395
    bp.setFilename(fn);
396
    bp.addLine(line);
397
    d->bps.push_back(bp);
398
    return true;
399
}
400

401
void PythonDebugger::runFile(const QString& fn)
402
{
403
    try {
404
        RunningState state(d->running);
405
        QByteArray pxFileName = fn.toUtf8();
406
#ifdef FC_OS_WIN32
407
        Base::FileInfo fi((const char*)pxFileName);
408
        FILE *fp = _wfopen(fi.toStdWString().c_str(),L"r");
409
#else
410
        FILE *fp = fopen((const char*)pxFileName,"r");
411
#endif
412
        if (!fp)
413
            return;
414

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);
422
            if (!pyObj) {
423
                fclose(fp);
424
                return;
425
            }
426
            if (PyDict_SetItemString(dict, "__file__", pyObj) < 0) {
427
                Py_DECREF(pyObj);
428
                fclose(fp);
429
                return;
430
            }
431
            Py_DECREF(pyObj);
432
        }
433

434
        PyObject *result = PyRun_File(fp, (const char*)pxFileName, Py_file_input, dict, dict);
435
        fclose(fp);
436
        Py_DECREF(dict);
437

438
        if (!result)
439
            PyErr_Print();
440
        else
441
            Py_DECREF(result);
442
    }
443
    catch (const Base::PyException&/* e*/) {
444
        //PySys_WriteStderr("Exception: %s\n", e.what());
445
    }
446
    catch (...) {
447
        Base::Console().Warning("Unknown exception thrown during macro debugging\n");
448
    }
449
}
450

451
bool PythonDebugger::isRunning() const
452
{
453
    return d->running;
454
}
455

456
bool PythonDebugger::start()
457
{
458
    if (d->init)
459
        return false;
460
    d->init = true;
461
    d->trystop = false;
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");
466

467
    PySys_SetObject("stdout", d->out_n);
468
    PySys_SetObject("stderr", d->err_n);
469
    PySys_SetObject("excepthook", d->exc_o);
470

471
    PyEval_SetTrace(tracer_callback, d->pydbg);
472
    return true;
473
}
474

475
bool PythonDebugger::stop()
476
{
477
    if (!d->init)
478
        return false;
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);
484
    d->init = false;
485
    return true;
486
}
487

488
void PythonDebugger::tryStop()
489
{
490
    d->trystop = true;
491
    Q_EMIT signalNextStep();
492
}
493

494
void PythonDebugger::stepOver()
495
{
496
    Q_EMIT signalNextStep();
497
}
498

499
void PythonDebugger::stepInto()
500
{
501
    Q_EMIT signalNextStep();
502
}
503

504
void PythonDebugger::stepRun()
505
{
506
    Q_EMIT signalNextStep();
507
}
508

509
void PythonDebugger::showDebugMarker(const QString& fn, int line)
510
{
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)
516
            break;
517
    }
518

519
    if (!edit) {
520
        auto editor = new PythonEditor();
521
        editor->setWindowIcon(Gui::BitmapFactory().iconFromTheme("applications-python"));
522
        edit = new PythonEditorView(editor, getMainWindow());
523
        edit->open(fn);
524
        edit->resize(400, 300);
525
        getMainWindow()->addWindow(edit);
526
    }
527

528
    getMainWindow()->setActiveWindow(edit);
529
    edit->showDebugMarker(line);
530
}
531

532
void PythonDebugger::hideDebugMarker(const QString& fn)
533
{
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();
540
            break;
541
        }
542
    }
543
}
544

545
#if PY_VERSION_HEX < 0x030900B1
546
static PyCodeObject* PyFrame_GetCode(PyFrameObject *frame)
547
{
548
    Py_INCREF(frame->f_code);
549
    return frame->f_code;
550
}
551
#endif
552

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
556

557
int PythonDebugger::tracer_callback(PyObject *obj, PyFrameObject *frame, int what, PyObject * /*arg*/)
558
{
559
    auto self = static_cast<PythonDebuggerPy*>(obj);
560
    PythonDebugger* dbg = self->dbg;
561
    if (dbg->d->trystop)
562
        PyErr_SetInterrupt();
563
    QCoreApplication::processEvents();
564
    PyCodeObject* code = PyFrame_GetCode(frame);
565
    QString file = QString::fromUtf8(PyUnicode_AsUTF8(code->co_filename));
566
    Py_DECREF(code);
567
    switch (what) {
568
    case PyTrace_CALL:
569
        self->depth++;
570
        return 0;
571
    case PyTrace_RETURN:
572
        if (self->depth > 0)
573
            self->depth--;
574
        return 0;
575
    case PyTrace_LINE:
576
        {
577
            PyCodeObject* f_code = PyFrame_GetCode(frame);
578
            int f_lasti = PyFrame_GetLineNumber(frame);
579
            int line = PyCode_Addr2Line(f_code, f_lasti);
580
            Py_DECREF(f_code);
581

582
            if (!dbg->d->trystop) {
583
                Breakpoint bp = dbg->getBreakpoint(file);
584
                if (bp.checkLine(line)) {
585
                    dbg->showDebugMarker(file, line);
586
                    QEventLoop loop;
587
                    QObject::connect(dbg, &PythonDebugger::signalNextStep, &loop, &QEventLoop::quit);
588
                    loop.exec();
589
                    dbg->hideDebugMarker(file);
590
                }
591
            }
592
            return 0;
593
        }
594
    case PyTrace_EXCEPTION:
595
        return 0;
596
    case PyTrace_C_CALL:
597
        return 0;
598
    case PyTrace_C_EXCEPTION:
599
        return 0;
600
    case PyTrace_C_RETURN:
601
        return 0;
602
    default:
603
        /* ignore PyTrace_EXCEPTION */
604
        break;
605
    }
606
    return 0;
607
}
608

609
#include "moc_PythonDebugger.cpp"
610

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.