From e1f8cecf5eba0d26432b73d103f956addaecd10d Mon Sep 17 00:00:00 2001 From: IzaiahSun Date: Mon, 12 May 2025 20:48:19 +0800 Subject: [PATCH 1/7] fix ssa dependency --- .../data_dependency/data_dependency.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/slither/analyses/data_dependency/data_dependency.py b/slither/analyses/data_dependency/data_dependency.py index 12e809fa53..cfea14dfef 100644 --- a/slither/analyses/data_dependency/data_dependency.py +++ b/slither/analyses/data_dependency/data_dependency.py @@ -440,18 +440,46 @@ def compute_dependency_function(function: Function) -> None: function.context[KEY_SSA] = {} function.context[KEY_SSA_UNPROTECTED] = {} + unset_local_ir_vars = {} + is_protected = function.is_protected() for node in function.nodes: for ir in node.irs_ssa: + for v in ir.used: + if isinstance(v, LocalIRVariable): + if v not in unset_local_ir_vars: + unset_local_ir_vars[v] = True if isinstance(ir, OperationWithLValue) and ir.lvalue: if isinstance(ir.lvalue, LocalIRVariable) and ir.lvalue.is_storage: continue if isinstance(ir.lvalue, ReferenceVariable): lvalue = ir.lvalue.points_to if lvalue: + unset_local_ir_vars[lvalue] = False add_dependency(lvalue, function, ir, is_protected) + unset_local_ir_vars[ir.lvalue] = False add_dependency(ir.lvalue, function, ir, is_protected) + # If an SSA locl IR variable is read but never written to,, + # and it is from a parameter, we should add a dependency edge from the variable to the parameter + for node in function.nodes: + for ir in node.irs_ssa: + for v in ir.used: + # We need to make sure that this variable is never used as lvalue + if isinstance(v, LocalIRVariable) and unset_local_ir_vars.get(v): + # We need to check the parameter + for param_ssa in function.parameters_ssa: + if v.non_ssa_version == param_ssa.non_ssa_version: + if v not in function.context[KEY_SSA]: + function.context[KEY_SSA][v] = set() + function.context[KEY_SSA][v].add(param_ssa) + if not is_protected: + if v not in function.context[KEY_SSA_UNPROTECTED]: + function.context[KEY_SSA_UNPROTECTED][v] = set() + function.context[KEY_SSA_UNPROTECTED][v].add(param_ssa) + unset_local_ir_vars[param_ssa] = False + break + function.context[KEY_NON_SSA] = convert_to_non_ssa(function.context[KEY_SSA]) function.context[KEY_NON_SSA_UNPROTECTED] = convert_to_non_ssa( function.context[KEY_SSA_UNPROTECTED] From 2a7893a62b777c239490d3454ab723e6f939f740 Mon Sep 17 00:00:00 2001 From: IzaiahSun Date: Mon, 12 May 2025 20:54:24 +0800 Subject: [PATCH 2/7] lint code --- slither/analyses/data_dependency/data_dependency.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/analyses/data_dependency/data_dependency.py b/slither/analyses/data_dependency/data_dependency.py index cfea14dfef..156e1f5403 100644 --- a/slither/analyses/data_dependency/data_dependency.py +++ b/slither/analyses/data_dependency/data_dependency.py @@ -460,7 +460,7 @@ def compute_dependency_function(function: Function) -> None: unset_local_ir_vars[ir.lvalue] = False add_dependency(ir.lvalue, function, ir, is_protected) - # If an SSA locl IR variable is read but never written to,, + # If an SSA locl IR variable is read but never written to,, # and it is from a parameter, we should add a dependency edge from the variable to the parameter for node in function.nodes: for ir in node.irs_ssa: From 670cdc5042a9dccfd5d9cf3913885117330f3a16 Mon Sep 17 00:00:00 2001 From: IzaiahSun Date: Mon, 12 May 2025 21:18:57 +0800 Subject: [PATCH 3/7] add test for dependency problems --- tests/unit/analyses/__init__.py | 0 .../test_data/parameter_dependency_ssa.sol | 36 ++++++++++++++++ tests/unit/analyses/test_data_dependencies.py | 43 +++++++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 tests/unit/analyses/__init__.py create mode 100644 tests/unit/analyses/test_data/parameter_dependency_ssa.sol create mode 100644 tests/unit/analyses/test_data_dependencies.py diff --git a/tests/unit/analyses/__init__.py b/tests/unit/analyses/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/unit/analyses/test_data/parameter_dependency_ssa.sol b/tests/unit/analyses/test_data/parameter_dependency_ssa.sol new file mode 100644 index 0000000000..e63dada3f6 --- /dev/null +++ b/tests/unit/analyses/test_data/parameter_dependency_ssa.sol @@ -0,0 +1,36 @@ +pragma solidity ^0.8.24; + +contract TSURUWrapper{ + bool private _opened = false; + address public immutable erc721Contract; + uint256 public constant _maxTotalSupply = 431_386_000 * 1e18; + uint256 private constant ERC721_RATIO = 400 * 1e18; + mapping(address owner => uint256) private _balancesOfOwner; + uint256 private _holders; + uint256 private _totalSupply; + function totalSupply() public view virtual returns (uint256) { + return _totalSupply; + } + function onERC721Received( + address, + address from, + uint256, + bytes calldata + ) external returns (bytes4) { + require(_opened, "Already yet open."); + require(msg.sender == address(erc721Contract), "Unauthorized token"); + _safeMint(from, ERC721_RATIO); // Adjust minting based on the ERC721_RATIO + return this.onERC721Received.selector; + } + + function _safeMint(address account, uint256 value) internal { + require(_maxTotalSupply > totalSupply() + value, "Max supply exceeded."); + + // _mint(account, value); + + if (_balancesOfOwner[account] == 0) { + ++_holders; + } + _balancesOfOwner[account] = _balancesOfOwner[account] + value; + } +} \ No newline at end of file diff --git a/tests/unit/analyses/test_data_dependencies.py b/tests/unit/analyses/test_data_dependencies.py new file mode 100644 index 0000000000..cfc7d5082f --- /dev/null +++ b/tests/unit/analyses/test_data_dependencies.py @@ -0,0 +1,43 @@ +from pathlib import Path + +from slither import Slither +from slither.analyses.data_dependency.data_dependency import is_dependent_ssa +from slither.slithir.variables import LocalIRVariable + +TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data" + + +def test_param_dependency(solc_binary_path) -> None: + solc_path = solc_binary_path("0.8.24") + slither = Slither( + Path(TEST_DATA_DIR, "parameter_dependency_ssa.sol").as_posix(), solc=solc_path + ) + + target_function = slither.contracts[0].get_function_from_signature( + "onERC721Received(address,address,uint256,bytes)" + ) + + # Param is from (from_0 in SSA) + # Local SSA variable is from_1 + + param_var = None + + for param_ssa in target_function.parameters_ssa: + if param_ssa.non_ssa_version.name == "from": + param_var = param_ssa + break + + assert param_var is not None, "Param variable not found in SSA" + + local_var = None + for ir in target_function.slithir_ssa_operations: + for v in ir.used: + if isinstance(v, LocalIRVariable) and v.non_ssa_version.name == "from" and v.index == 1: + local_var = v + break + + assert local_var is not None, "Local variable not found in SSA" + + assert is_dependent_ssa( + local_var, param_var, target_function + ), "Param and local variable are not dependent" From 473947494623f7d6e11288c6ee86a12de5169cbf Mon Sep 17 00:00:00 2001 From: IzaiahSun Date: Wed, 18 Jun 2025 19:54:03 +0800 Subject: [PATCH 4/7] refactor compute_dependency_function --- .../data_dependency/data_dependency.py | 83 +++++++++++-------- 1 file changed, 47 insertions(+), 36 deletions(-) diff --git a/slither/analyses/data_dependency/data_dependency.py b/slither/analyses/data_dependency/data_dependency.py index 156e1f5403..fbd63ea8f9 100644 --- a/slither/analyses/data_dependency/data_dependency.py +++ b/slither/analyses/data_dependency/data_dependency.py @@ -433,6 +433,48 @@ def add_dependency(lvalue: Variable, function: Function, ir: Operation, is_prote function.context[KEY_SSA_UNPROTECTED][lvalue].add(v) +def _init_unset_local_ir_vars(function: Function) -> dict: + unset_local_ir_vars = {} + for node in function.nodes: + for ir in node.irs_ssa: + for v in ir.used: + if isinstance(v, LocalIRVariable): + if v not in unset_local_ir_vars: + unset_local_ir_vars[v] = True + return unset_local_ir_vars + + +def _handle_operation_with_lvalue(function: Function, node, is_protected, unset_local_ir_vars): + for ir in node.irs_ssa: + if isinstance(ir, OperationWithLValue) and ir.lvalue: + if isinstance(ir.lvalue, LocalIRVariable) and ir.lvalue.is_storage: + return + if isinstance(ir.lvalue, ReferenceVariable): + lvalue = ir.lvalue.points_to + if lvalue: + unset_local_ir_vars[lvalue] = False + add_dependency(lvalue, function, ir, is_protected) + unset_local_ir_vars[ir.lvalue] = False + add_dependency(ir.lvalue, function, ir, is_protected) + + +def _add_param_dependency_if_needed(function: Function, node, is_protected, unset_local_ir_vars): + for ir in node.irs_ssa: + for v in ir.used: + if isinstance(v, LocalIRVariable) and unset_local_ir_vars.get(v): + for param_ssa in function.parameters_ssa: + if v.non_ssa_version == param_ssa.non_ssa_version: + if v not in function.context[KEY_SSA]: + function.context[KEY_SSA][v] = set() + function.context[KEY_SSA][v].add(param_ssa) + if not is_protected: + if v not in function.context[KEY_SSA_UNPROTECTED]: + function.context[KEY_SSA_UNPROTECTED][v] = set() + function.context[KEY_SSA_UNPROTECTED][v].add(param_ssa) + unset_local_ir_vars[param_ssa] = False + break + + def compute_dependency_function(function: Function) -> None: if KEY_SSA in function.context: return @@ -440,45 +482,14 @@ def compute_dependency_function(function: Function) -> None: function.context[KEY_SSA] = {} function.context[KEY_SSA_UNPROTECTED] = {} - unset_local_ir_vars = {} - is_protected = function.is_protected() + unset_local_ir_vars = _init_unset_local_ir_vars(function) + for node in function.nodes: - for ir in node.irs_ssa: - for v in ir.used: - if isinstance(v, LocalIRVariable): - if v not in unset_local_ir_vars: - unset_local_ir_vars[v] = True - if isinstance(ir, OperationWithLValue) and ir.lvalue: - if isinstance(ir.lvalue, LocalIRVariable) and ir.lvalue.is_storage: - continue - if isinstance(ir.lvalue, ReferenceVariable): - lvalue = ir.lvalue.points_to - if lvalue: - unset_local_ir_vars[lvalue] = False - add_dependency(lvalue, function, ir, is_protected) - unset_local_ir_vars[ir.lvalue] = False - add_dependency(ir.lvalue, function, ir, is_protected) - - # If an SSA locl IR variable is read but never written to,, - # and it is from a parameter, we should add a dependency edge from the variable to the parameter + _handle_operation_with_lvalue(function, node, is_protected, unset_local_ir_vars) + for node in function.nodes: - for ir in node.irs_ssa: - for v in ir.used: - # We need to make sure that this variable is never used as lvalue - if isinstance(v, LocalIRVariable) and unset_local_ir_vars.get(v): - # We need to check the parameter - for param_ssa in function.parameters_ssa: - if v.non_ssa_version == param_ssa.non_ssa_version: - if v not in function.context[KEY_SSA]: - function.context[KEY_SSA][v] = set() - function.context[KEY_SSA][v].add(param_ssa) - if not is_protected: - if v not in function.context[KEY_SSA_UNPROTECTED]: - function.context[KEY_SSA_UNPROTECTED][v] = set() - function.context[KEY_SSA_UNPROTECTED][v].add(param_ssa) - unset_local_ir_vars[param_ssa] = False - break + _add_param_dependency_if_needed(function, node, is_protected, unset_local_ir_vars) function.context[KEY_NON_SSA] = convert_to_non_ssa(function.context[KEY_SSA]) function.context[KEY_NON_SSA_UNPROTECTED] = convert_to_non_ssa( From 3d312e1d26ca8c28b5d4d9b722cd70aa23476ed8 Mon Sep 17 00:00:00 2001 From: IzaiahSun Date: Wed, 6 Aug 2025 22:24:02 +0800 Subject: [PATCH 5/7] fix lint error: R1702: Too many nested blocks in slither/analyses/data_dependency/data_dependency.py --- .../data_dependency/data_dependency.py | 40 +++++++++++++------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/slither/analyses/data_dependency/data_dependency.py b/slither/analyses/data_dependency/data_dependency.py index fbd63ea8f9..1f05673e3d 100644 --- a/slither/analyses/data_dependency/data_dependency.py +++ b/slither/analyses/data_dependency/data_dependency.py @@ -458,21 +458,37 @@ def _handle_operation_with_lvalue(function: Function, node, is_protected, unset_ add_dependency(ir.lvalue, function, ir, is_protected) +def _add_param_dependency_for_variable(function: Function, v: LocalIRVariable, param_ssa, is_protected, unset_local_ir_vars): + """Add parameter dependency for a given variable and parameter.""" + if v not in function.context[KEY_SSA]: + function.context[KEY_SSA][v] = set() + function.context[KEY_SSA][v].add(param_ssa) + + if not is_protected: + if v not in function.context[KEY_SSA_UNPROTECTED]: + function.context[KEY_SSA_UNPROTECTED][v] = set() + function.context[KEY_SSA_UNPROTECTED][v].add(param_ssa) + unset_local_ir_vars[param_ssa] = False + + +def _find_matching_parameter(function: Function, v: LocalIRVariable): + """Find the parameter that matches the given variable's non-SSA version.""" + for param_ssa in function.parameters_ssa: + if v.non_ssa_version == param_ssa.non_ssa_version: + return param_ssa + return None + + def _add_param_dependency_if_needed(function: Function, node, is_protected, unset_local_ir_vars): for ir in node.irs_ssa: for v in ir.used: - if isinstance(v, LocalIRVariable) and unset_local_ir_vars.get(v): - for param_ssa in function.parameters_ssa: - if v.non_ssa_version == param_ssa.non_ssa_version: - if v not in function.context[KEY_SSA]: - function.context[KEY_SSA][v] = set() - function.context[KEY_SSA][v].add(param_ssa) - if not is_protected: - if v not in function.context[KEY_SSA_UNPROTECTED]: - function.context[KEY_SSA_UNPROTECTED][v] = set() - function.context[KEY_SSA_UNPROTECTED][v].add(param_ssa) - unset_local_ir_vars[param_ssa] = False - break + if not (isinstance(v, LocalIRVariable) and unset_local_ir_vars.get(v)): + continue + + param_ssa = _find_matching_parameter(function, v) + if param_ssa: + _add_param_dependency_for_variable(function, v, param_ssa, is_protected, unset_local_ir_vars) + break def compute_dependency_function(function: Function) -> None: From f8da0ccd2bb81cf49ddcd51e4faf1edc3ca0dae2 Mon Sep 17 00:00:00 2001 From: IzaiahSun Date: Wed, 6 Aug 2025 22:53:10 +0800 Subject: [PATCH 6/7] fix lint error: C0303: Trailing whitespace in slither/analyses/data_dependency/data_dependency.py --- slither/analyses/data_dependency/data_dependency.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/slither/analyses/data_dependency/data_dependency.py b/slither/analyses/data_dependency/data_dependency.py index 1f05673e3d..f3baed28e2 100644 --- a/slither/analyses/data_dependency/data_dependency.py +++ b/slither/analyses/data_dependency/data_dependency.py @@ -458,12 +458,14 @@ def _handle_operation_with_lvalue(function: Function, node, is_protected, unset_ add_dependency(ir.lvalue, function, ir, is_protected) -def _add_param_dependency_for_variable(function: Function, v: LocalIRVariable, param_ssa, is_protected, unset_local_ir_vars): +def _add_param_dependency_for_variable( + function: Function, v: LocalIRVariable, param_ssa, is_protected, unset_local_ir_vars +): """Add parameter dependency for a given variable and parameter.""" if v not in function.context[KEY_SSA]: function.context[KEY_SSA][v] = set() function.context[KEY_SSA][v].add(param_ssa) - + if not is_protected: if v not in function.context[KEY_SSA_UNPROTECTED]: function.context[KEY_SSA_UNPROTECTED][v] = set() @@ -484,10 +486,12 @@ def _add_param_dependency_if_needed(function: Function, node, is_protected, unse for v in ir.used: if not (isinstance(v, LocalIRVariable) and unset_local_ir_vars.get(v)): continue - + param_ssa = _find_matching_parameter(function, v) if param_ssa: - _add_param_dependency_for_variable(function, v, param_ssa, is_protected, unset_local_ir_vars) + _add_param_dependency_for_variable( + function, v, param_ssa, is_protected, unset_local_ir_vars + ) break From 974a8bcb0f9a0b7faf2f239c10abde86b9b50841 Mon Sep 17 00:00:00 2001 From: IzaiahSun Date: Wed, 6 Aug 2025 23:12:16 +0800 Subject: [PATCH 7/7] fix error in test suite --- slither/analyses/data_dependency/data_dependency.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/analyses/data_dependency/data_dependency.py b/slither/analyses/data_dependency/data_dependency.py index f3baed28e2..cea3082956 100644 --- a/slither/analyses/data_dependency/data_dependency.py +++ b/slither/analyses/data_dependency/data_dependency.py @@ -448,7 +448,7 @@ def _handle_operation_with_lvalue(function: Function, node, is_protected, unset_ for ir in node.irs_ssa: if isinstance(ir, OperationWithLValue) and ir.lvalue: if isinstance(ir.lvalue, LocalIRVariable) and ir.lvalue.is_storage: - return + continue if isinstance(ir.lvalue, ReferenceVariable): lvalue = ir.lvalue.points_to if lvalue: