From 0a7738d22516995bb5b91b8f7245ca3761777ec5 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 14 Dec 2020 15:01:49 +0100 Subject: [PATCH] Change the way slither handles top level enum/struct - Split declaration.Enum/Structure to EnumContract/EnumToplevel (same for structure) - Update the parsing accordingly --- slither/core/declarations/__init__.py | 2 + slither/core/declarations/contract.py | 23 ++-- slither/core/declarations/enum.py | 16 +-- slither/core/declarations/enum_contract.py | 17 +++ slither/core/declarations/enum_top_level.py | 6 + slither/core/declarations/structure.py | 11 +- .../core/declarations/structure_contract.py | 12 ++ .../core/declarations/structure_top_level.py | 6 + slither/core/declarations/top_level.py | 5 + slither/core/slither_core.py | 29 ++--- slither/slither.py | 6 +- slither/solc_parsing/declarations/contract.py | 32 ++--- .../{structure.py => structure_contract.py} | 27 +++-- .../declarations/structure_top_level.py | 56 +++++++++ .../expressions/expression_parsing.py | 4 +- slither/solc_parsing/slitherSolc.py | 109 ++++++++++-------- .../solidity_types/type_parsing.py | 6 +- .../expected/struct-0.6.0-compact.json | 3 +- .../expected/struct-0.6.1-compact.json | 3 +- .../expected/struct-0.6.10-compact.json | 3 +- .../expected/struct-0.6.11-compact.json | 3 +- .../expected/struct-0.6.12-compact.json | 3 +- .../expected/struct-0.6.2-compact.json | 3 +- .../expected/struct-0.6.3-compact.json | 3 +- .../expected/struct-0.6.4-compact.json | 3 +- .../expected/struct-0.6.5-compact.json | 3 +- .../expected/struct-0.6.6-compact.json | 3 +- .../expected/struct-0.6.7-compact.json | 3 +- .../expected/struct-0.6.8-compact.json | 3 +- .../expected/struct-0.6.9-compact.json | 3 +- .../expected/struct-0.7.0-compact.json | 3 +- .../expected/struct-0.7.1-compact.json | 3 +- .../expected/struct-0.7.2-compact.json | 3 +- tests/test_ast_parsing.py | 2 +- 34 files changed, 246 insertions(+), 171 deletions(-) create mode 100644 slither/core/declarations/enum_contract.py create mode 100644 slither/core/declarations/enum_top_level.py create mode 100644 slither/core/declarations/structure_contract.py create mode 100644 slither/core/declarations/structure_top_level.py create mode 100644 slither/core/declarations/top_level.py rename slither/solc_parsing/declarations/{structure.py => structure_contract.py} (59%) create mode 100644 slither/solc_parsing/declarations/structure_top_level.py diff --git a/slither/core/declarations/__init__.py b/slither/core/declarations/__init__.py index 89de67e6b..d3fe3dd22 100644 --- a/slither/core/declarations/__init__.py +++ b/slither/core/declarations/__init__.py @@ -11,3 +11,5 @@ from .solidity_variables import ( SolidityFunction, ) from .structure import Structure +from .enum_contract import EnumContract +from .structure_contract import StructureContract diff --git a/slither/core/declarations/contract.py b/slither/core/declarations/contract.py index efacef319..012f5af8d 100644 --- a/slither/core/declarations/contract.py +++ b/slither/core/declarations/contract.py @@ -25,8 +25,7 @@ from slither.utils.tests_pattern import is_test_contract # pylint: disable=too-many-lines,too-many-instance-attributes,import-outside-toplevel,too-many-nested-blocks if TYPE_CHECKING: from slither.utils.type_helpers import LibraryCallType, HighLevelCallType, InternalCallType - from slither.core.declarations import Enum, Event, Modifier - from slither.core.declarations import Structure + from slither.core.declarations import Enum, Event, Modifier, EnumContract, StructureContract from slither.slithir.variables.variable import SlithIRVariable from slither.core.variables.variable import Variable from slither.core.variables.state_variable import StateVariable @@ -51,8 +50,8 @@ class Contract(ChildSlither, SourceMapping): # pylint: disable=too-many-public- # contract B is A(1) { .. self._explicit_base_constructor_calls: List["Contract"] = [] - self._enums: Dict[str, "Enum"] = {} - self._structures: Dict[str, "Structure"] = {} + self._enums: Dict[str, "EnumContract"] = {} + self._structures: Dict[str, "StructureContract"] = {} self._events: Dict[str, "Event"] = {} self._variables: Dict[str, "StateVariable"] = {} self._variables_ordered: List["StateVariable"] = [] @@ -135,28 +134,28 @@ class Contract(ChildSlither, SourceMapping): # pylint: disable=too-many-public- ################################################################################### @property - def structures(self) -> List["Structure"]: + def structures(self) -> List["StructureContract"]: """ list(Structure): List of the structures """ return list(self._structures.values()) @property - def structures_inherited(self) -> List["Structure"]: + def structures_inherited(self) -> List["StructureContract"]: """ list(Structure): List of the inherited structures """ return [s for s in self.structures if s.contract != self] @property - def structures_declared(self) -> List["Structure"]: + def structures_declared(self) -> List["StructureContract"]: """ list(Structues): List of the structures declared within the contract (not inherited) """ return [s for s in self.structures if s.contract == self] @property - def structures_as_dict(self) -> Dict[str, "Structure"]: + def structures_as_dict(self) -> Dict[str, "StructureContract"]: return self._structures # endregion @@ -167,25 +166,25 @@ class Contract(ChildSlither, SourceMapping): # pylint: disable=too-many-public- ################################################################################### @property - def enums(self) -> List["Enum"]: + def enums(self) -> List["EnumContract"]: return list(self._enums.values()) @property - def enums_inherited(self) -> List["Enum"]: + def enums_inherited(self) -> List["EnumContract"]: """ list(Enum): List of the inherited enums """ return [e for e in self.enums if e.contract != self] @property - def enums_declared(self) -> List["Enum"]: + def enums_declared(self) -> List["EnumContract"]: """ list(Enum): List of the enums declared within the contract (not inherited) """ return [e for e in self.enums if e.contract == self] @property - def enums_as_dict(self) -> Dict[str, "Enum"]: + def enums_as_dict(self) -> Dict[str, "EnumContract"]: return self._enums # endregion diff --git a/slither/core/declarations/enum.py b/slither/core/declarations/enum.py index ae306f266..76a43dc4d 100644 --- a/slither/core/declarations/enum.py +++ b/slither/core/declarations/enum.py @@ -1,13 +1,9 @@ -from typing import List, TYPE_CHECKING +from typing import List from slither.core.source_mapping.source_mapping import SourceMapping -from slither.core.children.child_contract import ChildContract -if TYPE_CHECKING: - from slither.core.declarations import Contract - -class Enum(ChildContract, SourceMapping): +class Enum(SourceMapping): def __init__(self, name: str, canonical_name: str, values: List[str]): super().__init__() self._name = name @@ -26,13 +22,5 @@ class Enum(ChildContract, SourceMapping): def values(self) -> List[str]: return self._values - def is_declared_by(self, contract: "Contract") -> bool: - """ - Check if the element is declared by the contract - :param contract: - :return: - """ - return self.contract == contract - def __str__(self): return self.name diff --git a/slither/core/declarations/enum_contract.py b/slither/core/declarations/enum_contract.py new file mode 100644 index 000000000..46168d107 --- /dev/null +++ b/slither/core/declarations/enum_contract.py @@ -0,0 +1,17 @@ +from typing import TYPE_CHECKING + +from slither.core.children.child_contract import ChildContract +from slither.core.declarations import Enum + +if TYPE_CHECKING: + from slither.core.declarations import Contract + + +class EnumContract(Enum, ChildContract): + def is_declared_by(self, contract: "Contract") -> bool: + """ + Check if the element is declared by the contract + :param contract: + :return: + """ + return self.contract == contract diff --git a/slither/core/declarations/enum_top_level.py b/slither/core/declarations/enum_top_level.py new file mode 100644 index 000000000..44dccd7c9 --- /dev/null +++ b/slither/core/declarations/enum_top_level.py @@ -0,0 +1,6 @@ +from slither.core.declarations import Enum +from slither.core.declarations.top_level import TopLevel + + +class EnumTopLevel(Enum, TopLevel): + pass diff --git a/slither/core/declarations/structure.py b/slither/core/declarations/structure.py index e7eac1ac5..ee3548f57 100644 --- a/slither/core/declarations/structure.py +++ b/slither/core/declarations/structure.py @@ -1,13 +1,12 @@ from typing import List, TYPE_CHECKING, Dict -from slither.core.children.child_contract import ChildContract from slither.core.source_mapping.source_mapping import SourceMapping if TYPE_CHECKING: from slither.core.variables.structure_variable import StructureVariable -class Structure(ChildContract, SourceMapping): +class Structure(SourceMapping): def __init__(self): super().__init__() self._name = None @@ -39,14 +38,6 @@ class Structure(ChildContract, SourceMapping): def add_elem_in_order(self, s: str): self._elems_ordered.append(s) - def is_declared_by(self, contract): - """ - Check if the element is declared by the contract - :param contract: - :return: - """ - return self.contract == contract - @property def elems_ordered(self) -> List["StructureVariable"]: ret = [] diff --git a/slither/core/declarations/structure_contract.py b/slither/core/declarations/structure_contract.py new file mode 100644 index 000000000..aaf660e1e --- /dev/null +++ b/slither/core/declarations/structure_contract.py @@ -0,0 +1,12 @@ +from slither.core.children.child_contract import ChildContract +from slither.core.declarations import Structure + + +class StructureContract(Structure, ChildContract): + def is_declared_by(self, contract): + """ + Check if the element is declared by the contract + :param contract: + :return: + """ + return self.contract == contract diff --git a/slither/core/declarations/structure_top_level.py b/slither/core/declarations/structure_top_level.py new file mode 100644 index 000000000..9385a6e9d --- /dev/null +++ b/slither/core/declarations/structure_top_level.py @@ -0,0 +1,6 @@ +from slither.core.declarations import Structure +from slither.core.declarations.top_level import TopLevel + + +class StructureTopLevel(Structure, TopLevel): + pass diff --git a/slither/core/declarations/top_level.py b/slither/core/declarations/top_level.py new file mode 100644 index 000000000..15facf2f9 --- /dev/null +++ b/slither/core/declarations/top_level.py @@ -0,0 +1,5 @@ +from slither.core.source_mapping.source_mapping import SourceMapping + + +class TopLevel(SourceMapping): + pass diff --git a/slither/core/slither_core.py b/slither/core/slither_core.py index 51fb96aec..7d77e2695 100644 --- a/slither/core/slither_core.py +++ b/slither/core/slither_core.py @@ -1,11 +1,11 @@ """ Main module """ -import os -import logging import json -import re +import logging import math +import os +import re from collections import defaultdict from typing import Optional, Dict, List, Set, Union, Tuple @@ -18,9 +18,9 @@ from slither.core.declarations import ( Import, Function, Modifier, - Structure, - Enum, ) +from slither.core.declarations.enum_top_level import EnumTopLevel +from slither.core.declarations.structure_top_level import StructureTopLevel from slither.core.variables.state_variable import StateVariable from slither.slithir.operations import InternalCall from slither.slithir.variables import Constant @@ -44,12 +44,17 @@ class SlitherCore(Context): # pylint: disable=too-many-instance-attributes,too- def __init__(self): super().__init__() + + # Top level object self._contracts: Dict[str, Contract] = {} + self._structures_top_level: List[StructureTopLevel] = [] + self._enums_top_level: List[EnumTopLevel] = [] + self._pragma_directives: List[Pragma] = [] + self._import_directives: List[Import] = [] + self._filename: Optional[str] = None self._source_units: Dict[int, str] = {} self._solc_version: Optional[str] = None # '0.3' or '0.4':! - self._pragma_directives: List[Pragma] = [] - self._import_directives: List[Import] = [] self._raw_source_code: Dict[str, str] = {} self._all_functions: Set[Function] = set() self._all_modifiers: Set[Modifier] = set() @@ -234,14 +239,12 @@ class SlitherCore(Context): # pylint: disable=too-many-instance-attributes,too- ################################################################################### @property - def top_level_structures(self) -> List[Structure]: - top_level_structures = [c.structures for c in self.contracts if c.is_top_level] - return [st for sublist in top_level_structures for st in sublist] + def structures_top_level(self) -> List[StructureTopLevel]: + return self._structures_top_level @property - def top_level_enums(self) -> List[Enum]: - top_level_enums = [c.enums for c in self.contracts if c.is_top_level] - return [st for sublist in top_level_enums for st in sublist] + def enums_top_level(self) -> List[EnumTopLevel]: + return self._enums_top_level # endregion ################################################################################### diff --git a/slither/slither.py b/slither/slither.py index 430b921b9..a5062e995 100644 --- a/slither/slither.py +++ b/slither/slither.py @@ -78,7 +78,7 @@ class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes # pylint: disable=raise-missing-from raise SlitherError(f"Invalid compilation: \n{str(e)}") for path, ast in crytic_compile.asts.items(): - self._parser.parse_contracts_from_loaded_json(ast, path) + self._parser.parse_top_level_from_loaded_json(ast, path) self.add_source_code(path) if kwargs.get("generate_patches", False): @@ -120,7 +120,7 @@ class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes self._parser = SlitherSolc(filename, self) for c in contracts_json: - self._parser.parse_contracts_from_json(c) + self._parser.parse_top_level_from_json(c) def _init_from_list(self, contract): self._parser = SlitherSolc("", self) @@ -129,7 +129,7 @@ class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes path = c["absolutePath"] else: path = c["attributes"]["absolutePath"] - self._parser.parse_contracts_from_loaded_json(c, path) + self._parser.parse_top_level_from_loaded_json(c, path) @property def detectors(self): diff --git a/slither/solc_parsing/declarations/contract.py b/slither/solc_parsing/declarations/contract.py index 4bdfdf7ff..661edbc2f 100644 --- a/slither/solc_parsing/declarations/contract.py +++ b/slither/solc_parsing/declarations/contract.py @@ -1,15 +1,14 @@ import logging from typing import List, Dict, Callable, TYPE_CHECKING, Union -from slither.core.declarations import Modifier, Structure, Event +from slither.core.declarations import Modifier, Event, EnumContract, StructureContract from slither.core.declarations.contract import Contract -from slither.core.declarations.enum import Enum from slither.core.declarations.function import Function from slither.core.variables.state_variable import StateVariable from slither.solc_parsing.declarations.event import EventSolc from slither.solc_parsing.declarations.function import FunctionSolc from slither.solc_parsing.declarations.modifier import ModifierSolc -from slither.solc_parsing.declarations.structure import StructureSolc +from slither.solc_parsing.declarations.structure_contract import StructureContractSolc from slither.solc_parsing.exceptions import ParsingError, VariableNotFound from slither.solc_parsing.solidity_types.type_parsing import parse_type from slither.solc_parsing.variables.state_variable import StateVariableSolc @@ -44,7 +43,7 @@ class ContractSolc: self._functions_parser: List[FunctionSolc] = [] self._modifiers_parser: List[ModifierSolc] = [] - self._structures_parser: List[StructureSolc] = [] + self._structures_parser: List[StructureContractSolc] = [] self._is_analyzed: bool = False @@ -252,28 +251,13 @@ class ContractSolc: return def _parse_struct(self, struct: Dict): - if self.is_compact_ast: - name = struct["name"] - attributes = struct - else: - name = struct["attributes"][self.get_key()] - attributes = struct["attributes"] - if "canonicalName" in attributes: - canonicalName = attributes["canonicalName"] - else: - canonicalName = self._contract.name + "." + name - - if self.get_children("members") in struct: - children = struct[self.get_children("members")] - else: - children = [] # empty struct - st = Structure() + st = StructureContract() st.set_contract(self._contract) st.set_offset(struct["src"], self._contract.slither) - st_parser = StructureSolc(st, name, canonicalName, children, self) - self._contract.structures_as_dict[name] = st + st_parser = StructureContractSolc(st, struct, self) + self._contract.structures_as_dict[st.name] = st self._structures_parser.append(st_parser) def parse_structs(self): @@ -573,12 +557,12 @@ class ContractSolc: else: values.append(child["attributes"][self.get_key()]) - new_enum = Enum(name, canonicalName, values) + new_enum = EnumContract(name, canonicalName, values) new_enum.set_contract(self._contract) new_enum.set_offset(enum["src"], self._contract.slither) self._contract.enums_as_dict[canonicalName] = new_enum - def _analyze_struct(self, struct: StructureSolc): # pylint: disable=no-self-use + def _analyze_struct(self, struct: StructureContractSolc): # pylint: disable=no-self-use struct.analyze() def analyze_structs(self): diff --git a/slither/solc_parsing/declarations/structure.py b/slither/solc_parsing/declarations/structure_contract.py similarity index 59% rename from slither/solc_parsing/declarations/structure.py rename to slither/solc_parsing/declarations/structure_contract.py index 0fa2a36a0..00edb614c 100644 --- a/slither/solc_parsing/declarations/structure.py +++ b/slither/solc_parsing/declarations/structure_contract.py @@ -1,7 +1,7 @@ """ Structure module """ -from typing import List, TYPE_CHECKING +from typing import List, TYPE_CHECKING, Dict from slither.core.variables.structure_variable import StructureVariable from slither.solc_parsing.variables.structure_variable import StructureVariableSolc @@ -11,7 +11,7 @@ if TYPE_CHECKING: from slither.solc_parsing.declarations.contract import ContractSolc -class StructureSolc: # pylint: disable=too-few-public-methods +class StructureContractSolc: # pylint: disable=too-few-public-methods """ Structure class """ @@ -19,19 +19,28 @@ class StructureSolc: # pylint: disable=too-few-public-methods # elems = [(type, name)] def __init__( # pylint: disable=too-many-arguments - self, - st: Structure, - name: str, - canonicalName: str, - elems: List[str], - contract_parser: "ContractSolc", + self, st: Structure, struct: Dict, contract_parser: "ContractSolc", ): + + if contract_parser.is_compact_ast: + name = struct["name"] + attributes = struct + else: + name = struct["attributes"][contract_parser.get_key()] + attributes = struct["attributes"] + if "canonicalName" in attributes: + canonicalName = attributes["canonicalName"] + else: + canonicalName = contract_parser.underlying_contract.name + "." + name + + children = struct["members"] if "members" in struct else struct.get("children", []) + self._structure = st st.name = name st.canonical_name = canonicalName self._contract_parser = contract_parser - self._elemsNotParsed = elems + self._elemsNotParsed = children def analyze(self): for elem_to_parse in self._elemsNotParsed: diff --git a/slither/solc_parsing/declarations/structure_top_level.py b/slither/solc_parsing/declarations/structure_top_level.py new file mode 100644 index 000000000..af178f3fe --- /dev/null +++ b/slither/solc_parsing/declarations/structure_top_level.py @@ -0,0 +1,56 @@ +""" + Structure module +""" +from typing import List, TYPE_CHECKING, Dict + +from slither.core.variables.structure_variable import StructureVariable +from slither.solc_parsing.variables.structure_variable import StructureVariableSolc +from slither.core.declarations.structure import Structure + +if TYPE_CHECKING: + from slither.solc_parsing.slitherSolc import SlitherSolc + + +class StructureTopLevelSolc: # pylint: disable=too-few-public-methods + """ + Structure class + """ + + # elems = [(type, name)] + + def __init__( # pylint: disable=too-many-arguments + self, st: Structure, struct: Dict, slither_parser: "SlitherSolc", + ): + + if slither_parser.is_compact_ast: + name = struct["name"] + attributes = struct + else: + name = struct["attributes"][slither_parser.get_key()] + attributes = struct["attributes"] + if "canonicalName" in attributes: + canonicalName = attributes["canonicalName"] + else: + canonicalName = name + + children = struct["members"] if "members" in struct else struct.get("children", []) + + self._structure = st + st.name = name + st.canonical_name = canonicalName + self._slither_parser = slither_parser + + self._elemsNotParsed = children + + def analyze(self): + for elem_to_parse in self._elemsNotParsed: + elem = StructureVariable() + elem.set_structure(self._structure) + elem.set_offset(elem_to_parse["src"], self._structure.contract.slither) + + elem_parser = StructureVariableSolc(elem, elem_to_parse) + elem_parser.analyze(self._slither_parser) + + self._structure.elems[elem.name] = elem + self._structure.add_elem_in_order(elem.name) + self._elemsNotParsed = [] diff --git a/slither/solc_parsing/expressions/expression_parsing.py b/slither/solc_parsing/expressions/expression_parsing.py index fbec11f8c..ef4b0c76e 100644 --- a/slither/solc_parsing/expressions/expression_parsing.py +++ b/slither/solc_parsing/expressions/expression_parsing.py @@ -178,7 +178,7 @@ def find_variable( # pylint: disable=too-many-locals,too-many-statements if var_name in structures: return structures[var_name] - structures_top_level = contract.slither.top_level_structures + structures_top_level = contract.slither.structures_top_level for st in structures_top_level: if st.name == var_name: return st @@ -191,7 +191,7 @@ def find_variable( # pylint: disable=too-many-locals,too-many-statements if var_name in enums: return enums[var_name] - enums_top_level = contract.slither.top_level_enums + enums_top_level = contract.slither.enums_top_level for enum in enums_top_level: if enum.name == var_name: return enum diff --git a/slither/solc_parsing/slitherSolc.py b/slither/solc_parsing/slitherSolc.py index 101204562..97320329b 100644 --- a/slither/solc_parsing/slitherSolc.py +++ b/slither/solc_parsing/slitherSolc.py @@ -5,6 +5,8 @@ import re from typing import List, Dict from slither.core.declarations import Contract +from slither.core.declarations.enum_top_level import EnumTopLevel +from slither.core.declarations.structure_top_level import StructureTopLevel from slither.exceptions import SlitherException from slither.solc_parsing.declarations.contract import ContractSolc @@ -13,6 +15,7 @@ from slither.core.slither_core import SlitherCore 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.solc_parsing.declarations.structure_top_level import StructureTopLevelSolc logging.basicConfig() logger = logging.getLogger("SlitherSolcParsing") @@ -29,6 +32,7 @@ class SlitherSolc: self._analyzed = False self._underlying_contract_to_parser: Dict[Contract, ContractSolc] = dict() + self._structures_top_level_parser: List[StructureTopLevelSolc] = [] self._is_compact_ast = False self._core: SlitherCore = core @@ -79,19 +83,19 @@ class SlitherSolc: ################################################################################### ################################################################################### - def parse_contracts_from_json(self, json_data: str) -> bool: + def parse_top_level_from_json(self, json_data: str) -> bool: 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"]) + self.parse_top_level_from_loaded_json(data_loaded["ast"], data_loaded["sourcePath"]) return True # solc AST, where the non-json text was removed if "attributes" in data_loaded: filename = data_loaded["attributes"]["absolutePath"] else: filename = data_loaded["absolutePath"] - self.parse_contracts_from_loaded_json(data_loaded, filename) + self.parse_top_level_from_loaded_json(data_loaded, filename) return True except ValueError: @@ -102,11 +106,38 @@ class SlitherSolc: json_data = json_data[first:last] data_loaded = json.loads(json_data) - self.parse_contracts_from_loaded_json(data_loaded, filename) + self.parse_top_level_from_loaded_json(data_loaded, filename) return True return False - def parse_contracts_from_loaded_json( + def _parse_enum(self, top_level_data:Dict): + if self.is_compact_ast: + name = top_level_data["name"] + canonicalName = top_level_data["canonicalName"] + else: + name = top_level_data["attributes"][self.get_key()] + if "canonicalName" in top_level_data["attributes"]: + canonicalName = top_level_data["attributes"]["canonicalName"] + else: + canonicalName = name + values = [] + children = ( + top_level_data["members"] + if "members" in top_level_data + else top_level_data.get("children", []) + ) + for child in children: + assert child[self.get_key()] == "EnumValue" + if self.is_compact_ast: + values.append(child["name"]) + else: + values.append(child["attributes"][self.get_key()]) + + enum = EnumTopLevel(name, canonicalName, values) + enum.set_offset(top_level_data["src"], self._core) + self.core.enums_top_level.append(enum) + + def parse_top_level_from_loaded_json( self, data_loaded: Dict, filename: str ): # pylint: disable=too-many-branches if "nodeType" in data_loaded: @@ -128,68 +159,48 @@ class SlitherSolc: logger.error("solc version is not supported") return - for contract_data in data_loaded[self.get_children()]: - assert contract_data[self.get_key()] in [ + for top_level_data in data_loaded[self.get_children()]: + assert top_level_data[self.get_key()] in [ "ContractDefinition", "PragmaDirective", "ImportDirective", "StructDefinition", "EnumDefinition", ] - if contract_data[self.get_key()] == "ContractDefinition": + if top_level_data[self.get_key()] == "ContractDefinition": contract = Contract() - contract_parser = ContractSolc(self, contract, contract_data) - if "src" in contract_data: - contract.set_offset(contract_data["src"], self._core) + contract_parser = ContractSolc(self, contract, top_level_data) + if "src" in top_level_data: + contract.set_offset(top_level_data["src"], self._core) self._underlying_contract_to_parser[contract] = contract_parser - elif contract_data[self.get_key()] == "PragmaDirective": + elif top_level_data[self.get_key()] == "PragmaDirective": if self._is_compact_ast: - pragma = Pragma(contract_data["literals"]) + pragma = Pragma(top_level_data["literals"]) else: - pragma = Pragma(contract_data["attributes"]["literals"]) - pragma.set_offset(contract_data["src"], self._core) + pragma = Pragma(top_level_data["attributes"]["literals"]) + pragma.set_offset(top_level_data["src"], self._core) self._core.pragma_directives.append(pragma) - elif contract_data[self.get_key()] == "ImportDirective": + elif top_level_data[self.get_key()] == "ImportDirective": if self.is_compact_ast: - import_directive = Import(contract_data["absolutePath"]) + import_directive = Import(top_level_data["absolutePath"]) else: - import_directive = Import(contract_data["attributes"].get("absolutePath", "")) - import_directive.set_offset(contract_data["src"], self._core) + import_directive = Import(top_level_data["attributes"].get("absolutePath", "")) + import_directive.set_offset(top_level_data["src"], self._core) self._core.import_directives.append(import_directive) - elif contract_data[self.get_key()] in [ - "StructDefinition", - "EnumDefinition", - ]: - # This can only happen for top-level structure and enum - # They were introduced with 0.6.5 - assert self._is_compact_ast # Do not support top level definition for legacy AST - fake_contract_data = { - "name": f"SlitherInternalTopLevelContract{self._top_level_contracts_counter}", - "id": -1000 - + self._top_level_contracts_counter, # TODO: determine if collission possible - "linearizedBaseContracts": [], - "fullyImplemented": True, - "contractKind": "SLitherInternal", - } - self._top_level_contracts_counter += 1 - contract = Contract() - top_level_contract = ContractSolc(self, contract, fake_contract_data) - contract.is_top_level = True - contract.set_offset(contract_data["src"], self._core) - - if contract_data[self.get_key()] == "StructDefinition": - top_level_contract.structures_not_parsed.append( - contract_data - ) # Todo add proper setters - else: - top_level_contract.enums_not_parsed.append( - contract_data - ) # Todo add proper setters + elif top_level_data[self.get_key()] == "StructDefinition": + st = StructureTopLevel() + st.set_offset(top_level_data["src"], self._core) + st_parser = StructureTopLevelSolc(st, top_level_data, self) + + self._core.structures_top_level.append(st) + self._structures_top_level_parser.append(st_parser) - self._underlying_contract_to_parser[contract] = top_level_contract + elif top_level_data[self.get_key()] == "EnumDefinition": + # Note enum don't need a complex parser, so everything is directly done + self._parse_enum(top_level_data) def _parse_source_unit(self, data: Dict, filename: str): if data[self.get_key()] != "SourceUnit": diff --git a/slither/solc_parsing/solidity_types/type_parsing.py b/slither/solc_parsing/solidity_types/type_parsing.py index ca796b6a8..d2a19d658 100644 --- a/slither/solc_parsing/solidity_types/type_parsing.py +++ b/slither/solc_parsing/solidity_types/type_parsing.py @@ -73,6 +73,7 @@ def _find_from_type_name( # pylint: disable=too-many-locals,too-many-branches,t enum_name = enum_name[len("enum ") :] all_enums = [c.enums for c in contracts] all_enums = [item for sublist in all_enums for item in sublist] + all_enums += contract.slither.enums_top_level var_type = next((e for e in all_enums if e.name == enum_name), None) if not var_type: var_type = next((e for e in all_enums if e.canonical_name == enum_name), None) @@ -84,6 +85,7 @@ def _find_from_type_name( # pylint: disable=too-many-locals,too-many-branches,t name_struct = name_struct.split(" ")[0] # remove stuff like storage pointer at the end all_structures = [c.structures for c in contracts] all_structures = [item for sublist in all_structures for item in sublist] + all_structures += contract.slither.structures_top_level var_type = next((st for st in all_structures if st.name == name_struct), None) if not var_type: var_type = next((st for st in all_structures if st.canonical_name == name_struct), None) @@ -175,8 +177,8 @@ def parse_type(t: Union[Dict, UnknownType], caller_context): else: key = "name" - structures = contract.structures + contract.slither.top_level_structures - enums = contract.enums + contract.slither.top_level_enums + structures = contract.structures + contract.slither.structures_top_level + enums = contract.enums + contract.slither.enums_top_level contracts = contract.slither.contracts if isinstance(t, UnknownType): diff --git a/tests/ast-parsing/expected/struct-0.6.0-compact.json b/tests/ast-parsing/expected/struct-0.6.0-compact.json index c2e1d2e55..0008a4469 100644 --- a/tests/ast-parsing/expected/struct-0.6.0-compact.json +++ b/tests/ast-parsing/expected/struct-0.6.0-compact.json @@ -1,4 +1,3 @@ { - "C": {}, - "SlitherInternalTopLevelContract0": {} + "C": {} } \ No newline at end of file diff --git a/tests/ast-parsing/expected/struct-0.6.1-compact.json b/tests/ast-parsing/expected/struct-0.6.1-compact.json index c2e1d2e55..0008a4469 100644 --- a/tests/ast-parsing/expected/struct-0.6.1-compact.json +++ b/tests/ast-parsing/expected/struct-0.6.1-compact.json @@ -1,4 +1,3 @@ { - "C": {}, - "SlitherInternalTopLevelContract0": {} + "C": {} } \ No newline at end of file diff --git a/tests/ast-parsing/expected/struct-0.6.10-compact.json b/tests/ast-parsing/expected/struct-0.6.10-compact.json index c2e1d2e55..0008a4469 100644 --- a/tests/ast-parsing/expected/struct-0.6.10-compact.json +++ b/tests/ast-parsing/expected/struct-0.6.10-compact.json @@ -1,4 +1,3 @@ { - "C": {}, - "SlitherInternalTopLevelContract0": {} + "C": {} } \ No newline at end of file diff --git a/tests/ast-parsing/expected/struct-0.6.11-compact.json b/tests/ast-parsing/expected/struct-0.6.11-compact.json index c2e1d2e55..0008a4469 100644 --- a/tests/ast-parsing/expected/struct-0.6.11-compact.json +++ b/tests/ast-parsing/expected/struct-0.6.11-compact.json @@ -1,4 +1,3 @@ { - "C": {}, - "SlitherInternalTopLevelContract0": {} + "C": {} } \ No newline at end of file diff --git a/tests/ast-parsing/expected/struct-0.6.12-compact.json b/tests/ast-parsing/expected/struct-0.6.12-compact.json index c2e1d2e55..0008a4469 100644 --- a/tests/ast-parsing/expected/struct-0.6.12-compact.json +++ b/tests/ast-parsing/expected/struct-0.6.12-compact.json @@ -1,4 +1,3 @@ { - "C": {}, - "SlitherInternalTopLevelContract0": {} + "C": {} } \ No newline at end of file diff --git a/tests/ast-parsing/expected/struct-0.6.2-compact.json b/tests/ast-parsing/expected/struct-0.6.2-compact.json index c2e1d2e55..0008a4469 100644 --- a/tests/ast-parsing/expected/struct-0.6.2-compact.json +++ b/tests/ast-parsing/expected/struct-0.6.2-compact.json @@ -1,4 +1,3 @@ { - "C": {}, - "SlitherInternalTopLevelContract0": {} + "C": {} } \ No newline at end of file diff --git a/tests/ast-parsing/expected/struct-0.6.3-compact.json b/tests/ast-parsing/expected/struct-0.6.3-compact.json index c2e1d2e55..0008a4469 100644 --- a/tests/ast-parsing/expected/struct-0.6.3-compact.json +++ b/tests/ast-parsing/expected/struct-0.6.3-compact.json @@ -1,4 +1,3 @@ { - "C": {}, - "SlitherInternalTopLevelContract0": {} + "C": {} } \ No newline at end of file diff --git a/tests/ast-parsing/expected/struct-0.6.4-compact.json b/tests/ast-parsing/expected/struct-0.6.4-compact.json index c2e1d2e55..0008a4469 100644 --- a/tests/ast-parsing/expected/struct-0.6.4-compact.json +++ b/tests/ast-parsing/expected/struct-0.6.4-compact.json @@ -1,4 +1,3 @@ { - "C": {}, - "SlitherInternalTopLevelContract0": {} + "C": {} } \ No newline at end of file diff --git a/tests/ast-parsing/expected/struct-0.6.5-compact.json b/tests/ast-parsing/expected/struct-0.6.5-compact.json index c2e1d2e55..0008a4469 100644 --- a/tests/ast-parsing/expected/struct-0.6.5-compact.json +++ b/tests/ast-parsing/expected/struct-0.6.5-compact.json @@ -1,4 +1,3 @@ { - "C": {}, - "SlitherInternalTopLevelContract0": {} + "C": {} } \ No newline at end of file diff --git a/tests/ast-parsing/expected/struct-0.6.6-compact.json b/tests/ast-parsing/expected/struct-0.6.6-compact.json index c2e1d2e55..0008a4469 100644 --- a/tests/ast-parsing/expected/struct-0.6.6-compact.json +++ b/tests/ast-parsing/expected/struct-0.6.6-compact.json @@ -1,4 +1,3 @@ { - "C": {}, - "SlitherInternalTopLevelContract0": {} + "C": {} } \ No newline at end of file diff --git a/tests/ast-parsing/expected/struct-0.6.7-compact.json b/tests/ast-parsing/expected/struct-0.6.7-compact.json index c2e1d2e55..0008a4469 100644 --- a/tests/ast-parsing/expected/struct-0.6.7-compact.json +++ b/tests/ast-parsing/expected/struct-0.6.7-compact.json @@ -1,4 +1,3 @@ { - "C": {}, - "SlitherInternalTopLevelContract0": {} + "C": {} } \ No newline at end of file diff --git a/tests/ast-parsing/expected/struct-0.6.8-compact.json b/tests/ast-parsing/expected/struct-0.6.8-compact.json index c2e1d2e55..0008a4469 100644 --- a/tests/ast-parsing/expected/struct-0.6.8-compact.json +++ b/tests/ast-parsing/expected/struct-0.6.8-compact.json @@ -1,4 +1,3 @@ { - "C": {}, - "SlitherInternalTopLevelContract0": {} + "C": {} } \ No newline at end of file diff --git a/tests/ast-parsing/expected/struct-0.6.9-compact.json b/tests/ast-parsing/expected/struct-0.6.9-compact.json index c2e1d2e55..0008a4469 100644 --- a/tests/ast-parsing/expected/struct-0.6.9-compact.json +++ b/tests/ast-parsing/expected/struct-0.6.9-compact.json @@ -1,4 +1,3 @@ { - "C": {}, - "SlitherInternalTopLevelContract0": {} + "C": {} } \ No newline at end of file diff --git a/tests/ast-parsing/expected/struct-0.7.0-compact.json b/tests/ast-parsing/expected/struct-0.7.0-compact.json index c2e1d2e55..0008a4469 100644 --- a/tests/ast-parsing/expected/struct-0.7.0-compact.json +++ b/tests/ast-parsing/expected/struct-0.7.0-compact.json @@ -1,4 +1,3 @@ { - "C": {}, - "SlitherInternalTopLevelContract0": {} + "C": {} } \ No newline at end of file diff --git a/tests/ast-parsing/expected/struct-0.7.1-compact.json b/tests/ast-parsing/expected/struct-0.7.1-compact.json index c2e1d2e55..0008a4469 100644 --- a/tests/ast-parsing/expected/struct-0.7.1-compact.json +++ b/tests/ast-parsing/expected/struct-0.7.1-compact.json @@ -1,4 +1,3 @@ { - "C": {}, - "SlitherInternalTopLevelContract0": {} + "C": {} } \ No newline at end of file diff --git a/tests/ast-parsing/expected/struct-0.7.2-compact.json b/tests/ast-parsing/expected/struct-0.7.2-compact.json index c2e1d2e55..0008a4469 100644 --- a/tests/ast-parsing/expected/struct-0.7.2-compact.json +++ b/tests/ast-parsing/expected/struct-0.7.2-compact.json @@ -1,4 +1,3 @@ { - "C": {}, - "SlitherInternalTopLevelContract0": {} + "C": {} } \ No newline at end of file diff --git a/tests/test_ast_parsing.py b/tests/test_ast_parsing.py index 316f1614d..dd34550ca 100644 --- a/tests/test_ast_parsing.py +++ b/tests/test_ast_parsing.py @@ -558,7 +558,7 @@ def test_parsing(test_item: Item): diff = DeepDiff(expected, actual, ignore_order=True, verbose_level=2, view="tree") if diff: - for change in diff["values_changed"]: + for change in diff.get("values_changed", []): path_list = re.findall(r"\['(.*?)'\]", change.path()) path = "_".join(path_list) with open(f"test_artifacts/{id_test(test_item)}_{path}_expected.dot", "w") as f: