diff --git a/slither/core/declarations/contract.py b/slither/core/declarations/contract.py index 20a8f3f8e..23317c6ce 100644 --- a/slither/core/declarations/contract.py +++ b/slither/core/declarations/contract.py @@ -386,7 +386,7 @@ class Contract(ChildSlither, SourceMapping): accessible_elements = {} contracts_visited = [] for father in self.inheritance_reverse: - functions = {v.full_name: v for (_, v) in getter_available(father) + functions = {v.full_name: v for (v) in getter_available(father) if not v.contract in contracts_visited} contracts_visited.append(father) inherited_elements.update(functions) diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index 8977a9b89..11d39dc92 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -126,6 +126,7 @@ class Function(ChildContract, ChildInheritance, SourceMapping): self._all_solidity_variables_used_as_args = None self._is_shadowed = False + self._shadows = False # set(ReacheableNode) self._reachable_from_nodes = set() @@ -316,6 +317,14 @@ class Function(ChildContract, ChildInheritance, SourceMapping): def is_shadowed(self, is_shadowed): self._is_shadowed = is_shadowed + @property + def shadows(self): + return self._shadows + + @shadows.setter + def shadows(self, _shadows): + self._shadows = _shadows + # endregion ################################################################################### ################################################################################### diff --git a/slither/printers/inheritance/inheritance_graph.py b/slither/printers/inheritance/inheritance_graph.py index 73ee72af8..4dbed9e0b 100644 --- a/slither/printers/inheritance/inheritance_graph.py +++ b/slither/printers/inheritance/inheritance_graph.py @@ -10,7 +10,6 @@ from slither.core.declarations.contract import Contract from slither.core.solidity_types.user_defined_type import UserDefinedType from slither.printers.abstract_printer import AbstractPrinter from slither.utils.inheritance_analysis import (detect_c3_function_shadowing, - detect_function_shadowing, detect_state_variable_shadowing) @@ -26,19 +25,6 @@ class PrinterInheritanceGraph(AbstractPrinter): inheritance = [x.inheritance for x in slither.contracts] self.inheritance = set([item for sublist in inheritance for item in sublist]) - # Create a lookup of direct shadowing instances. - self.direct_overshadowing_functions = {} - shadows = detect_function_shadowing(slither.contracts, True, False) - for overshadowing_instance in shadows: - overshadowing_function = overshadowing_instance[2] - - # Add overshadowing function entry. - if overshadowing_function not in self.direct_overshadowing_functions: - self.direct_overshadowing_functions[overshadowing_function] = set() - self.direct_overshadowing_functions[overshadowing_function].add(overshadowing_instance) - - # Create a lookup of shadowing state variables. - # Format: { colliding_variable : set([colliding_variables]) } self.overshadowing_state_variables = {} shadows = detect_state_variable_shadowing(slither.contracts) for overshadowing_instance in shadows: @@ -55,7 +41,7 @@ class PrinterInheritanceGraph(AbstractPrinter): func_name = func.full_name pattern = ' %s' pattern_shadow = ' %s' - if func in self.direct_overshadowing_functions: + if func.shadows: return pattern_shadow % func_name return pattern % func_name @@ -89,12 +75,10 @@ class PrinterInheritanceGraph(AbstractPrinter): # If this variable is an overshadowing variable, we'll want to return information describing it. result = [] indirect_shadows = detect_c3_function_shadowing(contract) - if indirect_shadows: - for collision_set in sorted(indirect_shadows, key=lambda x: x[0][1].name): - winner = collision_set[-1][1].contract_declarer.name - collision_steps = [colliding_function.contract_declarer.name for _, colliding_function in collision_set] - collision_steps = ', '.join(collision_steps) - result.append(f"'{collision_set[0][1].full_name}' collides in inherited contracts {collision_steps} where {winner} is chosen.") + for winner, colliding_functions in indirect_shadows.items(): + collision_steps = ', '.join([f.contract_declarer.name + for f in colliding_functions] + [winner.contract_declarer.name]) + result.append(f"'{winner.full_name}' collides in inherited contracts {collision_steps} where {winner.contract_declarer.name} is chosen.") return '\n'.join(result) def _get_port_id(self, var, contract): @@ -116,10 +100,12 @@ class PrinterInheritanceGraph(AbstractPrinter): # Functions visibilities = ['public', 'external'] public_functions = [self._get_pattern_func(f, contract) for f in contract.functions if - not f.is_constructor and f.contract_declarer == contract and f.visibility in visibilities] + not f.is_constructor and not f.is_constructor_variables + and f.contract_declarer == contract and f.visibility in visibilities] public_functions = ''.join(public_functions) private_functions = [self._get_pattern_func(f, contract) for f in contract.functions if - not f.is_constructor and f.contract_declarer == contract and f.visibility not in visibilities] + not f.is_constructor and not f.is_constructor_variables + and f.contract_declarer == contract and f.visibility not in visibilities] private_functions = ''.join(private_functions) # Modifiers diff --git a/slither/printers/summary/slithir.py b/slither/printers/summary/slithir.py index 4e936e038..70696c6fa 100644 --- a/slither/printers/summary/slithir.py +++ b/slither/printers/summary/slithir.py @@ -23,7 +23,7 @@ class PrinterSlithIR(AbstractPrinter): for contract in self.contracts: print('Contract {}'.format(contract.name)) for function in contract.functions: - print(f'\tFunction {function.canonical_name}') + print(f'\tFunction {function.canonical_name} {"" if function.is_shadowed else "(*)"}') for node in function.nodes: if node.expression: print('\t\tExpression: {}'.format(node.expression)) diff --git a/slither/solc_parsing/declarations/contract.py b/slither/solc_parsing/declarations/contract.py index b1c4cd83c..b667d51f1 100644 --- a/slither/solc_parsing/declarations/contract.py +++ b/slither/solc_parsing/declarations/contract.py @@ -290,7 +290,7 @@ class ContractSolc04(Contract): elements_no_params = self._modifiers_no_params getter = lambda f: f.modifiers - getter_available = lambda f: f.available_modifiers_as_dict().items() + getter_available = lambda f: f.modifiers_declared Cls = ModifierSolc self._modifiers = self._analyze_params_elements(elements_no_params, getter, getter_available, Cls) @@ -302,7 +302,7 @@ class ContractSolc04(Contract): elements_no_params = self._functions_no_params getter = lambda f: f.functions - getter_available = lambda f: f.available_functions_as_dict().items() + getter_available = lambda f: f.functions_declared Cls = FunctionSolc self._functions = self._analyze_params_elements(elements_no_params, getter, getter_available, Cls) @@ -354,6 +354,7 @@ class ContractSolc04(Contract): for element in all_elements.values(): if accessible_elements[element.full_name] != all_elements[element.canonical_name]: element.is_shadowed = True + accessible_elements[element.full_name].shadows = True return all_elements diff --git a/slither/solc_parsing/expressions/expression_parsing.py b/slither/solc_parsing/expressions/expression_parsing.py index dc565c574..a3d48c7f2 100644 --- a/slither/solc_parsing/expressions/expression_parsing.py +++ b/slither/solc_parsing/expressions/expression_parsing.py @@ -117,18 +117,18 @@ def find_variable(var_name, caller_context, referenced_declaration=None, is_supe return conc_variables_ptr[var_name] if is_super: - getter_available = lambda f: f.available_functions_as_dict().items() + getter_available = lambda f: f.functions_declared d = {f.canonical_name:f for f in contract.functions} - functions = {f.full_name:f for f in contract.available_elements_from_inheritances(d, getter_available).values()} + functions = {f.full_name:f for f in contract_declarer.available_elements_from_inheritances(d, getter_available).values()} else: functions = contract.available_functions_as_dict() if var_name in functions: return functions[var_name] if is_super: - getter_available = lambda m: m.available_modifiers_as_dict().items() + getter_available = lambda m: m.modifiers_declared d = {m.canonical_name: m for m in contract.modifiers} - modifiers = {m.full_name: m for m in contract.available_elements_from_inheritances(d, getter_available).values()} + modifiers = {m.full_name: m for m in contract_declarer.available_elements_from_inheritances(d, getter_available).values()} else: modifiers = contract.available_modifiers_as_dict() if var_name in modifiers: diff --git a/slither/utils/inheritance_analysis.py b/slither/utils/inheritance_analysis.py index 013de531e..11f6e19d7 100644 --- a/slither/utils/inheritance_analysis.py +++ b/slither/utils/inheritance_analysis.py @@ -2,6 +2,7 @@ Detects various properties of inheritance in provided contracts. """ +from collections import defaultdict def detect_c3_function_shadowing(contract): """ @@ -9,112 +10,21 @@ def detect_c3_function_shadowing(contract): properties, despite not directly inheriting from each other. :param contract: The contract to check for potential C3 linearization shadowing within. - :return: A list of list of tuples: (contract, function), where each inner list describes colliding functions. - The later elements in the inner list overshadow the earlier ones. The contract-function pair's function does not - need to be defined in its paired contract, it may have been inherited within it. + :return: A dict (function winner -> [shadowed functions]) """ - # Loop through all contracts, and all underlying functions. - results = {} - for i in range(0, len(contract.immediate_inheritance) - 1): - inherited_contract1 = contract.immediate_inheritance[i] + targets = {f.full_name:f for f in contract.functions_inherited if f.shadows and not f.is_constructor + and not f.is_constructor_variables} - for function1 in inherited_contract1.functions_and_modifiers_declared: - # If this function has already be handled or is unimplemented, we skip it - if function1.full_name in results or function1.is_constructor or not function1.is_implemented: - continue - - # Define our list of function instances which overshadow each other. - functions_matching = [(inherited_contract1, function1)] - already_processed = set([function1]) - - # Loop again through other contracts and functions to compare to. - for x in range(i + 1, len(contract.immediate_inheritance)): - inherited_contract2 = contract.immediate_inheritance[x] - - # Loop for each function in this contract - for function2 in inherited_contract2.functions_and_modifiers: - # Skip this function if it is the last function that was shadowed. - if function2 in already_processed or function2.is_constructor or not function2.is_implemented: - continue - - # If this function does have the same full name, it is shadowing through C3 linearization. - if function1.full_name == function2.full_name: - functions_matching.append((inherited_contract2, function2)) - already_processed.add(function2) - - # If we have more than one definition matching the same signature, we add it to the results. - if len(functions_matching) > 1: - results[function1.full_name] = functions_matching - - return list(results.values()) + collisions = defaultdict(set) - -def detect_direct_function_shadowing(contract): - """ - Detects and obtains functions which are shadowed immediately by the provided ancestor contract. - :param contract: The ancestor contract which we check for function shadowing within. - :return: A list of tuples (overshadowing_function, overshadowed_immediate_base_contract, overshadowed_function) - -overshadowing_function is the function defined within the provided contract that overshadows another - definition. - -overshadowed_immediate_base_contract is the immediate inherited-from contract that provided the shadowed - function (could have provided it through inheritance, does not need to directly define it). - -overshadowed_function is the function definition which is overshadowed by the provided contract's definition. - """ - functions_declared = {function.full_name: function for function in contract.functions_and_modifiers_declared} - results = {} - for base_contract in reversed(contract.immediate_inheritance): - for base_function in base_contract.functions_and_modifiers: - - # We already found the most immediate shadowed definition for this function, skip to the next. - if base_function.full_name in results: + for contract_inherited in contract.immediate_inheritance: + for candidate in contract_inherited.functions: + if candidate.full_name not in targets or candidate.is_shadowed: continue - - # If this function is implemented and it collides with a definition in our immediate contract, we add - # it to our results. - if base_function.is_implemented and base_function.full_name in functions_declared: - results[base_function.full_name] = (functions_declared[base_function.full_name], base_contract, base_function) - - return list(results.values()) - - -def detect_function_shadowing(contracts, direct_shadowing=True, indirect_shadowing=True): - """ - Detects all overshadowing and overshadowed functions in the provided contracts. - :param contracts: The contracts to detect shadowing within. - :param direct_shadowing: Include results from direct inheritance/inheritance ancestry. - :param indirect_shadowing: Include results from indirect inheritance collisions as a result of multiple - inheritance/c3 linearization. - :return: Returns a set of tuples(contract_scope, overshadowing_contract, overshadowing_function, - overshadowed_contract, overshadowed_function), where: - -The contract_scope defines where the detection of shadowing is most immediately found. - -For each contract-function pair, contract is the first contract where the function is seen, while the function - refers to the actual definition. The function does not need to be defined in the contract (could be inherited). - """ - results = set() - for contract in contracts: - - # Detect immediate inheritance shadowing. - if direct_shadowing: - shadows = detect_direct_function_shadowing(contract) - for (overshadowing_function, overshadowed_base_contract, overshadowed_function) in shadows: - results.add((contract, contract, overshadowing_function, overshadowed_base_contract, - overshadowed_function)) - - # Detect c3 linearization shadowing (multi inheritance shadowing). - if indirect_shadowing: - shadows = detect_c3_function_shadowing(contract) - for colliding_functions in shadows: - for x in range(0, len(colliding_functions) - 1): - for y in range(x + 1, len(colliding_functions)): - # The same function definition can appear more than once in the inheritance chain, - # overshadowing items between, so it is important to remember to filter it out here. - if colliding_functions[y][1].contract_declarer != colliding_functions[x][1].contract_declarer: - results.add((contract, colliding_functions[y][0], colliding_functions[y][1], - colliding_functions[x][0], colliding_functions[x][1])) - - return results - + if targets[candidate.full_name].canonical_name != candidate.canonical_name: + collisions[targets[candidate.full_name]].add(candidate) + return collisions def detect_state_variable_shadowing(contracts): """