mirror of https://github.com/crytic/slither
- Replaces sys.exit with SlitherError - Replaces prints with self.info - Moves slither.evm to slither.analyses.evm - Replaces class SourceToEVM static methods with functions - Makes KEY_EVM_INS into a constant - Adds wiki entry for evm printerpull/281/head
parent
e271df51ce
commit
3ffe4d3615
@ -0,0 +1,9 @@ |
||||
pragma solidity >=0.4.24 <0.5.4; |
||||
|
||||
contract Test { |
||||
|
||||
function foo() public returns (address) { |
||||
address from = msg.sender; |
||||
return(from); |
||||
} |
||||
} |
@ -0,0 +1,2 @@ |
||||
from .convert import generate_source_to_evm_ins_mapping |
||||
from .evm_cfg_builder import load_evm_cfg_builder |
@ -0,0 +1,189 @@ |
||||
import logging |
||||
import sys |
||||
from slither.core.declarations import (Contract, Function) |
||||
from slither.core.cfg.node import Node |
||||
from slither.utils.function import get_function_id |
||||
from .evm_cfg_builder import load_evm_cfg_builder |
||||
|
||||
logger = logging.getLogger('ConvertToEVM') |
||||
|
||||
KEY_EVM_INS = "EVM_INSTRUCTIONS" |
||||
|
||||
def get_evm_instructions (obj): |
||||
|
||||
assert isinstance(obj, (Function, Contract, Node)) |
||||
|
||||
if KEY_EVM_INS not in obj.context: |
||||
|
||||
CFG = load_evm_cfg_builder() |
||||
|
||||
slither = obj.slither |
||||
|
||||
contract_info = {} |
||||
function_info = {} |
||||
node_info = {} |
||||
|
||||
if isinstance(obj, Node): |
||||
contract_info['contract'] = obj.function.contract |
||||
elif isinstance(obj, Function): |
||||
contract_info['contract'] = obj.contract |
||||
else: |
||||
contract_info['contract'] = obj |
||||
|
||||
# Get contract runtime bytecode, srcmap and cfg |
||||
contract_info['bytecode_runtime'] = slither.crytic_compile.bytecode_runtime( |
||||
contract_info['contract'].name) |
||||
contract_info['srcmap_runtime'] = slither.crytic_compile.srcmap_runtime( |
||||
contract_info['contract'].name) |
||||
contract_info['cfg'] = CFG(contract_info['bytecode_runtime']) |
||||
|
||||
# Get contract init bytecode, srcmap and cfg |
||||
contract_info['bytecode_init']= slither.crytic_compile.bytecode_init(contract_info['contract'].name) |
||||
contract_info['srcmap_init'] = slither.crytic_compile.srcmap_init(contract_info['contract'].name) |
||||
contract_info['cfg_init'] = CFG(contract_info['bytecode_init']) |
||||
|
||||
# Get evm instructions |
||||
if isinstance(obj, Contract): |
||||
# Get evm instructions for contract |
||||
obj.context[KEY_EVM_INS] = _get_evm_instructions_contract(contract_info) |
||||
|
||||
elif isinstance(obj, Function): |
||||
# Get evm instructions for function |
||||
function_info['function'] = obj |
||||
function_info['contract_info'] = contract_info |
||||
obj.context[KEY_EVM_INS] = _get_evm_instructions_function(function_info) |
||||
|
||||
else: |
||||
# Get evm instructions for node |
||||
node_info['node'] = obj |
||||
|
||||
# CFG and srcmap depend on function being constructor or not |
||||
if node_info['node'].function.is_constructor: |
||||
cfg = contract_info['cfg_init'] |
||||
srcmap = contract_info['srcmap_init'] |
||||
else: |
||||
cfg = contract_info['cfg'] |
||||
srcmap = contract_info['srcmap_runtime'] |
||||
|
||||
node_info['cfg'] = cfg |
||||
node_info['srcmap'] = srcmap |
||||
node_info['contract'] = contract_info['contract'] |
||||
node_info['slither'] = slither |
||||
|
||||
obj.context[KEY_EVM_INS] = _get_evm_instructions_node(node_info) |
||||
|
||||
return obj.context.get(KEY_EVM_INS, []) |
||||
|
||||
|
||||
def _get_evm_instructions_contract (contract_info): |
||||
|
||||
# Combine the instructions of constructor and the rest of the contract |
||||
return contract_info['cfg_init'].instructions + contract_info['cfg'].instructions |
||||
|
||||
|
||||
def _get_evm_instructions_function (function_info): |
||||
|
||||
function = function_info['function'] |
||||
|
||||
# CFG depends on function being constructor or not |
||||
if function.is_constructor: |
||||
cfg = function_info['contract_info']['cfg_init'] |
||||
# _dispatcher is the only function recognised by evm-cfg-builder in bytecode_init. |
||||
# _dispatcher serves the role of the constructor in init code, |
||||
# given that there are no other functions. |
||||
# Todo: Could rename it appropriately in evm-cfg-builder |
||||
# by detecting that init bytecode is being parsed. |
||||
name = "_dispatcher" |
||||
hash = "" |
||||
else: |
||||
cfg = function_info['contract_info']['cfg'] |
||||
name = function.name |
||||
# Get first four bytes of function singature's keccak-256 hash used as function selector |
||||
hash = str(hex(get_function_id(function.full_name))) |
||||
|
||||
function_evm = _get_function_evm(cfg, name, hash) |
||||
if function_evm == "None": |
||||
logger.error("Function " + function.name + " not found") |
||||
sys.exit(-1) |
||||
|
||||
function_ins = [] |
||||
for basic_block in sorted(function_evm.basic_blocks, key=lambda x:x.start.pc): |
||||
for ins in basic_block.instructions: |
||||
function_ins.append(ins) |
||||
|
||||
return function_ins |
||||
|
||||
|
||||
def _get_evm_instructions_node (node_info): |
||||
# Get evm instructions for node's contract |
||||
contract_pcs = generate_source_to_evm_ins_mapping(node_info['cfg'].instructions, |
||||
node_info['srcmap'], |
||||
node_info['slither'], |
||||
node_info['contract'].source_mapping['filename_absolute']) |
||||
contract_file = node_info['slither'].source_code[node_info['contract'].source_mapping['filename_absolute']].encode('utf-8') |
||||
|
||||
# Get evm instructions corresponding to node's source line number |
||||
node_source_line = contract_file[0:node_info['node'].source_mapping['start']].count("\n".encode("utf-8"))\ |
||||
+ 1 |
||||
node_pcs = contract_pcs.get(node_source_line, []) |
||||
node_ins = [] |
||||
for pc in node_pcs: |
||||
node_ins.append(node_info['cfg'].get_instruction_at(pc)) |
||||
|
||||
return node_ins |
||||
|
||||
|
||||
def _get_function_evm(cfg, function_name, function_hash): |
||||
for function_evm in cfg.functions: |
||||
# Match function hash |
||||
if function_evm.name[:2] == "0x" and function_evm.name == function_hash: |
||||
return function_evm |
||||
# Match function name |
||||
elif function_evm.name[:2] != "0x" and function_evm.name.split('(')[0] == function_name: |
||||
return function_evm |
||||
return "None" |
||||
|
||||
|
||||
def generate_source_to_evm_ins_mapping(evm_instructions, srcmap_runtime, slither, filename): |
||||
""" |
||||
Generate Solidity source to EVM instruction mapping using evm_cfg_builder:cfg.instructions |
||||
and solc:srcmap_runtime |
||||
|
||||
Returns: Solidity source to EVM instruction mapping |
||||
""" |
||||
|
||||
source_to_evm_mapping = {} |
||||
file_source = slither.source_code[filename].encode('utf-8') |
||||
prev_mapping = [] |
||||
|
||||
for idx, mapping in enumerate(srcmap_runtime): |
||||
# Parse srcmap_runtime according to its format |
||||
# See https://solidity.readthedocs.io/en/v0.5.9/miscellaneous.html#source-mappings |
||||
# In order to compress these source mappings especially for bytecode, the following rules are used: |
||||
# If a field is empty, the value of the preceding element is used. |
||||
# If a : is missing, all following fields are considered empty. |
||||
|
||||
mapping_item = mapping.split(':') |
||||
mapping_item += prev_mapping[len(mapping_item):] |
||||
|
||||
for i in range(len(mapping_item)): |
||||
if mapping_item[i] == '': |
||||
mapping_item[i] = int(prev_mapping[i]) |
||||
|
||||
offset, length, file_id, _ = mapping_item |
||||
prev_mapping = mapping_item |
||||
|
||||
if file_id == '-1': |
||||
# Internal compiler-generated code snippets to be ignored |
||||
# See https://github.com/ethereum/solidity/issues/6119#issuecomment-467797635 |
||||
continue |
||||
|
||||
offset = int(offset) |
||||
line_number = file_source[0:offset].count("\n".encode("utf-8")) + 1 |
||||
|
||||
# Append evm instructions to the corresponding source line number |
||||
# Note: Some evm instructions in mapping are not necessarily in program execution order |
||||
# Note: The order depends on how solc creates the srcmap_runtime |
||||
source_to_evm_mapping.setdefault(line_number, []).append(evm_instructions[idx].pc) |
||||
|
||||
return(source_to_evm_mapping) |
@ -1,2 +0,0 @@ |
||||
from .convert import SourceToEVM |
||||
from .evm_cfg_builder import load_evm_cfg_builder |
@ -1,195 +0,0 @@ |
||||
import logging |
||||
import sys |
||||
from slither.core.declarations import (Contract, Function) |
||||
from slither.core.cfg.node import Node |
||||
from slither.utils.function import get_function_id |
||||
from .evm_cfg_builder import load_evm_cfg_builder |
||||
|
||||
logger = logging.getLogger('ConvertToEVM') |
||||
|
||||
class SourceToEVM: |
||||
|
||||
@staticmethod |
||||
def get_evm_instructions (obj): |
||||
|
||||
assert isinstance(obj, (Function, Contract, Node)) |
||||
|
||||
if "KEY_EVM_INS" not in obj.context: |
||||
|
||||
CFG = load_evm_cfg_builder() |
||||
|
||||
slither = obj.slither |
||||
|
||||
contract_info = {} |
||||
function_info = {} |
||||
node_info = {} |
||||
|
||||
if isinstance(obj, Node): |
||||
contract_info['contract'] = obj.function.contract |
||||
elif isinstance(obj, Function): |
||||
contract_info['contract'] = obj.contract |
||||
else: |
||||
contract_info['contract'] = obj |
||||
|
||||
# Get contract runtime bytecode, srcmap and cfg |
||||
contract_info['bytecode_runtime'] = slither.crytic_compile.bytecode_runtime( |
||||
contract_info['contract'].name) |
||||
contract_info['srcmap_runtime'] = slither.crytic_compile.srcmap_runtime( |
||||
contract_info['contract'].name) |
||||
contract_info['cfg'] = CFG(contract_info['bytecode_runtime']) |
||||
|
||||
# Get contract init bytecode, srcmap and cfg |
||||
contract_info['bytecode_init']= slither.crytic_compile.bytecode_init(contract_info['contract'].name) |
||||
contract_info['srcmap_init'] = slither.crytic_compile.srcmap_init(contract_info['contract'].name) |
||||
contract_info['cfg_init'] = CFG(contract_info['bytecode_init']) |
||||
|
||||
# Get evm instructions |
||||
if isinstance(obj, Contract): |
||||
# Get evm instructions for contract |
||||
obj.context["KEY_EVM_INS"] = SourceToEVM._get_evm_instructions_contract(contract_info) |
||||
|
||||
elif isinstance(obj, Function): |
||||
# Get evm instructions for function |
||||
function_info['function'] = obj |
||||
function_info['contract_info'] = contract_info |
||||
obj.context["KEY_EVM_INS"] = SourceToEVM._get_evm_instructions_function(function_info) |
||||
|
||||
else: |
||||
# Get evm instructions for node |
||||
node_info['node'] = obj |
||||
|
||||
# CFG and srcmap depend on function being constructor or not |
||||
if node_info['node'].function.is_constructor: |
||||
cfg = contract_info['cfg_init'] |
||||
srcmap = contract_info['srcmap_init'] |
||||
else: |
||||
cfg = contract_info['cfg'] |
||||
srcmap = contract_info['srcmap_runtime'] |
||||
|
||||
node_info['cfg'] = cfg |
||||
node_info['srcmap'] = srcmap |
||||
node_info['contract'] = contract_info['contract'] |
||||
node_info['slither'] = slither |
||||
|
||||
obj.context["KEY_EVM_INS"] = SourceToEVM._get_evm_instructions_node(node_info) |
||||
|
||||
return obj.context.get("KEY_EVM_INS", []) |
||||
|
||||
|
||||
@staticmethod |
||||
def _get_evm_instructions_contract (contract_info): |
||||
|
||||
# Combine the instructions of constructor and the rest of the contract |
||||
return contract_info['cfg_init'].instructions + contract_info['cfg'].instructions |
||||
|
||||
|
||||
@staticmethod |
||||
def _get_evm_instructions_function (function_info): |
||||
|
||||
function = function_info['function'] |
||||
|
||||
# CFG depends on function being constructor or not |
||||
if function.is_constructor: |
||||
cfg = function_info['contract_info']['cfg_init'] |
||||
# _dispatcher is the only function recognised by evm-cfg-builder in bytecode_init. |
||||
# _dispatcher serves the role of the constructor in init code, |
||||
# given that there are no other functions. |
||||
# Todo: Could rename it appropriately in evm-cfg-builder |
||||
# by detecting that init bytecode is being parsed. |
||||
name = "_dispatcher" |
||||
hash = "" |
||||
else: |
||||
cfg = function_info['contract_info']['cfg'] |
||||
name = function.name |
||||
# Get first four bytes of function singature's keccak-256 hash used as function selector |
||||
hash = str(hex(get_function_id(function.full_name))) |
||||
|
||||
function_evm = SourceToEVM._get_function_evm(cfg, name, hash) |
||||
if function_evm == "None": |
||||
logger.error("Function " + function.name + " not found") |
||||
sys.exit(-1) |
||||
|
||||
function_ins = [] |
||||
for basic_block in sorted(function_evm.basic_blocks, key=lambda x:x.start.pc): |
||||
for ins in basic_block.instructions: |
||||
function_ins.append(ins) |
||||
|
||||
return function_ins |
||||
|
||||
|
||||
@staticmethod |
||||
def _get_evm_instructions_node (node_info): |
||||
# Get evm instructions for node's contract |
||||
contract_pcs = SourceToEVM.generate_source_to_evm_ins_mapping(node_info['cfg'].instructions, |
||||
node_info['srcmap'], |
||||
node_info['slither'], |
||||
node_info['contract'].source_mapping['filename_absolute']) |
||||
contract_file = node_info['slither'].source_code[node_info['contract'].source_mapping['filename_absolute']].encode('utf-8') |
||||
|
||||
# Get evm instructions corresponding to node's source line number |
||||
node_source_line = contract_file[0:node_info['node'].source_mapping['start']].count("\n".encode("utf-8"))\ |
||||
+ 1 |
||||
node_pcs = contract_pcs.get(node_source_line, []) |
||||
node_ins = [] |
||||
for pc in node_pcs: |
||||
node_ins.append(node_info['cfg'].get_instruction_at(pc)) |
||||
|
||||
return node_ins |
||||
|
||||
|
||||
@staticmethod |
||||
def _get_function_evm(cfg, function_name, function_hash): |
||||
for function_evm in cfg.functions: |
||||
# Match function hash |
||||
if function_evm.name[:2] == "0x" and function_evm.name == function_hash: |
||||
return function_evm |
||||
# Match function name |
||||
elif function_evm.name[:2] != "0x" and function_evm.name.split('(')[0] == function_name: |
||||
return function_evm |
||||
return "None" |
||||
|
||||
|
||||
@staticmethod |
||||
def generate_source_to_evm_ins_mapping(evm_instructions, srcmap_runtime, slither, filename): |
||||
""" |
||||
Generate Solidity source to EVM instruction mapping using evm_cfg_builder:cfg.instructions |
||||
and solc:srcmap_runtime |
||||
|
||||
Returns: Solidity source to EVM instruction mapping |
||||
""" |
||||
|
||||
source_to_evm_mapping = {} |
||||
file_source = slither.source_code[filename].encode('utf-8') |
||||
prev_mapping = [] |
||||
|
||||
for idx, mapping in enumerate(srcmap_runtime): |
||||
# Parse srcmap_runtime according to its format |
||||
# See https://solidity.readthedocs.io/en/v0.5.9/miscellaneous.html#source-mappings |
||||
# In order to compress these source mappings especially for bytecode, the following rules are used: |
||||
# If a field is empty, the value of the preceding element is used. |
||||
# If a : is missing, all following fields are considered empty. |
||||
|
||||
mapping_item = mapping.split(':') |
||||
mapping_item += prev_mapping[len(mapping_item):] |
||||
|
||||
for i in range(len(mapping_item)): |
||||
if mapping_item[i] == '': |
||||
mapping_item[i] = int(prev_mapping[i]) |
||||
|
||||
offset, length, file_id, _ = mapping_item |
||||
prev_mapping = mapping_item |
||||
|
||||
if file_id == '-1': |
||||
# Internal compiler-generated code snippets to be ignored |
||||
# See https://github.com/ethereum/solidity/issues/6119#issuecomment-467797635 |
||||
continue |
||||
|
||||
offset = int(offset) |
||||
line_number = file_source[0:offset].count("\n".encode("utf-8")) + 1 |
||||
|
||||
# Append evm instructions to the corresponding source line number |
||||
# Note: Some evm instructions in mapping are not necessarily in program execution order |
||||
# Note: The order depends on how solc creates the srcmap_runtime |
||||
source_to_evm_mapping.setdefault(line_number, []).append(evm_instructions[idx].pc) |
||||
|
||||
return(source_to_evm_mapping) |
Loading…
Reference in new issue