diff --git a/mythril/analysis/report.py b/mythril/analysis/report.py index d40c466b..3a8c650d 100644 --- a/mythril/analysis/report.py +++ b/mythril/analysis/report.py @@ -3,9 +3,10 @@ import logging import json import operator from jinja2 import PackageLoader, Environment -from typing import Dict, List +from typing import Dict, List, Any, Optional import hashlib +from mythril.laser.execution_info import ExecutionInfo from mythril.solidity.soliditycontract import SolidityContract from mythril.analysis.swc_data import SWC_TO_TITLE from mythril.support.source_support import Source @@ -184,18 +185,24 @@ class Report: loader=PackageLoader("mythril.analysis"), trim_blocks=True ) - def __init__(self, contracts=None, exceptions=None): + def __init__( + self, + contracts=None, + exceptions=None, + execution_info: Optional[List[ExecutionInfo]] = None, + ): """ :param contracts: :param exceptions: """ - self.issues = {} + self.issues = {} # type: Dict[bytes, Issue] self.solc_version = "" - self.meta = {} + self.meta = {} # type: Dict[str, Any] self.source = Source() self.source.get_source_from_contracts_list(contracts) self.exceptions = exceptions or [] + self.execution_info = execution_info or [] def sorted_issues(self): """ @@ -246,6 +253,7 @@ class Report: :return: """ + # Setup issues _issues = [] for key, issue in self.issues.items(): @@ -272,7 +280,17 @@ class Report: "extra": extra, } ) - meta_data = self._get_exception_data() + # Setup meta + meta_data = self.meta + + # Add logs to meta + meta_data.update(self._get_exception_data()) + + # Add execution info to meta + meta_data["mythril_execution_info"] = {} + for execution_info in self.execution_info: + meta_data["mythril_execution_info"].update(execution_info.as_dict()) + result = [ { "issues": _issues, diff --git a/mythril/analysis/symbolic.py b/mythril/analysis/symbolic.py index e5efca76..35e3517c 100644 --- a/mythril/analysis/symbolic.py +++ b/mythril/analysis/symbolic.py @@ -2,6 +2,7 @@ purposes.""" from mythril.analysis.module import EntryPoint, ModuleLoader, get_detection_module_hooks +from mythril.laser.execution_info import ExecutionInfo from mythril.laser.ethereum import svm from mythril.laser.ethereum.state.account import Account from mythril.laser.ethereum.state.world_state import WorldState @@ -300,3 +301,7 @@ class SymExecWrapper: ) state_index += 1 + + @property + def execution_info(self) -> List[ExecutionInfo]: + return self.laser.execution_info diff --git a/mythril/laser/ethereum/svm.py b/mythril/laser/ethereum/svm.py index a042ef02..97f5712f 100644 --- a/mythril/laser/ethereum/svm.py +++ b/mythril/laser/ethereum/svm.py @@ -7,6 +7,7 @@ from typing import Callable, Dict, DefaultDict, List, Tuple, Optional from mythril.support.opcodes import opcodes as OPCODES from mythril.analysis.potential_issues import check_potential_issues +from mythril.laser.execution_info import ExecutionInfo from mythril.laser.ethereum.cfg import NodeFlags, Node, Edge, JumpType from mythril.laser.ethereum.evm_exceptions import StackUnderflowException from mythril.laser.ethereum.evm_exceptions import VmException @@ -72,8 +73,9 @@ class LaserEVM: :param requires_statespace: Variable indicating whether the statespace should be recorded :param iprof: Instruction Profiler """ - self.open_states = [] # type: List[WorldState] + self.execution_info = [] # type: List[ExecutionInfo] + self.open_states = [] # type: List[WorldState] self.total_states = 0 self.dynamic_loader = dynamic_loader diff --git a/mythril/laser/execution_info.py b/mythril/laser/execution_info.py new file mode 100644 index 00000000..641ff160 --- /dev/null +++ b/mythril/laser/execution_info.py @@ -0,0 +1,11 @@ +from abc import ABC, abstractmethod + + +class ExecutionInfo(ABC): + @abstractmethod + def as_dict(self): + """Returns a dictionary with the execution info contained in this object + + The returned dictionary only uses primitive types. + """ + pass diff --git a/mythril/mythril/mythril_analyzer.py b/mythril/mythril/mythril_analyzer.py index c542be67..b0795cb7 100644 --- a/mythril/mythril/mythril_analyzer.py +++ b/mythril/mythril/mythril_analyzer.py @@ -19,6 +19,7 @@ from mythril.ethereum.evmcontract import EVMContract from mythril.laser.smt import SolverStatistics from mythril.support.start_time import StartTime from mythril.exceptions import DetectorNotFoundError +from mythril.laser.execution_info import ExecutionInfo log = logging.getLogger(__name__) @@ -139,6 +140,7 @@ class MythrilAnalyzer: all_issues = [] # type: List[Issue] SolverStatistics().enabled = True exceptions = [] + execution_info = None # type: Optional[List[ExecutionInfo]] for contract in self.contracts: StartTime() # Reinitialize start time for new contracts try: @@ -158,6 +160,7 @@ class MythrilAnalyzer: custom_modules_directory=self.custom_modules_directory, ) issues = fire_lasers(sym, modules) + execution_info = sym.execution_info except DetectorNotFoundError as e: # Bubble up raise e @@ -179,8 +182,13 @@ class MythrilAnalyzer: source_data = Source() source_data.get_source_from_contracts_list(self.contracts) + # Finally, output the results - report = Report(contracts=self.contracts, exceptions=exceptions) + report = Report( + contracts=self.contracts, + exceptions=exceptions, + execution_info=execution_info, + ) for issue in all_issues: report.append_issue(issue) diff --git a/tests/mythril/mythril_analyzer_test.py b/tests/mythril/mythril_analyzer_test.py index 0e826cc8..2f100ac1 100644 --- a/tests/mythril/mythril_analyzer_test.py +++ b/tests/mythril/mythril_analyzer_test.py @@ -1,7 +1,7 @@ from pathlib import Path from mythril.mythril import MythrilDisassembler, MythrilAnalyzer from mythril.analysis.report import Issue -from mock import patch +from mock import patch, PropertyMock @patch("mythril.analysis.report.Issue.add_code_info", return_value=None) @@ -9,8 +9,9 @@ from mock import patch "mythril.mythril.mythril_analyzer.fire_lasers", return_value=[Issue("", "", "234", "101", "title", "0x02445")], ) -@patch("mythril.mythril.mythril_analyzer.SymExecWrapper", return_value=None) +@patch("mythril.mythril.mythril_analyzer.SymExecWrapper") def test_fire_lasers(mock_sym, mock_fire_lasers, mock_code_info): + type(mock_sym.return_value).execution_info = PropertyMock(return_value=[]) disassembler = MythrilDisassembler(eth=None) disassembler.load_from_solidity( [