Skip to content

Commit 2d2c77a

Browse files
authored
Merge branch 'dev' into issue-1654
2 parents 00bd1d4 + 2c76c94 commit 2d2c77a

File tree

728 files changed

+670
-3962
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

728 files changed

+670
-3962
lines changed

slither/__main__.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ def process_single(
7979
ast = "--ast-json"
8080
slither = Slither(target, ast_format=ast, **vars(args))
8181

82+
if args.sarif_input:
83+
slither.sarif_input = args.sarif_input
84+
if args.sarif_triage:
85+
slither.sarif_triage = args.sarif_triage
86+
8287
return _process(slither, detector_classes, printer_classes)
8388

8489

@@ -469,6 +474,20 @@ def parse_args(
469474
default=defaults_flag_in_config["sarif"],
470475
)
471476

477+
group_misc.add_argument(
478+
"--sarif-input",
479+
help="Sarif input (beta)",
480+
action="store",
481+
default=defaults_flag_in_config["sarif_input"],
482+
)
483+
484+
group_misc.add_argument(
485+
"--sarif-triage",
486+
help="Sarif triage (beta)",
487+
action="store",
488+
default=defaults_flag_in_config["sarif_triage"],
489+
)
490+
472491
group_misc.add_argument(
473492
"--json-types",
474493
help="Comma-separated list of result types to output to JSON, defaults to "

slither/core/cfg/node.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ class NodeType(Enum):
7474
IF = "IF"
7575
VARIABLE = "NEW VARIABLE" # Variable declaration
7676
ASSEMBLY = "INLINE ASM"
77+
ENDASSEMBLY = "END INLINE ASM"
7778
IFLOOP = "IF_LOOP"
7879

7980
# Nodes where control flow merges
@@ -193,6 +194,8 @@ def __init__(
193194
self.file_scope: "FileScope" = file_scope
194195
self._function: Optional["Function"] = None
195196

197+
self._is_reachable: bool = False
198+
196199
###################################################################################
197200
###################################################################################
198201
# region General's properties
@@ -234,6 +237,13 @@ def set_function(self, function: "Function") -> None:
234237
def function(self) -> "Function":
235238
return self._function
236239

240+
@property
241+
def is_reachable(self) -> bool:
242+
return self._is_reachable
243+
244+
def set_is_reachable(self, new_is_reachable: bool) -> None:
245+
self._is_reachable = new_is_reachable
246+
237247
# endregion
238248
###################################################################################
239249
###################################################################################

slither/core/declarations/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@
2020
from .custom_error_contract import CustomErrorContract
2121
from .custom_error_top_level import CustomErrorTopLevel
2222
from .custom_error import CustomError
23+
from .solidity_import_placeholder import SolidityImportPlaceHolder

slither/core/declarations/contract.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ def __init__(self, compilation_unit: "SlitherCompilationUnit", scope: "FileScope
7878
# do not contain private variables inherited from contract
7979
self._variables: Dict[str, "StateVariable"] = {}
8080
self._variables_ordered: List["StateVariable"] = []
81+
# Reference id -> variable declaration (only available for compact AST)
82+
self._state_variables_by_ref_id: Dict[int, "StateVariable"] = {}
8183
self._modifiers: Dict[str, "Modifier"] = {}
8284
self._functions: Dict[str, "FunctionContract"] = {}
8385
self._linearizedBaseContracts: List[int] = []
@@ -404,6 +406,12 @@ def type_aliases_as_dict(self) -> Dict[str, "TypeAliasContract"]:
404406
# region Variables
405407
###################################################################################
406408
###################################################################################
409+
@property
410+
def state_variables_by_ref_id(self) -> Dict[int, "StateVariable"]:
411+
"""
412+
Returns the state variables by reference id (only available for compact AST).
413+
"""
414+
return self._state_variables_by_ref_id
407415

408416
@property
409417
def variables(self) -> List["StateVariable"]:

slither/core/dominators/utils.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,21 @@
99
def intersection_predecessor(node: "Node") -> Set["Node"]:
1010
if not node.fathers:
1111
return set()
12+
13+
# Revert PR1984
1214
ret = node.fathers[0].dominators
1315
for pred in node.fathers[1:]:
1416
ret = ret.intersection(pred.dominators)
17+
# if not any(father.is_reachable for father in node.fathers):
18+
# return set()
19+
#
20+
# ret = set()
21+
# for pred in node.fathers:
22+
# ret = ret.union(pred.dominators)
23+
#
24+
# for pred in node.fathers:
25+
# if pred.is_reachable:
26+
# ret = ret.intersection(pred.dominators)
1527
return ret
1628

1729

@@ -84,6 +96,9 @@ def compute_dominance_frontier(nodes: List["Node"]) -> None:
8496
for node in nodes:
8597
if len(node.fathers) >= 2:
8698
for father in node.fathers:
99+
# Revert PR1984
100+
# if not father.is_reachable:
101+
# continue
87102
runner = father
88103
# Corner case: if there is a if without else
89104
# we need to add update the conditional node

slither/core/slither_core.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from slither.core.source_mapping.source_mapping import SourceMapping, Source
2222
from slither.slithir.variables import Constant
2323
from slither.utils.colors import red
24+
from slither.utils.sarif import read_triage_info
2425
from slither.utils.source_mapping import get_definition, get_references, get_implementation
2526

2627
logger = logging.getLogger("Slither")
@@ -48,6 +49,10 @@ def __init__(self) -> None:
4849
self._source_code_to_line: Optional[Dict[str, List[str]]] = None
4950

5051
self._previous_results_filename: str = "slither.db.json"
52+
53+
# TODO: add cli flag to set these variables
54+
self.sarif_input: str = "export.sarif"
55+
self.sarif_triage: str = "export.sarif.sarifexplorer"
5156
self._results_to_hide: List = []
5257
self._previous_results: List = []
5358
# From triaged result
@@ -444,6 +449,8 @@ def valid_result(self, r: Dict) -> bool:
444449
return True
445450

446451
def load_previous_results(self) -> None:
452+
self.load_previous_results_from_sarif()
453+
447454
filename = self._previous_results_filename
448455
try:
449456
if os.path.isfile(filename):
@@ -453,9 +460,24 @@ def load_previous_results(self) -> None:
453460
for r in self._previous_results:
454461
if "id" in r:
455462
self._previous_results_ids.add(r["id"])
463+
456464
except json.decoder.JSONDecodeError:
457465
logger.error(red(f"Impossible to decode {filename}. Consider removing the file"))
458466

467+
def load_previous_results_from_sarif(self) -> None:
468+
sarif = pathlib.Path(self.sarif_input)
469+
triage = pathlib.Path(self.sarif_triage)
470+
471+
if not sarif.exists():
472+
return
473+
if not triage.exists():
474+
return
475+
476+
triaged = read_triage_info(sarif, triage)
477+
478+
for id_triaged in triaged:
479+
self._previous_results_ids.add(id_triaged)
480+
459481
def write_results_to_hide(self) -> None:
460482
if not self._results_to_hide:
461483
return

slither/printers/guidance/echidna.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
TypeConversion,
3131
)
3232
from slither.slithir.operations.binary import Binary
33-
from slither.slithir.variables import Constant
33+
from slither.slithir.variables import Constant, ReferenceVariable
3434
from slither.utils.output import Output
3535
from slither.visitors.expression.constants_folding import ConstantFolding, NotConstant
3636

@@ -217,19 +217,20 @@ def _extract_constants_from_irs( # pylint: disable=too-many-branches,too-many-n
217217
except ValueError: # index could fail; should never happen in working solidity code
218218
pass
219219
for r in ir.read:
220+
var_read = r.points_to_origin if isinstance(r, ReferenceVariable) else r
220221
# Do not report struct_name in a.struct_name
221222
if isinstance(ir, Member):
222223
continue
223-
if isinstance(r, Constant):
224-
all_cst_used.append(ConstantValue(str(r.value), str(r.type)))
225-
if isinstance(r, StateVariable):
226-
if r.node_initialization:
227-
if r.node_initialization.irs:
228-
if r.node_initialization in context_explored:
224+
if isinstance(var_read, Constant):
225+
all_cst_used.append(ConstantValue(str(var_read.value), str(var_read.type)))
226+
if isinstance(var_read, StateVariable):
227+
if var_read.node_initialization:
228+
if var_read.node_initialization.irs:
229+
if var_read.node_initialization in context_explored:
229230
continue
230-
context_explored.add(r.node_initialization)
231+
context_explored.add(var_read.node_initialization)
231232
_extract_constants_from_irs(
232-
r.node_initialization.irs,
233+
var_read.node_initialization.irs,
233234
all_cst_used,
234235
all_cst_used_in_binary,
235236
context_explored,

slither/solc_parsing/declarations/contract.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,8 @@ def parse_state_variables(self) -> None:
357357
self._variables_parser.append(var_parser)
358358

359359
assert var.name
360+
if var_parser.reference_id is not None:
361+
self._contract.state_variables_by_ref_id[var_parser.reference_id] = var
360362
self._contract.variables_as_dict[var.name] = var
361363
self._contract.add_variables_ordered([var])
362364

slither/solc_parsing/declarations/function.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,9 @@ def analyze_content(self) -> None:
315315

316316
self._remove_alone_endif()
317317

318+
if self._function.entry_point:
319+
self._update_reachability(self._function.entry_point)
320+
318321
# endregion
319322
###################################################################################
320323
###################################################################################
@@ -983,7 +986,9 @@ def _parse_statement(
983986
# technically, entrypoint and exitpoint are YulNodes and we should be returning a NodeSolc here
984987
# but they both expose an underlying_node so oh well
985988
link_underlying_nodes(node, entrypoint)
986-
node = exitpoint
989+
end_assembly = self._new_node(NodeType.ENDASSEMBLY, statement["src"], scope)
990+
link_underlying_nodes(exitpoint, end_assembly)
991+
node = end_assembly
987992
else:
988993
asm_node = self._new_node(NodeType.ASSEMBLY, statement["src"], scope)
989994
self._function.contains_assembly = True
@@ -1100,6 +1105,13 @@ def _parse_unchecked_block(self, block: Dict, node: NodeSolc, scope):
11001105
node = self._parse_statement(statement, node, new_scope)
11011106
return node
11021107

1108+
def _update_reachability(self, node: Node) -> None:
1109+
if node.is_reachable:
1110+
return
1111+
node.set_is_reachable(True)
1112+
for son in node.sons:
1113+
self._update_reachability(son)
1114+
11031115
def _parse_cfg(self, cfg: Dict) -> None:
11041116

11051117
assert cfg[self.get_key()] == "Block"

slither/solc_parsing/expressions/expression_parsing.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -487,13 +487,18 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression)
487487

488488
t = None
489489

490+
referenced_declaration = None
490491
if caller_context.is_compact_ast:
491492
value = expression["name"]
492493
t = expression["typeDescriptions"]["typeString"]
494+
if "referencedDeclaration" in expression:
495+
referenced_declaration = expression["referencedDeclaration"]
493496
else:
494497
value = expression["attributes"]["value"]
495498
if "type" in expression["attributes"]:
496499
t = expression["attributes"]["type"]
500+
if "referencedDeclaration" in expression["attributes"]:
501+
referenced_declaration = expression["attributes"]["referencedDeclaration"]
497502

498503
if t:
499504
found = re.findall(r"[struct|enum|function|modifier] \(([\[\] ()a-zA-Z0-9\.,_]*)\)", t)
@@ -502,10 +507,6 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression)
502507
value = value + "(" + found[0] + ")"
503508
value = filter_name(value)
504509

505-
if "referencedDeclaration" in expression:
506-
referenced_declaration = expression["referencedDeclaration"]
507-
else:
508-
referenced_declaration = None
509510
var, was_created = find_variable(value, caller_context, referenced_declaration)
510511
if was_created:
511512
var.set_offset(src, caller_context.compilation_unit)

slither/solc_parsing/expressions/find_variable.py

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,24 @@ def _find_variable_from_ref_declaration(
5656
referenced_declaration: Optional[int],
5757
all_contracts: List["Contract"],
5858
all_functions: List["Function"],
59+
function_parser: Optional["FunctionSolc"],
60+
contract_declarer: Optional["Contract"],
5961
) -> Optional[Union[Contract, Function]]:
62+
"""
63+
Reference declarations take the highest priority, but they are not available for legacy AST.
64+
"""
6065
if referenced_declaration is None:
6166
return None
62-
# id of the contracts is the referenced declaration
67+
# We look for variable declared with the referencedDeclaration attribute
68+
if function_parser is not None and referenced_declaration in function_parser.variables_renamed:
69+
return function_parser.variables_renamed[referenced_declaration].underlying_variable
70+
71+
if (
72+
contract_declarer is not None
73+
and referenced_declaration in contract_declarer.state_variables_by_ref_id
74+
):
75+
return contract_declarer.state_variables_by_ref_id[referenced_declaration]
76+
# Ccontracts ids are the referenced declaration
6377
# This is not true for the functions, as we dont always have the referenced_declaration
6478
# But maybe we could? (TODO)
6579
for contract_candidate in all_contracts:
@@ -74,14 +88,9 @@ def _find_variable_from_ref_declaration(
7488
def _find_variable_in_function_parser(
7589
var_name: str,
7690
function_parser: Optional["FunctionSolc"],
77-
referenced_declaration: Optional[int] = None,
7891
) -> Optional[Variable]:
7992
if function_parser is None:
8093
return None
81-
# We look for variable declared with the referencedDeclaration attr
82-
func_variables_renamed = function_parser.variables_renamed
83-
if referenced_declaration and referenced_declaration in func_variables_renamed:
84-
return func_variables_renamed[referenced_declaration].underlying_variable
8594
# If not found, check for name
8695
func_variables = function_parser.underlying_function.variables_as_dict
8796
if var_name in func_variables:
@@ -391,20 +400,6 @@ def find_variable(
391400
if var_name in current_scope.renaming:
392401
var_name = current_scope.renaming[var_name]
393402

394-
# Use ret0/ret1 to help mypy
395-
ret0 = _find_variable_from_ref_declaration(
396-
referenced_declaration, direct_contracts, direct_functions
397-
)
398-
if ret0:
399-
return ret0, False
400-
401-
function_parser: Optional[FunctionSolc] = (
402-
caller_context if isinstance(caller_context, FunctionSolc) else None
403-
)
404-
ret1 = _find_variable_in_function_parser(var_name, function_parser, referenced_declaration)
405-
if ret1:
406-
return ret1, False
407-
408403
contract: Optional[Contract] = None
409404
contract_declarer: Optional[Contract] = None
410405
if isinstance(caller_context, ContractSolc):
@@ -427,6 +422,24 @@ def find_variable(
427422
if var_name == var.name:
428423
return var, False
429424

425+
function_parser: Optional[FunctionSolc] = (
426+
caller_context if isinstance(caller_context, FunctionSolc) else None
427+
)
428+
# Use ret0/ret1 to help mypy
429+
ret0 = _find_variable_from_ref_declaration(
430+
referenced_declaration,
431+
direct_contracts,
432+
direct_functions,
433+
function_parser,
434+
contract_declarer,
435+
)
436+
if ret0:
437+
return ret0, False
438+
439+
ret1 = _find_variable_in_function_parser(var_name, function_parser)
440+
if ret1:
441+
return ret1, False
442+
430443
ret = _find_in_contract(var_name, contract, contract_declarer, is_super, is_identifier_path)
431444
if ret:
432445
return ret, False
@@ -477,6 +490,8 @@ def find_variable(
477490
referenced_declaration,
478491
list(current_scope.contracts.values()),
479492
list(current_scope.functions),
493+
None,
494+
None,
480495
)
481496
if ret:
482497
return ret, False

0 commit comments

Comments
 (0)