Skip to content

Commit ffbb62b

Browse files
authored
Merge pull request #838 from crytic/dev-dead-code
Open source dead-code detector
2 parents ac5cee7 + cde06f4 commit ffbb62b

File tree

7 files changed

+337
-56
lines changed

7 files changed

+337
-56
lines changed

README.md

Lines changed: 56 additions & 55 deletions
Large diffs are not rendered by default.

slither/core/declarations/contract.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ def functions_entry_points(self) -> List["FunctionContract"]:
445445
return [
446446
f
447447
for f in self.functions
448-
if f.visibility in ["public", "external"] and not f.is_shadowed
448+
if f.visibility in ["public", "external"] and not f.is_shadowed or f.is_fallback
449449
]
450450

451451
@property

slither/detectors/all_detectors.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
from .variables.predeclaration_usage_local import PredeclarationUsageLocal
7878
from .statements.unary import IncorrectUnaryExpressionDetection
7979
from .operations.missing_zero_address_validation import MissingZeroAddressValidation
80+
from .functions.dead_code import DeadCode
8081

8182
#
8283
#
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"""
2+
Module detecting dead code
3+
"""
4+
from typing import List, Tuple
5+
6+
from slither.core.declarations import Function, FunctionContract, Contract
7+
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
8+
9+
10+
class DeadCode(AbstractDetector):
11+
"""
12+
Unprotected function detector
13+
"""
14+
15+
ARGUMENT = "dead-code"
16+
HELP = "Functions that are not used"
17+
IMPACT = DetectorClassification.INFORMATIONAL
18+
CONFIDENCE = DetectorClassification.MEDIUM
19+
20+
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code"
21+
22+
WIKI_TITLE = "Dead-code"
23+
WIKI_DESCRIPTION = "Functions that are not sued."
24+
WIKI_EXPLOIT_SCENARIO = """
25+
```solidity
26+
contract Contract{
27+
function dead_code() internal() {}
28+
}
29+
```
30+
`dead_code` is not used in the contract, and make the code's review more difficult."""
31+
32+
WIKI_RECOMMENDATION = "Remove unused functions."
33+
34+
def _detect(self):
35+
36+
results = []
37+
38+
functions_used = set()
39+
for contract in self.compilation_unit.contracts_derived:
40+
all_functionss_called = [
41+
f.all_internal_calls() for f in contract.functions_entry_points
42+
]
43+
all_functions_called = [item for sublist in all_functionss_called for item in sublist]
44+
functions_used |= {
45+
f.canonical_name for f in all_functions_called if isinstance(f, Function)
46+
}
47+
all_libss_called = [f.all_library_calls() for f in contract.functions_entry_points]
48+
all_libs_called: List[Tuple[Contract, Function]] = [
49+
item for sublist in all_libss_called for item in sublist
50+
]
51+
functions_used |= {
52+
lib[1].canonical_name for lib in all_libs_called if isinstance(lib, tuple)
53+
}
54+
for function in sorted(self.compilation_unit.functions, key=lambda x: x.canonical_name):
55+
if (
56+
function.visibility in ["public", "external"]
57+
or function.is_constructor
58+
or function.is_fallback
59+
or function.is_constructor_variables
60+
):
61+
continue
62+
if function.canonical_name in functions_used:
63+
continue
64+
if isinstance(function, FunctionContract) and (
65+
function.contract_declarer.is_from_dependency()
66+
):
67+
continue
68+
info = [function, " is never used and should be removed\n"]
69+
res = self.generate_result(info)
70+
results.append(res)
71+
72+
return results
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
contract Test{
2+
function unused() internal{
3+
4+
}
5+
}
6+
7+
8+
contract Test2{
9+
10+
function unused_but_shadowed() internal virtual{
11+
12+
}
13+
}
14+
15+
contract Test3 is Test2{
16+
function unused_but_shadowed() internal override{
17+
18+
}
19+
20+
function f() public{
21+
unused_but_shadowed();
22+
}
23+
}
24+
25+
contract Test4 is Test2{
26+
function unused_but_shadowed() internal override{
27+
28+
}
29+
}
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
[
2+
[
3+
{
4+
"elements": [
5+
{
6+
"type": "function",
7+
"name": "unused",
8+
"source_mapping": {
9+
"start": 19,
10+
"length": 34,
11+
"filename_used": "/GENERIC_PATH",
12+
"filename_relative": "tests/detectors/dead-code/0.8.0/dead-code.sol",
13+
"filename_absolute": "/GENERIC_PATH",
14+
"filename_short": "tests/detectors/dead-code/0.8.0/dead-code.sol",
15+
"is_dependency": false,
16+
"lines": [
17+
2,
18+
3,
19+
4
20+
],
21+
"starting_column": 5,
22+
"ending_column": 6
23+
},
24+
"type_specific_fields": {
25+
"parent": {
26+
"type": "contract",
27+
"name": "Test",
28+
"source_mapping": {
29+
"start": 0,
30+
"length": 55,
31+
"filename_used": "/GENERIC_PATH",
32+
"filename_relative": "tests/detectors/dead-code/0.8.0/dead-code.sol",
33+
"filename_absolute": "/GENERIC_PATH",
34+
"filename_short": "tests/detectors/dead-code/0.8.0/dead-code.sol",
35+
"is_dependency": false,
36+
"lines": [
37+
1,
38+
2,
39+
3,
40+
4,
41+
5
42+
],
43+
"starting_column": 1,
44+
"ending_column": 2
45+
}
46+
},
47+
"signature": "unused()"
48+
}
49+
}
50+
],
51+
"description": "Test.unused() (tests/detectors/dead-code/0.8.0/dead-code.sol#2-4) is never used and should be removed\n",
52+
"markdown": "[Test.unused()](tests/detectors/dead-code/0.8.0/dead-code.sol#L2-L4) is never used and should be removed\n",
53+
"first_markdown_element": "tests/detectors/dead-code/0.8.0/dead-code.sol#L2-L4",
54+
"id": "a7c13823116566bbbbb68e8a7efa78fe64785fcb8582069373eda7f27c523cb3",
55+
"check": "dead-code",
56+
"impact": "Informational",
57+
"confidence": "Medium"
58+
},
59+
{
60+
"elements": [
61+
{
62+
"type": "function",
63+
"name": "unused_but_shadowed",
64+
"source_mapping": {
65+
"start": 79,
66+
"length": 55,
67+
"filename_used": "/GENERIC_PATH",
68+
"filename_relative": "tests/detectors/dead-code/0.8.0/dead-code.sol",
69+
"filename_absolute": "/GENERIC_PATH",
70+
"filename_short": "tests/detectors/dead-code/0.8.0/dead-code.sol",
71+
"is_dependency": false,
72+
"lines": [
73+
10,
74+
11,
75+
12
76+
],
77+
"starting_column": 5,
78+
"ending_column": 6
79+
},
80+
"type_specific_fields": {
81+
"parent": {
82+
"type": "contract",
83+
"name": "Test2",
84+
"source_mapping": {
85+
"start": 58,
86+
"length": 78,
87+
"filename_used": "/GENERIC_PATH",
88+
"filename_relative": "tests/detectors/dead-code/0.8.0/dead-code.sol",
89+
"filename_absolute": "/GENERIC_PATH",
90+
"filename_short": "tests/detectors/dead-code/0.8.0/dead-code.sol",
91+
"is_dependency": false,
92+
"lines": [
93+
8,
94+
9,
95+
10,
96+
11,
97+
12,
98+
13
99+
],
100+
"starting_column": 1,
101+
"ending_column": 2
102+
}
103+
},
104+
"signature": "unused_but_shadowed()"
105+
}
106+
}
107+
],
108+
"description": "Test2.unused_but_shadowed() (tests/detectors/dead-code/0.8.0/dead-code.sol#10-12) is never used and should be removed\n",
109+
"markdown": "[Test2.unused_but_shadowed()](tests/detectors/dead-code/0.8.0/dead-code.sol#L10-L12) is never used and should be removed\n",
110+
"first_markdown_element": "tests/detectors/dead-code/0.8.0/dead-code.sol#L10-L12",
111+
"id": "aaba496684b73955e90b555de174e1cd03f0fee337849c4d58c10ef76ff93582",
112+
"check": "dead-code",
113+
"impact": "Informational",
114+
"confidence": "Medium"
115+
},
116+
{
117+
"elements": [
118+
{
119+
"type": "function",
120+
"name": "unused_but_shadowed",
121+
"source_mapping": {
122+
"start": 319,
123+
"length": 56,
124+
"filename_used": "/GENERIC_PATH",
125+
"filename_relative": "tests/detectors/dead-code/0.8.0/dead-code.sol",
126+
"filename_absolute": "/GENERIC_PATH",
127+
"filename_short": "tests/detectors/dead-code/0.8.0/dead-code.sol",
128+
"is_dependency": false,
129+
"lines": [
130+
26,
131+
27,
132+
28
133+
],
134+
"starting_column": 5,
135+
"ending_column": 6
136+
},
137+
"type_specific_fields": {
138+
"parent": {
139+
"type": "contract",
140+
"name": "Test4",
141+
"source_mapping": {
142+
"start": 290,
143+
"length": 87,
144+
"filename_used": "/GENERIC_PATH",
145+
"filename_relative": "tests/detectors/dead-code/0.8.0/dead-code.sol",
146+
"filename_absolute": "/GENERIC_PATH",
147+
"filename_short": "tests/detectors/dead-code/0.8.0/dead-code.sol",
148+
"is_dependency": false,
149+
"lines": [
150+
25,
151+
26,
152+
27,
153+
28,
154+
29
155+
],
156+
"starting_column": 1,
157+
"ending_column": 2
158+
}
159+
},
160+
"signature": "unused_but_shadowed()"
161+
}
162+
}
163+
],
164+
"description": "Test4.unused_but_shadowed() (tests/detectors/dead-code/0.8.0/dead-code.sol#26-28) is never used and should be removed\n",
165+
"markdown": "[Test4.unused_but_shadowed()](tests/detectors/dead-code/0.8.0/dead-code.sol#L26-L28) is never used and should be removed\n",
166+
"first_markdown_element": "tests/detectors/dead-code/0.8.0/dead-code.sol#L26-L28",
167+
"id": "74ea8421cf7fa9e04d243014b61f3eee7a9c7648df98316c3881dd4f1f2ab3f7",
168+
"check": "dead-code",
169+
"impact": "Informational",
170+
"confidence": "Medium"
171+
}
172+
]
173+
]

tests/test_detectors.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,6 +1134,11 @@ def id_test(test_item: Test):
11341134
"predeclaration_usage_local.sol",
11351135
"0.4.25",
11361136
),
1137+
Test(
1138+
all_detectors.DeadCode,
1139+
"dead-code.sol",
1140+
"0.8.0",
1141+
),
11371142
]
11381143
GENERIC_PATH = "/GENERIC_PATH"
11391144

0 commit comments

Comments
 (0)