Kill CutterPythonPlugin and use Bindings instead

Simplify CutterPlugin Interface

Pull Python Plugin Metadata from class vars

Also kill cutter_plugin.py

Fix Python Plugin Module Loading
This commit is contained in:
Florian Märkl 2019-02-09 17:21:31 +01:00
parent d544bbed77
commit 40274e4bf4
17 changed files with 168 additions and 207 deletions

View File

@ -123,6 +123,7 @@ set(BINDINGS_SOURCE
"${CMAKE_CURRENT_BINARY_DIR}/bindings/CutterBindings/cuttercore_wrapper.cpp"
"${CMAKE_CURRENT_BINARY_DIR}/bindings/CutterBindings/configuration_wrapper.cpp"
"${CMAKE_CURRENT_BINARY_DIR}/bindings/CutterBindings/cutterdockwidget_wrapper.cpp"
"${CMAKE_CURRENT_BINARY_DIR}/bindings/CutterBindings/cutterplugin_wrapper.cpp"
"${CMAKE_CURRENT_BINARY_DIR}/bindings/CutterBindings/mainwindow_wrapper.cpp")
set_property(SOURCE ${BINDINGS_SOURCE} PROPERTY SKIP_AUTOGEN ON)
@ -130,6 +131,7 @@ set_property(SOURCE ${BINDINGS_SOURCE} PROPERTY SKIP_AUTOGEN ON)
include_directories("${CMAKE_CURRENT_BINARY_DIR}/bindings/CutterBindings")
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/widgets")
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/common")
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/plugins")
add_custom_command(OUTPUT ${BINDINGS_SOURCE}
COMMAND shiboken2 --project-file="${CMAKE_CURRENT_BINARY_DIR}/bindings/bindings.txt"

View File

@ -229,7 +229,6 @@ SOURCES += \
dialogs/EditMethodDialog.cpp \
dialogs/LoadNewTypesDialog.cpp \
widgets/SdbWidget.cpp \
plugins/CutterPythonPlugin.cpp \
common/PythonManager.cpp \
plugins/PluginManager.cpp
@ -343,7 +342,6 @@ HEADERS += \
dialogs/EditMethodDialog.h \
dialogs/LoadNewTypesDialog.h \
widgets/SdbWidget.h \
plugins/CutterPythonPlugin.h \
common/PythonManager.h \
plugins/PluginManager.h

View File

@ -6,5 +6,6 @@
#include "../common/Configuration.h"
#include "../MainWindow.h"
#include "../widgets/CutterDockWidget.h"
#include "../plugins/CutterPlugin.h"
#endif //CUTTER_BINDINGS_H

View File

@ -8,4 +8,98 @@
<object-type name="Configuration" />
<object-type name="MainWindow" />
<object-type name="CutterDockWidget" />
<object-type name="CutterPlugin">
<modify-function signature="getName()const">
<inject-code class="shell" position="beginning">
SbkObject *wrapper = Shiboken::BindingManager::instance().retrieveWrapper(this);
PyObject *classObject = PyObject_GetAttrString(reinterpret_cast&lt;PyObject *&gt;(wrapper), "__class__");
if (!classObject) {
PyErr_Print();
return QString();
}
PyObject *pyResult = PyObject_GetAttrString(classObject, "name");
if (!pyResult) {
PyErr_Print();
return QString();
}
PythonToCppFunc pythonToCpp = Shiboken::Conversions::isPythonToCppConvertible(SbkPySide2_QtCoreTypeConverters[SBK_QSTRING_IDX], pyResult);
if (!pythonToCpp) {
Shiboken::warning(PyExc_RuntimeWarning, 2, "Invalid return value in function %s, expected %s, got %s.", "CutterPlugin.getName", "QString", Py_TYPE(pyResult)->tp_name);
return ::QString();
}
QString cppResult;
pythonToCpp(pyResult, &amp;cppResult);
return cppResult;
</inject-code>
</modify-function>
<modify-function signature="getAuthor()const">
<inject-code class="shell" position="beginning">
SbkObject *wrapper = Shiboken::BindingManager::instance().retrieveWrapper(this);
PyObject *classObject = PyObject_GetAttrString(reinterpret_cast&lt;PyObject *&gt;(wrapper), "__class__");
if (!classObject) {
PyErr_Print();
return QString();
}
PyObject *pyResult = PyObject_GetAttrString(classObject, "author");
if (!pyResult) {
PyErr_Print();
return QString();
}
PythonToCppFunc pythonToCpp = Shiboken::Conversions::isPythonToCppConvertible(SbkPySide2_QtCoreTypeConverters[SBK_QSTRING_IDX], pyResult);
if (!pythonToCpp) {
Shiboken::warning(PyExc_RuntimeWarning, 2, "Invalid return value in function %s, expected %s, got %s.", "CutterPlugin.getAuthor", "QString", Py_TYPE(pyResult)->tp_name);
return ::QString();
}
QString cppResult;
pythonToCpp(pyResult, &amp;cppResult);
return cppResult;
</inject-code>
</modify-function>
<modify-function signature="getDescription()const">
<inject-code class="shell" position="beginning">
SbkObject *wrapper = Shiboken::BindingManager::instance().retrieveWrapper(this);
PyObject *classObject = PyObject_GetAttrString(reinterpret_cast&lt;PyObject *&gt;(wrapper), "__class__");
if (!classObject) {
PyErr_Print();
return QString();
}
PyObject *pyResult = PyObject_GetAttrString(classObject, "description");
if (!pyResult) {
PyErr_Print();
return QString();
}
PythonToCppFunc pythonToCpp = Shiboken::Conversions::isPythonToCppConvertible(SbkPySide2_QtCoreTypeConverters[SBK_QSTRING_IDX], pyResult);
if (!pythonToCpp) {
Shiboken::warning(PyExc_RuntimeWarning, 2, "Invalid return value in function %s, expected %s, got %s.", "CutterPlugin.getDescription", "QString", Py_TYPE(pyResult)->tp_name);
return ::QString();
}
QString cppResult;
pythonToCpp(pyResult, &amp;cppResult);
return cppResult;
</inject-code>
</modify-function>
<modify-function signature="getVersion()const">
<inject-code class="shell" position="beginning">
SbkObject *wrapper = Shiboken::BindingManager::instance().retrieveWrapper(this);
PyObject *classObject = PyObject_GetAttrString(reinterpret_cast&lt;PyObject *&gt;(wrapper), "__class__");
if (!classObject) {
PyErr_Print();
return QString();
}
PyObject *pyResult = PyObject_GetAttrString(classObject, "version");
if (!pyResult) {
PyErr_Print();
return QString();
}
PythonToCppFunc pythonToCpp = Shiboken::Conversions::isPythonToCppConvertible(SbkPySide2_QtCoreTypeConverters[SBK_QSTRING_IDX], pyResult);
if (!pythonToCpp) {
Shiboken::warning(PyExc_RuntimeWarning, 2, "Invalid return value in function %s, expected %s, got %s.", "CutterPlugin.getVersion", "QString", Py_TYPE(pyResult)->tp_name);
return ::QString();
}
QString cppResult;
pythonToCpp(pyResult, &amp;cppResult);
return cppResult;
</inject-code>
</modify-function>
</object-type>
</typesystem>

View File

@ -7,7 +7,6 @@
#include <QDebug>
#include "QtResImporter.h"
#include "plugins/CutterPythonPlugin.h"
static PythonManager *uniqueInstance = nullptr;
@ -71,9 +70,6 @@ void PythonManager::initialize()
RegQtResImporter();
// Import other modules
cutterPluginModule = QtResImport("cutter_plugin");
saveThread();
}

View File

@ -28,8 +28,6 @@ public:
void restoreThread();
void saveThread();
PyObject *getCutterPluginModule() { return cutterPluginModule; }
/*!
* \brief RAII Helper class to call restoreThread() and saveThread() automatically
*
@ -50,8 +48,6 @@ private:
wchar_t *pythonHome = nullptr;
PyThreadState *pyThreadState = nullptr;
int pyThreadStateCounter = 0;
PyObject *cutterPluginModule;
};
#define Python() (PythonManager::getInstance())

View File

@ -54,10 +54,10 @@ R2PluginsDialog::R2PluginsDialog(QWidget *parent) :
for (CutterPlugin *plugin : Plugins()->getPlugins()) {
QTreeWidgetItem *item = new QTreeWidgetItem();
item->setText(0, plugin->name);
item->setText(1, plugin->description);
item->setText(2, plugin->version);
item->setText(3, plugin->author);
item->setText(0, plugin->getName());
item->setText(1, plugin->getDescription());
item->setText(2, plugin->getVersion());
item->setText(3, plugin->getAuthor());
ui->CutterTreeWidget->addTopLevelItem(item);
}
}

View File

@ -11,16 +11,13 @@ class CutterPlugin
{
public:
virtual ~CutterPlugin() {}
virtual void setupPlugin(CutterCore *core) = 0;
virtual void setupPlugin() = 0;
virtual void setupInterface(MainWindow *main) = 0;
QString name;
QString description;
QString version;
QString author;
protected:
CutterCore *core;
virtual QString getName() const = 0;
virtual QString getAuthor() const = 0;
virtual QString getDescription() const = 0;
virtual QString getVersion() const = 0;
};
#define CutterPlugin_iid "org.radare.cutter.plugins.CutterPlugin"

View File

@ -1,112 +0,0 @@
#include <Python.h>
#include "CutterPythonPlugin.h"
CutterPythonPlugin::CutterPythonPlugin(PyObject *pluginModule)
{
this->pluginModule = pluginModule;
if (!pluginModule) {
qWarning() << "Could not find plugin module.";
return;
}
pInstance = PyObject_GetAttrString(pluginModule, "plugin");
if (!pInstance) {
qWarning() << "Cannot find plugin instance.";
return;
}
auto getStringAttr = [this](const char *attrName) -> QString {
if (!PyObject_HasAttrString(pInstance, attrName)) {
return QString();
}
PyObject *attr = PyObject_GetAttrString(pInstance, attrName);
if (!attr) {
PyErr_Print();
return QString();
}
if (!PyUnicode_Check(attr)) {
return QString();
}
return QString::fromUtf8(PyUnicode_AsUTF8(attr));
};
name = getStringAttr("name");
description = getStringAttr("description");
version = getStringAttr("version");
author = getStringAttr("author");
}
CutterPythonPlugin::~CutterPythonPlugin()
{
Python()->restoreThread();
if (pInstance) {
Py_DECREF(pInstance);
}
if (pluginModule) {
Py_DECREF(pluginModule);
}
Python()->saveThread();
}
void CutterPythonPlugin::setupPlugin(CutterCore *core)
{
Q_UNUSED(core)
Python()->restoreThread();
// Check setupPlugin method exists
PyObject *setupPlugin = PyObject_GetAttrString(pInstance, "setupPlugin");
if (!setupPlugin) {
qWarning() << "Cannot find setupPlugin method.";
Python()->saveThread();
return;
}
Py_DECREF(setupPlugin);
// Call that method
PyObject *result = PyObject_CallMethod(pInstance, "setupPlugin", nullptr);
if (!result) {
qWarning() << "Error in setupPlugin().";
PyErr_Print();
Python()->saveThread();
}
Py_DECREF(result);
this->name = getAttributeFromPython("name");
this->description = getAttributeFromPython("description");
this->version = getAttributeFromPython("version");
this->author = getAttributeFromPython("author");
Python()->saveThread();
}
QString CutterPythonPlugin::getAttributeFromPython(const char *attribute)
{
QString result;
PyObject *pName = PyObject_GetAttrString(pInstance, attribute);
if (pName) {
result = QString(PyUnicode_AsUTF8(pName));
}
Py_DECREF(pName);
return result;
}
void CutterPythonPlugin::setupInterface(MainWindow *main)
{
Q_UNUSED(main)
PyObject *pWidget = nullptr;
Python()->restoreThread();
pWidget = PyObject_CallMethod(pInstance, "setupInterface", nullptr);
if (!pWidget) {
qWarning() << "Error in setupInterface().";
PyErr_Print();
Python()->saveThread();
return;
}
Python()->saveThread();
}

View File

@ -1,21 +0,0 @@
#ifndef CUTTERPYTHONPLUGIN_H
#define CUTTERPYTHONPLUGIN_H
#include "common/PythonManager.h"
#include "CutterPlugin.h"
class CutterPythonPlugin : public CutterPlugin
{
public:
CutterPythonPlugin(PyObject* pluginModule);
~CutterPythonPlugin();
void setupPlugin(CutterCore *core);
void setupInterface(MainWindow *main);
private:
PyObject *pluginModule = nullptr;
PyObject *pInstance = nullptr;
QString getAttributeFromPython(const char *attribute);
};
#endif // CUTTERPYTHONPLUGIN_H

View File

@ -1,12 +1,12 @@
#ifdef CUTTER_ENABLE_PYTHON
#include <Python.h>
#include <cutterbindings_python.h>
#endif
#include "PluginManager.h"
#include "PythonManager.h"
#include "CutterPlugin.h"
#include "CutterPythonPlugin.h"
#include <QDir>
#include <QCoreApplication>
@ -80,7 +80,7 @@ void PluginManager::loadNativePlugins(const QDir &directory)
if (!cutterPlugin) {
continue;
}
cutterPlugin->setupPlugin(Core());
cutterPlugin->setupPlugin();
plugins.append(cutterPlugin);
}
}
@ -91,18 +91,22 @@ void PluginManager::loadPythonPlugins(const QDir &directory)
{
Python()->addPythonPath(directory.absolutePath().toLocal8Bit().data());
for (const QString &fileName : directory.entryList(QDir::Files)) {
CutterPlugin *cutterPlugin = nullptr;
for (const QString &fileName : directory.entryList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot)) {
if (fileName == "__pycache__") {
continue;
}
QString moduleName;
if (fileName.endsWith(".py")) {
QStringList l = fileName.split(".py");
moduleName = l[0];
} else {
moduleName = fileName;
}
cutterPlugin = loadPythonPlugin(moduleName.toLocal8Bit().constData());
CutterPlugin *cutterPlugin = loadPythonPlugin(moduleName.toLocal8Bit().constData());
if (!cutterPlugin) {
continue;
}
cutterPlugin->setupPlugin(Core());
cutterPlugin->setupPlugin();
plugins.append(cutterPlugin);
}
@ -112,20 +116,35 @@ void PluginManager::loadPythonPlugins(const QDir &directory)
CutterPlugin *PluginManager::loadPythonPlugin(const char *moduleName)
{
PythonManager::ThreadHolder threadHolder;
PyObject *cutterPluginModule = Python()->getCutterPluginModule();
CutterPythonPlugin *plugin = nullptr;
if (!cutterPluginModule) {
return static_cast<CutterPlugin *>(plugin);
}
PyObject *pluginModule = PyImport_ImportModule(moduleName);
if (!pluginModule) {
qWarning() << "Couldn't load module for plugin:" << QString(moduleName);
PyErr_PrintEx(10);
} else {
plugin = new CutterPythonPlugin(pluginModule);
PyErr_Print();
return nullptr;
}
PyObject *createPluginFunc = PyObject_GetAttrString(pluginModule, "create_cutter_plugin");
if (!createPluginFunc || !PyCallable_Check(createPluginFunc)) {
qWarning() << "Plugin module does not contain create_plugin() function:" << QString(moduleName);
if (createPluginFunc) {
Py_DECREF(createPluginFunc);
}
Py_DECREF(pluginModule);
return nullptr;
}
PyObject *pluginObject = PyObject_CallFunction(createPluginFunc, nullptr);
Py_DECREF(createPluginFunc);
Py_DECREF(pluginModule);
PythonToCppFunc pythonToCpp = Shiboken::Conversions::isPythonToCppPointerConvertible(reinterpret_cast<SbkObjectType *>(SbkCutterBindingsTypes[SBK_CUTTERPLUGIN_IDX]), pluginObject);
if (!pythonToCpp) {
qWarning() << "Plugin's create_plugin() function did not return an instance of CutterPlugin:" << QString(moduleName);
return nullptr;
}
CutterPlugin *plugin;
pythonToCpp(pluginObject, &plugin);
return plugin;
}
#endif

View File

@ -8,13 +8,8 @@
#include "common/Configuration.h"
#include "MainWindow.h"
void CutterSamplePlugin::setupPlugin(CutterCore *core)
void CutterSamplePlugin::setupPlugin()
{
this->core = core;
this->name = "SamplePlugin";
this->description = "Just a sample plugin.";
this->version = "1.0";
this->author = "xarkes";
}
void CutterSamplePlugin::setupInterface(MainWindow *main)

View File

@ -12,8 +12,13 @@ class CutterSamplePlugin : public QObject, CutterPlugin
Q_INTERFACES(CutterPlugin)
public:
void setupPlugin(CutterCore *core) override;
void setupPlugin() override;
void setupInterface(MainWindow *main) override;
const QString &getName() const { return "SamplePlugin"; }
const QString &getAuthor() const { return "xarkes"; }
const QString &getDescription() const { return "Just a sample plugin."; }
const QString &getVersion() const { return "1.0"; }
};
class CutterSamplePluginWidget : public CutterDockWidget

View File

@ -1,6 +1,5 @@
import cutter
from cutter_plugin import CutterPlugin
import CutterBindings
from PySide2.QtCore import QObject, SIGNAL, Qt
@ -32,31 +31,36 @@ class FortuneWidget(CutterBindings.CutterDockWidget):
layout.addWidget(button)
layout.setAlignment(button, Qt.AlignHCenter)
QObject.connect(CutterBindings.CutterCore.getInstance(), SIGNAL("seekChanged(RVA)"), self.generate_fortune)
QObject.connect(cutter.core(), SIGNAL("seekChanged(RVA)"), self.generate_fortune)
QObject.connect(button, SIGNAL("clicked()"), self.generate_fortune)
self.show()
def generate_fortune(self):
fortune = cutter.cmd("fo").replace("\n", "")
res = CutterBindings.CutterCore.getInstance().cmdRaw(f"?E {fortune}")
res = cutter.core().cmdRaw(f"?E {fortune}")
self.text.setText(res)
class CutterSamplePlugin(CutterPlugin):
class CutterSamplePlugin(CutterBindings.CutterPlugin):
name = "SamplePlugin"
description = "A sample plugin written in python."
version = "1.0"
author = "xarkes and thestr4ng3r :-P"
def setupInterface(self):
super().setupInterface()
def __init__(self):
super(CutterSamplePlugin, self).__init__()
self.action = QAction("Sample Python Plugin", self.main)
def setupPlugin(self):
pass
def setupInterface(self, main):
self.action = QAction("Sample Python Plugin", main)
self.action.setCheckable(True)
self.widget = FortuneWidget(self.main, self.action) # we MUST keep a reference to this!
self.main.addPluginDockWidget(self.widget, self.action)
self.widget = FortuneWidget(main, self.action) # we MUST keep a reference to this!
main.addPluginDockWidget(self.widget, self.action)
# Instantiate our plugin
plugin = CutterSamplePlugin()
# This function will be called by Cutter and should return an instance of the plugin.
def create_cutter_plugin():
return CutterSamplePlugin()

View File

@ -1,6 +1,12 @@
import json
from _cutter import *
from CutterBindings import *
def cmdj(command):
'''Execute a JSON command and return the result as a dictionnary'''
"""Execute a JSON command and return the result as a dictionary"""
return json.loads(cmd(command))
def core():
return CutterCore.getInstance()

View File

@ -1,18 +0,0 @@
from PySide2 import QtCore, QtWidgets
import shiboken2
class CutterPlugin(object):
name = ''
description = ''
version = ''
author = ''
def setupPlugin(self):
self.app = QtCore.QCoreApplication.instance()
def setupInterface(self):
for widget in QtWidgets.QApplication.topLevelWidgets():
if widget.objectName() == "MainWindow":
self.main = widget
break

View File

@ -86,6 +86,5 @@
<file>img/icons/fork.svg</file>
<file>python/cutter.py</file>
<file>python/reg_qtres_importer.py</file>
<file>python/cutter_plugin.py</file>
</qresource>
</RCC>