Skip to content

Commit 52416ba

Browse files
committed
must depend on analysis with relevant test cases
1 parent 84e8633 commit 52416ba

File tree

3 files changed

+128
-0
lines changed

3 files changed

+128
-0
lines changed

Diff for: slither/analyses/data_dependency/data_dependency.py

+68
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,74 @@ def get_all_dependencies_ssa(
293293
return context.context[KEY_SSA]
294294

295295

296+
def get_must_depends_on(variable: SUPPORTED_TYPES) -> List:
297+
"""
298+
Return must dependency of a variable if exist otherwise return None.
299+
300+
:param variable: target variable whose must dependency needs to be computed
301+
:return: Variable | None
302+
"""
303+
must_dependencies = compute_must_dependencies(variable)
304+
if len(must_dependencies) > 1 or len(must_dependencies) == 0:
305+
return []
306+
return [list(must_dependencies)[0]]
307+
308+
309+
def compute_must_dependencies(v: SUPPORTED_TYPES) -> Set[Variable]:
310+
if isinstance(v, (SolidityVariableComposed, Constant)) or (
311+
v.function.visibility in ["public", "external"] and v in v.function.parameters
312+
):
313+
return set([v])
314+
315+
function_dependencies = {}
316+
function_dependencies["context"] = {}
317+
lvalues = []
318+
319+
for node in v.function.nodes:
320+
for ir in node.irs_ssa:
321+
if isinstance(ir, OperationWithLValue) and ir.lvalue:
322+
if isinstance(ir.lvalue, LocalIRVariable) and ir.lvalue.is_storage:
323+
continue
324+
if isinstance(ir.lvalue, ReferenceVariable):
325+
lvalue = ir.lvalue.points_to
326+
if lvalue:
327+
lvalues.append((lvalue, v.function, ir))
328+
lvalues.append((ir.lvalue, v.function, ir))
329+
330+
for lvalue_details in lvalues:
331+
lvalue = lvalue_details[0]
332+
ir = lvalue_details[2]
333+
334+
if not lvalue in function_dependencies["context"]:
335+
function_dependencies["context"][lvalue] = set()
336+
read: Union[List[Union[LVALUE, SolidityVariableComposed]], List[SlithIRVariable]]
337+
338+
if isinstance(ir, Index):
339+
read = [ir.variable_left]
340+
elif isinstance(ir, InternalCall) and ir.function:
341+
read = ir.function.return_values_ssa
342+
else:
343+
read = ir.read
344+
for variable in read:
345+
# if not isinstance(variable, Constant):
346+
function_dependencies["context"][lvalue].add(variable)
347+
function_dependencies["context"] = convert_to_non_ssa(function_dependencies["context"])
348+
349+
must_dependencies = set()
350+
data_dependencies = (
351+
list(function_dependencies["context"][v])
352+
if function_dependencies["context"] is not None
353+
else []
354+
)
355+
for i, data_dependency in enumerate(data_dependencies):
356+
result = compute_must_dependencies(data_dependency)
357+
if i > 0:
358+
must_dependencies = must_dependencies.intersection(result)
359+
else:
360+
must_dependencies = must_dependencies.union(result)
361+
return must_dependencies
362+
363+
296364
# endregion
297365
###################################################################################
298366
###################################################################################

Diff for: tests/unit/core/test_data/must_depend_on.sol

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
pragma solidity ^0.8.19;
2+
3+
interface IERC20 {
4+
function transferFrom(address from, address to, uint amount) external returns (bool);
5+
}
6+
7+
/**
8+
* @title MissingReturnBug
9+
* @author IllIllI
10+
*/
11+
12+
// test case of the missing return bug described here:
13+
// https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca
14+
contract Unsafe {
15+
IERC20 erc20;
16+
function good2(address to, uint256 am) public {
17+
address from_msgsender = msg.sender;
18+
int_transferFrom(from_msgsender, to, am); // from is constant
19+
}
20+
21+
// This is not detected
22+
function bad2(address from, address to, uint256 am) public {
23+
address from_msgsender = msg.sender;
24+
int_transferFrom(from_msgsender, to, amount); // from is not a constant
25+
}
26+
27+
function int_transferFrom(address from, address to, uint256 amount) internal {
28+
erc20.transferFrom(from, to, amount); // not a constant = not a constant U constant
29+
}
30+
}

Diff for: tests/unit/core/test_must_depend_on.py

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from pathlib import Path
2+
from slither import Slither
3+
from slither.analyses.data_dependency.data_dependency import get_must_depends_on
4+
from slither.core.variables.variable import Variable
5+
from slither.core.declarations import SolidityVariable, SolidityVariableComposed
6+
from typing import Union
7+
from slither.slithir.variables import (
8+
Constant,
9+
)
10+
11+
TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data"
12+
SUPPORTED_TYPES = Union[Variable, SolidityVariable, Constant]
13+
14+
15+
def test_must_depend_on_returns(solc_binary_path):
16+
solc_path = solc_binary_path("0.8.19")
17+
file = Path(TEST_DATA_DIR, "must_depend_on.sol").as_posix()
18+
slither_obj = Slither(file, solc=solc_path)
19+
20+
for contract in slither_obj.contracts:
21+
for function in contract.functions:
22+
if contract == "Unsafe" and function == "int_transferFrom":
23+
result = get_must_depends_on(function.parameters[0])
24+
break
25+
assert isinstance(result, list)
26+
assert result[0] == SolidityVariableComposed("msg.sender"), "Output should be msg.sender"
27+
28+
result = get_must_depends_on(slither_obj.contracts[1].functions[2].parameters[1])
29+
assert isinstance(result, list)
30+
assert len(result) == 0, "Output should be empty"

0 commit comments

Comments
 (0)