Static Analyzer for Solidity
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
slither/slither/solc_parsing/slitherSolc.py

386 lines
15 KiB

import os
import json
import re
import logging
logging.basicConfig()
logger = logging.getLogger("SlitherSolcParsing")
logger.setLevel(logging.INFO)
from slither.solc_parsing.declarations.contract import ContractSolc04
from slither.core.slither_core import Slither
from slither.core.declarations.pragma_directive import Pragma
from slither.core.declarations.import_directive import Import
from slither.analyses.data_dependency.data_dependency import compute_dependency
from slither.utils.colors import red
from .exceptions import ParsingNameReuse, ParsingContractNotFound
class SlitherSolc(Slither):
def __init__(self, filename):
super(SlitherSolc, self).__init__()
self._filename = filename
self._contractsNotParsed = []
self._contracts_by_id = {}
self._analyzed = False
self._is_compact_ast = False
###################################################################################
###################################################################################
# region AST
###################################################################################
###################################################################################
def get_key(self):
if self._is_compact_ast:
return 'nodeType'
return 'name'
def get_children(self):
if self._is_compact_ast:
return 'nodes'
return 'children'
@property
def is_compact_ast(self):
return self._is_compact_ast
# endregion
###################################################################################
###################################################################################
# region Parsing
###################################################################################
###################################################################################
def _parse_contracts_from_json(self, json_data):
try:
data_loaded = json.loads(json_data)
# Truffle AST
if 'ast' in data_loaded:
self._parse_contracts_from_loaded_json(data_loaded['ast'], data_loaded['sourcePath'])
return True
# solc AST, where the non-json text was removed
else:
if 'attributes' in data_loaded:
filename = data_loaded['attributes']['absolutePath']
else:
filename = data_loaded['absolutePath']
self._parse_contracts_from_loaded_json(data_loaded, filename)
return True
except ValueError:
first = json_data.find('{')
if first != -1:
last = json_data.rfind('}') + 1
filename = json_data[0:first]
json_data = json_data[first:last]
data_loaded = json.loads(json_data)
self._parse_contracts_from_loaded_json(data_loaded, filename)
return True
return False
def _parse_contracts_from_loaded_json(self, data_loaded, filename):
if 'nodeType' in data_loaded:
self._is_compact_ast = True
if 'sourcePaths' in data_loaded:
for sourcePath in data_loaded['sourcePaths']:
if os.path.isfile(sourcePath):
self._add_source_code(sourcePath)
if data_loaded[self.get_key()] == 'root':
self._solc_version = '0.3'
logger.error('solc <0.4 is not supported')
return
elif data_loaded[self.get_key()] == 'SourceUnit':
self._solc_version = '0.4'
self._parse_source_unit(data_loaded, filename)
else:
logger.error('solc version is not supported')
return
for contract_data in data_loaded[self.get_children()]:
# if self.solc_version == '0.3':
# assert contract_data[self.get_key()] == 'Contract'
# contract = ContractSolc03(self, contract_data)
if self.solc_version == '0.4':
assert contract_data[self.get_key()] in ['ContractDefinition', 'PragmaDirective', 'ImportDirective']
if contract_data[self.get_key()] == 'ContractDefinition':
contract = ContractSolc04(self, contract_data)
if 'src' in contract_data:
contract.set_offset(contract_data['src'], self)
self._contractsNotParsed.append(contract)
elif contract_data[self.get_key()] == 'PragmaDirective':
if self._is_compact_ast:
pragma = Pragma(contract_data['literals'])
else:
pragma = Pragma(contract_data['attributes']["literals"])
pragma.set_offset(contract_data['src'], self)
self._pragma_directives.append(pragma)
elif contract_data[self.get_key()] == 'ImportDirective':
if self.is_compact_ast:
import_directive = Import(contract_data["absolutePath"])
else:
import_directive = Import(contract_data['attributes']["absolutePath"])
import_directive.set_offset(contract_data['src'], self)
self._import_directives.append(import_directive)
def _parse_source_unit(self, data, filename):
if data[self.get_key()] != 'SourceUnit':
return -1 # handle solc prior 0.3.6
# match any char for filename
# filename can contain space, /, -, ..
name = re.findall('=+ (.+) =+', filename)
if name:
assert len(name) == 1
name = name[0]
else:
name = filename
sourceUnit = -1 # handle old solc, or error
if 'src' in data:
sourceUnit = re.findall('[0-9]*:[0-9]*:([0-9]*)', data['src'])
if len(sourceUnit) == 1:
sourceUnit = int(sourceUnit[0])
if sourceUnit == -1:
# if source unit is not found
# We can still deduce it, by assigning to the last source_code added
# This works only for crytic compile.
# which used --combined-json ast, rather than --ast-json
# As a result -1 is not used as index
if self.crytic_compile is not None:
sourceUnit = len(self.source_code)
self._source_units[sourceUnit] = name
if os.path.isfile(name) and not name in self.source_code:
self._add_source_code(name)
else:
lib_name = os.path.join('node_modules', name)
if os.path.isfile(lib_name) and not name in self.source_code:
self._add_source_code(lib_name)
# endregion
###################################################################################
###################################################################################
# region Analyze
###################################################################################
###################################################################################
@property
def analyzed(self):
return self._analyzed
def _analyze_contracts(self):
if not self._contractsNotParsed:
logger.info(f'No contract were found in {self.filename}, check the correct compilation')
if self._analyzed:
raise Exception('Contract analysis can be run only once!')
# First we save all the contracts in a dict
# the key is the contractid
for contract in self._contractsNotParsed:
if contract.name in self._contracts:
if contract.id != self._contracts[contract.name].id:
info = 'Slither does not handle projects with contract names re-use'
info += '\n{} is defined in:'.format(contract.name)
info += '\n- {}\n- {}'.format(contract.source_mapping_str,
self._contracts[contract.name].source_mapping_str)
raise ParsingNameReuse(info)
else:
self._contracts_by_id[contract.id] = contract
self._contracts[contract.name] = contract
# Update of the inheritance
for contract in self._contractsNotParsed:
# remove the first elem in linearizedBaseContracts as it is the contract itself
ancestors = []
fathers = []
father_constructors = []
try:
# Resolve linearized base contracts.
for i in contract.linearizedBaseContracts[1:]:
if i in contract.remapping:
ancestors.append(self.get_contract_from_name(contract.remapping[i]))
else:
ancestors.append(self._contracts_by_id[i])
# Resolve immediate base contracts
for i in contract.baseContracts:
if i in contract.remapping:
fathers.append(self.get_contract_from_name(contract.remapping[i]))
else:
fathers.append(self._contracts_by_id[i])
# Resolve immediate base constructor calls
for i in contract.baseConstructorContractsCalled:
if i in contract.remapping:
father_constructors.append(self.get_contract_from_name(contract.remapping[i]))
else:
father_constructors.append(self._contracts_by_id[i])
except KeyError:
txt = 'A contract was not found, it is likely that your codebase contains muliple contracts with the same name'
txt += 'Truffle does not handle this case during compilation'
txt += 'Please read https://github.com/trailofbits/slither/wiki#keyerror-or-nonetype-error'
txt += 'And update your code to remove the duplicate'
raise ParsingContractNotFound(txt)
contract.setInheritance(ancestors, fathers, father_constructors)
contracts_to_be_analyzed = self.contracts
# Any contract can refer another contract enum without need for inheritance
self._analyze_all_enums(contracts_to_be_analyzed)
[c.set_is_analyzed(False) for c in self.contracts]
libraries = [c for c in contracts_to_be_analyzed if c.contract_kind == 'library']
contracts_to_be_analyzed = [c for c in contracts_to_be_analyzed if c.contract_kind != 'library']
# We first parse the struct/variables/functions/contract
self._analyze_first_part(contracts_to_be_analyzed, libraries)
[c.set_is_analyzed(False) for c in self.contracts]
# We analyze the struct and parse and analyze the events
# A contract can refer in the variables a struct or a event from any contract
# (without inheritance link)
self._analyze_second_part(contracts_to_be_analyzed, libraries)
[c.set_is_analyzed(False) for c in self.contracts]
# Then we analyse state variables, functions and modifiers
self._analyze_third_part(contracts_to_be_analyzed, libraries)
self._analyzed = True
self._convert_to_slithir()
compute_dependency(self)
def _analyze_all_enums(self, contracts_to_be_analyzed):
while contracts_to_be_analyzed:
contract = contracts_to_be_analyzed[0]
contracts_to_be_analyzed = contracts_to_be_analyzed[1:]
all_father_analyzed = all(father.is_analyzed for father in contract.inheritance)
if not contract.inheritance or all_father_analyzed:
self._analyze_enums(contract)
else:
contracts_to_be_analyzed += [contract]
return
def _analyze_first_part(self, contracts_to_be_analyzed, libraries):
for lib in libraries:
self._parse_struct_var_modifiers_functions(lib)
# Start with the contracts without inheritance
# Analyze a contract only if all its fathers
# Were analyzed
while contracts_to_be_analyzed:
contract = contracts_to_be_analyzed[0]
contracts_to_be_analyzed = contracts_to_be_analyzed[1:]
all_father_analyzed = all(father.is_analyzed for father in contract.inheritance)
if not contract.inheritance or all_father_analyzed:
self._parse_struct_var_modifiers_functions(contract)
else:
contracts_to_be_analyzed += [contract]
return
def _analyze_second_part(self, contracts_to_be_analyzed, libraries):
for lib in libraries:
self._analyze_struct_events(lib)
# Start with the contracts without inheritance
# Analyze a contract only if all its fathers
# Were analyzed
while contracts_to_be_analyzed:
contract = contracts_to_be_analyzed[0]
contracts_to_be_analyzed = contracts_to_be_analyzed[1:]
all_father_analyzed = all(father.is_analyzed for father in contract.inheritance)
if not contract.inheritance or all_father_analyzed:
self._analyze_struct_events(contract)
else:
contracts_to_be_analyzed += [contract]
return
def _analyze_third_part(self, contracts_to_be_analyzed, libraries):
for lib in libraries:
self._analyze_variables_modifiers_functions(lib)
# Start with the contracts without inheritance
# Analyze a contract only if all its fathers
# Were analyzed
while contracts_to_be_analyzed:
contract = contracts_to_be_analyzed[0]
contracts_to_be_analyzed = contracts_to_be_analyzed[1:]
all_father_analyzed = all(father.is_analyzed for father in contract.inheritance)
if not contract.inheritance or all_father_analyzed:
self._analyze_variables_modifiers_functions(contract)
else:
contracts_to_be_analyzed += [contract]
return
def _analyze_enums(self, contract):
# Enum must be analyzed first
contract.analyze_enums()
contract.set_is_analyzed(True)
def _parse_struct_var_modifiers_functions(self, contract):
contract.parse_structs() # struct can refer another struct
contract.parse_state_variables()
contract.parse_modifiers()
contract.parse_functions()
contract.set_is_analyzed(True)
def _analyze_struct_events(self, contract):
contract.analyze_constant_state_variables()
# Struct can refer to enum, or state variables
contract.analyze_structs()
# Event can refer to struct
contract.analyze_events()
contract.analyze_using_for()
contract.set_is_analyzed(True)
def _analyze_variables_modifiers_functions(self, contract):
# State variables, modifiers and functions can refer to anything
contract.analyze_params_modifiers()
contract.analyze_params_functions()
contract.analyze_state_variables()
contract.analyze_content_modifiers()
contract.analyze_content_functions()
contract.set_is_analyzed(True)
def _convert_to_slithir(self):
for contract in self.contracts:
contract.convert_expression_to_slithir()
self._propagate_function_calls()
for contract in self.contracts:
contract.fix_phi()
contract.update_read_write_using_ssa()
# endregion