Skip to content

Commit aa932ef

Browse files
committed
cleanup code and add test
1 parent e2d0047 commit aa932ef

File tree

17 files changed

+112
-166
lines changed

17 files changed

+112
-166
lines changed

slither/core/declarations/function_contract.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ def is_declared_by(self, contract: "Contract") -> bool:
6565

6666
@property
6767
def file_scope(self) -> "FileScope":
68+
# This is the contract declarer's file scope because inherited functions have access
69+
# to the file scope which their declared in. This scope may contain references not
70+
# available in the child contract's scope. See inherited_function_scope.sol for an example.
6871
return self.contract_declarer.file_scope
6972

7073
# endregion

slither/core/declarations/using_for_top_level.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
from typing import TYPE_CHECKING, List, Dict, Union
1+
from typing import TYPE_CHECKING
22

3-
from slither.core.solidity_types.type import Type
43
from slither.core.declarations.top_level import TopLevel
54
from slither.utils.using_for import USING_FOR
65

slither/core/scope/scope.py

Lines changed: 7 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -60,62 +60,32 @@ def __init__(self, filename: Filename) -> None:
6060
def add_accessible_scopes(self) -> bool: # pylint: disable=too-many-branches
6161
"""
6262
Add information from accessible scopes. Return true if new information was obtained
63-
6463
:return:
6564
:rtype:
6665
"""
6766

6867
learn_something = False
69-
70-
# This is a hacky way to support using for directives on user defined types and user defined functions
71-
# since it is not reflected in the "exportedSymbols" field of the AST.
7268
for new_scope in self.accessible_scopes:
69+
# To support using for directives on user defined types and user defined functions,
70+
# we need to propagate the using for directives from the imported file to the importing file
71+
# since it is not reflected in the "exportedSymbols" field of the AST.
7372
if not new_scope.using_for_directives.issubset(self.using_for_directives):
7473
self.using_for_directives |= new_scope.using_for_directives
7574
learn_something = True
76-
print("using_for_directives", learn_something)
7775
if not _dict_contain(new_scope.type_aliases, self.type_aliases):
7876
self.type_aliases.update(new_scope.type_aliases)
7977
learn_something = True
8078
if not new_scope.functions.issubset(self.functions):
8179
self.functions |= new_scope.functions
8280
learn_something = True
8381

84-
# Hack to get around https://github.com/ethereum/solidity/pull/11881
82+
# To get around this bug for aliases https://github.com/ethereum/solidity/pull/11881,
83+
# we propagate the exported_symbols from the imported file to the importing file
84+
# See tests/e2e/solc_parsing/test_data/top-level-nested-import-0.7.1.sol
8585
if not new_scope.exported_symbols.issubset(self.exported_symbols):
8686
self.exported_symbols |= new_scope.exported_symbols
8787
learn_something = True
88-
# if not new_scope.imports.issubset(self.imports):
89-
# self.imports |= new_scope.imports
90-
# learn_something = True
91-
# if not _dict_contain(new_scope.contracts, self.contracts):
92-
# self.contracts.update(new_scope.contracts)
93-
# learn_something = True
94-
# if not new_scope.custom_errors.issubset(self.custom_errors):
95-
# self.custom_errors |= new_scope.custom_errors
96-
# learn_something = True
97-
# if not _dict_contain(new_scope.enums, self.enums):
98-
# self.enums.update(new_scope.enums)
99-
# learn_something = True
100-
# if not new_scope.events.issubset(self.events):
101-
# self.events |= new_scope.events
102-
# learn_something = True
103-
# if not new_scope.functions.issubset(self.functions):
104-
# self.functions |= new_scope.functions
105-
# learn_something = True
106-
# if not new_scope.using_for_directives.issubset(self.using_for_directives):
107-
# self.using_for_directives |= new_scope.using_for_directives
108-
# learn_something = True
109-
110-
# if not new_scope.pragmas.issubset(self.pragmas):
111-
# self.pragmas |= new_scope.pragmas
112-
# learn_something = True
113-
# if not _dict_contain(new_scope.structures, self.structures):
114-
# self.structures.update(new_scope.structures)
115-
# learn_something = True
116-
# if not _dict_contain(new_scope.variables, self.variables):
117-
# self.variables.update(new_scope.variables)
118-
# learn_something = True
88+
11989
# This is need to support aliasing when we do a late lookup using SolidityImportPlaceholder
12090
if not _dict_contain(new_scope.renaming, self.renaming):
12191
self.renaming.update(new_scope.renaming)

slither/slither.py

Lines changed: 49 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -27,28 +27,66 @@ def _check_common_things(
2727
) -> None:
2828

2929
if not issubclass(cls, base_cls) or cls is base_cls:
30-
raise Exception(
30+
raise SlitherError(
3131
f"You can't register {cls!r} as a {thing_name}. You need to pass a class that inherits from {base_cls.__name__}"
3232
)
3333

3434
if any(type(obj) == cls for obj in instances_list): # pylint: disable=unidiomatic-typecheck
35-
raise Exception(f"You can't register {cls!r} twice.")
35+
raise SlitherError(f"You can't register {cls!r} twice.")
3636

3737

38-
def _update_file_scopes(candidates: ValuesView[FileScope]):
38+
def _update_file_scopes(sol_parser: SlitherCompilationUnitSolc):
3939
"""
40-
Because solc's import allows cycle in the import
41-
We iterate until we aren't adding new information to the scope
42-
40+
Since all definitions in a file are exported by default, including definitions from its (transitive) dependencies,
41+
we can identify all top level items that could possibly be referenced within the file from its exportedSymbols.
42+
It is not as straightforward for user defined types and functions as well as aliasing. See add_accessible_scopes for more details.
4343
"""
44+
candidates = sol_parser.compilation_unit.scopes.values()
4445
learned_something = False
46+
# Because solc's import allows cycle in the import graph, iterate until we aren't adding new information to the scope.
4547
while True:
4648
for candidate in candidates:
4749
learned_something |= candidate.add_accessible_scopes()
4850
if not learned_something:
4951
break
5052
learned_something = False
5153

54+
for scope in candidates:
55+
for refId in scope.exported_symbols:
56+
if refId in sol_parser.contracts_by_id:
57+
contract = sol_parser.contracts_by_id[refId]
58+
scope.contracts[contract.name] = contract
59+
elif refId in sol_parser.functions_by_id:
60+
functions = sol_parser.functions_by_id[refId]
61+
assert len(functions) == 1
62+
function = functions[0]
63+
scope.functions.add(function)
64+
elif refId in sol_parser._imports_by_id:
65+
import_directive = sol_parser._imports_by_id[refId]
66+
scope.imports.add(import_directive)
67+
elif refId in sol_parser._top_level_variables_by_id:
68+
top_level_variable = sol_parser._top_level_variables_by_id[refId]
69+
scope.variables[top_level_variable.name] = top_level_variable
70+
elif refId in sol_parser._top_level_events_by_id:
71+
top_level_event = sol_parser._top_level_events_by_id[refId]
72+
scope.events.add(top_level_event)
73+
elif refId in sol_parser._top_level_structures_by_id:
74+
top_level_struct = sol_parser._top_level_structures_by_id[refId]
75+
scope.structures[top_level_struct.name] = top_level_struct
76+
elif refId in sol_parser._top_level_type_aliases_by_id:
77+
top_level_type_alias = sol_parser._top_level_type_aliases_by_id[refId]
78+
scope.type_aliases[top_level_type_alias.name] = top_level_type_alias
79+
elif refId in sol_parser._top_level_enums_by_id:
80+
top_level_enum = sol_parser._top_level_enums_by_id[refId]
81+
scope.enums[top_level_enum.name] = top_level_enum
82+
elif refId in sol_parser._top_level_errors_by_id:
83+
top_level_custom_error = sol_parser._top_level_errors_by_id[refId]
84+
scope.custom_errors.add(top_level_custom_error)
85+
else:
86+
logger.warning(
87+
f"Failed to resolved name for reference id {refId} in {scope.filename}."
88+
)
89+
5290

5391
class Slither(
5492
SlitherCore
@@ -118,61 +156,18 @@ def __init__(self, target: Union[str, CryticCompile], **kwargs) -> None:
118156
sol_parser.parse_top_level_items(ast, path)
119157
self.add_source_code(path)
120158

121-
_update_file_scopes(compilation_unit_slither.scopes.values())
122-
# First we save all the contracts in a dict
123-
# the key is the contractid
124159
for contract in sol_parser._underlying_contract_to_parser:
125160
if contract.name.startswith("SlitherInternalTopLevelContract"):
126-
raise Exception(
161+
raise SlitherError(
127162
# region multi-line-string
128163
"""Your codebase has a contract named 'SlitherInternalTopLevelContract'.
129164
Please rename it, this name is reserved for Slither's internals"""
130165
# endregion multi-line
131166
)
132167
sol_parser._contracts_by_id[contract.id] = contract
133168
sol_parser._compilation_unit.contracts.append(contract)
134-
print("avalilable")
135-
for k, v in sol_parser.contracts_by_id.items():
136-
print(k, v.name)
137-
for scope in compilation_unit_slither.scopes.values():
138-
for refId in scope.exported_symbols:
139-
print("scope", scope)
140-
print("target", refId)
141-
if refId in sol_parser.contracts_by_id:
142-
contract = sol_parser.contracts_by_id[refId]
143-
scope.contracts[contract.name] = contract
144-
elif refId in sol_parser.functions_by_id:
145-
print("found in functions")
146-
functions = sol_parser.functions_by_id[refId]
147-
assert len(functions) == 1
148-
function = functions[0]
149-
scope.functions.add(function)
150-
elif refId in sol_parser._imports_by_id:
151-
import_directive = sol_parser._imports_by_id[refId]
152-
scope.imports.add(import_directive)
153-
elif refId in sol_parser._top_level_variables_by_id:
154-
top_level_variable = sol_parser._top_level_variables_by_id[refId]
155-
scope.variables[top_level_variable.name] = top_level_variable
156-
elif refId in sol_parser._top_level_events_by_id:
157-
top_level_event = sol_parser._top_level_events_by_id[refId]
158-
scope.events.add(top_level_event)
159-
elif refId in sol_parser._top_level_structures_by_id:
160-
top_level_struct = sol_parser._top_level_structures_by_id[refId]
161-
scope.structures[top_level_struct.name] = top_level_struct
162-
elif refId in sol_parser._top_level_type_aliases_by_id:
163-
top_level_type_alias = sol_parser._top_level_type_aliases_by_id[refId]
164-
scope.type_aliases[top_level_type_alias.name] = top_level_type_alias
165-
elif refId in sol_parser._top_level_enums_by_id:
166-
top_level_enum = sol_parser._top_level_enums_by_id[refId]
167-
scope.enums[top_level_enum.name] = top_level_enum
168-
elif refId in sol_parser._top_level_errors_by_id:
169-
print("found in errors")
170-
top_level_custom_error = sol_parser._top_level_errors_by_id[refId]
171-
print(top_level_custom_error.name)
172-
scope.custom_errors.add(top_level_custom_error)
173-
else:
174-
print("not found", refId)
175-
assert False
169+
170+
_update_file_scopes(sol_parser)
176171

177172
if kwargs.get("generate_patches", False):
178173
self.generate_patches = True
@@ -208,7 +203,7 @@ def _init_parsing_and_analyses(self, skip_analyze: bool) -> None:
208203
for parser in self._parsers:
209204
try:
210205
parser.parse_contracts()
211-
except Exception as e:
206+
except SlitherError as e:
212207
if self.no_fail:
213208
continue
214209
raise e
@@ -218,7 +213,7 @@ def _init_parsing_and_analyses(self, skip_analyze: bool) -> None:
218213
for parser in self._parsers:
219214
try:
220215
parser.analyze_contracts()
221-
except Exception as e:
216+
except SlitherError as e:
222217
if self.no_fail:
223218
continue
224219
raise e

slither/slithir/convert.py

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -600,12 +600,6 @@ def propagate_types(ir: Operation, node: "Node"): # pylint: disable=too-many-lo
600600
using_for = node_function.contract.using_for_complete
601601
elif isinstance(node_function, FunctionTopLevel):
602602
using_for = node_function.using_for_complete
603-
# print("\n")
604-
# print("using_for", )
605-
# for key,v in using_for.items():
606-
# print("key",key, )
607-
# for i in v:
608-
# print("value",i,i.__class__ )
609603

610604
if isinstance(ir, OperationWithLValue) and ir.lvalue:
611605
# Force assignment in case of missing previous correct type
@@ -668,7 +662,6 @@ def propagate_types(ir: Operation, node: "Node"): # pylint: disable=too-many-lo
668662
ir, node_function.contract
669663
)
670664
if can_be_low_level(ir):
671-
print("can be low level")
672665
return convert_to_low_level(ir)
673666

674667
# Convert push operations
@@ -1509,7 +1502,6 @@ def convert_to_pop(ir: HighLevelCall, node: "Node") -> List[Operation]:
15091502

15101503

15111504
def look_for_library_or_top_level(
1512-
contract: Contract,
15131505
ir: HighLevelCall,
15141506
using_for,
15151507
t: Union[
@@ -1519,11 +1511,7 @@ def look_for_library_or_top_level(
15191511
TypeAliasTopLevel,
15201512
],
15211513
) -> Optional[Union[LibraryCall, InternalCall,]]:
1522-
print("look_for_library_or_top_level")
1523-
print(ir.expression.source_mapping.to_detailed_str())
1524-
print(ir.function_name)
15251514
for destination in using_for[t]:
1526-
print("destionation", destination, destination.__class__)
15271515
if isinstance(destination, FunctionTopLevel) and destination.name == ir.function_name:
15281516
arguments = [ir.destination] + ir.arguments
15291517
if (
@@ -1566,7 +1554,6 @@ def look_for_library_or_top_level(
15661554
new_ir = convert_type_library_call(lib_call, lib_contract)
15671555
if new_ir:
15681556
new_ir.set_node(ir.node)
1569-
print("new_ir", new_ir)
15701557
return new_ir
15711558
return None
15721559

@@ -1583,12 +1570,12 @@ def convert_to_library_or_top_level(
15831570
t = ir.destination.type
15841571

15851572
if t in using_for:
1586-
new_ir = look_for_library_or_top_level(contract, ir, using_for, t)
1573+
new_ir = look_for_library_or_top_level(ir, using_for, t)
15871574
if new_ir:
15881575
return new_ir
15891576

15901577
if "*" in using_for:
1591-
new_ir = look_for_library_or_top_level(contract, ir, using_for, "*")
1578+
new_ir = look_for_library_or_top_level(ir, using_for, "*")
15921579
if new_ir:
15931580
return new_ir
15941581

@@ -1599,7 +1586,7 @@ def convert_to_library_or_top_level(
15991586
and UserDefinedType(node.function.contract) in using_for
16001587
):
16011588
new_ir = look_for_library_or_top_level(
1602-
contract, ir, using_for, UserDefinedType(node.function.contract)
1589+
ir, using_for, UserDefinedType(node.function.contract)
16031590
)
16041591
if new_ir:
16051592
return new_ir
@@ -1754,9 +1741,6 @@ def convert_type_of_high_and_internal_level_call(
17541741
]
17551742

17561743
for import_statement in contract.file_scope.imports:
1757-
print(import_statement)
1758-
print(import_statement.alias)
1759-
print(ir.contract_name)
17601744
if (
17611745
import_statement.alias is not None
17621746
and import_statement.alias == ir.contract_name
@@ -1961,7 +1945,6 @@ def convert_constant_types(irs: List[Operation]) -> None:
19611945
if isinstance(func, StateVariable):
19621946
types = export_nested_types_from_variable(func)
19631947
else:
1964-
print("func", func, ir.expression.source_mapping.to_detailed_str())
19651948
types = [p.type for p in func.parameters]
19661949
assert len(types) == len(ir.arguments)
19671950
for idx, arg in enumerate(ir.arguments):

slither/solc_parsing/declarations/contract.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -585,11 +585,6 @@ def _analyze_params_elements( # pylint: disable=too-many-arguments,too-many-loc
585585
element.is_shadowed = True
586586
accessible_elements[element.full_name].shadows = True
587587
except (VariableNotFound, KeyError) as e:
588-
for c in self._contract.inheritance:
589-
print(c.name, c.id)
590-
for c2 in c.inheritance:
591-
print("\t", c2.name, c2.id)
592-
print("\n")
593588
self.log_incorrect_parsing(
594589
f"Missing params {e} {self._contract.source_mapping.to_detailed_str()}"
595590
)
@@ -626,11 +621,6 @@ def analyze_using_for(self) -> None: # pylint: disable=too-many-branches
626621
self._contract.using_for[type_name] = []
627622

628623
if "libraryName" in using_for:
629-
# print(using_for["libraryName"])
630-
# x =
631-
# for f in x.type.functions:
632-
633-
# assert isinstance(f, Function), x.__class__
634624
self._contract.using_for[type_name].append(
635625
parse_type(using_for["libraryName"], self)
636626
)
@@ -649,7 +639,6 @@ def analyze_using_for(self) -> None: # pylint: disable=too-many-branches
649639
old = "*"
650640
if old not in self._contract.using_for:
651641
self._contract.using_for[old] = []
652-
653642
self._contract.using_for[old].append(new)
654643
self._usingForNotParsed = []
655644
except (VariableNotFound, KeyError) as e:
@@ -689,7 +678,6 @@ def _check_aliased_import(
689678
def _analyze_top_level_function(self, function_name: str, type_name: USING_FOR_KEY) -> None:
690679
for tl_function in self.compilation_unit.functions_top_level:
691680
if tl_function.name == function_name:
692-
assert isinstance(tl_function, Function)
693681
self._contract.using_for[type_name].append(tl_function)
694682

695683
def _analyze_library_function(
@@ -703,7 +691,6 @@ def _analyze_library_function(
703691
if c.name == library_name:
704692
for f in c.functions:
705693
if f.name == function_name:
706-
assert isinstance(f, FunctionContract)
707694
self._contract.using_for[type_name].append(f)
708695
found = True
709696
break

slither/solc_parsing/declarations/using_for_top_level.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,7 @@ def analyze(self) -> None:
5151

5252
if self._library_name:
5353
library_name = parse_type(self._library_name, self)
54-
assert isinstance(library_name, UserDefinedType)
55-
# for f in library_name.type.functions:
5654
self._using_for.using_for[type_name].append(library_name)
57-
5855
self._propagate_global(type_name)
5956
else:
6057
for f in self._functions:

0 commit comments

Comments
 (0)