mirror of https://github.com/ConsenSys/mythril
Merge pull request #1331 from ConsenSys/feature/mythril_plugins
Base implementation for mythril pluginspull/1337/head
commit
c57269a82d
@ -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…
Reference in new issue