Skip to content

Commit acecf97

Browse files
authored
Fix 'pragma solidity' parsing (#1887)
* Fix 'pragma solidity' parsing remove comments and strings before searching for a line containing 'pragma solidity' * Fix formatting * Fix: search solc version in comments if regular pragma is missing * Remove redundant "else" * add test version_4 for pragma extraction * pragma extraction: code refactored * Reformatting ethereum/util.py * rename variables * Add type information to pragma extraction
1 parent 0cd0a89 commit acecf97

File tree

3 files changed

+86
-12
lines changed

3 files changed

+86
-12
lines changed

mythril/ethereum/util.py

+74-12
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import logging
77
import os
88
import platform
9+
import re
910
import typing
1011
from json.decoder import JSONDecodeError
1112
from subprocess import PIPE, Popen
@@ -168,16 +169,77 @@ def parse_pragma(solidity_code):
168169
all_versions = solcx.get_installed_solc_versions()
169170

170171

171-
def extract_version(file: typing.Optional[str]):
172-
if file is None:
172+
VOID_START = re.compile("//|/\\*|\"|'")
173+
QUOTE_END = re.compile("(?<!\\\\)'")
174+
DQUOTE_END = re.compile('(?<!\\\\)"')
175+
176+
177+
def remove_comments_strings(program: str) -> str:
178+
"""Return program without Solidity comments and strings
179+
180+
:param str program: Solidity program with lines separated by \\n
181+
:return: program with strings emptied and comments removed
182+
:rtype: str
183+
"""
184+
result = ""
185+
while True:
186+
match_start_of_void = VOID_START.search(program)
187+
if not match_start_of_void:
188+
result += program
189+
break
190+
else:
191+
result += program[: match_start_of_void.start()]
192+
if match_start_of_void[0] == "//":
193+
end = program.find("\n", match_start_of_void.end())
194+
program = "" if end == -1 else program[end:]
195+
elif match_start_of_void[0] == "/*":
196+
end = program.find("*/", match_start_of_void.end())
197+
result += " "
198+
program = "" if end == -1 else program[end + 2 :]
199+
else:
200+
if match_start_of_void[0] == "'":
201+
match_end_of_string = QUOTE_END.search(
202+
program[match_start_of_void.end() :]
203+
)
204+
else:
205+
match_end_of_string = DQUOTE_END.search(
206+
program[match_start_of_void.end() :]
207+
)
208+
if not match_end_of_string: # unclosed string
209+
break
210+
program = program[
211+
match_start_of_void.end() + match_end_of_string.end() :
212+
]
213+
return result
214+
215+
216+
def extract_version_line(program: typing.Optional[str]) -> typing.Optional[str]:
217+
if not program:
173218
return None
174-
version_line = None
175-
for line in file.split("\n"):
176-
if "pragma solidity" not in line:
177-
continue
178-
version_line = line.rstrip()
179-
break
180-
if version_line is None:
219+
220+
# normalize line endings
221+
if "\n" in program:
222+
program = program.replace("\r", "")
223+
else:
224+
program = program.replace("\r", "\n")
225+
226+
# extract regular pragma
227+
program_wo_comments_strings = remove_comments_strings(program)
228+
for line in program_wo_comments_strings.split("\n"):
229+
if "pragma solidity" in line:
230+
return line.rstrip()
231+
232+
# extract pragma from comments
233+
for line in program.split("\n"):
234+
if "pragma solidity" in line:
235+
return line.rstrip()
236+
237+
return None
238+
239+
240+
def extract_version(program: typing.Optional[str]) -> typing.Optional[str]:
241+
version_line = extract_version_line(program)
242+
if not version_line:
181243
return None
182244

183245
assert "pragma solidity" in version_line
@@ -212,11 +274,11 @@ def extract_version(file: typing.Optional[str]):
212274

213275

214276
def extract_binary(file: str) -> Tuple[str, str]:
215-
file_data = None
277+
program = None
216278
with open(file) as f:
217-
file_data = f.read()
279+
program = f.read()
218280

219-
version = extract_version(file_data)
281+
version = extract_version(program)
220282

221283
if version is None:
222284
return os.environ.get("SOLC") or "solc", version

tests/integration_tests/version_test.py

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
("version_chaos.sol", None, True),
1313
("version_2.sol", None, True),
1414
("version_3.sol", None, True),
15+
("version_4.sol", None, False),
1516
("version_patch.sol", None, False),
1617
("integer_edge_case.sol", None, True),
1718
("integer_edge_case.sol", "v0.8.19", True),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
// VERSION: pragma solidity ^0.7.0;
3+
/* ORIGINAL: pragma solidity ^0.7.0; */
4+
pragma solidity ^0.8.0;
5+
6+
contract Test {
7+
uint256 input;
8+
function add(uint256 a, uint256 b) public {
9+
input = a + b;
10+
}
11+
}

0 commit comments

Comments
 (0)