Skip to content

Commit 2308fd8

Browse files
authored
feat: allow slither check disabling in Yul preprocessor (#1035)
# Rationale for this change We need this feature in the Yul preprocessor since we have two legit Yul functions with high cyclomatic complexity.
1 parent a3b7de6 commit 2308fd8

File tree

4 files changed

+139
-2
lines changed

4 files changed

+139
-2
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.24;
3+
4+
contract SlitherTestMain {
5+
function testFunc() public pure {
6+
assembly {
7+
// import complexFunction from source.presl
8+
// import simpleFunction from source.presl
9+
10+
let x := complexFunction(1, 2, 3)
11+
let y := simpleFunction(5)
12+
}
13+
}
14+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.24;
3+
4+
contract SlitherTestSource {
5+
function testFunc() public pure {
6+
assembly {
7+
// slither-disable-start cyclomatic-complexity
8+
function complexFunction(a, b, c) -> result {
9+
result := add(a, add(b, c))
10+
}
11+
// slither-disable-end cyclomatic-complexity
12+
13+
// slither-disable-next-line write-after-write
14+
function simpleFunction(x) -> y {
15+
y := mul(x, 2)
16+
}
17+
}
18+
}
19+
}

solidity/preprocessor/test_yul_preprocessor.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,37 @@ def test_no_presl_references_in_post_sol_imports(self):
582582
if output_file.exists():
583583
output_file.unlink()
584584

585+
def test_slither_comments_preserved(self):
586+
"""Test that Slither exemption comments are preserved with imported functions."""
587+
test_dir = self.test_files_dir / "slither_comments"
588+
main_file = test_dir / "main.presl"
589+
590+
preprocessor = YulPreprocessor(root_dir=test_dir)
591+
result = preprocessor.process_file(main_file)
592+
593+
# Verify both functions were imported
594+
assert "function complexFunction(a, b, c) -> result" in result
595+
assert "function simpleFunction(x) -> y" in result
596+
597+
# Verify Slither comments are preserved
598+
# Should have slither-disable-start before complexFunction
599+
assert "// slither-disable-start cyclomatic-complexity" in result
600+
# Should have slither-disable-end after complexFunction
601+
assert "// slither-disable-end cyclomatic-complexity" in result
602+
# Should have slither-disable-next-line before simpleFunction
603+
assert "// slither-disable-next-line write-after-write" in result
604+
605+
# Verify the ordering is correct (disable-start comes before the function)
606+
start_idx = result.find("// slither-disable-start cyclomatic-complexity")
607+
func_idx = result.find("function complexFunction")
608+
end_idx = result.find("// slither-disable-end cyclomatic-complexity")
609+
assert (
610+
start_idx < func_idx < end_idx
611+
), "Slither comments should wrap the function"
612+
613+
# Verify no duplicate disable-end comments
614+
assert result.count("// slither-disable-end cyclomatic-complexity") == 1
615+
585616

586617
if __name__ == "__main__":
587618
pytest.main([__file__, "-v"])

solidity/preprocessor/yul_preprocessor.py

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,25 @@
3232
class YulFunction:
3333
"""Represents a parsed Yul function."""
3434

35-
def __init__(self, name: str, signature: str, body: str, full_text: str):
35+
def __init__(
36+
self,
37+
name: str,
38+
signature: str,
39+
body: str,
40+
full_text: str,
41+
pre_comments: str = "",
42+
post_comments: str = "",
43+
):
3644
self.name = name
3745
self.signature = signature # function name(...) -> ...
3846
self.body = body
3947
self.full_text = full_text # Complete function including definition
48+
self.pre_comments = (
49+
pre_comments # Comments before function (e.g., slither-disable-start)
50+
)
51+
self.post_comments = (
52+
post_comments # Comments after function (e.g., slither-disable-end)
53+
)
4054

4155
def __eq__(self, other):
4256
if not isinstance(other, YulFunction):
@@ -111,6 +125,7 @@ def extract_yul_functions(self, assembly_content: str) -> Dict[str, YulFunction]
111125
Extract all Yul function definitions from an assembly block.
112126
Returns dict mapping function name to YulFunction object.
113127
Handles multiline function signatures.
128+
Captures Slither exemption comments before and after functions.
114129
"""
115130
functions = {}
116131

@@ -123,6 +138,29 @@ def extract_yul_functions(self, assembly_content: str) -> Dict[str, YulFunction]
123138

124139
# Check if line starts with "function" keyword
125140
if line.strip().startswith("function"):
141+
# Look back for Slither disable comments before the function
142+
# Only capture slither-disable-start or slither-disable-next-line
143+
# Do NOT capture slither-disable-end as that belongs to the previous function
144+
pre_comment_lines = []
145+
j = i - 1
146+
while j >= 0:
147+
prev_line = lines[j].strip()
148+
# Check for slither-disable-start or slither-disable-next-line (not disable-end)
149+
if "slither-disable" in prev_line and prev_line.startswith("//"):
150+
if "slither-disable-end" not in prev_line:
151+
# This is a disable-start or disable-next-line, include it
152+
pre_comment_lines.insert(0, lines[j])
153+
j -= 1
154+
else:
155+
# This is a disable-end from a previous function, stop here
156+
break
157+
elif prev_line == "":
158+
# Allow empty lines but don't add them
159+
j -= 1
160+
else:
161+
# Stop when we hit non-comment/non-empty content
162+
break
163+
126164
# Extract function name using simple pattern
127165
func_match = self.function_name_pattern.search(line)
128166

@@ -167,11 +205,39 @@ def extract_yul_functions(self, assembly_content: str) -> Dict[str, YulFunction]
167205
brace_count += lines[i].count("{") - lines[i].count("}")
168206
i += 1
169207

208+
# Look ahead for Slither disable-end comments after the function
209+
post_comment_lines = []
210+
temp_i = i
211+
while temp_i < len(lines):
212+
next_line = lines[temp_i].strip()
213+
# Check for slither-disable-end
214+
if "slither-disable-end" in next_line and next_line.startswith(
215+
"//"
216+
):
217+
post_comment_lines.append(lines[temp_i])
218+
i = temp_i + 1
219+
break
220+
elif next_line == "":
221+
# Allow empty lines
222+
temp_i += 1
223+
else:
224+
# Stop when we hit any other content (function or non-comment)
225+
break
226+
170227
full_text = "\n".join(func_lines)
171228
body = "\n".join(func_lines[len(sig_lines) :])
229+
pre_comments = "\n".join(pre_comment_lines) if pre_comment_lines else ""
230+
post_comments = (
231+
"\n".join(post_comment_lines) if post_comment_lines else ""
232+
)
172233

173234
functions[func_name] = YulFunction(
174-
name=func_name, signature=signature, body=body, full_text=full_text
235+
name=func_name,
236+
signature=signature,
237+
body=body,
238+
full_text=full_text,
239+
pre_comments=pre_comments,
240+
post_comments=post_comments,
175241
)
176242
else:
177243
i += 1
@@ -557,7 +623,14 @@ def process_assembly_block(
557623
if imported_functions:
558624
func_lines = []
559625
for func in imported_functions.values():
626+
# Include pre-comments (e.g., slither-disable-start)
627+
if func.pre_comments:
628+
func_lines.append(func.pre_comments)
629+
# Add the function itself
560630
func_lines.append(func.full_text)
631+
# Include post-comments (e.g., slither-disable-end)
632+
if func.post_comments:
633+
func_lines.append(func.post_comments)
561634

562635
# Add imported functions before other content
563636
return "\n".join(func_lines) + "\n" + "\n".join(result_lines)

0 commit comments

Comments
 (0)