Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix 'pragma solidity' parsing #1887

Merged
merged 13 commits into from
Jan 10, 2025
86 changes: 74 additions & 12 deletions mythril/ethereum/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import logging
import os
import platform
import re
import typing
from json.decoder import JSONDecodeError
from subprocess import PIPE, Popen
Expand Down Expand Up @@ -168,16 +169,77 @@ def parse_pragma(solidity_code):
all_versions = solcx.get_installed_solc_versions()


def extract_version(file: typing.Optional[str]):
if file is None:
VOID_START = re.compile("//|/\\*|\"|'")
QUOTE_END = re.compile("(?<!\\\\)'")
DQUOTE_END = re.compile('(?<!\\\\)"')


def remove_comments_strings(program: str) -> str:
"""Return program without Solidity comments and strings

:param str program: Solidity program with lines separated by \\n
:return: program with strings emptied and comments removed
:rtype: str
"""
result = ""
while True:
match_start_of_void = VOID_START.search(program)
if not match_start_of_void:
result += program
break
else:
result += program[: match_start_of_void.start()]
if match_start_of_void[0] == "//":
end = program.find("\n", match_start_of_void.end())
program = "" if end == -1 else program[end:]
elif match_start_of_void[0] == "/*":
end = program.find("*/", match_start_of_void.end())
result += " "
program = "" if end == -1 else program[end + 2 :]
else:
if match_start_of_void[0] == "'":
match_end_of_string = QUOTE_END.search(
program[match_start_of_void.end() :]
)
else:
match_end_of_string = DQUOTE_END.search(
program[match_start_of_void.end() :]
)
if not match_end_of_string: # unclosed string
break
program = program[
match_start_of_void.end() + match_end_of_string.end() :
]
return result


def extract_version_line(program: typing.Optional[str]) -> typing.Optional[str]:
if not program:
return None
version_line = None
for line in file.split("\n"):
if "pragma solidity" not in line:
continue
version_line = line.rstrip()
break
if version_line is None:

# normalize line endings
gsalzer marked this conversation as resolved.
Show resolved Hide resolved
if "\n" in program:
program = program.replace("\r", "")
else:
program = program.replace("\r", "\n")

# extract regular pragma
program_wo_comments_strings = remove_comments_strings(program)
for line in program_wo_comments_strings.split("\n"):
if "pragma solidity" in line:
return line.rstrip()

# extract pragma from comments
for line in program.split("\n"):
if "pragma solidity" in line:
return line.rstrip()

return None


def extract_version(program: typing.Optional[str]) -> typing.Optional[str]:
version_line = extract_version_line(program)
if not version_line:
return None

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


def extract_binary(file: str) -> Tuple[str, str]:
file_data = None
program = None
with open(file) as f:
file_data = f.read()
program = f.read()

version = extract_version(file_data)
version = extract_version(program)

if version is None:
return os.environ.get("SOLC") or "solc", version
Expand Down
1 change: 1 addition & 0 deletions tests/integration_tests/version_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
("version_chaos.sol", None, True),
("version_2.sol", None, True),
("version_3.sol", None, True),
("version_4.sol", None, False),
("version_patch.sol", None, False),
("integer_edge_case.sol", None, True),
("integer_edge_case.sol", "v0.8.19", True),
Expand Down
11 changes: 11 additions & 0 deletions tests/testdata/input_contracts/version_4.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

// VERSION: pragma solidity ^0.7.0;
/* ORIGINAL: pragma solidity ^0.7.0; */
pragma solidity ^0.8.0;

contract Test {
uint256 input;
function add(uint256 a, uint256 b) public {
input = a + b;
}
}
Loading