diff --git a/mythril/ethereum/util.py b/mythril/ethereum/util.py index ac80863b..2b6c7771 100644 --- a/mythril/ethereum/util.py +++ b/mythril/ethereum/util.py @@ -33,7 +33,7 @@ def get_solc_json(file, solc_binary="solc", solc_args=None): :return: """ - cmd = [solc_binary, "--combined-json", "bin,bin-runtime,srcmap,srcmap-runtime"] + cmd = [solc_binary, "--combined-json", "bin,bin-runtime,srcmap,srcmap-runtime,ast"] if solc_args: cmd.extend(solc_args.split()) diff --git a/mythril/solidity/soliditycontract.py b/mythril/solidity/soliditycontract.py index 6f83ead7..05760554 100644 --- a/mythril/solidity/soliditycontract.py +++ b/mythril/solidity/soliditycontract.py @@ -1,5 +1,7 @@ """This module contains representation classes for Solidity files, contracts and source mappings.""" +from typing import Dict, Set + import mythril.laser.ethereum.util as helper from mythril.ethereum.evmcontract import EVMContract from mythril.ethereum.util import get_solc_json @@ -20,9 +22,16 @@ class SourceMapping: class SolidityFile: """Representation of a file containing Solidity code.""" - def __init__(self, filename, data): + def __init__(self, filename: str, data: str, full_contract_source: Set[str]): + """ + Metadata class containing data regarding a specific solidity file + :param filename: The filename of the solidity file + :param data: The code of the solidity file + :param full_contract_source: The set of contract source mappings of all the contracts in the file + """ self.filename = filename self.data = data + self.full_contract_source = full_contract_source class SourceCodeInfo: @@ -69,7 +78,12 @@ class SolidityContract(EVMContract): for filename in data["sourceList"]: with open(filename, "r", encoding="utf-8") as file: code = file.read() - self.solidity_files.append(SolidityFile(filename, code)) + full_contract_sources = self.get_full_contract_sources( + data["sources"][filename]["AST"] + ) + self.solidity_files.append( + SolidityFile(filename, code, full_contract_sources) + ) has_contract = False @@ -117,6 +131,19 @@ class SolidityContract(EVMContract): super().__init__(code, creation_code, name=name) + @staticmethod + def get_full_contract_sources(ast: Dict) -> Set[str]: + """ + Takes a solc AST and gets the src mappings for all the contracts defined in the top level of the ast + :param ast: AST of the contract + :return: The source map + """ + source_map = set() + for child in ast["children"]: + if "contractKind" in child["attributes"]: + source_map.add(child["src"]) + return source_map + def get_source_info(self, address, constructor=False): """ @@ -140,6 +167,26 @@ class SolidityContract(EVMContract): lineno = mappings[index].lineno return SourceCodeInfo(filename, lineno, code, mappings[index].solc_mapping) + def _is_autogenerated_code(self, offset: int, length: int, file_index: int) -> bool: + """ + Checks whether the code is autogenerated or not + :param offset: offset of the code + :param length: length of the code + :param file_index: file the code corresponds to + :return: True if the code is internally generated, else false + """ + # Handle internal compiler files + if file_index == -1: + return True + # Handle the common code src map for the entire code. + if ( + "{}:{}:{}".format(offset, length, file_index) + in self.solidity_files[file_index].full_contract_source + ): + return True + + return False + def _get_solc_mappings(self, srcmap, constructor=False): """ @@ -161,7 +208,8 @@ class SolidityContract(EVMContract): if len(mapping) > 2 and len(mapping[2]) > 0: idx = int(mapping[2]) - if idx == -1: + + if self._is_autogenerated_code(offset, length, idx): lineno = None else: lineno = (