Merge pull request #1331 from ConsenSys/feature/mythril_plugins

Base implementation for mythril plugins
pull/1337/head
JoranHonig 5 years ago committed by GitHub
commit c57269a82d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      mythril/plugin/__init__.py
  2. 32
      mythril/plugin/discovery.py
  3. 34
      mythril/plugin/interface.py
  4. 39
      mythril/plugin/loader.py
  5. 9
      tests/plugin/interface_test.py
  6. 22
      tests/plugin/loader_test.py

@ -0,0 +1,2 @@
from mythril.plugin.interface import MythrilPlugin, MythrilCLIPlugin
from mythril.plugin.loader import MythrilPluginLoader

@ -0,0 +1,32 @@
import pkg_resources
from mythril.support.support_utils import Singleton
from mythril.plugin.interface import MythrilPlugin
class PluginDiscovery(object, metaclass=Singleton):
"""PluginDiscovery class
This plugin implements the logic to discover and build plugins in installed python packages
"""
# Installed plugins structure. Retreives all modules that have an entry point for mythril.plugins
_installed_plugins = {
entry_point.name: entry_point.load()
for entry_point in pkg_resources.iter_entry_points("mythril.plugins")
}
def is_installed(self, plugin_name: str) -> bool:
"""Returns whether there is python package with a plugin with plugin_name"""
return plugin_name in self._installed_plugins.keys()
def build_plugin(self, plugin_name: str) -> MythrilPlugin:
"""Returns the plugin for the given plugin_name if it is installed"""
if not self.is_installed(plugin_name):
raise ValueError(f"Plugin with name: `{plugin_name}` is not installed")
plugin = self._installed_plugins.get(plugin_name)
if plugin is None or not isinstance(plugin, MythrilPlugin):
raise ValueError(f"No valid plugin was found for {plugin_name}")
return plugin

@ -0,0 +1,34 @@
from abc import ABC, abstractmethod
class MythrilPlugin:
"""MythrilPlugin interface
Mythril Plugins can be used to extend Mythril in different ways:
1. Extend Laser, in which case the LaserPlugin interface must also be extended
2. Extend Laser with a new search strategy in which case the SearchStrategy needs to be implemented
3. Add an analysis module, in this case the AnalysisModule interface needs to be implemented
4. Add new commands to the Mythril cli, using the MythrilCLIPlugin Interface
"""
author = "Default Author"
plugin_license = "All rights reserved."
plugin_type = "Mythril Plugin"
plugin_version = "0.0.1 "
plugin_description = "This is an example plugin description"
def __init__(self):
pass
def __repr__(self):
plugin_name = type(self).__name__
return f"{plugin_name} - {self.plugin_version} - {self.author}"
class MythrilCLIPlugin(MythrilPlugin):
"""MythrilCLIPlugin interface
This interface should be implemented by mythril plugins that aim to add commands to the mythril cli
"""
pass

@ -0,0 +1,39 @@
from mythril.analysis.modules.base import DetectionModule
from mythril.plugin.interface import MythrilCLIPlugin, MythrilPlugin
from mythril.support.support_utils import Singleton
import logging
log = logging.getLogger(__name__)
class UnsupportedPluginType(Exception):
pass
class MythrilPluginLoader(object, metaclass=Singleton):
"""MythrilPluginLoader singleton
This object permits loading MythrilPlugin's
"""
def __init__(self):
self.loaded_plugins = []
def load(self, plugin: MythrilPlugin):
"""Loads the passed plugin"""
if not isinstance(plugin, MythrilPlugin):
raise ValueError("Passed plugin is not of type MythrilPlugin")
log.info(f"Loading plugin: {str(plugin)}")
if isinstance(plugin, DetectionModule):
self._load_detection_module(plugin)
else:
raise UnsupportedPluginType("Passed plugin type is not yet supported")
self.loaded_plugins.append(plugin)
def _load_detection_module(self, plugin: DetectionModule):
pass

@ -0,0 +1,9 @@
from mythril.plugin.interface import MythrilCLIPlugin, MythrilPlugin
def test_construct_cli_plugin():
_ = MythrilCLIPlugin()
def test_construct_mythril_plugin():
_ = MythrilPlugin

@ -0,0 +1,22 @@
from mythril.plugin import MythrilPluginLoader, MythrilPlugin
from mythril.plugin.loader import UnsupportedPluginType
import pytest
def test_typecheck_load():
# Arrange
loader = MythrilPluginLoader()
# Act
with pytest.raises(ValueError):
loader.load(None)
def test_unsupported_plugin_type():
# Arrange
loader = MythrilPluginLoader()
# Act
with pytest.raises(UnsupportedPluginType):
loader.load(MythrilPlugin())
Loading…
Cancel
Save