def __init__(self, var): ''' A variable can be declared through a statement, or directly. If it is through a statement, the following children may contain the init value. It may be possible that the variable is declared through a statement, but the init value is declared at the VariableDeclaration children level ''' super(VariableDeclarationSolc, self).__init__() self._was_analyzed = False self._elem_to_parse = None self._initializedNotParsed = None self._is_compact_ast = False self._reference_id = None if 'nodeType' in var: self._is_compact_ast = True nodeType = var['nodeType'] if nodeType in [ 'VariableDeclarationStatement', 'VariableDefinitionStatement' ]: if len(var['declarations']) > 1: raise MultipleVariablesDeclaration init = None if 'initialValue' in var: init = var['initialValue'] self._init_from_declaration(var['declarations'][0], init) elif nodeType == 'VariableDeclaration': self._init_from_declaration(var, var['value']) else: raise ParsingError( 'Incorrect variable declaration type {}'.format(nodeType)) else: nodeType = var['name'] if nodeType in [ 'VariableDeclarationStatement', 'VariableDefinitionStatement' ]: if len(var['children']) == 2: init = var['children'][1] elif len(var['children']) == 1: init = None elif len(var['children']) > 2: raise MultipleVariablesDeclaration else: raise ParsingError( 'Variable declaration without children?' + var) declaration = var['children'][0] self._init_from_declaration(declaration, init) elif nodeType == 'VariableDeclaration': self._init_from_declaration(var, None) else: raise ParsingError( 'Incorrect variable declaration type {}'.format(nodeType))
def __init__(self, variable: Variable, variable_data: Dict): """ A variable can be declared through a statement, or directly. If it is through a statement, the following children may contain the init value. It may be possible that the variable is declared through a statement, but the init value is declared at the VariableDeclaration children level """ self._variable = variable self._was_analyzed = False self._elem_to_parse = None self._initializedNotParsed = None self._is_compact_ast = False self._reference_id = None if "nodeType" in variable_data: self._is_compact_ast = True nodeType = variable_data["nodeType"] if nodeType in ["VariableDeclarationStatement", "VariableDefinitionStatement"]: if len(variable_data["declarations"]) > 1: raise MultipleVariablesDeclaration init = None if "initialValue" in variable_data: init = variable_data["initialValue"] self._init_from_declaration(variable_data["declarations"][0], init) elif nodeType == "VariableDeclaration": self._init_from_declaration(variable_data, variable_data["value"]) else: raise ParsingError("Incorrect variable declaration type {}".format(nodeType)) else: nodeType = variable_data["name"] if nodeType in ["VariableDeclarationStatement", "VariableDefinitionStatement"]: if len(variable_data["children"]) == 2: init = variable_data["children"][1] elif len(variable_data["children"]) == 1: init = None elif len(variable_data["children"]) > 2: raise MultipleVariablesDeclaration else: raise ParsingError( "Variable declaration without children?" + str(variable_data) ) declaration = variable_data["children"][0] self._init_from_declaration(declaration, init) elif nodeType == "VariableDeclaration": self._init_from_declaration(variable_data, False) else: raise ParsingError("Incorrect variable declaration type {}".format(nodeType))
def _parse_contract_items(self): if not self.get_children() in self._data: # empty contract return for item in self._data[self.get_children()]: if item[self.get_key()] == 'FunctionDefinition': self._functionsNotParsed.append(item) elif item[self.get_key()] == 'EventDefinition': self._eventsNotParsed.append(item) elif item[self.get_key()] == 'InheritanceSpecifier': # we dont need to parse it as it is redundant # with self.linearizedBaseContracts continue elif item[self.get_key()] == 'VariableDeclaration': self._variablesNotParsed.append(item) elif item[self.get_key()] == 'EnumDefinition': self._enumsNotParsed.append(item) elif item[self.get_key()] == 'ModifierDefinition': self._modifiersNotParsed.append(item) elif item[self.get_key()] == 'StructDefinition': self._structuresNotParsed.append(item) elif item[self.get_key()] == 'UsingForDirective': self._usingForNotParsed.append(item) else: raise ParsingError('Unknown contract item: ' + item[self.get_key()]) return
def _parse_contract_items(self): # pylint: disable=too-many-branches if not self.get_children() in self._data: # empty contract return for item in self._data[self.get_children()]: if item[self.get_key()] == "FunctionDefinition": self._functionsNotParsed.append(item) elif item[self.get_key()] == "EventDefinition": self._eventsNotParsed.append(item) elif item[self.get_key()] == "InheritanceSpecifier": # we dont need to parse it as it is redundant # with self.linearizedBaseContracts continue elif item[self.get_key()] == "VariableDeclaration": self._variablesNotParsed.append(item) elif item[self.get_key()] == "EnumDefinition": self._enumsNotParsed.append(item) elif item[self.get_key()] == "ModifierDefinition": self._modifiersNotParsed.append(item) elif item[self.get_key()] == "StructDefinition": self._structuresNotParsed.append(item) elif item[self.get_key()] == "UsingForDirective": self._usingForNotParsed.append(item) elif item[self.get_key()] == "ErrorDefinition": self._customErrorParsed.append(item) elif item[self.get_key()] == "UserDefinedValueTypeDefinition": self._parse_type_alias(item) else: raise ParsingError("Unknown contract item: " + item[self.get_key()]) return
def _fix_break_node(self, node): end_node = self._find_end_loop(node, [], 0) if not end_node: raise ParsingError('Break in no-loop context {}'.format(node)) for son in node.sons: son.remove_father(node) node.set_sons([end_node]) end_node.add_father(node)
def _fix_continue_node(self, node): start_node = self._find_start_loop(node, []) if not start_node: raise ParsingError('Continue in no-loop context {}'.format(node.nodeId())) for son in node.sons: son.remove_father(node) node.set_sons([start_node]) start_node.add_father(node)
def _parse_try_catch(self, statement, node): externalCall = statement.get('externalCall', None) if externalCall is None: raise ParsingError('Try/Catch not correctly parsed by Slither %s' % statement) new_node = self._new_node(NodeType.TRY, statement['src']) new_node.add_unparsed_expression(externalCall) link_nodes(node, new_node) node = new_node for clause in statement.get('clauses', []): self._parse_catch(clause, node) return node
def _fix_break_node(self, node): end_node = self._find_end_loop(node, [], 0) if not end_node: # If there is not end condition on the loop # The exploration will reach a STARTLOOP before reaching the endloop # We start with -1 as counter to catch this corner case end_node = self._find_end_loop(node, [], -1) if not end_node: raise ParsingError('Break in no-loop context {}'.format( node.function)) for son in node.sons: son.remove_father(node) node.set_sons([end_node]) end_node.add_father(node)
def _filter_ternary(self): ternary_found = True while ternary_found: ternary_found = False for node in self.nodes: has_cond = HasConditional(node.expression) if has_cond.result(): st = SplitTernaryExpression(node.expression) condition = st.condition if not condition: raise ParsingError(f'Incorrect ternary conversion {node.expression} {node.source_mapping_str}') true_expr = st.true_expression false_expr = st.false_expression self.split_ternary_node(node, condition, true_expr, false_expr) ternary_found = True break
def _parse_catch(self, statement, node): block = statement.get('block', None) if block is None: raise ParsingError('Catch not correctly parsed by Slither %s' % statement) try_node = self._new_node(NodeType.CATCH, statement['src']) link_nodes(node, try_node) if self.is_compact_ast: params = statement['parameters'] else: params = statement[self.get_children('children')] for param in params.get('parameters', []): assert param[self.get_key()] == 'VariableDeclaration' self._add_param(param) return self._parse_statement(block, try_node)
def find_variable(var_name, caller_context, referenced_declaration=None): if isinstance(caller_context, Contract): function = None contract = caller_context elif isinstance(caller_context, Function): function = caller_context contract = function.contract else: raise ParsingError('Incorrect caller context') if function: # We look for variable declared with the referencedDeclaration attr func_variables = function.variables_renamed if referenced_declaration and referenced_declaration in func_variables: return func_variables[referenced_declaration] # If not found, check for name func_variables = function.variables_as_dict() if var_name in func_variables: return func_variables[var_name] # A local variable can be a pointer # for example # function test(function(uint) internal returns(bool) t) interna{ # Will have a local variable t which will match the signature # t(uint256) func_variables_ptr = { get_pointer_name(f): f for f in function.variables } if var_name and var_name in func_variables_ptr: return func_variables_ptr[var_name] contract_variables = contract.variables_as_dict() if var_name in contract_variables: return contract_variables[var_name] # A state variable can be a pointer conc_variables_ptr = {get_pointer_name(f): f for f in contract.variables} if var_name and var_name in conc_variables_ptr: return conc_variables_ptr[var_name] functions = contract.functions_as_dict() if var_name in functions: return functions[var_name] modifiers = contract.modifiers_as_dict() if var_name in modifiers: return modifiers[var_name] structures = contract.structures_as_dict() if var_name in structures: return structures[var_name] events = contract.events_as_dict() if var_name in events: return events[var_name] enums = contract.enums_as_dict() if var_name in enums: return enums[var_name] # If the enum is refered as its name rather than its canonicalName enums = {e.name: e for e in contract.enums} if var_name in enums: return enums[var_name] # Could refer to any enum all_enums = [c.enums_as_dict() for c in contract.slither.contracts] all_enums = {k: v for d in all_enums for k, v in d.items()} if var_name in all_enums: return all_enums[var_name] if var_name in SOLIDITY_VARIABLES: return SolidityVariable(var_name) if var_name in SOLIDITY_FUNCTIONS: return SolidityFunction(var_name) contracts = contract.slither.contracts_as_dict() if var_name in contracts: return contracts[var_name] if referenced_declaration: for contract in contract.slither.contracts: if contract.id == referenced_declaration: return contract for function in contract.slither.functions: if function.referenced_declaration == referenced_declaration: return function raise VariableNotFound('Variable not found: {} (context {})'.format( var_name, caller_context))
def parse_expression(expression, caller_context): """ Returns: str: expression """ # Expression # = Expression ('++' | '--') # | NewExpression # | IndexAccess # | MemberAccess # | FunctionCall # | '(' Expression ')' # | ('!' | '~' | 'delete' | '++' | '--' | '+' | '-') Expression # | Expression '**' Expression # | Expression ('*' | '/' | '%') Expression # | Expression ('+' | '-') Expression # | Expression ('<<' | '>>') Expression # | Expression '&' Expression # | Expression '^' Expression # | Expression '|' Expression # | Expression ('<' | '>' | '<=' | '>=') Expression # | Expression ('==' | '!=') Expression # | Expression '&&' Expression # | Expression '||' Expression # | Expression '?' Expression ':' Expression # | Expression ('=' | '|=' | '^=' | '&=' | '<<=' | '>>=' | '+=' | '-=' | '*=' | '/=' | '%=') Expression # | PrimaryExpression # The AST naming does not follow the spec name = expression[caller_context.get_key()] is_compact_ast = caller_context.is_compact_ast if name == 'UnaryOperation': if is_compact_ast: attributes = expression else: attributes = expression['attributes'] assert 'prefix' in attributes operation_type = UnaryOperationType.get_type(attributes['operator'], attributes['prefix']) if is_compact_ast: expression = parse_expression(expression['subExpression'], caller_context) else: assert len(expression['children']) == 1 expression = parse_expression(expression['children'][0], caller_context) unary_op = UnaryOperation(expression, operation_type) return unary_op elif name == 'BinaryOperation': if is_compact_ast: attributes = expression else: attributes = expression['attributes'] operation_type = BinaryOperationType.get_type(attributes['operator']) if is_compact_ast: left_expression = parse_expression(expression['leftExpression'], caller_context) right_expression = parse_expression(expression['rightExpression'], caller_context) else: assert len(expression['children']) == 2 left_expression = parse_expression(expression['children'][0], caller_context) right_expression = parse_expression(expression['children'][1], caller_context) binary_op = BinaryOperation(left_expression, right_expression, operation_type) return binary_op elif name == 'FunctionCall': return parse_call(expression, caller_context) elif name == 'TupleExpression': """ For expression like (a,,c) = (1,2,3) the AST provides only two children in the left side We check the type provided (tuple(uint256,,uint256)) To determine that there is an empty variable Otherwhise we would not be able to determine that a = 1, c = 3, and 2 is lost Note: this is only possible with Solidity >= 0.4.12 """ if is_compact_ast: expressions = [ parse_expression(e, caller_context) if e else None for e in expression['components'] ] else: if 'children' not in expression: attributes = expression['attributes'] components = attributes['components'] expressions = [ parse_expression(c, caller_context) if c else None for c in components ] else: expressions = [ parse_expression(e, caller_context) for e in expression['children'] ] # Add none for empty tuple items if "attributes" in expression: if "type" in expression['attributes']: t = expression['attributes']['type'] if ',,' in t or '(,' in t or ',)' in t: t = t[len('tuple('):-1] elems = t.split(',') for idx in range(len(elems)): if elems[idx] == '': expressions.insert(idx, None) t = TupleExpression(expressions) return t elif name == 'Conditional': if is_compact_ast: if_expression = parse_expression(expression['condition'], caller_context) then_expression = parse_expression(expression['trueExpression'], caller_context) else_expression = parse_expression(expression['falseExpression'], caller_context) else: children = expression['children'] assert len(children) == 3 if_expression = parse_expression(children[0], caller_context) then_expression = parse_expression(children[1], caller_context) else_expression = parse_expression(children[2], caller_context) conditional = ConditionalExpression(if_expression, then_expression, else_expression) return conditional elif name == 'Assignment': if is_compact_ast: left_expression = parse_expression(expression['leftHandSide'], caller_context) right_expression = parse_expression(expression['rightHandSide'], caller_context) operation_type = AssignmentOperationType.get_type( expression['operator']) operation_return_type = expression['typeDescriptions'][ 'typeString'] else: attributes = expression['attributes'] children = expression['children'] assert len(expression['children']) == 2 left_expression = parse_expression(children[0], caller_context) right_expression = parse_expression(children[1], caller_context) operation_type = AssignmentOperationType.get_type( attributes['operator']) operation_return_type = attributes['type'] assignement = AssignmentOperation(left_expression, right_expression, operation_type, operation_return_type) return assignement elif name == 'Literal': assert 'children' not in expression if is_compact_ast: value = expression['value'] if value: if 'subdenomination' in expression and expression[ 'subdenomination']: value = str( convert_subdenomination(value, expression['subdenomination'])) elif not value and value != "": value = '0x' + expression['hexValue'] type = expression['typeDescriptions']['typeString'] # Length declaration for array was None until solc 0.5.5 if type is None: if expression['kind'] == 'number': type = 'int_const' else: value = expression['attributes']['value'] if value: if 'subdenomination' in expression['attributes'] and expression[ 'attributes']['subdenomination']: value = str( convert_subdenomination( value, expression['attributes']['subdenomination'])) elif value is None: # for literal declared as hex # see https://solidity.readthedocs.io/en/v0.4.25/types.html?highlight=hex#hexadecimal-literals assert 'hexvalue' in expression['attributes'] value = '0x' + expression['attributes']['hexvalue'] type = expression['attributes']['type'] if type is None: if value.isdecimal(): type = ElementaryType('uint256') else: type = ElementaryType('string') elif type.startswith('int_const '): type = ElementaryType('uint256') elif type.startswith('bool'): type = ElementaryType('bool') elif type.startswith('address'): type = ElementaryType('address') else: type = ElementaryType('string') literal = Literal(value, type) return literal elif name == 'Identifier': assert 'children' not in expression t = None if caller_context.is_compact_ast: value = expression['name'] t = expression['typeDescriptions']['typeString'] else: value = expression['attributes']['value'] if 'type' in expression['attributes']: t = expression['attributes']['type'] if t: found = re.findall( '[struct|enum|function|modifier] \(([\[\] ()a-zA-Z0-9\.,_]*)\)', t) assert len(found) <= 1 if found: value = value + '(' + found[0] + ')' value = filter_name(value) if 'referencedDeclaration' in expression: referenced_declaration = expression['referencedDeclaration'] else: referenced_declaration = None var = find_variable(value, caller_context, referenced_declaration) identifier = Identifier(var) return identifier elif name == 'IndexAccess': if is_compact_ast: index_type = expression['typeDescriptions']['typeString'] left = expression['baseExpression'] right = expression['indexExpression'] else: index_type = expression['attributes']['type'] children = expression['children'] assert len(children) == 2 left = children[0] right = children[1] # IndexAccess is used to describe ElementaryTypeNameExpression # if abi.decode is used # For example, abi.decode(data, ...(uint[]) ) if right is None: return _parse_elementary_type_name_expression( left, is_compact_ast, caller_context) left_expression = parse_expression(left, caller_context) right_expression = parse_expression(right, caller_context) index = IndexAccess(left_expression, right_expression, index_type) return index elif name == 'MemberAccess': if caller_context.is_compact_ast: member_name = expression['memberName'] member_type = expression['typeDescriptions']['typeString'] member_expression = parse_expression(expression['expression'], caller_context) else: member_name = expression['attributes']['member_name'] member_type = expression['attributes']['type'] children = expression['children'] assert len(children) == 1 member_expression = parse_expression(children[0], caller_context) if str(member_expression) == 'super': super_name = parse_super_name(expression, is_compact_ast) if isinstance(caller_context, Contract): inheritance = caller_context.inheritance else: assert isinstance(caller_context, Function) inheritance = caller_context.contract.inheritance var = None for father in inheritance: try: var = find_variable(super_name, father) break except VariableNotFound: continue if var is None: raise VariableNotFound( 'Variable not found: {}'.format(super_name)) return SuperIdentifier(var) member_access = MemberAccess(member_name, member_type, member_expression) if str(member_access) in SOLIDITY_VARIABLES_COMPOSED: return Identifier(SolidityVariableComposed(str(member_access))) return member_access elif name == 'ElementaryTypeNameExpression': return _parse_elementary_type_name_expression(expression, is_compact_ast, caller_context) # NewExpression is not a root expression, it's always the child of another expression elif name == 'NewExpression': if is_compact_ast: type_name = expression['typeName'] else: children = expression['children'] assert len(children) == 1 type_name = children[0] if type_name[caller_context.get_key()] == 'ArrayTypeName': depth = 0 while type_name[caller_context.get_key()] == 'ArrayTypeName': # Note: dont conserve the size of the array if provided # We compute it directly if is_compact_ast: type_name = type_name['baseType'] else: type_name = type_name['children'][0] depth += 1 if type_name[caller_context.get_key()] == 'ElementaryTypeName': if is_compact_ast: array_type = ElementaryType(type_name['name']) else: array_type = ElementaryType( type_name['attributes']['name']) elif type_name[caller_context.get_key()] == 'UserDefinedTypeName': if is_compact_ast: array_type = parse_type(UnknownType(type_name['name']), caller_context) else: array_type = parse_type( UnknownType(type_name['attributes']['name']), caller_context) elif type_name[caller_context.get_key()] == 'FunctionTypeName': array_type = parse_type(type_name, caller_context) else: raise ParsingError('Incorrect type array {}'.format(type_name)) array = NewArray(depth, array_type) return array if type_name[caller_context.get_key()] == 'ElementaryTypeName': if is_compact_ast: elem_type = ElementaryType(type_name['name']) else: elem_type = ElementaryType(type_name['attributes']['name']) new_elem = NewElementaryType(elem_type) return new_elem assert type_name[caller_context.get_key()] == 'UserDefinedTypeName' if is_compact_ast: contract_name = type_name['name'] else: contract_name = type_name['attributes']['name'] new = NewContract(contract_name) return new elif name == 'ModifierInvocation': if is_compact_ast: called = parse_expression(expression['modifierName'], caller_context) arguments = [] if expression['arguments']: arguments = [ parse_expression(a, caller_context) for a in expression['arguments'] ] else: children = expression['children'] called = parse_expression(children[0], caller_context) arguments = [ parse_expression(a, caller_context) for a in children[1::] ] call = CallExpression(called, arguments, 'Modifier') return call raise ParsingError('Expression not parsed %s' % name)
def log_incorrect_parsing(self, error): if self._contract.slither.disallow_partial: raise ParsingError(error) LOGGER.error(error) self._contract.is_incorrectly_parsed = True
def find_variable(var_name, caller_context, referenced_declaration=None, is_super=False): # variable are looked from the contract declarer # functions can be shadowed, but are looked from the contract instance, rather than the contract declarer # the difference between function and variable come from the fact that an internal call, or an variable access # in a function does not behave similariy, for example in: # contract C{ # function f(){ # state_var = 1 # f2() # } # state_var will refer to C.state_var, no mater if C is inherited # while f2() will refer to the function definition of the inherited contract (C.f2() in the context of C, or # the contract inheriting from C) # for events it's unclear what should be the behavior, as they can be shadowed, but there is not impact # structure/enums cannot be shadowed if isinstance(caller_context, Contract): function = None contract = caller_context contract_declarer = caller_context elif isinstance(caller_context, Function): function = caller_context contract = function.contract contract_declarer = function.contract_declarer else: raise ParsingError('Incorrect caller context') if function: # We look for variable declared with the referencedDeclaration attr func_variables = function.variables_renamed if referenced_declaration and referenced_declaration in func_variables: return func_variables[referenced_declaration] # If not found, check for name func_variables = function.variables_as_dict() if var_name in func_variables: return func_variables[var_name] # A local variable can be a pointer # for example # function test(function(uint) internal returns(bool) t) interna{ # Will have a local variable t which will match the signature # t(uint256) func_variables_ptr = { get_pointer_name(f): f for f in function.variables } if var_name and var_name in func_variables_ptr: return func_variables_ptr[var_name] # variable are looked from the contract declarer contract_variables = contract_declarer.variables_as_dict() if var_name in contract_variables: return contract_variables[var_name] # A state variable can be a pointer conc_variables_ptr = { get_pointer_name(f): f for f in contract_declarer.variables } if var_name and var_name in conc_variables_ptr: return conc_variables_ptr[var_name] if is_super: 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_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.modifiers_declared d = {m.canonical_name: m for m in contract.modifiers} 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: return modifiers[var_name] # structures are looked on the contract declarer structures = contract.structures_as_dict() if var_name in structures: return structures[var_name] events = contract.events_as_dict() if var_name in events: return events[var_name] enums = contract.enums_as_dict() if var_name in enums: return enums[var_name] # If the enum is refered as its name rather than its canonicalName enums = {e.name: e for e in contract.enums} if var_name in enums: return enums[var_name] # Could refer to any enum all_enums = [c.enums_as_dict() for c in contract.slither.contracts] all_enums = {k: v for d in all_enums for k, v in d.items()} if var_name in all_enums: return all_enums[var_name] if var_name in SOLIDITY_VARIABLES: return SolidityVariable(var_name) if var_name in SOLIDITY_FUNCTIONS: return SolidityFunction(var_name) contracts = contract.slither.contracts_as_dict() if var_name in contracts: return contracts[var_name] if referenced_declaration: for contract in contract.slither.contracts: if contract.id == referenced_declaration: return contract for function in contract.slither.functions: if function.referenced_declaration == referenced_declaration: return function raise VariableNotFound('Variable not found: {} (context {})'.format( var_name, caller_context))
def parse_type( t: Union[Dict, UnknownType], caller_context: Union[CallerContextExpression, "SlitherCompilationUnitSolc"], ) -> Type: """ caller_context can be a SlitherCompilationUnitSolc because we recursively call the function and go up in the context's scope. If we are really lost we just go over the SlitherCompilationUnitSolc :param t: :type t: :param caller_context: :type caller_context: :return: :rtype: """ # local import to avoid circular dependency # pylint: disable=too-many-locals,too-many-branches,too-many-statements # pylint: disable=import-outside-toplevel from slither.solc_parsing.expressions.expression_parsing import parse_expression from slither.solc_parsing.variables.function_type_variable import FunctionTypeVariableSolc from slither.solc_parsing.declarations.contract import ContractSolc from slither.solc_parsing.declarations.function import FunctionSolc from slither.solc_parsing.declarations.custom_error import CustomErrorSolc from slither.solc_parsing.declarations.structure_top_level import StructureTopLevelSolc from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc from slither.solc_parsing.variables.top_level_variable import TopLevelVariableSolc sl: "SlitherCompilationUnit" renaming: Dict[str, str] user_defined_types: Dict[str, TypeAlias] # Note: for convenicence top level functions use the same parser than function in contract # but contract_parser is set to None if isinstance(caller_context, SlitherCompilationUnitSolc) or ( isinstance(caller_context, FunctionSolc) and caller_context.contract_parser is None): structures_direct_access: List["Structure"] if isinstance(caller_context, SlitherCompilationUnitSolc): sl = caller_context.compilation_unit next_context = caller_context renaming = {} user_defined_types = {} else: assert isinstance(caller_context, FunctionSolc) sl = caller_context.underlying_function.compilation_unit next_context = caller_context.slither_parser renaming = caller_context.underlying_function.file_scope.renaming user_defined_types = caller_context.underlying_function.file_scope.user_defined_types structures_direct_access = sl.structures_top_level all_structuress = [c.structures for c in sl.contracts] all_structures = [ item for sublist in all_structuress for item in sublist ] all_structures += structures_direct_access enums_direct_access = sl.enums_top_level all_enumss = [c.enums for c in sl.contracts] all_enums = [item for sublist in all_enumss for item in sublist] all_enums += enums_direct_access contracts = sl.contracts functions = [] elif isinstance( caller_context, (StructureTopLevelSolc, CustomErrorSolc, TopLevelVariableSolc)): if isinstance(caller_context, StructureTopLevelSolc): scope = caller_context.underlying_structure.file_scope elif isinstance(caller_context, TopLevelVariableSolc): scope = caller_context.underlying_variable.file_scope else: assert isinstance(caller_context, CustomErrorSolc) custom_error = caller_context.underlying_custom_error if isinstance(custom_error, CustomErrorTopLevel): scope = custom_error.file_scope else: assert isinstance(custom_error, CustomErrorContract) scope = custom_error.contract.file_scope next_context = caller_context.slither_parser structures_direct_access = list(scope.structures.values()) all_structuress = [c.structures for c in scope.contracts.values()] all_structures = [ item for sublist in all_structuress for item in sublist ] all_structures += structures_direct_access enums_direct_access = [] all_enums = scope.enums.values() contracts = scope.contracts.values() functions = list(scope.functions) renaming = scope.renaming user_defined_types = scope.user_defined_types elif isinstance(caller_context, (ContractSolc, FunctionSolc)): if isinstance(caller_context, FunctionSolc): underlying_func = caller_context.underlying_function # If contract_parser is set to None, then underlying_function is a functionContract # See note above assert isinstance(underlying_func, FunctionContract) contract = underlying_func.contract next_context = caller_context.contract_parser scope = caller_context.underlying_function.file_scope else: contract = caller_context.underlying_contract next_context = caller_context scope = caller_context.underlying_contract.file_scope structures_direct_access = contract.structures structures_direct_access += contract.file_scope.structures.values() all_structuress = [ c.structures for c in contract.file_scope.contracts.values() ] all_structures = [ item for sublist in all_structuress for item in sublist ] all_structures += contract.file_scope.structures.values() enums_direct_access: List["Enum"] = contract.enums enums_direct_access += contract.file_scope.enums.values() all_enumss = [c.enums for c in contract.file_scope.contracts.values()] all_enums = [item for sublist in all_enumss for item in sublist] all_enums += contract.file_scope.enums.values() contracts = contract.file_scope.contracts.values() functions = contract.functions + contract.modifiers renaming = scope.renaming user_defined_types = scope.user_defined_types else: raise ParsingError(f"Incorrect caller context: {type(caller_context)}") is_compact_ast = caller_context.is_compact_ast if is_compact_ast: key = "nodeType" else: key = "name" if isinstance(t, UnknownType): name = t.name if name in renaming: name = renaming[name] if name in user_defined_types: return user_defined_types[name] return _find_from_type_name( name, functions, contracts, structures_direct_access, all_structures, enums_direct_access, all_enums, ) if t[key] == "ElementaryTypeName": if is_compact_ast: return ElementaryType(t["name"]) return ElementaryType(t["attributes"][key]) if t[key] == "UserDefinedTypeName": if is_compact_ast: name = t["typeDescriptions"]["typeString"] if name in renaming: name = renaming[name] if name in user_defined_types: return user_defined_types[name] return _find_from_type_name( name, functions, contracts, structures_direct_access, all_structures, enums_direct_access, all_enums, ) # Determine if we have a type node (otherwise we use the name node, as some older solc did not have 'type'). type_name_key = "type" if "type" in t["attributes"] else key name = t["attributes"][type_name_key] if name in renaming: name = renaming[name] if name in user_defined_types: return user_defined_types[name] return _find_from_type_name( name, functions, contracts, structures_direct_access, all_structures, enums_direct_access, all_enums, ) # Introduced with Solidity 0.8 if t[key] == "IdentifierPath": if is_compact_ast: name = t["name"] if name in renaming: name = renaming[name] if name in user_defined_types: return user_defined_types[name] return _find_from_type_name( name, functions, contracts, structures_direct_access, all_structures, enums_direct_access, all_enums, ) raise SlitherError("Solidity 0.8 not supported with the legacy AST") if t[key] == "ArrayTypeName": length = None if is_compact_ast: if t.get("length", None): length = parse_expression(t["length"], caller_context) array_type = parse_type(t["baseType"], next_context) else: if len(t["children"]) == 2: length = parse_expression(t["children"][1], caller_context) else: assert len(t["children"]) == 1 array_type = parse_type(t["children"][0], next_context) return ArrayType(array_type, length) if t[key] == "Mapping": if is_compact_ast: mappingFrom = parse_type(t["keyType"], next_context) mappingTo = parse_type(t["valueType"], next_context) else: assert len(t["children"]) == 2 mappingFrom = parse_type(t["children"][0], next_context) mappingTo = parse_type(t["children"][1], next_context) return MappingType(mappingFrom, mappingTo) if t[key] == "FunctionTypeName": if is_compact_ast: params = t["parameterTypes"] return_values = t["returnParameterTypes"] index = "parameters" else: assert len(t["children"]) == 2 params = t["children"][0] return_values = t["children"][1] index = "children" assert params[key] == "ParameterList" assert return_values[key] == "ParameterList" params_vars: List[FunctionTypeVariable] = [] return_values_vars: List[FunctionTypeVariable] = [] for p in params[index]: var = FunctionTypeVariable() var.set_offset(p["src"], caller_context.compilation_unit) var_parser = FunctionTypeVariableSolc(var, p) var_parser.analyze(caller_context) params_vars.append(var) for p in return_values[index]: var = FunctionTypeVariable() var.set_offset(p["src"], caller_context.compilation_unit) var_parser = FunctionTypeVariableSolc(var, p) var_parser.analyze(caller_context) return_values_vars.append(var) return FunctionType(params_vars, return_values_vars) raise ParsingError("Type name not found " + str(t))
def log_incorrect_parsing(self, error): if self._contract.compilation_unit.core.disallow_partial: raise ParsingError(error) LOGGER.error(error) self._contract.is_incorrectly_parsed = True
def parse_type(t, caller_context): # local import to avoid circular dependency from slither.solc_parsing.expressions.expression_parsing import parse_expression from slither.solc_parsing.variables.function_type_variable import FunctionTypeVariableSolc if isinstance(caller_context, Contract): contract = caller_context elif isinstance(caller_context, Function): contract = caller_context.contract else: raise ParsingError('Incorrect caller context') is_compact_ast = caller_context.is_compact_ast if is_compact_ast: key = 'nodeType' else: key = 'name' structures = contract.structures enums = contract.enums contracts = contract.slither.contracts if isinstance(t, UnknownType): return _find_from_type_name(t.name, contract, contracts, structures, enums) elif t[key] == 'ElementaryTypeName': if is_compact_ast: return ElementaryType(t['name']) return ElementaryType(t['attributes'][key]) elif t[key] == 'UserDefinedTypeName': if is_compact_ast: return _find_from_type_name(t['typeDescriptions']['typeString'], contract, contracts, structures, enums) # Determine if we have a type node (otherwise we use the name node, as some older solc did not have 'type'). type_name_key = 'type' if 'type' in t['attributes'] else key return _find_from_type_name(t['attributes'][type_name_key], contract, contracts, structures, enums) elif t[key] == 'ArrayTypeName': length = None if is_compact_ast: if t['length']: length = parse_expression(t['length'], caller_context) array_type = parse_type(t['baseType'], contract) else: if len(t['children']) == 2: length = parse_expression(t['children'][1], caller_context) else: assert len(t['children']) == 1 array_type = parse_type(t['children'][0], contract) return ArrayType(array_type, length) elif t[key] == 'Mapping': if is_compact_ast: mappingFrom = parse_type(t['keyType'], contract) mappingTo = parse_type(t['valueType'], contract) else: assert len(t['children']) == 2 mappingFrom = parse_type(t['children'][0], contract) mappingTo = parse_type(t['children'][1], contract) return MappingType(mappingFrom, mappingTo) elif t[key] == 'FunctionTypeName': if is_compact_ast: params = t['parameterTypes'] return_values = t['returnParameterTypes'] index = 'parameters' else: assert len(t['children']) == 2 params = t['children'][0] return_values = t['children'][1] index = 'children' assert params[key] == 'ParameterList' assert return_values[key] == 'ParameterList' params_vars = [] return_values_vars = [] for p in params[index]: var = FunctionTypeVariableSolc(p) var.set_offset(p['src'], caller_context.slither) var.analyze(caller_context) params_vars.append(var) for p in return_values[index]: var = FunctionTypeVariableSolc(p) var.set_offset(p['src'], caller_context.slither) var.analyze(caller_context) return_values_vars.append(var) return FunctionType(params_vars, return_values_vars) raise ParsingError('Type name not found ' + str(t))
def find_variable( var_name: str, caller_context: CallerContext, referenced_declaration: Optional[int] = None, is_super=False, ) -> Union[Variable, Function, Contract, SolidityVariable, SolidityFunction, Event, Enum, Structure]: from slither.solc_parsing.declarations.contract import ContractSolc from slither.solc_parsing.declarations.function import FunctionSolc # variable are looked from the contract declarer # functions can be shadowed, but are looked from the contract instance, rather than the contract declarer # the difference between function and variable come from the fact that an internal call, or an variable access # in a function does not behave similariy, for example in: # contract C{ # function f(){ # state_var = 1 # f2() # } # state_var will refer to C.state_var, no mater if C is inherited # while f2() will refer to the function definition of the inherited contract (C.f2() in the context of C, or # the contract inheriting from C) # for events it's unclear what should be the behavior, as they can be shadowed, but there is not impact # structure/enums cannot be shadowed if isinstance(caller_context, ContractSolc): function: Optional[FunctionSolc] = None contract = caller_context.underlying_contract contract_declarer = caller_context.underlying_contract elif isinstance(caller_context, FunctionSolc): function = caller_context contract = function.underlying_function.contract contract_declarer = function.underlying_function.contract_declarer else: raise ParsingError("Incorrect caller context") if function: # We look for variable declared with the referencedDeclaration attr func_variables = function.variables_renamed if referenced_declaration and referenced_declaration in func_variables: return func_variables[referenced_declaration].underlying_variable # If not found, check for name func_variables = function.underlying_function.variables_as_dict if var_name in func_variables: return func_variables[var_name] # A local variable can be a pointer # for example # function test(function(uint) internal returns(bool) t) interna{ # Will have a local variable t which will match the signature # t(uint256) func_variables_ptr = { get_pointer_name(f): f for f in function.underlying_function.variables } if var_name and var_name in func_variables_ptr: return func_variables_ptr[var_name] # variable are looked from the contract declarer contract_variables = contract_declarer.variables_as_dict if var_name in contract_variables: return contract_variables[var_name] # A state variable can be a pointer conc_variables_ptr = { get_pointer_name(f): f for f in contract_declarer.variables } if var_name and var_name in conc_variables_ptr: return conc_variables_ptr[var_name] if is_super: 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_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.modifiers_declared d = {m.canonical_name: m for m in contract.modifiers} 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: return modifiers[var_name] # structures are looked on the contract declarer structures = contract.structures_as_dict if var_name in structures: return structures[var_name] structures_top_level = contract.slither.top_level_structures for st in structures_top_level: if st.name == var_name: return st events = contract.events_as_dict if var_name in events: return events[var_name] enums = contract.enums_as_dict if var_name in enums: return enums[var_name] enums_top_level = contract.slither.top_level_enums for enum in enums_top_level: if enum.name == var_name: return enum # If the enum is refered as its name rather than its canonicalName enums = {e.name: e for e in contract.enums} if var_name in enums: return enums[var_name] # Could refer to any enum all_enums = [c.enums_as_dict for c in contract.slither.contracts] all_enums = {k: v for d in all_enums for k, v in d.items()} if var_name in all_enums: return all_enums[var_name] if var_name in SOLIDITY_VARIABLES: return SolidityVariable(var_name) if var_name in SOLIDITY_FUNCTIONS: return SolidityFunction(var_name) contracts = contract.slither.contracts_as_dict if var_name in contracts: return contracts[var_name] if referenced_declaration: # id of the contracts is the referenced declaration # This is not true for the functions, as we dont always have the referenced_declaration # But maybe we could? (TODO) for contract_candidate in contract.slither.contracts: if contract_candidate.id == referenced_declaration: return contract_candidate for function_candidate in caller_context.slither_parser.all_functions_parser: if function_candidate.referenced_declaration == referenced_declaration: return function_candidate.underlying_function raise VariableNotFound("Variable not found: {} (context {})".format( var_name, caller_context))
def parse_type(t: Union[Dict, UnknownType], caller_context): # local import to avoid circular dependency # pylint: disable=too-many-locals,too-many-branches,too-many-statements # pylint: disable=import-outside-toplevel from slither.solc_parsing.expressions.expression_parsing import parse_expression from slither.solc_parsing.variables.function_type_variable import FunctionTypeVariableSolc from slither.solc_parsing.declarations.contract import ContractSolc from slither.solc_parsing.declarations.function import FunctionSolc if isinstance(caller_context, ContractSolc): contract = caller_context.underlying_contract contract_parser = caller_context is_compact_ast = caller_context.is_compact_ast elif isinstance(caller_context, FunctionSolc): contract = caller_context.underlying_function.contract contract_parser = caller_context.contract_parser is_compact_ast = caller_context.is_compact_ast else: raise ParsingError(f"Incorrect caller context: {type(caller_context)}") if is_compact_ast: key = "nodeType" else: key = "name" structures = contract.structures + contract.slither.top_level_structures enums = contract.enums + contract.slither.top_level_enums contracts = contract.slither.contracts if isinstance(t, UnknownType): return _find_from_type_name(t.name, contract, contracts, structures, enums) if t[key] == "ElementaryTypeName": if is_compact_ast: return ElementaryType(t["name"]) return ElementaryType(t["attributes"][key]) if t[key] == "UserDefinedTypeName": if is_compact_ast: return _find_from_type_name( t["typeDescriptions"]["typeString"], contract, contracts, structures, enums, ) # Determine if we have a type node (otherwise we use the name node, as some older solc did not have 'type'). type_name_key = "type" if "type" in t["attributes"] else key return _find_from_type_name( t["attributes"][type_name_key], contract, contracts, structures, enums ) if t[key] == "ArrayTypeName": length = None if is_compact_ast: if t.get("length", None): length = parse_expression(t["length"], caller_context) array_type = parse_type(t["baseType"], contract_parser) else: if len(t["children"]) == 2: length = parse_expression(t["children"][1], caller_context) else: assert len(t["children"]) == 1 array_type = parse_type(t["children"][0], contract_parser) return ArrayType(array_type, length) if t[key] == "Mapping": if is_compact_ast: mappingFrom = parse_type(t["keyType"], contract_parser) mappingTo = parse_type(t["valueType"], contract_parser) else: assert len(t["children"]) == 2 mappingFrom = parse_type(t["children"][0], contract_parser) mappingTo = parse_type(t["children"][1], contract_parser) return MappingType(mappingFrom, mappingTo) if t[key] == "FunctionTypeName": if is_compact_ast: params = t["parameterTypes"] return_values = t["returnParameterTypes"] index = "parameters" else: assert len(t["children"]) == 2 params = t["children"][0] return_values = t["children"][1] index = "children" assert params[key] == "ParameterList" assert return_values[key] == "ParameterList" params_vars: List[FunctionTypeVariable] = [] return_values_vars: List[FunctionTypeVariable] = [] for p in params[index]: var = FunctionTypeVariable() var.set_offset(p["src"], caller_context.slither) var_parser = FunctionTypeVariableSolc(var, p) var_parser.analyze(caller_context) params_vars.append(var) for p in return_values[index]: var = FunctionTypeVariable() var.set_offset(p["src"], caller_context.slither) var_parser = FunctionTypeVariableSolc(var, p) var_parser.analyze(caller_context) return_values_vars.append(var) return FunctionType(params_vars, return_values_vars) raise ParsingError("Type name not found " + str(t))
def _parse_statement(self, statement, node): """ Return: node """ # Statement = IfStatement | WhileStatement | ForStatement | Block | InlineAssemblyStatement | # ( DoWhileStatement | PlaceholderStatement | Continue | Break | Return | # Throw | EmitStatement | SimpleStatement ) ';' # SimpleStatement = VariableDefinition | ExpressionStatement name = statement[self.get_key()] # SimpleStatement = VariableDefinition | ExpressionStatement if name == 'IfStatement': node = self._parse_if(statement, node) elif name == 'WhileStatement': node = self._parse_while(statement, node) elif name == 'ForStatement': node = self._parse_for(statement, node) elif name == 'Block': node = self._parse_block(statement, node) elif name == 'InlineAssembly': break_node = self._new_node(NodeType.ASSEMBLY, statement['src']) self._contains_assembly = True link_nodes(node, break_node) node = break_node elif name == 'DoWhileStatement': node = self._parse_dowhile(statement, node) # For Continue / Break / Return / Throw # The is fixed later elif name == 'Continue': continue_node = self._new_node(NodeType.CONTINUE, statement['src']) link_nodes(node, continue_node) node = continue_node elif name == 'Break': break_node = self._new_node(NodeType.BREAK, statement['src']) link_nodes(node, break_node) node = break_node elif name == 'Return': return_node = self._new_node(NodeType.RETURN, statement['src']) link_nodes(node, return_node) if self.is_compact_ast: if statement['expression']: return_node.add_unparsed_expression( statement['expression']) else: if self.get_children('children') in statement and statement[ self.get_children('children')]: assert len(statement[self.get_children('children')]) == 1 expression = statement[self.get_children('children')][0] return_node.add_unparsed_expression(expression) node = return_node elif name == 'Throw': throw_node = self._new_node(NodeType.THROW, statement['src']) link_nodes(node, throw_node) node = throw_node elif name == 'EmitStatement': #expression = parse_expression(statement[self.get_children('children')][0], self) if self.is_compact_ast: expression = statement['eventCall'] else: expression = statement[self.get_children('children')][0] new_node = self._new_node(NodeType.EXPRESSION, statement['src']) new_node.add_unparsed_expression(expression) link_nodes(node, new_node) node = new_node elif name in [ 'VariableDefinitionStatement', 'VariableDeclarationStatement' ]: node = self._parse_variable_definition(statement, node) elif name == 'ExpressionStatement': #assert len(statement[self.get_children('expression')]) == 1 #assert not 'attributes' in statement #expression = parse_expression(statement[self.get_children('children')][0], self) if self.is_compact_ast: expression = statement[self.get_children('expression')] else: expression = statement[self.get_children('expression')][0] new_node = self._new_node(NodeType.EXPRESSION, statement['src']) new_node.add_unparsed_expression(expression) link_nodes(node, new_node) node = new_node else: raise ParsingError('Statement not parsed %s' % name) return node
def parse_expression(expression: Dict, caller_context: CallerContext) -> "Expression": # pylint: disable=too-many-nested-blocks,too-many-statements """ Returns: str: expression """ # Expression # = Expression ('++' | '--') # | NewExpression # | IndexAccess # | MemberAccess # | FunctionCall # | '(' Expression ')' # | ('!' | '~' | 'delete' | '++' | '--' | '+' | '-') Expression # | Expression '**' Expression # | Expression ('*' | '/' | '%') Expression # | Expression ('+' | '-') Expression # | Expression ('<<' | '>>') Expression # | Expression '&' Expression # | Expression '^' Expression # | Expression '|' Expression # | Expression ('<' | '>' | '<=' | '>=') Expression # | Expression ('==' | '!=') Expression # | Expression '&&' Expression # | Expression '||' Expression # | Expression '?' Expression ':' Expression # | Expression ('=' | '|=' | '^=' | '&=' | '<<=' | '>>=' | '+=' | '-=' | '*=' | '/=' | '%=') Expression # | PrimaryExpression # The AST naming does not follow the spec name = expression[caller_context.get_key()] is_compact_ast = caller_context.is_compact_ast src = expression["src"] if name == "UnaryOperation": if is_compact_ast: attributes = expression else: attributes = expression["attributes"] assert "prefix" in attributes operation_type = UnaryOperationType.get_type(attributes["operator"], attributes["prefix"]) if is_compact_ast: expression = parse_expression(expression["subExpression"], caller_context) else: assert len(expression["children"]) == 1 expression = parse_expression(expression["children"][0], caller_context) unary_op = UnaryOperation(expression, operation_type) unary_op.set_offset(src, caller_context.slither) return unary_op if name == "BinaryOperation": if is_compact_ast: attributes = expression else: attributes = expression["attributes"] operation_type = BinaryOperationType.get_type(attributes["operator"]) if is_compact_ast: left_expression = parse_expression(expression["leftExpression"], caller_context) right_expression = parse_expression(expression["rightExpression"], caller_context) else: assert len(expression["children"]) == 2 left_expression = parse_expression(expression["children"][0], caller_context) right_expression = parse_expression(expression["children"][1], caller_context) binary_op = BinaryOperation(left_expression, right_expression, operation_type) binary_op.set_offset(src, caller_context.slither) return binary_op if name in "FunctionCall": return parse_call(expression, caller_context) if name == "FunctionCallOptions": # call/gas info are handled in parse_call if is_compact_ast: called = parse_expression(expression["expression"], caller_context) else: called = parse_expression(expression["children"][0], caller_context) assert isinstance(called, (MemberAccess, NewContract, Identifier, TupleExpression)) return called if name == "TupleExpression": # For expression like # (a,,c) = (1,2,3) # the AST provides only two children in the left side # We check the type provided (tuple(uint256,,uint256)) # To determine that there is an empty variable # Otherwhise we would not be able to determine that # a = 1, c = 3, and 2 is lost # # Note: this is only possible with Solidity >= 0.4.12 if is_compact_ast: expressions = [ parse_expression(e, caller_context) if e else None for e in expression["components"] ] else: if "children" not in expression: attributes = expression["attributes"] components = attributes["components"] expressions = [ parse_expression(c, caller_context) if c else None for c in components ] else: expressions = [parse_expression(e, caller_context) for e in expression["children"]] # Add none for empty tuple items if "attributes" in expression: if "type" in expression["attributes"]: t = expression["attributes"]["type"] if ",," in t or "(," in t or ",)" in t: t = t[len("tuple(") : -1] elems = t.split(",") for idx, _ in enumerate(elems): if elems[idx] == "": expressions.insert(idx, None) t = TupleExpression(expressions) t.set_offset(src, caller_context.slither) return t if name == "Conditional": if is_compact_ast: if_expression = parse_expression(expression["condition"], caller_context) then_expression = parse_expression(expression["trueExpression"], caller_context) else_expression = parse_expression(expression["falseExpression"], caller_context) else: children = expression["children"] assert len(children) == 3 if_expression = parse_expression(children[0], caller_context) then_expression = parse_expression(children[1], caller_context) else_expression = parse_expression(children[2], caller_context) conditional = ConditionalExpression(if_expression, then_expression, else_expression) conditional.set_offset(src, caller_context.slither) return conditional if name == "Assignment": if is_compact_ast: left_expression = parse_expression(expression["leftHandSide"], caller_context) right_expression = parse_expression(expression["rightHandSide"], caller_context) operation_type = AssignmentOperationType.get_type(expression["operator"]) operation_return_type = expression["typeDescriptions"]["typeString"] else: attributes = expression["attributes"] children = expression["children"] assert len(expression["children"]) == 2 left_expression = parse_expression(children[0], caller_context) right_expression = parse_expression(children[1], caller_context) operation_type = AssignmentOperationType.get_type(attributes["operator"]) operation_return_type = attributes["type"] assignement = AssignmentOperation( left_expression, right_expression, operation_type, operation_return_type ) assignement.set_offset(src, caller_context.slither) return assignement if name == "Literal": subdenomination = None assert "children" not in expression if is_compact_ast: value = expression["value"] if value: if "subdenomination" in expression and expression["subdenomination"]: subdenomination = expression["subdenomination"] elif not value and value != "": value = "0x" + expression["hexValue"] type_candidate = expression["typeDescriptions"]["typeString"] # Length declaration for array was None until solc 0.5.5 if type_candidate is None: if expression["kind"] == "number": type_candidate = "int_const" else: value = expression["attributes"]["value"] if value: if ( "subdenomination" in expression["attributes"] and expression["attributes"]["subdenomination"] ): subdenomination = expression["attributes"]["subdenomination"] elif value is None: # for literal declared as hex # see https://solidity.readthedocs.io/en/v0.4.25/types.html?highlight=hex#hexadecimal-literals assert "hexvalue" in expression["attributes"] value = "0x" + expression["attributes"]["hexvalue"] type_candidate = expression["attributes"]["type"] if type_candidate is None: if value.isdecimal(): type_candidate = ElementaryType("uint256") else: type_candidate = ElementaryType("string") elif type_candidate.startswith("int_const "): type_candidate = ElementaryType("uint256") elif type_candidate.startswith("bool"): type_candidate = ElementaryType("bool") elif type_candidate.startswith("address"): type_candidate = ElementaryType("address") else: type_candidate = ElementaryType("string") literal = Literal(value, type_candidate, subdenomination) literal.set_offset(src, caller_context.slither) return literal if name == "Identifier": assert "children" not in expression t = None if caller_context.is_compact_ast: value = expression["name"] t = expression["typeDescriptions"]["typeString"] else: value = expression["attributes"]["value"] if "type" in expression["attributes"]: t = expression["attributes"]["type"] if t: found = re.findall("[struct|enum|function|modifier] \(([\[\] ()a-zA-Z0-9\.,_]*)\)", t) assert len(found) <= 1 if found: value = value + "(" + found[0] + ")" value = filter_name(value) if "referencedDeclaration" in expression: referenced_declaration = expression["referencedDeclaration"] else: referenced_declaration = None var = find_variable(value, caller_context, referenced_declaration) identifier = Identifier(var) identifier.set_offset(src, caller_context.slither) return identifier if name == "IndexAccess": if is_compact_ast: index_type = expression["typeDescriptions"]["typeString"] left = expression["baseExpression"] right = expression.get("indexExpression", None) else: index_type = expression["attributes"]["type"] children = expression["children"] left = children[0] right = children[1] if len(children) > 1 else None # IndexAccess is used to describe ElementaryTypeNameExpression # if abi.decode is used # For example, abi.decode(data, ...(uint[]) ) if right is None: ret = parse_expression(left, caller_context) # Nested array are not yet available in abi.decode if isinstance(ret, ElementaryTypeNameExpression): old_type = ret.type ret.type = ArrayType(old_type, None) return ret left_expression = parse_expression(left, caller_context) right_expression = parse_expression(right, caller_context) index = IndexAccess(left_expression, right_expression, index_type) index.set_offset(src, caller_context.slither) return index if name == "MemberAccess": if caller_context.is_compact_ast: member_name = expression["memberName"] member_type = expression["typeDescriptions"]["typeString"] # member_type = parse_type( # UnknownType(expression["typeDescriptions"]["typeString"]), caller_context # ) member_expression = parse_expression(expression["expression"], caller_context) else: member_name = expression["attributes"]["member_name"] member_type = expression["attributes"]["type"] # member_type = parse_type(UnknownType(expression["attributes"]["type"]), caller_context) children = expression["children"] assert len(children) == 1 member_expression = parse_expression(children[0], caller_context) if str(member_expression) == "super": super_name = parse_super_name(expression, is_compact_ast) var = find_variable(super_name, caller_context, is_super=True) if var is None: raise VariableNotFound("Variable not found: {}".format(super_name)) sup = SuperIdentifier(var) sup.set_offset(src, caller_context.slither) return sup member_access = MemberAccess(member_name, member_type, member_expression) member_access.set_offset(src, caller_context.slither) if str(member_access) in SOLIDITY_VARIABLES_COMPOSED: id_idx = Identifier(SolidityVariableComposed(str(member_access))) id_idx.set_offset(src, caller_context.slither) return id_idx return member_access if name == "ElementaryTypeNameExpression": return _parse_elementary_type_name_expression(expression, is_compact_ast, caller_context) # NewExpression is not a root expression, it's always the child of another expression if name == "NewExpression": if is_compact_ast: type_name = expression["typeName"] else: children = expression["children"] assert len(children) == 1 type_name = children[0] if type_name[caller_context.get_key()] == "ArrayTypeName": depth = 0 while type_name[caller_context.get_key()] == "ArrayTypeName": # Note: dont conserve the size of the array if provided # We compute it directly if is_compact_ast: type_name = type_name["baseType"] else: type_name = type_name["children"][0] depth += 1 if type_name[caller_context.get_key()] == "ElementaryTypeName": if is_compact_ast: array_type = ElementaryType(type_name["name"]) else: array_type = ElementaryType(type_name["attributes"]["name"]) elif type_name[caller_context.get_key()] == "UserDefinedTypeName": if is_compact_ast: array_type = parse_type(UnknownType(type_name["name"]), caller_context) else: array_type = parse_type( UnknownType(type_name["attributes"]["name"]), caller_context ) elif type_name[caller_context.get_key()] == "FunctionTypeName": array_type = parse_type(type_name, caller_context) else: raise ParsingError("Incorrect type array {}".format(type_name)) array = NewArray(depth, array_type) array.set_offset(src, caller_context.slither) return array if type_name[caller_context.get_key()] == "ElementaryTypeName": if is_compact_ast: elem_type = ElementaryType(type_name["name"]) else: elem_type = ElementaryType(type_name["attributes"]["name"]) new_elem = NewElementaryType(elem_type) new_elem.set_offset(src, caller_context.slither) return new_elem assert type_name[caller_context.get_key()] == "UserDefinedTypeName" if is_compact_ast: # Changed introduced in Solidity 0.8 # see https://github.com/crytic/slither/issues/794 # TODO explore more the changes introduced in 0.8 and the usage of pathNode/IdentifierPath if "name" not in type_name: assert "pathNode" in type_name and "name" in type_name["pathNode"] contract_name = type_name["pathNode"]["name"] else: contract_name = type_name["name"] else: contract_name = type_name["attributes"]["name"] new = NewContract(contract_name) new.set_offset(src, caller_context.slither) return new if name == "ModifierInvocation": if is_compact_ast: called = parse_expression(expression["modifierName"], caller_context) arguments = [] if expression.get("arguments", None): arguments = [parse_expression(a, caller_context) for a in expression["arguments"]] else: children = expression["children"] called = parse_expression(children[0], caller_context) arguments = [parse_expression(a, caller_context) for a in children[1::]] call = CallExpression(called, arguments, "Modifier") call.set_offset(src, caller_context.slither) return call if name == "IndexRangeAccess": # For now, we convert array slices to a direct array access # As a result the generated IR will lose the slices information # As far as I understand, array slice are only used in abi.decode # https://solidity.readthedocs.io/en/v0.6.12/types.html # TODO: Investigate array slices usage and implication for the IR base = parse_expression(expression["baseExpression"], caller_context) return base # Introduced with solc 0.8 if name == "IdentifierPath": if caller_context.is_compact_ast: value = expression["name"] if "referencedDeclaration" in expression: referenced_declaration = expression["referencedDeclaration"] else: referenced_declaration = None var = find_variable(value, caller_context, referenced_declaration) identifier = Identifier(var) identifier.set_offset(src, caller_context.slither) return identifier raise ParsingError("IdentifierPath not currently supported for the legacy ast") raise ParsingError("Expression not parsed %s" % name)
def _find_from_type_name( # pylint: disable=too-many-locals,too-many-branches,too-many-statements,too-many-arguments name: str, functions_direct_access: List["Function"], contracts_direct_access: List["Contract"], structures_direct_access: List["Structure"], all_structures: List["Structure"], enums_direct_access: List["Enum"], all_enums: List["Enum"], ) -> Type: name_elementary = name.split(" ")[0] if "[" in name_elementary: name_elementary = name_elementary[0 : name_elementary.find("[")] if name_elementary in ElementaryTypeName: depth = name.count("[") if depth: return ArrayType(ElementaryType(name_elementary), Literal(depth, "uint256")) return ElementaryType(name_elementary) # We first look for contract # To avoid collision # Ex: a structure with the name of a contract name_contract = name if name_contract.startswith("contract "): name_contract = name_contract[len("contract ") :] if name_contract.startswith("library "): name_contract = name_contract[len("library ") :] var_type = next((c for c in contracts_direct_access if c.name == name_contract), None) if not var_type: var_type = next((st for st in structures_direct_access if st.name == name), None) if not var_type: var_type = next((e for e in enums_direct_access if e.name == name), None) if not var_type: # any contract can refer to another contract's enum enum_name = name if enum_name.startswith("enum "): enum_name = enum_name[len("enum ") :] elif enum_name.startswith("type(enum"): enum_name = enum_name[len("type(enum ") : -1] # 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) if not var_type: # any contract can refer to another contract's structure name_struct = name if name_struct.startswith("struct "): name_struct = name_struct[len("struct ") :] 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) # case where struct xxx.xx[] where not well formed in the AST if not var_type: depth = 0 while name_struct.endswith("[]"): name_struct = name_struct[0:-2] depth += 1 var_type = next((st for st in all_structures if st.canonical_name == name_struct), None) if var_type: return ArrayType(UserDefinedType(var_type), Literal(depth, "uint256")) if not var_type: var_type = next((f for f in functions_direct_access if f.name == name), None) if not var_type: if name.startswith("function "): found = re.findall( "function \(([ ()\[\]a-zA-Z0-9\.,]*?)\)(?: payable)?(?: (?:external|internal|pure|view))?(?: returns \(([a-zA-Z0-9() \.,]*)\))?", name, ) assert len(found) == 1 params = [v for v in found[0][0].split(",") if v != ""] return_values = ( [v for v in found[0][1].split(",") if v != ""] if len(found[0]) > 1 else [] ) params = [ _find_from_type_name( p, functions_direct_access, contracts_direct_access, structures_direct_access, all_structures, enums_direct_access, all_enums, ) for p in params ] return_values = [ _find_from_type_name( r, functions_direct_access, contracts_direct_access, structures_direct_access, all_structures, enums_direct_access, all_enums, ) for r in return_values ] params_vars = [] return_vars = [] for p in params: var = FunctionTypeVariable() var.set_type(p) params_vars.append(var) for r in return_values: var = FunctionTypeVariable() var.set_type(r) return_vars.append(var) return FunctionType(params_vars, return_vars) if not var_type: if name.startswith("mapping("): # nested mapping declared with var if name.count("mapping(") == 1: found = re.findall("mapping\(([a-zA-Z0-9\.]*) => ([ a-zA-Z0-9\.\[\]]*)\)", name) else: found = re.findall( "mapping\(([a-zA-Z0-9\.]*) => (mapping\([=> a-zA-Z0-9\.\[\]]*\))\)", name, ) assert len(found) == 1 from_ = found[0][0] to_ = found[0][1] from_type = _find_from_type_name( from_, functions_direct_access, contracts_direct_access, structures_direct_access, all_structures, enums_direct_access, all_enums, ) to_type = _find_from_type_name( to_, functions_direct_access, contracts_direct_access, structures_direct_access, all_structures, enums_direct_access, all_enums, ) return MappingType(from_type, to_type) if not var_type: raise ParsingError("Type not found " + str(name)) return UserDefinedType(var_type)
def _find_from_type_name(name, contract, contracts, structures, enums): name_elementary = name.split(' ')[0] if '[' in name_elementary: name_elementary = name_elementary[0:name_elementary.find('[')] if name_elementary in ElementaryTypeName: depth = name.count('[') if depth: return ArrayType(ElementaryType(name_elementary), Literal(depth, 'uint256')) else: return ElementaryType(name_elementary) # We first look for contract # To avoid collision # Ex: a structure with the name of a contract name_contract = name if name_contract.startswith('contract '): name_contract = name_contract[len('contract '):] if name_contract.startswith('library '): name_contract = name_contract[len('library '):] var_type = next((c for c in contracts if c.name == name_contract), None) if not var_type: var_type = next((st for st in structures if st.name == name), None) if not var_type: var_type = next((e for e in enums if e.name == name), None) if not var_type: # any contract can refer to another contract's enum enum_name = name if enum_name.startswith('enum '): 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] 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) if not var_type: # any contract can refer to another contract's structure name_struct = name if name_struct.startswith('struct '): name_struct = name_struct[len('struct '):] 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 ] 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) # case where struct xxx.xx[] where not well formed in the AST if not var_type: depth = 0 while name_struct.endswith('[]'): name_struct = name_struct[0:-2] depth += 1 var_type = next( (st for st in all_structures if st.canonical_name == name_struct), None) if var_type: return ArrayType(UserDefinedType(var_type), Literal(depth, 'uint256')) if not var_type: var_type = next((f for f in contract.functions if f.name == name), None) if not var_type: if name.startswith('function '): found = re.findall( 'function \(([ ()a-zA-Z0-9\.,]*)\) returns \(([a-zA-Z0-9\.,]*)\)', name) assert len(found) == 1 params = found[0][0].split(',') return_values = found[0][1].split(',') params = [ _find_from_type_name(p, contract, contracts, structures, enums) for p in params ] return_values = [ _find_from_type_name(r, contract, contracts, structures, enums) for r in return_values ] params_vars = [] return_vars = [] for p in params: var = FunctionTypeVariable() var.set_type(p) params_vars.append(var) for r in return_values: var = FunctionTypeVariable() var.set_type(r) return_vars.append(var) return FunctionType(params_vars, return_vars) if not var_type: if name.startswith('mapping('): # nested mapping declared with var if name.count('mapping(') == 1: found = re.findall( 'mapping\(([a-zA-Z0-9\.]*) => ([a-zA-Z0-9\.\[\]]*)\)', name) else: found = re.findall( 'mapping\(([a-zA-Z0-9\.]*) => (mapping\([=> a-zA-Z0-9\.\[\]]*\))\)', name) assert len(found) == 1 from_ = found[0][0] to_ = found[0][1] from_type = _find_from_type_name(from_, contract, contracts, structures, enums) to_type = _find_from_type_name(to_, contract, contracts, structures, enums) return MappingType(from_type, to_type) if not var_type: raise ParsingError('Type not found ' + str(name)) return UserDefinedType(var_type)
def parse_type(t: Union[Dict, UnknownType], caller_context): # local import to avoid circular dependency # pylint: disable=too-many-locals,too-many-branches,too-many-statements # pylint: disable=import-outside-toplevel from slither.solc_parsing.expressions.expression_parsing import parse_expression from slither.solc_parsing.variables.function_type_variable import FunctionTypeVariableSolc from slither.solc_parsing.declarations.contract import ContractSolc from slither.solc_parsing.declarations.function import FunctionSolc from slither.solc_parsing.slitherSolc import SlitherSolc # Note: for convenicence top level functions use the same parser than function in contract # but contract_parser is set to None if isinstance(caller_context, SlitherSolc) or ( isinstance(caller_context, FunctionSolc) and caller_context.contract_parser is None ): if isinstance(caller_context, SlitherSolc): sl = caller_context.core next_context = caller_context else: assert isinstance(caller_context, FunctionSolc) sl = caller_context.underlying_function.slither next_context = caller_context.slither_parser structures_direct_access = sl.structures_top_level all_structuress = [c.structures for c in sl.contracts] all_structures = [item for sublist in all_structuress for item in sublist] all_structures += structures_direct_access enums_direct_access = sl.enums_top_level all_enumss = [c.enums for c in sl.contracts] all_enums = [item for sublist in all_enumss for item in sublist] all_enums += enums_direct_access contracts = sl.contracts functions = [] elif isinstance(caller_context, (ContractSolc, FunctionSolc)): if isinstance(caller_context, FunctionSolc): underlying_func = caller_context.underlying_function # If contract_parser is set to None, then underlying_function is a functionContract # See note above assert isinstance(underlying_func, FunctionContract) contract = underlying_func.contract next_context = caller_context.contract_parser else: contract = caller_context.underlying_contract next_context = caller_context structures_direct_access = contract.structures + contract.slither.structures_top_level all_structuress = [c.structures for c in contract.slither.contracts] all_structures = [item for sublist in all_structuress for item in sublist] all_structures += contract.slither.structures_top_level enums_direct_access = contract.enums + contract.slither.enums_top_level all_enumss = [c.enums for c in contract.slither.contracts] all_enums = [item for sublist in all_enumss for item in sublist] all_enums += contract.slither.enums_top_level contracts = contract.slither.contracts functions = contract.functions + contract.modifiers else: raise ParsingError(f"Incorrect caller context: {type(caller_context)}") is_compact_ast = caller_context.is_compact_ast if is_compact_ast: key = "nodeType" else: key = "name" if isinstance(t, UnknownType): return _find_from_type_name( t.name, functions, contracts, structures_direct_access, all_structures, enums_direct_access, all_enums, ) if t[key] == "ElementaryTypeName": if is_compact_ast: return ElementaryType(t["name"]) return ElementaryType(t["attributes"][key]) if t[key] == "UserDefinedTypeName": if is_compact_ast: return _find_from_type_name( t["typeDescriptions"]["typeString"], functions, contracts, structures_direct_access, all_structures, enums_direct_access, all_enums, ) # Determine if we have a type node (otherwise we use the name node, as some older solc did not have 'type'). type_name_key = "type" if "type" in t["attributes"] else key return _find_from_type_name( t["attributes"][type_name_key], functions, contracts, structures_direct_access, all_structures, enums_direct_access, all_enums, ) if t[key] == "ArrayTypeName": length = None if is_compact_ast: if t.get("length", None): length = parse_expression(t["length"], caller_context) array_type = parse_type(t["baseType"], next_context) else: if len(t["children"]) == 2: length = parse_expression(t["children"][1], caller_context) else: assert len(t["children"]) == 1 array_type = parse_type(t["children"][0], next_context) return ArrayType(array_type, length) if t[key] == "Mapping": if is_compact_ast: mappingFrom = parse_type(t["keyType"], next_context) mappingTo = parse_type(t["valueType"], next_context) else: assert len(t["children"]) == 2 mappingFrom = parse_type(t["children"][0], next_context) mappingTo = parse_type(t["children"][1], next_context) return MappingType(mappingFrom, mappingTo) if t[key] == "FunctionTypeName": if is_compact_ast: params = t["parameterTypes"] return_values = t["returnParameterTypes"] index = "parameters" else: assert len(t["children"]) == 2 params = t["children"][0] return_values = t["children"][1] index = "children" assert params[key] == "ParameterList" assert return_values[key] == "ParameterList" params_vars: List[FunctionTypeVariable] = [] return_values_vars: List[FunctionTypeVariable] = [] for p in params[index]: var = FunctionTypeVariable() var.set_offset(p["src"], caller_context.slither) var_parser = FunctionTypeVariableSolc(var, p) var_parser.analyze(caller_context) params_vars.append(var) for p in return_values[index]: var = FunctionTypeVariable() var.set_offset(p["src"], caller_context.slither) var_parser = FunctionTypeVariableSolc(var, p) var_parser.analyze(caller_context) return_values_vars.append(var) return FunctionType(params_vars, return_values_vars) raise ParsingError("Type name not found " + str(t))