diff --git a/scripts/bash/evmlisa-job-cpu.sh b/scripts/bash/evmlisa-job-cpu.sh
index 29f515b35..1c68e0770 100644
--- a/scripts/bash/evmlisa-job-cpu.sh
+++ b/scripts/bash/evmlisa-job-cpu.sh
@@ -6,7 +6,7 @@
#SBATCH --cpus-per-task=1
#SBATCH --time=3-00:00:00
#SBATCH --mem=64G
-#SBATCH --mail-user=saveriomattia.merenda@studenti.unipr.it
+#SBATCH --mail-user=
#SBATCH --mail-type=FAIL
#SBATCH --partition=cpu
#SBATCH --qos=cpu
diff --git a/scripts/bash/evmlisa-job-cpu_guest.sh b/scripts/bash/evmlisa-job-cpu_guest.sh
index ffa44ce96..2f024f36b 100644
--- a/scripts/bash/evmlisa-job-cpu_guest.sh
+++ b/scripts/bash/evmlisa-job-cpu_guest.sh
@@ -6,7 +6,7 @@
#SBATCH --cpus-per-task=1
#SBATCH --time=3-00:00:00
#SBATCH --mem=64G
-#SBATCH --mail-user=saveriomattia.merenda@studenti.unipr.it
+#SBATCH --mail-user=
#SBATCH --mail-type=FAIL
#SBATCH --partition=cpu_guest
#SBATCH --qos=cpu_guest
diff --git a/scripts/bash/job-handler.sh b/scripts/bash/job-handler.sh
index 18823e92e..7653a31cb 100644
--- a/scripts/bash/job-handler.sh
+++ b/scripts/bash/job-handler.sh
@@ -5,7 +5,7 @@
#SBATCH --cpus-per-task=1
#SBATCH --time=0-06:00:00
#SBATCH --mem=16G
-#SBATCH --mail-user=saveriomattia.merenda@studenti.unipr.it
+#SBATCH --mail-user=
#SBATCH --mail-type=FAIL
#SBATCH --partition=cpu
#SBATCH --qos=cpu
diff --git a/scripts/python/benchmark-checkers/compile-smart-contracts.py b/scripts/python/benchmark-checkers/compile-smart-contracts.py
index f46d186c2..187ee5f2a 100644
--- a/scripts/python/benchmark-checkers/compile-smart-contracts.py
+++ b/scripts/python/benchmark-checkers/compile-smart-contracts.py
@@ -33,16 +33,16 @@ def load_version_mapping(version_file):
def generate_file_index(folder_path, output_json="file_index.json"):
file_index = {}
-
+
files = sorted(f for f in os.listdir(folder_path) if f.endswith(".sol") and os.path.isfile(os.path.join(folder_path, f)))
-
+
for i, file_name in enumerate(files, start=1):
- file_name_no_ext = os.path.splitext(file_name)[0]
+ file_name_no_ext = os.path.splitext(file_name)[0]
file_index[file_name_no_ext] = i
with open(output_json, 'w', encoding='utf-8') as json_file:
json.dump(file_index, json_file, indent=4)
-
+
print(f"File index saved in {output_json}")
def extract_solidity_versions(src_folder, output_csv="solidity_versions.csv"):
@@ -59,7 +59,7 @@ def extract_solidity_versions(src_folder, output_csv="solidity_versions.csv"):
pragma_match = pragma_regex.search(content)
if pragma_match:
original_version = pragma_match.group(1).strip()
-
+
cleaned_version = re.sub(r"[^\d\.\s]", "", original_version).strip()
versions = cleaned_version.split()
@@ -82,7 +82,7 @@ def extract_solidity_versions(src_folder, output_csv="solidity_versions.csv"):
def compile_solidity_sources_with_different_version(source_dir, json_dir, version_file):
"""
- Compiles all .sol files in the specified source directory using different versions of solc,
+ Compiles all .sol files in the specified source directory using different versions of solc,
saving the bytecode for each file in JSON format in the specified output directory.
"""
version_mapping = load_version_mapping(version_file)
@@ -105,7 +105,7 @@ def compile_solidity_sources_with_different_version(source_dir, json_dir, versio
output_file = os.path.join(json_dir, f"{os.path.splitext(filename)[0]}.json")
compiled_version = version_mapping.get(filename, None)
-
+
if compiled_version is None:
print(f"Version not specified for {filename} in {version_file}. Skipping file.")
pbar.update(1)
@@ -125,24 +125,24 @@ def compile_solidity_sources_with_different_version(source_dir, json_dir, versio
# Command to compile and save the bytecode in JSON format
command = (
f"solc-select use {compiled_version} > /dev/null && "
- f"solc --combined-json bin,bin-runtime,abi {input_file} > {output_file} 2> /dev/null"
+ f"solc --combined-json bin,bin-runtime,abi {input_file} > {output_file} 2> /dev/null"
)
-
+
# Execute the compilation command
try:
subprocess.run(command, shell=True, check=True)
count_success += 1
except subprocess.CalledProcessError as e:
count_failure += 1
-
+
# Update the progress bar
pbar.update(1)
-
+
print(f"Compiled successfully {count_success}/{count_success + count_failure} files.")
def compile_solidity_sources(source_dir, json_dir):
"""
- Compiles all .sol files in the specified source directory using solc,
+ Compiles all .sol files in the specified source directory using solc,
saving the bytecode for each file in JSON format in the specified output directory.
"""
# Clear and create JSON output directory
@@ -173,20 +173,20 @@ def compile_solidity_sources(source_dir, json_dir):
# Full paths for input and output files
input_file = os.path.join(source_dir, filename)
output_file = os.path.join(json_dir, f"{os.path.splitext(filename)[0]}.json")
-
+
# Command to compile and save the bytecode in JSON format
command = f"solc --combined-json bin,bin-runtime,abi --pretty-json {input_file} > {output_file} 2> /dev/null "
-
+
# Execute the compilation command
try:
subprocess.run(command, shell=True, check=True)
count_success += 1
except subprocess.CalledProcessError as e:
count_failure += 1
-
+
# Update the progress bar
pbar.update(1)
-
+
print(f"Compiled successfully {count_success}/{count_success + count_failure} files.")
def extract_and_save_longest_bytecode(bytecode_dir, json_dir, is_ethersolve=False, file_index=None, abi_dir=None):
@@ -208,7 +208,7 @@ def extract_and_save_longest_bytecode(bytecode_dir, json_dir, is_ethersolve=Fals
for json_filename in os.listdir(json_dir):
if json_filename.endswith(".json"):
json_filepath = os.path.join(json_dir, json_filename)
-
+
# Check if the file is empty
if os.path.getsize(json_filepath) == 0:
# print(f"Skipping empty file: {json_filename}")
@@ -218,7 +218,7 @@ def extract_and_save_longest_bytecode(bytecode_dir, json_dir, is_ethersolve=Fals
with open(json_filepath, 'r') as json_file:
data = json.load(json_file)
contracts = data.get("contracts", {})
-
+
longest_bytecode = None
longest_contract_name = None
max_bytecode_length = 0 # Variable to store the maximum file size
@@ -242,23 +242,23 @@ def extract_and_save_longest_bytecode(bytecode_dir, json_dir, is_ethersolve=Fals
# Save the longest bytecode, if it exists
if longest_bytecode:
base_filename = os.path.splitext(json_filename)[0]
-
+
if file_index is not None:
file_id = file_index.get(base_filename) # Match string name to integer
base_filename = str(file_id) if file_id is not None else base_filename
-
+
bytecode_filename = os.path.join(bytecode_dir, f"{base_filename}.bytecode")
-
+
with open(bytecode_filename, 'w') as bytecode_file:
bytecode_file.write("0x" + longest_bytecode)
-
+
# Save ABI if available
if abi and abi_dir is not None:
- abi_filename = os.path.join(abi_dir, f"{base_filename}.abi.json")
-
- if isinstance(abi, str):
+ abi_filename = os.path.join(abi_dir, f"{base_filename}.abi")
+
+ if isinstance(abi, str):
abi = json.loads(abi)
-
+
with open(abi_filename, 'w') as abi_file:
json.dump(abi, abi_file, indent=4)
# Update the progress bar
@@ -279,79 +279,59 @@ def extract_and_save_bytecode(bytecode_dir, json_dir, is_ethersolve=False, file_
num_files = [f for f in os.listdir(json_dir) if f.endswith('.json')]
# Progress bar setup
- with tqdm(total=len(num_files), desc="Extracting files...") as pbar:
- for json_filename in os.listdir(json_dir):
- if json_filename.endswith(".json"):
- json_filepath = os.path.join(json_dir, json_filename)
- with open(json_filepath, 'r') as json_file:
- # Check if the file is empty by reading the first character
- if json_file.read(1) == "":
- # print(f"Skipping empty file: {json_filename}")
- pbar.update(1)
- continue
- json_file.seek(0) # Reset the file pointer to the beginning
-
- data = json.load(json_file)
- contracts = data.get("contracts", {})
- count = 1 # Sequential counter for each bytecode in the same JSON
- abi = None
+ # with tqdm(total=len(num_files), desc="Extracting files...") as pbar:
+ for json_filename in os.listdir(json_dir):
+ if json_filename.endswith(".json"):
+ json_filepath = os.path.join(json_dir, json_filename)
+ with open(json_filepath, 'r') as json_file:
+ # Check if the file is empty by reading the first character
+ if json_file.read(1) == "":
+ # print(f"Skipping empty file: {json_filename}")
+ # pbar.update(1)
+ continue
+ json_file.seek(0) # Reset the file pointer to the beginning
- for contract_name, contract_data in contracts.items():
- if(is_ethersolve):
- bytecode = contract_data.get("bin")
- else:
- bytecode = contract_data.get("bin-runtime")
- abi = contract_data.get("abi")
-
- if bytecode:
+ data = json.load(json_file)
+ contracts = data.get("contracts", {})
+ count = 1 # Sequential counter for each bytecode in the same JSON
+ abi = None
+
+ for contract_name, contract_data in contracts.items():
+ if(is_ethersolve):
+ bytecode = contract_data.get("bin")
+ else:
+ bytecode = contract_data.get("bin-runtime")
+ abi = contract_data.get("abi")
+
+ if bytecode:
+ bytecode_filename = os.path.join(
+ bytecode_dir, f"{os.path.splitext(json_filename)[0]}_{count}.bytecode"
+ )
+
+ if file_index is not None:
+ file_id = file_index.get(os.path.splitext(json_filename)[0]) # Match string name to integer
+ # Add a sequential number to the filename
bytecode_filename = os.path.join(
- bytecode_dir, f"{os.path.splitext(json_filename)[0]}_{count}.bytecode"
+ bytecode_dir, f"{file_id}_{count}.bytecode"
)
- if file_index is not None:
- file_id = file_index.get(os.path.splitext(json_filename)[0]) # Match string name to integer
- # Add a sequential number to the filename
- bytecode_filename = os.path.join(
- bytecode_dir, f"{file_id}_{count}.bytecode"
- )
-
- with open(bytecode_filename, 'w') as bytecode_file:
- bytecode_file.write("0x" + bytecode)
-
- # Save ABI if available
- if abi and abi_dir is not None:
- abi_filename = os.path.join(abi_dir, f"{file_id}_{count}.abi.json")
-
- if isinstance(abi, str):
- abi = json.loads(abi)
-
- with open(abi_filename, 'w') as abi_file:
- json.dump(abi, abi_file, indent=4)
-
- # print(f"Extracted bytecode to {bytecode_filename}")
- count += 1 # Increment counter for next bytecode
- # Update the progress bar
- pbar.update(1)
+ with open(bytecode_filename, 'w') as bytecode_file:
+ bytecode_file.write("0x" + bytecode)
-def compile_bridge(name):
- extract_solidity_versions(src_folder=f'./{name}/source-code',
- output_csv=f'./{name}/source-code/version.csv')
-
- compile_solidity_sources_with_different_version(source_dir=f'./{name}/source-code',
- json_dir=f'./{name}/json',
- version_file=f'./{name}/source-code/version.csv')
-
- generate_file_index(folder_path=f'./{name}/source-code',
- output_json=f'./{name}/match-file-index.json')
-
- with open(f'./{name}/match-file-index.json', 'r') as index_file:
- match_file_index = json.load(index_file)
-
- extract_and_save_bytecode(bytecode_dir=f'./{name}/bytecode',
- json_dir=f'./{name}/json',
- abi_dir=f'./{name}/abi',
- file_index=match_file_index)
+ # Save ABI if available
+ if abi and abi_dir is not None:
+ abi_filename = os.path.join(abi_dir, f"{file_id}_{count}.abi")
+ if isinstance(abi, str):
+ abi = json.loads(abi)
+
+ with open(abi_filename, 'w') as abi_file:
+ json.dump(abi, abi_file, indent=4)
+
+ # print(f"Extracted bytecode to {bytecode_filename}")
+ count += 1 # Increment counter for next bytecode
+ # Update the progress bar
+ # pbar.update(1)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Compile datasets.")
@@ -359,26 +339,9 @@ def compile_bridge(name):
parser.add_argument("--smartbugs", action="store_true", help="Compile SmartBugs dataset")
parser.add_argument("--slise", action="store_true", help="Compile SliSE dataset")
parser.add_argument("--longest-bytecode", action="store_true", help="Save only the longest bytecode")
- parser.add_argument("--manual", action="store_true", help="Manual mode")
- parser.add_argument("--cross-chain-xguard", action="store_true", help="Compile XGuard dataset")
args = parser.parse_args()
- if args.cross_chain_xguard:
- compile_bridge('cross-chain/XGuard/QBridge') # QBridge
- compile_bridge('cross-chain/XGuard/MeterBridge') # MeterBridge
-
- if args.manual:
- # Test ThorChain Bridge
- extract_solidity_versions(src_folder='./cross-chain/THORChain-bridge/source-code',
- output_csv='./cross-chain/THORChain-bridge/source-code/version.csv')
- compile_solidity_sources_with_different_version(source_dir='./cross-chain/THORChain-bridge/source-code',
- json_dir='./cross-chain/THORChain-bridge/json',
- version_file='./cross-chain/THORChain-bridge/source-code/version.csv')
- extract_and_save_longest_bytecode(bytecode_dir='./cross-chain/THORChain-bridge/bytecode/',
- json_dir='./cross-chain/THORChain-bridge/json',
- abi_dir='./cross-chain/THORChain-bridge/abi/')
-
if args.solidifi:
compile_solidity_sources('./solidifi/reentrancy/source-code',
'./solidifi/reentrancy/json')
@@ -388,7 +351,7 @@ def compile_bridge(name):
'./solidifi/tx-origin/json')
compile_solidity_sources('./solidifi/timestamp-dependency/source-code',
'./solidifi/timestamp-dependency/json')
-
+
if args.longest_bytecode:
# EVMLiSA
extract_and_save_longest_bytecode('./solidifi/vanilla/bytecode/evmlisa',
@@ -425,7 +388,7 @@ def compile_bridge(name):
# Timestamp-dependency
extract_and_save_bytecode('./solidifi/timestamp-dependency/bytecode/evmlisa',
'./solidifi/timestamp-dependency/json')
-
+
# EtherSolve
extract_and_save_bytecode('./solidifi/vanilla/bytecode/ethersolve',
'./solidifi/vanilla/json',
@@ -433,7 +396,7 @@ def compile_bridge(name):
extract_and_save_bytecode('./solidifi/reentrancy/bytecode/ethersolve',
'./solidifi/reentrancy/json',
True)
-
+
if args.smartbugs:
compile_solidity_sources_with_different_version('./smartbugs/reentrancy/source-code',
'./smartbugs/reentrancy/json',
@@ -459,7 +422,7 @@ def compile_bridge(name):
extract_and_save_bytecode('./smartbugs/reentrancy/bytecode/ethersolve',
'./smartbugs/reentrancy/json',
True)
-
+
if args.slise:
extract_solidity_versions('./slise/reentrancy-db1/source-code',
'./slise/reentrancy-db1/source-code/version.csv')
@@ -468,7 +431,7 @@ def compile_bridge(name):
compile_solidity_sources_with_different_version('./slise/reentrancy-db1/source-code',
'./slise/reentrancy-db1/json',
'./slise/reentrancy-db1/source-code/version.csv')
-
+
with open('./slise/reentrancy-db1/match-file-index.json', 'r') as index_file:
match_file_index = json.load(index_file)
@@ -482,10 +445,10 @@ def compile_bridge(name):
True,
match_file_index)
else:
-
+
extract_and_save_bytecode('./slise/reentrancy-db1/bytecode/evmlisa',
- './slise/reentrancy-db1/json',
- False,
+ './slise/reentrancy-db1/json',
+ False,
match_file_index)
extract_and_save_bytecode('./slise/reentrancy-db1/bytecode/ethersolve',
'./slise/reentrancy-db1/json',
diff --git a/scripts/python/benchmark-checkers/run-benchmark.py b/scripts/python/benchmark-checkers/run-benchmark.py
index 809c1d015..0938ef980 100644
--- a/scripts/python/benchmark-checkers/run-benchmark.py
+++ b/scripts/python/benchmark-checkers/run-benchmark.py
@@ -46,19 +46,19 @@ def delete_files(directory):
def clean_files(directory_path):
"""
Removes lines containing "Picked up _JAVA_OPTIONS" from all files in the specified directory.
-
+
Args:
directory_path (str): The path to the directory containing files to clean.
"""
for filename in os.listdir(directory_path):
file_path = os.path.join(directory_path, filename)
-
+
if os.path.isfile(file_path):
with open(file_path, 'r') as file:
- lines = file.readlines()
-
+ lines = file.readlines()
+
cleaned_lines = [line for line in lines if "Picked up _JAVA_OPTIONS" not in line]
-
+
if len(cleaned_lines) != len(lines):
with open(file_path, 'w') as file:
file.writelines(cleaned_lines)
@@ -140,7 +140,7 @@ def plot_results_timestamp(data_evmlisa, data_solidifi, name="no-name"):
plt.xlabel('Problem ID')
plt.ylabel('Value')
- plt.title(f'[{name}] Comparison of results (timestamp-dependency)')
+ plt.title(f'[{name}] Comparison of results (randomness-dependency)')
plt.xticks(sorted(set(keys2).union(keys3))) # Show all problem IDs on x-axis
plt.legend()
plt.grid()
@@ -153,18 +153,18 @@ def plot_results_timestamp(data_evmlisa, data_solidifi, name="no-name"):
def subtract_dicts(dict1, dict2):
result = {}
-
+
for key in dict1:
if key in dict2:
result[key] = dict1[key] - dict2[key]
else:
result[key] = dict1[key]
-
+
return result
def calculate_average(data):
if not data:
- return 0
+ return 0
total = sum(data.values())
count = len(data)
@@ -176,7 +176,7 @@ def map_file_names_to_ids(sorted_data, index_path):
"""
Map the file names in sorted_data to their corresponding IDs from the index JSON.
"""
-
+
with open(index_path, 'r') as index_file:
file_index = json.load(index_file)
@@ -210,20 +210,20 @@ def build_evmlisa():
def run_evmlisa(bytecode_file, result_evmlisa_dir, type="reentrancy"):
"""
Runs the EVMLiSA analysis for a given bytecode file.
-
+
Args:
bytecode_file (str): The path to the bytecode file.
-
+
Returns:
str: The name of the results file.
"""
bytecode_filename = os.path.basename(bytecode_file)
result_filename = f"{os.path.splitext(bytecode_filename)[0]}-result.json"
result_filepath = os.path.join(result_evmlisa_dir, result_filename)
-
+
command = (
f"java -jar jars/evm-lisa.jar "
- f"--filepath-bytecode {bytecode_file} "
+ f"--bytecode-path {bytecode_file} "
# f"--stack-size 40 "
# f"--stack-set-size 15 "
# f"--creation-code "
@@ -233,7 +233,7 @@ def run_evmlisa(bytecode_file, result_evmlisa_dir, type="reentrancy"):
# f"--output {os.path.splitext(bytecode_filename)[0]}"
f"> /dev/null 2> {result_filepath}"
)
-
+
try:
subprocess.run(command, shell=True, check=True)
return result_filepath
@@ -247,7 +247,7 @@ def evmlisa(bytecode_dir, results_dir, result_evmlisa_dir, type="reentrancy"):
"""
delete_files(result_evmlisa_dir)
os.makedirs(result_evmlisa_dir, exist_ok=True)
-
+
# Find all bytecode files
bytecode_files = [os.path.join(bytecode_dir, f) for f in os.listdir(bytecode_dir) if f.endswith(".bytecode")]
num_files = len(bytecode_files)
@@ -258,14 +258,14 @@ def evmlisa(bytecode_dir, results_dir, result_evmlisa_dir, type="reentrancy"):
# Run analyses in parallel
future_to_file = {_executor.submit(run_evmlisa, file, result_evmlisa_dir, type): file for file in bytecode_files}
-
+
with tqdm(total=num_files, desc=f"[EVMLISA] Analyzing bytecode files in '{bytecode_dir}'") as pbar:
for future in as_completed(future_to_file):
result_file = future.result()
if result_file:
analysis_ended += 1
pbar.update(1)
-
+
print(f"[EVMLISA] Completed {analysis_ended}/{num_files} from {bytecode_dir}.")
delete_tmp_files(results_dir)
delete_tmp_files("./execution/results")
@@ -274,47 +274,63 @@ def evmlisa(bytecode_dir, results_dir, result_evmlisa_dir, type="reentrancy"):
def check_sound_analysis_evmlisa(directory_path):
sound = True
-
+
for filename in os.listdir(directory_path):
if filename.endswith(".json"):
file_path = os.path.join(directory_path, filename)
try:
with open(file_path, 'r') as file:
data = json.load(file)
-
- if "solved-jumps-percent" in data:
- if data["solved-jumps-percent"] != 1 and data["solved-jumps-percent"] != -1:
- print(f"[EVMLiSA] {filename} - solved-jumps-percent: {data['solved-jumps-percent']}")
- sound = False
+
+ if "statistics" in data:
+ statistics = data["statistics"]
+
+ if "unsound_jumps" in statistics:
+ if statistics["unsound_jumps"] != 0:
+ print(f"[EVMLiSA] {filename} - unsound_jumps: {statistics['unsound_jumps']}")
+ sound = False
+ else:
+ print(f"[EVMLiSA] Warning: 'unsound_jumps' not found in {filename}")
+
+ if "maybe_unsound_jumps" in statistics:
+ if statistics["maybe_unsound_jumps"] != 0:
+ print(f"[EVMLiSA] {filename} - maybe_unsound_jumps: {statistics['maybe_unsound_jumps']}")
+ sound = False
+ else:
+ print(f"[EVMLiSA] Warning: 'maybe_unsound_jumps' not found in {filename}")
else:
- print(f"[EVMLiSA] Warning: 'solved-jumps-percent' not found in {filename}")
+ print(f"[EVMLiSA] Warning: 'statistics' not found in {filename}")
except Exception as e:
print(f"[EVMLiSA] ERROR: {filename}: {e}")
-
+
if sound:
- print("[EVMLiSA] All analysis are SOUND")
+ print("[EVMLiSA] All analysis are SOUND.")
-def get_results_evmlisa(directory_path, print_data):
+def get_results_evmlisa(directory_path, print_data, vulnerability_type):
warnings_counts = {}
failed = 0
-
+
for filename in os.listdir(directory_path):
if filename.endswith(".json"):
file_path = os.path.join(directory_path, filename)
try:
with open(file_path, 'r') as file:
data = json.load(file)
- if "re-entrancy-warning" in data:
- warnings_counts[filename] = data['re-entrancy-warning']
- elif "tx-origin-warning" in data:
- warnings_counts[filename] = data['tx-origin-warning']
- elif "timestamp-dependency-warning" in data:
- warnings_counts[filename] = data['timestamp-dependency-warning']
+ if "vulnerabilities" in data:
+ vulnerabilities = data["vulnerabilities"]
+ if "reentrancy" in vulnerabilities and vulnerability_type == "reentrancy":
+ warnings_counts[filename] = vulnerabilities['reentrancy']
+ elif "tx_origin" in vulnerabilities and vulnerability_type == "tx-origin":
+ warnings_counts[filename] = vulnerabilities['tx_origin']
+ elif "randomness_dependency" in vulnerabilities and vulnerability_type == "randomness-dependency":
+ warnings_counts[filename] = vulnerabilities['randomness_dependency']
+ else:
+ print(f"[EVMLiSA] Warning: 'reentrancy', 'tx_origin' and 'timestam_dependency' not found in {filename}")
else:
- print(f"[EVMLiSA] Warning: 're-entrancy-warning', 'tx-origin-warning' and 'timestamp-dependency-warning' not found in {filename}")
+ print(f"[EVMLiSA] Warning: 'vulnerabilities' not found in {filename}")
except Exception as e:
failed += 1
- # print(f"[EVMLiSA] ERROR: {filename}: {e}")
+ # print(f"[EVMLiSA] ERROR: {filename}: {e}")
results = defaultdict(int)
for file, result in warnings_counts.items():
@@ -323,12 +339,12 @@ def get_results_evmlisa(directory_path, print_data):
if match:
id = int(match.group(1))
results[id] += result
-
+
match = re.match(r'buggy_(\d+)_(\d+)-\w+\.json', file)
if match:
id = int(match.group(1))
results[id] += result
-
+
# smartbug & slise case
match = re.match(r'(\d+)-\w+\.json', file)
if match:
@@ -341,7 +357,7 @@ def get_results_evmlisa(directory_path, print_data):
results[id] += result
sorted_data = dict(sorted(results.items()))
-
+
print(print_data)
print(sorted_data)
@@ -352,10 +368,10 @@ def get_results_evmlisa(directory_path, print_data):
def run_ethersolve(bytecode_file, result_ethersolve_dir):
"""
Runs the EtherSolve analysis for a given bytecode file.
-
+
Args:
bytecode_file (str): The path to the bytecode file.
-
+
Returns:
str: The name of the results file.
"""
@@ -363,17 +379,17 @@ def run_ethersolve(bytecode_file, result_ethersolve_dir):
filename = os.path.splitext(bytecode_filename)[0]
result_filename = filename + "-result.json"
result_filepath = os.path.join(result_ethersolve_dir, result_filename)
-
+
command = (
f"sleep 0.9 && "
f"java -jar jars/EtherSolve.jar "
f"--re-entrancy "
- f"-o {result_filepath} "
+ f"-o {result_filepath} "
f"--creation --json "
f"{bytecode_file} 2> /dev/null && "
f"mv *re-entrancy.csv {result_ethersolve_dir}/{filename}-reentrancy.csv"
)
-
+
try:
subprocess.run(command, shell=True, check=True)
return result_filepath
@@ -387,7 +403,7 @@ def ethersolve(bytecode_dir, result_ethersolve_dir):
"""
delete_files(result_ethersolve_dir)
os.makedirs(result_ethersolve_dir, exist_ok=True)
-
+
# Find all bytecode files
bytecode_files = [os.path.join(bytecode_dir, f) for f in os.listdir(bytecode_dir) if f.endswith(".bytecode")]
num_files = len(bytecode_files)
@@ -399,7 +415,7 @@ def ethersolve(bytecode_dir, result_ethersolve_dir):
# Run analyses in parallel
with ThreadPoolExecutor(max_workers=1) as executor:
future_to_file = {executor.submit(run_ethersolve, file, result_ethersolve_dir): file for file in bytecode_files}
-
+
with tqdm(total=num_files, desc="[ETHERSOLVE] Analyzing bytecode files") as pbar:
for future in as_completed(future_to_file):
result_file = future.result()
@@ -412,7 +428,7 @@ def ethersolve(bytecode_dir, result_ethersolve_dir):
def get_results_ethersolve(directory_path, print_data):
"""
- Counts occurrences of the word "SSTORE" in files with "reentrancy" in their names
+ Counts occurrences of the word "SSTORE" in files with "reentrancy" in their names
within the specified directory.
Args:
@@ -426,9 +442,9 @@ def get_results_ethersolve(directory_path, print_data):
if os.path.isfile(file_path):
with open(file_path, 'r') as file:
content = file.read()
-
+
sstore_count = content.count("SSTORE")
-
+
# print(f"{filename}: {sstore_count}")
sstore_counts[filename] = sstore_count
@@ -439,12 +455,12 @@ def get_results_ethersolve(directory_path, print_data):
if match:
id = int(match.group(1))
results[id] += result
-
+
match = re.match(r'buggy_(\d+)_(\d+)-\w+\.csv', file)
if match:
id = int(match.group(1))
results[id] += result
-
+
# smartbugs & slise case
match = re.match(r'(\d+)-\w+\.csv', file)
if match:
@@ -455,12 +471,12 @@ def get_results_ethersolve(directory_path, print_data):
if match:
id = int(match.group(1))
results[id] += result
-
+
sorted_data = dict(sorted(results.items()))
-
+
print(print_data)
print(sorted_data)
-
+
return sorted_data
#################################### SolidiFI
@@ -477,7 +493,7 @@ def get_results_solidifi(folder_path, type, print_data):
11: 1, 12: 9, 18: 1, 20: 2, 21: 4, 22: 7, 29: 3, 33: 3, 36: 7, 37: 1, 42: 3, 48: 1
}
- timestampdependency_subtraction_values = {
+ randomnessdependency_subtraction_values = {
11: 1, 12: 9, 18: 1, 20: 2, 21: 4, 22: 7, 29: 3, 33: 3, 36: 7, 37: 1, 42: 3, 48: 1
}
@@ -489,22 +505,21 @@ def get_results_solidifi(folder_path, type, print_data):
# Extract the problem number from the file name
problem_id = int(match.group(1))
file_path = os.path.join(folder_path, file_name)
-
+
# Count the number of lines in the file
with open(file_path, 'r') as file:
num_lines = sum(1 for _ in file)
-
+
# Store the line count in the dictionary
if type == 'reentrancy':
line_counts[problem_id] = num_lines - 1 - reentrancy_subtraction_values.get(problem_id, 0)
elif type == 'tx-origin':
line_counts[problem_id] = num_lines - 1 - txorigin_subtraction_values.get(problem_id, 0)
- elif type == 'timestamp-dependency':
- line_counts[problem_id] = num_lines - 1 - timestampdependency_subtraction_values.get(problem_id, 0)
-
+ elif type == 'randomness-dependency':
+ line_counts[problem_id] = num_lines - 1 - randomnessdependency_subtraction_values.get(problem_id, 0)
- sorted_data = dict(sorted(line_counts.items()))
+ sorted_data = dict(sorted(line_counts.items()))
print(print_data)
print(sorted_data)
@@ -522,12 +537,12 @@ def get_results_smartbugs(json_path, print_data):
# Load JSON data
with open(json_path, 'r') as file:
data = json.load(file)
-
+
# Iterate over each entry in the JSON data
for entry in data:
# Extract the filename without the extension
file_id = int(os.path.splitext(entry["name"])[0])
-
+
# Count the vulnerabilities for the file
vulnerability_counts[file_id] = len(entry.get("vulnerabilities", []))
@@ -536,7 +551,7 @@ def get_results_smartbugs(json_path, print_data):
print(print_data)
print(sorted_data)
-
+
return sorted_data
#################################### slise
@@ -551,35 +566,35 @@ def get_results_slise(json_path, print_data):
# Load JSON data
with open(json_path, 'r') as file:
data = json.load(file)
-
+
# Iterate over each entry in the JSON data
for entry in data:
# Extract the filename without the extension
file_id = str(os.path.splitext(entry["name"])[0])
-
+
# Count the vulnerabilities for the file
vulnerability_counts[file_id] = len(entry.get("vulnerabilities", []))
# Sort the data by file ID
sorted_data = dict(sorted(vulnerability_counts.items()))
- sorted_data = map_file_names_to_ids(sorted_data, './reentrancy-slise-db1/match-file-index.json')
+ sorted_data = map_file_names_to_ids(sorted_data, './slise/reentrancy-db1/match-file-index.json')
print(print_data)
print(sorted_data)
-
+
return sorted_data
#################################### Statistics
def calculate_precision(data, truth):
results_with_precision = {}
-
+
for key, value in data.items():
truth_value = truth.get(key) if truth.get(key) is not None else 0
diff = value - truth_value
tp = fp = fn = 0
if diff == 0: # True positive
- tp = value
+ tp = value
elif diff > 0: # False positive
fp = diff
tp = truth_value
@@ -593,7 +608,7 @@ def calculate_precision(data, truth):
def calculate_recall(data, truth):
results_with_recall = {}
-
+
for key, value in data.items():
truth_value = truth.get(key) if truth.get(key) is not None else 0
diff = value - truth_value
@@ -610,23 +625,23 @@ def calculate_recall(data, truth):
recall = tp / (tp + fn) if (tp + fn) != 0 else 0
results_with_recall[key] = recall
-
+
return results_with_recall
def calculate_f_measure(precision, recall):
results_with_f_measure = {}
-
+
for key, p in precision.items():
r = recall.get(key)
f_measure = 2 * ((p * r) / (p + r)) if (p + r) != 0 else 0
results_with_f_measure[key] = f_measure
-
+
return results_with_f_measure
#################################### Main
if __name__ == "__main__":
-
+
parser = argparse.ArgumentParser(description="EVMLiSA and EtherSolve analysis.")
parser.add_argument("--solidifi", action="store_true", help="Run analysis on SolidiFI dataset")
parser.add_argument("--smartbugs", action="store_true", help="Run analysis on SmartBugs dataset")
@@ -634,36 +649,36 @@ def calculate_f_measure(precision, recall):
parser.add_argument("--no-analysis", action="store_true", help="Do not run the analysis, compute only the results")
parser.add_argument("--reentrancy", action="store_true", help="Run analysis on reentrancy contracts")
parser.add_argument("--tx-origin", action="store_true", help="Run analysis on tx-origin contracts")
- parser.add_argument("--timestamp-dependency", action="store_true", help="Run analysis on timestamp-dependency contracts")
+ parser.add_argument("--randomness-dependency", action="store_true", help="Run analysis on randomness-dependency contracts")
args = parser.parse_args()
-
+
if not args.no_analysis:
build_evmlisa()
if args.tx_origin:
if args.solidifi:
if not args.no_analysis:
- evmlisa_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './solidifi/tx-origin/bytecode/evmlisa',
+ evmlisa_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './solidifi/tx-origin/bytecode/evmlisa',
'results_dir': './solidifi/tx-origin/results',
'result_evmlisa_dir': './solidifi/tx-origin/results/evmlisa',
'type': 'txorigin'})
- evmlisa_vanilla_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './solidifi/vanilla/bytecode/evmlisa',
+ evmlisa_vanilla_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './solidifi/vanilla/bytecode/evmlisa',
'results_dir': './solidifi/vanilla/results',
'result_evmlisa_dir': './solidifi/vanilla/results/evmlisa',
'type': 'txorigin'})
-
+
evmlisa_vanilla_thread.start()
evmlisa_thread.start()
evmlisa_thread.join()
evmlisa_vanilla_thread.join()
check_sound_analysis_evmlisa('./solidifi/tx-origin/results/evmlisa')
-
+
results_solidifi = get_results_solidifi('./solidifi/SolidiFI-buggy-contracts/tx.origin', 'tx-origin', 'solidify')
- results_evmlisa = subtract_dicts(get_results_evmlisa('./solidifi/tx-origin/results/evmlisa', 'evmlisa-buggy-solidifi'),
- get_results_evmlisa('./solidifi/vanilla/results/evmlisa', 'evmlisa-solidifi/vanilla'))
-
+ results_evmlisa = subtract_dicts(get_results_evmlisa('./solidifi/tx-origin/results/evmlisa', 'evmlisa-buggy-solidifi', 'tx-origin'),
+ get_results_evmlisa('./solidifi/vanilla/results/evmlisa', 'evmlisa-solidifi/vanilla', 'tx-origin'))
+
# Precision
evmlisa_precision = calculate_precision(results_evmlisa, results_solidifi)
print(f"Precision evmlisa (avg.): {calculate_average(evmlisa_precision)}")
@@ -674,34 +689,34 @@ def calculate_f_measure(precision, recall):
# F-measure
print(f"F-measure evmlisa (avg.): {calculate_average(calculate_f_measure(evmlisa_precision, evmlisa_recall))}")
-
+
# Plot results
- plot_results_txorigin(data_evmlisa=results_evmlisa,
+ plot_results_txorigin(data_evmlisa=results_evmlisa,
data_solidifi=results_solidifi,
name='solidifi_tx-origin')
- if args.timestamp_dependency:
+ if args.randomness_dependency:
if args.solidifi:
if not args.no_analysis:
- evmlisa_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './solidifi/timestamp-dependency/bytecode/evmlisa',
- 'results_dir': './solidifi/timestamp-dependency/results',
- 'result_evmlisa_dir': './solidifi/timestamp-dependency/results/evmlisa',
- 'type': 'timestampdependency'})
+ evmlisa_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './solidifi/randomness-dependency/bytecode/evmlisa',
+ 'results_dir': './solidifi/randomness-dependency/results',
+ 'result_evmlisa_dir': './solidifi/randomness-dependency/results/evmlisa',
+ 'type': 'randomnessdependency'})
evmlisa_vanilla_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './solidifi/vanilla/bytecode/evmlisa',
'results_dir': './solidifi/vanilla/results',
'result_evmlisa_dir': './solidifi/vanilla/results/evmlisa',
- 'type': 'timestampdependency'})
+ 'type': 'randomnessdependency'})
evmlisa_vanilla_thread.start()
evmlisa_thread.start()
evmlisa_thread.join()
evmlisa_vanilla_thread.join()
- check_sound_analysis_evmlisa('./solidifi/timestamp-dependency/results/evmlisa')
+ check_sound_analysis_evmlisa('./solidifi/randomness-dependency/results/evmlisa')
- results_solidifi = get_results_solidifi('./solidifi/SolidiFI-buggy-contracts/Timestamp-Dependency', 'timestamp-dependency', 'solidify')
- results_evmlisa = subtract_dicts(get_results_evmlisa('./solidifi/timestamp-dependency/results/evmlisa', 'evmlisa-buggy-solidifi'),
- get_results_evmlisa('./solidifi/vanilla/results/evmlisa', 'evmlisa-solidifi/vanilla'))
+ results_solidifi = get_results_solidifi('./solidifi/SolidiFI-buggy-contracts/Timestamp-Dependency', 'randomness-dependency', 'solidify')
+ results_evmlisa = subtract_dicts(get_results_evmlisa('./solidifi/randomness-dependency/results/evmlisa', 'evmlisa-buggy-solidifi', 'randomness-dependency'),
+ get_results_evmlisa('./solidifi/vanilla/results/evmlisa', 'evmlisa-solidifi/vanilla', 'randomness-dependency'))
# Precision
evmlisa_precision = calculate_precision(results_evmlisa, results_solidifi)
@@ -717,21 +732,21 @@ def calculate_f_measure(precision, recall):
# Plot results
plot_results_timestamp(data_evmlisa=results_evmlisa,
data_solidifi=results_solidifi,
- name='solidifi_timestamp-dependency')
+ name='solidifi_randomness-dependency')
if args.smartbugs:
- evmlisa_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './smartbugs/timestamp-dependency/bytecode/evmlisa',
- 'results_dir': './smartbugs/timestamp-dependency/results',
- 'result_evmlisa_dir': './smartbugs/timestamp-dependency/results/evmlisa',
- 'type': 'timestampdependency'})
-
+ evmlisa_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './smartbugs/randomness-dependency/bytecode/evmlisa',
+ 'results_dir': './smartbugs/randomness-dependency/results',
+ 'result_evmlisa_dir': './smartbugs/randomness-dependency/results/evmlisa',
+ 'type': 'randomnessdependency'})
+
evmlisa_thread.start()
evmlisa_thread.join()
- check_sound_analysis_evmlisa('./smartbugs/timestamp-dependency/results/evmlisa')
+ check_sound_analysis_evmlisa('./smartbugs/randomness-dependency/results/evmlisa')
+
+ results_evmlisa = get_results_evmlisa('./smartbugs/randomness-dependency/results/evmlisa', 'evmlisa-buggy-smartbugs', 'randomness-dependency')
+ results_smartbugs = get_results_smartbugs('./smartbugs/randomness-dependency/source-code/vulnerabilities.json', 'smartbugs')
- results_evmlisa = get_results_evmlisa('./smartbugs/timestamp-dependency/results/evmlisa', 'evmlisa-buggy-smartbugs')
- results_smartbugs = get_results_smartbugs('./smartbugs/timestamp-dependency/source-code/vulnerabilities.json', 'smartbugs')
-
# Precision
evmlisa_precision = calculate_precision(results_evmlisa, results_smartbugs)
print(f"Precision evmlisa (avg.): {calculate_average(evmlisa_precision)}")
@@ -742,9 +757,9 @@ def calculate_f_measure(precision, recall):
# F-measure
print(f"F-measure evmlisa (avg.): {calculate_average(calculate_f_measure(evmlisa_precision, evmlisa_recall))}")
-
+
# Plot results
- plot_results_timestamp(data_evmlisa=results_evmlisa,
+ plot_results_timestamp(data_evmlisa=results_evmlisa,
data_solidifi=results_smartbugs,
name='smartbugs')
@@ -752,22 +767,22 @@ def calculate_f_measure(precision, recall):
if args.solidifi:
# SolidiFI dataset
if not args.no_analysis:
- evmlisa_vanilla_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './solidifi/vanilla/bytecode/evmlisa',
+ evmlisa_vanilla_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './solidifi/vanilla/bytecode/evmlisa',
'results_dir': './solidifi/vanilla/results',
'result_evmlisa_dir': './solidifi/vanilla/results/evmlisa'})
- evmlisa_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './solidifi/reentrancy/bytecode/evmlisa',
+ evmlisa_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './solidifi/reentrancy/bytecode/evmlisa',
'results_dir': './solidifi/reentrancy/results',
'result_evmlisa_dir': './solidifi/reentrancy/results/evmlisa'})
-
+
ethersolve_thread = threading.Thread(target=ethersolve, kwargs={'bytecode_dir': './solidifi/reentrancy/bytecode/ethersolve',
'result_ethersolve_dir': './solidifi/reentrancy/results/ethersolve'})
ethersolve_vanilla_thread = threading.Thread(target=ethersolve, kwargs={'bytecode_dir': './solidifi/vanilla/bytecode/ethersolve',
'result_ethersolve_dir': './solidifi/vanilla/results/ethersolve'})
-
+
evmlisa_vanilla_thread.start()
evmlisa_thread.start()
ethersolve_thread.start()
-
+
ethersolve_thread.join()
evmlisa_vanilla_thread.join()
evmlisa_thread.join()
@@ -777,13 +792,13 @@ def calculate_f_measure(precision, recall):
check_sound_analysis_evmlisa('./solidifi/reentrancy/results/evmlisa')
check_sound_analysis_evmlisa('./solidifi/vanilla/results/evmlisa')
-
- results_evmlisa = subtract_dicts(get_results_evmlisa('./solidifi/reentrancy/results/evmlisa', 'evmlisa-buggy-solidifi'),
- get_results_evmlisa('./solidifi/vanilla/results/evmlisa', 'evmlisa-solidifi/vanilla'))
+
+ results_evmlisa = subtract_dicts(get_results_evmlisa('./solidifi/reentrancy/results/evmlisa', 'evmlisa-buggy-solidifi', 'reentrancy'),
+ get_results_evmlisa('./solidifi/vanilla/results/evmlisa', 'evmlisa-solidifi/vanilla', 'reentrancy'))
results_ethersolve = subtract_dicts(get_results_ethersolve('./solidifi/reentrancy/results/ethersolve', 'ethersolve-buggy-solidifi'),
get_results_ethersolve('./solidifi/vanilla/results/ethersolve', 'ethersolve-solidifi/vanilla'))
results_solidifi = get_results_solidifi('./solidifi/SolidiFI-buggy-contracts/Re-entrancy', 'reentrancy', 'solidify')
-
+
# Precision
evmlisa_precision = calculate_precision(results_evmlisa, results_solidifi)
ethersolve_precision = calculate_precision(results_ethersolve, results_solidifi)
@@ -799,34 +814,34 @@ def calculate_f_measure(precision, recall):
# F-measure
print(f"F-measure evmlisa (avg.): {calculate_average(calculate_f_measure(evmlisa_precision, evmlisa_recall))}")
print(f"F-measure ethersolve (avg.): {calculate_average(calculate_f_measure(ethersolve_precision, ethersolve_recall))}")
-
+
# Plot results
- plot_results(data_evmlisa=results_evmlisa,
+ plot_results(data_evmlisa=results_evmlisa,
data_ethersolve=results_ethersolve,
data_solidifi=results_solidifi,
name='solidifi')
-
+
if args.smartbugs:
# SmartBugs dataset
if not args.no_analysis:
- evmlisa_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './reentrancy-smartbugs/bytecode/evmlisa',
- 'results_dir': './reentrancy-smartbugs/results',
- 'result_evmlisa_dir': './reentrancy-smartbugs/results/evmlisa'})
- ethersolve_thread = threading.Thread(target=ethersolve, kwargs={'bytecode_dir': './reentrancy-smartbugs/bytecode/ethersolve',
- 'result_ethersolve_dir': './reentrancy-smartbugs/results/ethersolve'})
-
+ evmlisa_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './smartbugs/reentrancy/bytecode/evmlisa',
+ 'results_dir': './smartbugs/reentrancy/results',
+ 'result_evmlisa_dir': './smartbugs/reentrancy/results/evmlisa'})
+ ethersolve_thread = threading.Thread(target=ethersolve, kwargs={'bytecode_dir': './smartbugs/reentrancy/bytecode/ethersolve',
+ 'result_ethersolve_dir': './smartbugs/reentrancy/results/ethersolve'})
+
evmlisa_thread.start()
ethersolve_thread.start()
-
+
ethersolve_thread.join()
evmlisa_thread.join()
- check_sound_analysis_evmlisa('./reentrancy-smartbugs/results/evmlisa')
+ check_sound_analysis_evmlisa('./smartbugs/reentrancy/results/evmlisa')
+
+ results_evmlisa = get_results_evmlisa('./smartbugs/reentrancy/results/evmlisa', 'evmlisa-buggy-smartbugs', 'reentrancy')
+ results_ethersolve = get_results_ethersolve('./smartbugs/reentrancy/results/ethersolve', 'ethersolve-buggy-smartbugs')
+ results_smartbugs = get_results_smartbugs('./smartbugs/reentrancy/source-code/vulnerabilities.json', 'smartbugs')
- results_evmlisa = get_results_evmlisa('./reentrancy-smartbugs/results/evmlisa', 'evmlisa-buggy-smartbugs')
- results_ethersolve = get_results_ethersolve('./reentrancy-smartbugs/results/ethersolve', 'ethersolve-buggy-smartbugs')
- results_smartbugs = get_results_smartbugs('./reentrancy-smartbugs/source-code/vulnerabilities.json', 'smartbugs')
-
# Precision
evmlisa_precision = calculate_precision(results_evmlisa, results_smartbugs)
ethersolve_precision = calculate_precision(results_ethersolve, results_smartbugs)
@@ -842,34 +857,34 @@ def calculate_f_measure(precision, recall):
# F-measure
print(f"F-measure evmlisa (avg.): {calculate_average(calculate_f_measure(evmlisa_precision, evmlisa_recall))}")
print(f"F-measure ethersolve (avg.): {calculate_average(calculate_f_measure(ethersolve_precision, ethersolve_recall))}")
-
+
# Plot results
- plot_results(data_evmlisa=results_evmlisa,
+ plot_results(data_evmlisa=results_evmlisa,
data_ethersolve=results_ethersolve,
data_solidifi=results_smartbugs,
name='smartbugs')
-
+
if args.slise:
# SliSE dataset
if not args.no_analysis:
- evmlisa_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './reentrancy-slise-db1/bytecode/evmlisa',
- 'results_dir': './reentrancy-slise-db1/results',
- 'result_evmlisa_dir': './reentrancy-slise-db1/results/evmlisa'})
- ethersolve_thread = threading.Thread(target=ethersolve, kwargs={'bytecode_dir': './reentrancy-slise-db1/bytecode/ethersolve',
- 'result_ethersolve_dir': './reentrancy-slise-db1/results/ethersolve'})
-
+ evmlisa_thread = threading.Thread(target=evmlisa, kwargs={'bytecode_dir': './slise/reentrancy-db1/bytecode/evmlisa',
+ 'results_dir': './slise/reentrancy-db1/results',
+ 'result_evmlisa_dir': './slise/reentrancy-db1/results/evmlisa'})
+ ethersolve_thread = threading.Thread(target=ethersolve, kwargs={'bytecode_dir': './slise/reentrancy-db1/bytecode/ethersolve',
+ 'result_ethersolve_dir': './slise/reentrancy-db1/results/ethersolve'})
+
evmlisa_thread.start()
ethersolve_thread.start()
-
+
ethersolve_thread.join()
evmlisa_thread.join()
- check_sound_analysis_evmlisa('./reentrancy-slise-db1/results/evmlisa')
+ check_sound_analysis_evmlisa('./slise/reentrancy-db1/results/evmlisa')
+
+ results_evmlisa = get_results_evmlisa('./slise/reentrancy-db1/results/evmlisa', 'evmlisa-buggy-slise-db1', 'reentrancy')
+ results_ethersolve = get_results_ethersolve('./slise/reentrancy-db1/results/ethersolve', 'ethersolve-buggy-slise-db1')
+ results_slise = get_results_slise('./slise/reentrancy-db1/source-code/vulnerabilities.json', 'slise-db1')
- results_evmlisa = get_results_evmlisa('./reentrancy-slise-db1/results/evmlisa', 'evmlisa-buggy-slise-db1')
- results_ethersolve = get_results_ethersolve('./reentrancy-slise-db1/results/ethersolve', 'ethersolve-buggy-slise-db1')
- results_slise = get_results_slise('./reentrancy-slise-db1/source-code/vulnerabilities.json', 'slise-db1')
-
# Precision
evmlisa_precision = calculate_precision(results_evmlisa, results_slise)
ethersolve_precision = calculate_precision(results_ethersolve, results_slise)
@@ -885,12 +900,12 @@ def calculate_f_measure(precision, recall):
# F-measure
print(f"F-measure evmlisa (avg.): {calculate_average(calculate_f_measure(evmlisa_precision, evmlisa_recall))}")
print(f"F-measure ethersolve (avg.): {calculate_average(calculate_f_measure(ethersolve_precision, ethersolve_recall))}")
-
+
# Plot results
- plot_results(data_evmlisa=results_evmlisa,
+ plot_results(data_evmlisa=results_evmlisa,
data_ethersolve=results_ethersolve,
data_solidifi=results_slise,
name='slise')
-
+
# Shut down the global executor after all analyses are complete
shutdown_executor()
\ No newline at end of file
diff --git a/scripts/python/benchmark-checkers/smartbugs/timestamp-dependency/source-code/1.sol b/scripts/python/benchmark-checkers/smartbugs/randomness-dependency/source-code/1.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/smartbugs/timestamp-dependency/source-code/1.sol
rename to scripts/python/benchmark-checkers/smartbugs/randomness-dependency/source-code/1.sol
diff --git a/scripts/python/benchmark-checkers/smartbugs/timestamp-dependency/source-code/2.sol b/scripts/python/benchmark-checkers/smartbugs/randomness-dependency/source-code/2.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/smartbugs/timestamp-dependency/source-code/2.sol
rename to scripts/python/benchmark-checkers/smartbugs/randomness-dependency/source-code/2.sol
diff --git a/scripts/python/benchmark-checkers/smartbugs/timestamp-dependency/source-code/3.sol b/scripts/python/benchmark-checkers/smartbugs/randomness-dependency/source-code/3.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/smartbugs/timestamp-dependency/source-code/3.sol
rename to scripts/python/benchmark-checkers/smartbugs/randomness-dependency/source-code/3.sol
diff --git a/scripts/python/benchmark-checkers/smartbugs/timestamp-dependency/source-code/4.sol b/scripts/python/benchmark-checkers/smartbugs/randomness-dependency/source-code/4.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/smartbugs/timestamp-dependency/source-code/4.sol
rename to scripts/python/benchmark-checkers/smartbugs/randomness-dependency/source-code/4.sol
diff --git a/scripts/python/benchmark-checkers/smartbugs/timestamp-dependency/source-code/5.sol b/scripts/python/benchmark-checkers/smartbugs/randomness-dependency/source-code/5.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/smartbugs/timestamp-dependency/source-code/5.sol
rename to scripts/python/benchmark-checkers/smartbugs/randomness-dependency/source-code/5.sol
diff --git a/scripts/python/benchmark-checkers/smartbugs/timestamp-dependency/source-code/version.csv b/scripts/python/benchmark-checkers/smartbugs/randomness-dependency/source-code/version.csv
similarity index 100%
rename from scripts/python/benchmark-checkers/smartbugs/timestamp-dependency/source-code/version.csv
rename to scripts/python/benchmark-checkers/smartbugs/randomness-dependency/source-code/version.csv
diff --git a/scripts/python/benchmark-checkers/smartbugs/timestamp-dependency/source-code/vulnerabilities.json b/scripts/python/benchmark-checkers/smartbugs/randomness-dependency/source-code/vulnerabilities.json
similarity index 100%
rename from scripts/python/benchmark-checkers/smartbugs/timestamp-dependency/source-code/vulnerabilities.json
rename to scripts/python/benchmark-checkers/smartbugs/randomness-dependency/source-code/vulnerabilities.json
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_1.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_1.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_1.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_1.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_10.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_10.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_10.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_10.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_11.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_11.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_11.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_11.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_12.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_12.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_12.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_12.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_13.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_13.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_13.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_13.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_14.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_14.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_14.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_14.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_15.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_15.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_15.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_15.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_16.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_16.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_16.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_16.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_17.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_17.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_17.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_17.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_18.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_18.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_18.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_18.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_19.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_19.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_19.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_19.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_2.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_2.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_2.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_2.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_20.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_20.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_20.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_20.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_21.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_21.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_21.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_21.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_22.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_22.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_22.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_22.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_23.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_23.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_23.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_23.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_24.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_24.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_24.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_24.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_25.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_25.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_25.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_25.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_26.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_26.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_26.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_26.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_27.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_27.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_27.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_27.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_28.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_28.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_28.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_28.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_29.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_29.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_29.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_29.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_3.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_3.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_3.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_3.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_30.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_30.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_30.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_30.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_31.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_31.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_31.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_31.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_32.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_32.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_32.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_32.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_33.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_33.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_33.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_33.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_34.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_34.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_34.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_34.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_35.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_35.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_35.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_35.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_36.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_36.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_36.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_36.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_37.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_37.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_37.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_37.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_38.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_38.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_38.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_38.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_39.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_39.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_39.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_39.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_4.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_4.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_4.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_4.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_40.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_40.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_40.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_40.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_41.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_41.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_41.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_41.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_42.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_42.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_42.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_42.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_43.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_43.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_43.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_43.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_44.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_44.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_44.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_44.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_45.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_45.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_45.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_45.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_46.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_46.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_46.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_46.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_47.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_47.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_47.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_47.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_48.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_48.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_48.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_48.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_49.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_49.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_49.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_49.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_5.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_5.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_5.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_5.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_50.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_50.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_50.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_50.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_6.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_6.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_6.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_6.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_7.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_7.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_7.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_7.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_8.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_8.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_8.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_8.sol
diff --git a/scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_9.sol b/scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_9.sol
similarity index 100%
rename from scripts/python/benchmark-checkers/solidifi/timestamp-dependency/source-code/buggy_9.sol
rename to scripts/python/benchmark-checkers/solidifi/randomness-dependency/source-code/buggy_9.sol
diff --git a/src/main/java/it/unipr/EVMLiSA.java b/src/main/java/it/unipr/EVMLiSA.java
index c76aa3283..befbfef57 100644
--- a/src/main/java/it/unipr/EVMLiSA.java
+++ b/src/main/java/it/unipr/EVMLiSA.java
@@ -2,16 +2,18 @@
import it.unipr.analysis.*;
import it.unipr.analysis.contract.SmartContract;
-import it.unipr.analysis.taint.TimestampDependencyAbstractDomain;
+import it.unipr.analysis.taint.RandomnessDependencyAbstractDomain;
import it.unipr.analysis.taint.TxOriginAbstractDomain;
import it.unipr.cfg.EVMCFG;
import it.unipr.cfg.Jump;
import it.unipr.cfg.Jumpi;
import it.unipr.checker.JumpSolver;
+import it.unipr.checker.RandomnessDependencyChecker;
import it.unipr.checker.ReentrancyChecker;
-import it.unipr.checker.TimestampDependencyChecker;
import it.unipr.checker.TxOriginChecker;
import it.unipr.frontend.EVMFrontend;
+import it.unipr.frontend.EVMLiSAFeatures;
+import it.unipr.frontend.EVMLiSATypeSystem;
import it.unipr.utils.*;
import it.unive.lisa.LiSA;
import it.unive.lisa.analysis.SimpleAbstractState;
@@ -45,7 +47,6 @@ public class EVMLiSA {
private static final Logger log = LogManager.getLogger(EVMLiSA.class);
// Configuration
- private static int CORES = 1;
private static boolean TEST_MODE = false;
private static Path OUTPUT_DIRECTORY_PATH;
@@ -86,15 +87,21 @@ public static void setWorkingDirectory(Path workingDirectoryPath) {
SmartContract.setWorkingDirectory(workingDirectoryPath);
}
+ public static Path getWorkingDirectory() {
+ return EVMLiSA.OUTPUT_DIRECTORY_PATH;
+ }
+
+ public static int getCores() {
+ return EVMLiSAExecutor.getCoresAvailable();
+ }
+
/**
* Sets the number of processing cores.
*
* @param cores the number of cores
*/
public static void setCores(int cores) {
- if (cores > Runtime.getRuntime().availableProcessors())
- cores = Runtime.getRuntime().availableProcessors() - 1;
- EVMLiSA.CORES = Math.max(cores, 1);
+ EVMLiSAExecutor.setCoresAvailable(cores);
}
/**
@@ -105,11 +112,12 @@ public static void setLinkUnsoundJumpsToAllJumpdest() {
}
/**
- * Enables all security checkers.
+ * Enables all security checkers (i.e., reentrancy, randomness dependency,
+ * tx.origin).
*/
public static void enableAllSecurityCheckers() {
EVMLiSA.enableReentrancyChecker();
- EVMLiSA.enableTimestampDependencyCheckerChecker();
+ EVMLiSA.enableRandomnessDependencyChecker();
EVMLiSA.enableTxOriginChecker();
}
@@ -121,10 +129,10 @@ public static void enableReentrancyChecker() {
}
/**
- * Enables the timestamp dependency checker.
+ * Enables the randomness dependency checker.
*/
- public static void enableTimestampDependencyCheckerChecker() {
- TimestampDependencyChecker.enableChecker();
+ public static void enableRandomnessDependencyChecker() {
+ RandomnessDependencyChecker.enableChecker();
}
/**
@@ -141,6 +149,10 @@ public static void setTestMode() {
TEST_MODE = true;
}
+ public static boolean isInTestMode() {
+ return TEST_MODE;
+ }
+
/**
* Executes the analysis workflow.
*
@@ -196,11 +208,13 @@ else if (cmd.hasOption("bytecode")) {
else {
JSONManager.throwNewError("No valid option provided.");
+ new HelpFormatter().printHelp("help", getOptions());
System.exit(1);
}
EVMLiSA.analyzeContract(contract);
System.err.println(contract);
+ EVMLiSAExecutor.shutdown();
}
/**
@@ -209,7 +223,7 @@ else if (cmd.hasOption("bytecode")) {
* @param filePath the path to the file containing contract addresses
*/
public static void analyzeSetOfContracts(Path filePath) {
- log.info("Building contracts...");
+ log.info("Building contracts.");
List contracts = buildContractsFromFile(filePath);
analyzeSetOfContracts(contracts);
}
@@ -220,19 +234,18 @@ public static void analyzeSetOfContracts(Path filePath) {
* @param contracts the list of {@link SmartContract} to be analyzed
*/
public static void analyzeSetOfContracts(List contracts) {
- log.info("Analyzing {} contracts...", contracts.size());
+ log.info("Analyzing {} contracts.", contracts.size());
- ExecutorService executor = Executors.newFixedThreadPool(CORES);
List> futures = new ArrayList<>();
for (SmartContract contract : contracts)
- futures.add(executor.submit(() -> analyzeContract(contract)));
+ futures.add(EVMLiSAExecutor.submit(() -> analyzeContract(contract)));
- log.info("{} contracts submitted to Thread pool with {} workers.", contracts.size(), CORES);
+ log.debug("{} contracts submitted to Thread pool with {} workers.", contracts.size(),
+ EVMLiSAExecutor.getCoresAvailable());
- waitForCompletion(futures);
+ EVMLiSAExecutor.awaitCompletionFutures(futures);
- executor.shutdown();
log.info("Finished analysis of {} contracts.", contracts.size());
Path outputDir = OUTPUT_DIRECTORY_PATH.resolve("set-of-contracts");
@@ -247,22 +260,29 @@ public static void analyzeSetOfContracts(List contracts) {
System.err.println(JSONManager.throwNewError("Failed to save results in " + outputDir));
System.exit(1);
}
+ EVMLiSAExecutor.shutdown();
}
/**
- * Analyzes a given smart contract.
+ * Builds the Control Flow Graph (CFG) for the given smart contract. This
+ * method generates the CFG from the contract's mnemonic bytecode,
+ * configures and runs the LiSA analysis, and stores the computed CFG and
+ * statistics in the contract object.
*
- * @param contract the smart contract to analyze
+ * @param contract the smart contract for which the CFG is to be built
*/
- public static void analyzeContract(SmartContract contract) {
- log.info("Analyzing contract {}...", contract.getAddress());
+ public static void buildCFG(SmartContract contract) {
+ if (contract == null)
+ return;
+
+ log.info("[IN] Building CFG of contract {}.", contract.getName());
Program program = null;
try {
program = EVMFrontend.generateCfgFromFile(contract.getMnemonicBytecodePath().toString());
} catch (IOException e) {
System.err.println(
- JSONManager.throwNewError("Unable to generate partial CFG from file", contract.toJson()));
+ JSONManager.throwNewError("Unable to generate partial CFG from file.", contract.toJson()));
System.exit(1);
}
@@ -273,62 +293,156 @@ public static void analyzeContract(SmartContract contract) {
conf.semanticChecks.add(checker);
LiSA lisa = new LiSA(conf);
- lisa.run(program);
- log.info("Analysis ended {}", contract.getAddress());
+ long startTime = System.currentTimeMillis();
+ lisa.run(program);
+ contract.setExecutionTime(System.currentTimeMillis() - startTime);
+ log.info("[OUT] CFG of contract {} built.", contract.getName());
+ log.info("[IN] Computing statistics of contract {}.", contract.getName());
contract.setStatistics(
computeStatistics(checker, lisa, program));
contract.setCFG(checker.getComputedCFG());
+ log.debug("[OUT] Contract {} statistics: {}", contract.getAddress(), contract.getStatistics());
+
if (TEST_MODE)
return;
+ log.info("[IN] Computing functions and events of contract {}.", contract.getName());
contract.computeFunctionsSignatureEntryPoints();
contract.computeFunctionsSignatureExitPoints();
contract.computeEventsSignatureEntryPoints();
contract.computeEventsExitPoints();
+ log.info("[OUT] Functions and events of contract {} computed.", contract.getName());
+ }
- if (ReentrancyChecker.isEnabled()) {
- log.info("Running reentrancy checker...");
- conf.semanticChecks.clear();
- conf.semanticChecks.add(new ReentrancyChecker());
- lisa.run(program);
- log.info("{} vulnerabilities found",
- MyCache.getInstance().getReentrancyWarnings(checker.getComputedCFG().hashCode()));
- }
- if (TxOriginChecker.isEnabled()) {
- log.info("Running tx. origin checker...");
- conf.semanticChecks.clear();
- conf.semanticChecks.add(new TxOriginChecker());
- conf.abstractState = new SimpleAbstractState<>(new MonolithicHeap(), new TxOriginAbstractDomain(),
- new TypeEnvironment<>(new InferredTypes()));
- lisa.run(program);
- log.info("{} vulnerabilities found",
- MyCache.getInstance().getTxOriginWarnings(checker.getComputedCFG().hashCode()));
- }
- if (TimestampDependencyChecker.isEnabled()) {
- log.info("Running timestamp dependency checker...");
- conf.semanticChecks.clear();
- conf.semanticChecks.add(new TimestampDependencyChecker());
- conf.abstractState = new SimpleAbstractState<>(new MonolithicHeap(),
- new TimestampDependencyAbstractDomain(),
- new TypeEnvironment<>(new InferredTypes()));
- lisa.run(program);
- log.info("{} vulnerabilities found",
- MyCache.getInstance().getTimestampDependencyWarnings(checker.getComputedCFG().hashCode()));
- }
+ /**
+ * Runs all enabled security checkers on the given smart contract. This
+ * method executes various security checkers (e.g., Reentrancy, TxOrigin,
+ * and Randomness Dependency checkers) on the computed CFG of the contract.
+ * It then stores the detected vulnerabilities in the contract object.
+ *
+ * @param contract the smart contract on which security checkers are
+ * executed
+ */
+ public static void runCheckers(SmartContract contract) {
+ if (contract == null || contract.getCFG() == null)
+ return;
+
+ log.info("[IN] Running checkers on contract {}.", contract.getName());
+
+ if (ReentrancyChecker.isEnabled())
+ runReentrancyChecker(contract);
+ if (TxOriginChecker.isEnabled())
+ runTxOriginChecker(contract);
+ if (RandomnessDependencyChecker.isEnabled())
+ runRandomnessDependencyChecker(contract);
contract.setVulnerabilities(
- VulnerabilitiesObject.newVulnerabilitiesObject()
- .reentrancy(
- MyCache.getInstance().getReentrancyWarnings(checker.getComputedCFG().hashCode()))
- .txOrigin(MyCache.getInstance().getTxOriginWarnings(checker.getComputedCFG().hashCode()))
- .timestamp(MyCache.getInstance()
- .getTimestampDependencyWarnings(checker.getComputedCFG().hashCode()))
- .build());
- contract.generateCFGWithBasicBlocks();
- contract.toFile();
+ VulnerabilitiesObject.buildFromCFG(
+ contract.getCFG()));
+
+ log.info("[OUT] Checkers run on contract {}.", contract.getName());
+ }
+
+ /**
+ * Analyzes a given smart contract.
+ *
+ * @param contract the smart contract to analyze
+ */
+ public static void analyzeContract(SmartContract contract) {
+ log.info("[IN] Analyzing contract {}.", contract.getName());
+
+ buildCFG(contract);
+
+ if (!TEST_MODE) {
+ runCheckers(contract);
+ contract.generateCFGWithBasicBlocks();
+ contract.toFile();
+ }
+
+ log.info("[OUT] Analysis ended of contract {}.", contract.getName());
+ }
+
+ /**
+ * Runs the reentrancy checker on the given smart contract.
+ *
+ * @param contract The smart contract to analyze.
+ */
+ public static void runReentrancyChecker(SmartContract contract) {
+ log.info("[IN] Running reentrancy checker on {}.", contract.getName());
+
+ // Setup configuration
+ Program program = new Program(new EVMLiSAFeatures(), new EVMLiSATypeSystem());
+ program.addCodeMember(contract.getCFG());
+ LiSAConfiguration conf = LiSAConfigurationManager.createConfiguration(contract);
+ LiSA lisa = new LiSA(conf);
+
+ // Reentrancy checker
+ ReentrancyChecker checker = new ReentrancyChecker();
+ conf.semanticChecks.add(checker);
+ lisa.run(program);
+
+ log.info("[OUT] Reentrancy checker ended on {}, with {} vulnerabilities found.",
+ contract.getName(),
+ MyCache.getInstance().getReentrancyWarnings(contract.getCFG().hashCode()));
+ }
+
+ /**
+ * Runs the randomness dependency checker on the given smart contract.
+ *
+ * @param contract The smart contract to analyze.
+ */
+ public static void runRandomnessDependencyChecker(SmartContract contract) {
+ log.info("[IN] Running randomness dependency checker on {}.", contract.getName());
+
+ // Setup configuration
+ Program program = new Program(new EVMLiSAFeatures(), new EVMLiSATypeSystem());
+ program.addCodeMember(contract.getCFG());
+ LiSAConfiguration conf = LiSAConfigurationManager.createConfiguration(contract);
+ LiSA lisa = new LiSA(conf);
+
+ // Randomness dependency checker
+ RandomnessDependencyChecker checker = new RandomnessDependencyChecker();
+ conf.semanticChecks.add(checker);
+ conf.abstractState = new SimpleAbstractState<>(new MonolithicHeap(),
+ new RandomnessDependencyAbstractDomain(),
+ new TypeEnvironment<>(new InferredTypes()));
+ lisa.run(program);
+
+ log.info(
+ "[OUT] Randomness dependency checker ended on {}, with {} definite and {} possible vulnerabilities found.",
+ contract.getName(),
+ MyCache.getInstance().getRandomnessDependencyWarnings(contract.getCFG().hashCode()),
+ MyCache.getInstance().getPossibleRandomnessDependencyWarnings(contract.getCFG().hashCode()));
+ }
+
+ /**
+ * Runs the tx. origin checker on the given smart contract.
+ *
+ * @param contract The smart contract to analyze.
+ */
+ public static void runTxOriginChecker(SmartContract contract) {
+ log.info("[IN] Running tx. origin checker on {}.", contract.getName());
+
+ // Setup configuration
+ Program program = new Program(new EVMLiSAFeatures(), new EVMLiSATypeSystem());
+ program.addCodeMember(contract.getCFG());
+ LiSAConfiguration conf = LiSAConfigurationManager.createConfiguration(contract);
+ LiSA lisa = new LiSA(conf);
+
+ // Tx. Origin checker
+ TxOriginChecker checker = new TxOriginChecker();
+ conf.semanticChecks.add(checker);
+ conf.abstractState = new SimpleAbstractState<>(new MonolithicHeap(),
+ new TxOriginAbstractDomain(),
+ new TypeEnvironment<>(new InferredTypes()));
+ lisa.run(program);
+
+ log.info("[OUT] Tx. origin checker ended on {}, with {} vulnerabilities found.",
+ contract.getName(),
+ MyCache.getInstance().getTxOriginWarnings(contract.getCFG().hashCode()));
}
/**
@@ -448,8 +562,6 @@ private static StatisticsObject computeJumps(JumpSolver checker, Set
.maybeUnsoundJumps(maybeUnsoundJumps)
.build();
- log.info("### Calculating statistics ###\n{}", stats);
-
return stats;
}
@@ -529,18 +641,19 @@ public static List buildContractsFromFile(Path filePath) {
private void setupGlobalOptions(CommandLine cmd) {
try {
- CORES = cmd.hasOption("cores") ? Integer.parseInt(cmd.getOptionValue("cores")) : 1;
+ EVMLiSAExecutor
+ .setCoresAvailable(cmd.hasOption("cores") ? Integer.parseInt(cmd.getOptionValue("cores")) : 1);
} catch (Exception e) {
log.warn("Cores set to 1: {}", e.getMessage());
- CORES = 1;
+ EVMLiSAExecutor.setCoresAvailable(1);
}
if (cmd.hasOption("checker-reentrancy") || cmd.hasOption("checker-all"))
ReentrancyChecker.enableChecker();
if (cmd.hasOption("checker-txorigin") || cmd.hasOption("checker-all"))
TxOriginChecker.enableChecker();
- if (cmd.hasOption("checker-timestampdependency") || cmd.hasOption("checker-all"))
- TimestampDependencyChecker.enableChecker();
+ if (cmd.hasOption("checker-randomnessdependency") || cmd.hasOption("checker-all"))
+ RandomnessDependencyChecker.enableChecker();
if (cmd.hasOption("output-directory-path"))
OUTPUT_DIRECTORY_PATH = Path.of(cmd.getOptionValue("output-directory-path"));
@@ -565,6 +678,8 @@ private void setupGlobalOptions(CommandLine cmd) {
EVMAbstractState.setUseStorageLive();
if (cmd.hasOption("etherscan-api-key"))
EVMFrontend.setEtherscanAPIKey(cmd.getOptionValue("etherscan-api-key"));
+ if (cmd.hasOption("test-mode"))
+ EVMLiSA.setTestMode();
}
private Options getOptions() {
@@ -605,6 +720,7 @@ private Options getOptions() {
.required(false)
.hasArg(true)
.build();
+
Option abiOption = Option.builder()
.longOpt("abi")
.desc("ABI of the bytecode to be analyzed (JSON format).")
@@ -675,9 +791,9 @@ private Options getOptions() {
.hasArg(false)
.build();
- Option enableTimestampDependencyCheckerOption = Option.builder()
- .longOpt("checker-timestampdependency")
- .desc("Enable timestamp-dependency checker.")
+ Option enableRandomnessDependencyCheckerOption = Option.builder()
+ .longOpt("checker-randomnessdependency")
+ .desc("Enable randomness-dependency checker.")
.required(false)
.hasArg(false)
.build();
@@ -689,6 +805,13 @@ private Options getOptions() {
.hasArg(true)
.build();
+ Option useTestModeOption = Option.builder()
+ .longOpt("test-mode")
+ .desc("Use the test mode (i.e., do not compute functions and events).")
+ .required(false)
+ .hasArg(false)
+ .build();
+
options.addOption(addressOption);
options.addOption(bytecodeOption);
options.addOption(bytecodePathOption);
@@ -702,10 +825,11 @@ private Options getOptions() {
options.addOption(enableAllCheckerOption);
options.addOption(enableReentrancyCheckerOption);
options.addOption(enableTxOriginCheckerOption);
- options.addOption(enableTimestampDependencyCheckerOption);
+ options.addOption(enableRandomnessDependencyCheckerOption);
options.addOption(outputDirectoryPathOption);
options.addOption(etherscanAPIKeyOption);
options.addOption(abiOption);
+ options.addOption(useTestModeOption);
return options;
}
@@ -724,23 +848,4 @@ private CommandLine parseCommandLine(String[] args) {
return null;
}
}
-
- /**
- * Waits for all submitted tasks in the given list of futures to complete.
- *
- * @param futures A list of {@link Future} objects representing running
- * tasks.
- */
- static private void waitForCompletion(List> futures) {
- for (Future> future : futures)
- try {
- future.get();
- } catch (ExecutionException e) {
- System.err.println(JSONManager.throwNewError("Error during task execution: " + e.getMessage()));
- System.exit(1);
- } catch (InterruptedException ie) {
- System.err.println(JSONManager.throwNewError("Interrupted during task execution: " + ie.getMessage()));
- System.exit(1);
- }
- }
}
diff --git a/src/main/java/it/unipr/analysis/EVMAbstractState.java b/src/main/java/it/unipr/analysis/EVMAbstractState.java
index 8619201b8..50fbaad45 100644
--- a/src/main/java/it/unipr/analysis/EVMAbstractState.java
+++ b/src/main/java/it/unipr/analysis/EVMAbstractState.java
@@ -975,8 +975,13 @@ public EVMAbstractState smallStepSemantics(ValueExpression expression, ProgramPo
} else if (memory.isTop()) {
resultStack.push(StackElement.TOP);
} else {
- byte[] mloadValue = memory.mload(offset.getNumber().intValue());
+ BigInteger os = Number.toBigInteger(offset.getNumber());
+ if (os.compareTo(BigInteger.ZERO) < 0
+ || os.compareTo(Number.MAX_INT) > 0)
+ continue;
+
+ byte[] mloadValue = memory.mload(offset.getNumber().intValue());
StackElement mload = StackElement.fromBytes(mloadValue);
if (mload.isBottom())
@@ -1009,6 +1014,12 @@ public EVMAbstractState smallStepSemantics(ValueExpression expression, ProgramPo
} else if (memory.isTop()) {
memoryResult = AbstractMemory.TOP;
} else {
+
+ BigInteger os = Number.toBigInteger(offset.getNumber());
+ if (os.compareTo(BigInteger.ZERO) < 0
+ || os.compareTo(Number.MAX_INT) > 0)
+ continue;
+
byte[] valueBytes = convertStackElementToBytes(value);
memoryResult = memoryResult.lub(memory.mstore(offset.getNumber().intValue(), valueBytes));
}
@@ -1035,10 +1046,15 @@ public EVMAbstractState smallStepSemantics(ValueExpression expression, ProgramPo
memoryResult = AbstractMemory.TOP;
} else if (memory.isTop()) {
memoryResult = AbstractMemory.TOP;
- } else
+ } else {
+ BigInteger os = Number.toBigInteger(offset.getNumber());
+ if (os.compareTo(BigInteger.ZERO) < 0
+ || os.compareTo(Number.MAX_INT) > 0)
+ continue;
+
memoryResult = memoryResult.lub(
memory.mstore8(offset.getNumber().intValue(), (byte) value.getNumber().intValue()));
-
+ }
result.add(stackResult);
}
diff --git a/src/main/java/it/unipr/analysis/Number.java b/src/main/java/it/unipr/analysis/Number.java
index b89bd922c..9eeb6f3d9 100644
--- a/src/main/java/it/unipr/analysis/Number.java
+++ b/src/main/java/it/unipr/analysis/Number.java
@@ -15,14 +15,14 @@ public class Number implements Comparable {
/**
* Maximal representable integer value.
*/
- private static final BigInteger MAX_INT = BigInteger.valueOf(2).pow(31);
+ public static final BigInteger MAX_INT = BigInteger.valueOf(2).pow(31);
/**
* Maximal representable long value.
*/
- private static final BigInteger MAX_LONG = BigInteger.valueOf(2).pow(63);
+ public static final BigInteger MAX_LONG = BigInteger.valueOf(2).pow(63);
- private static enum Type {
+ public enum Type {
INT,
LONG,
BIGINTEGER
diff --git a/src/main/java/it/unipr/analysis/contract/EventKnowledge.java b/src/main/java/it/unipr/analysis/contract/EventKnowledge.java
deleted file mode 100644
index 380efc584..000000000
--- a/src/main/java/it/unipr/analysis/contract/EventKnowledge.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package it.unipr.analysis.contract;
-
-import java.util.Arrays;
-import java.util.List;
-
-public class EventKnowledge {
- public enum EventType {
- DEPOSIT, WITHDRAWAL, UNKNOWN
- }
-
- public static final List DEPOSIT_KNOWLEDGE = Arrays.asList(
- "Deposit", "Send", "LiFiTransferStarted", "LogAnySwapTradeTokensForTokens",
- "TokenDeposit", "TokenDepositAndSwap", "LockApplied", "CrossChainEvent",
- "TransferAllowance", "Sent");
-
- public static final List WITHDRAWAL_KNOWLEDGE = Arrays.asList(
- "Transfer", "ProposalEvent", "Approval", "Authorize", "LiFiTransferCompleted",
- "LogWithdraw", "ProposalEvent", "ProposalVote", "LogAnySwapTradeTokensForNative",
- "Withdraw", "EmergencyWithdraw", "Harvest", "LockRemoved",
- "VerifyHeaderAndExecuteTxEvent", "TransferOut", "VaultTransfer");
-
- public static EventType getKnowledge(String signature) {
- signature = signature.toLowerCase();
-
- for (String keyword : DEPOSIT_KNOWLEDGE) {
- if (signature.contains(keyword.toLowerCase())) {
- return EventType.DEPOSIT;
- }
- }
-
- for (String keyword : WITHDRAWAL_KNOWLEDGE) {
- if (signature.contains(keyword.toLowerCase())) {
- return EventType.WITHDRAWAL;
- }
- }
-
- return EventType.UNKNOWN;
- }
-
- public static void main(String[] args) {
- System.out.println("Deposit Knowledge: " + DEPOSIT_KNOWLEDGE);
- System.out.println("Withdrawal Knowledge: " + WITHDRAWAL_KNOWLEDGE);
- }
-}
diff --git a/src/main/java/it/unipr/analysis/contract/EventsExitPointsComputer.java b/src/main/java/it/unipr/analysis/contract/EventsExitPointsComputer.java
deleted file mode 100644
index cbca8bf07..000000000
--- a/src/main/java/it/unipr/analysis/contract/EventsExitPointsComputer.java
+++ /dev/null
@@ -1,151 +0,0 @@
-package it.unipr.analysis.contract;
-
-import it.unipr.analysis.*;
-import it.unipr.cfg.*;
-import it.unipr.utils.MyCache;
-import it.unive.lisa.analysis.AnalysisState;
-import it.unive.lisa.analysis.AnalyzedCFG;
-import it.unive.lisa.analysis.SemanticException;
-import it.unive.lisa.analysis.SimpleAbstractState;
-import it.unive.lisa.analysis.heap.MonolithicHeap;
-import it.unive.lisa.analysis.nonrelational.value.TypeEnvironment;
-import it.unive.lisa.analysis.types.InferredTypes;
-import it.unive.lisa.checks.semantic.CheckToolWithAnalysisResults;
-import it.unive.lisa.checks.semantic.SemanticCheck;
-import it.unive.lisa.program.cfg.CFG;
-import it.unive.lisa.program.cfg.statement.Statement;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-public class EventsExitPointsComputer implements
- SemanticCheck>> {
-
- private static final Logger log = LogManager.getLogger(EventsExitPointsComputer.class);
-
- @Override
- public boolean visit(
- CheckToolWithAnalysisResults<
- SimpleAbstractState>> tool,
- CFG graph, Statement node) {
-
- boolean isLog = (node instanceof Log1) || (node instanceof Log2) || (node instanceof Log3)
- || (node instanceof Log4);
-
- if (isLog) {
- EVMCFG cfg = ((EVMCFG) graph);
- Statement logStatement = node;
-
- for (AnalyzedCFG>> result : tool.getResultOf(cfg)) {
- AnalysisState>> analysisResult = null;
-
- try {
- analysisResult = result.getAnalysisStateBefore(logStatement);
- } catch (SemanticException e1) {
- log.error("(EventsExitPointsComputer): {}", e1.getMessage());
- }
-
- // Retrieve the symbolic stack from the analysis result
- EVMAbstractState valueState = analysisResult.getState().getValueState();
-
- // If the value state is bottom, the jump is definitely
- // unreachable
- if (valueState.isBottom())
- // Nothing to do
- continue;
- else if (valueState.isTop())
- continue;
- else {
- checkForEventSignature(node, valueState.getStacks());
- }
- }
-
- }
-
- return true;
- }
-
- /**
- * Extracts and stores event signatures from the EVM stack based on LOG
- * instructions. Depending on the LOG opcode type (LOG1, LOG2, LOG3, LOG4),
- * it retrieves the corresponding topics from the stack and stores their
- * first 4 bytes as event exit points.
- *
- * @param node The statement representing the LOG instruction.
- * @param stacks The abstract stack set containing stack states to analyze.
- */
- private void checkForEventSignature(Statement node, AbstractStackSet stacks) {
- for (AbstractStack originalStack : stacks) {
- AbstractStack stack = originalStack.clone();
-
- stack.pop(); // offset
- stack.pop(); // size
-
- if (node instanceof Log1) {
- StackElement topic = stack.pop();
- MyCache.getInstance().addEventExitPoint(node, toHexFirst4Bytes(topic));
- } else if (node instanceof Log2) {
- StackElement topic1 = stack.pop();
- StackElement topic2 = stack.pop();
- MyCache.getInstance().addEventExitPoint(node, toHexFirst4Bytes(topic1));
- MyCache.getInstance().addEventExitPoint(node, toHexFirst4Bytes(topic2));
- } else if (node instanceof Log3) {
- StackElement topic1 = stack.pop();
- StackElement topic2 = stack.pop();
- StackElement topic3 = stack.pop();
- MyCache.getInstance().addEventExitPoint(node, toHexFirst4Bytes(topic1));
- MyCache.getInstance().addEventExitPoint(node, toHexFirst4Bytes(topic2));
- MyCache.getInstance().addEventExitPoint(node, toHexFirst4Bytes(topic3));
- } else if (node instanceof Log4) {
- StackElement topic1 = stack.pop();
- StackElement topic2 = stack.pop();
- StackElement topic3 = stack.pop();
- StackElement topic4 = stack.pop();
- MyCache.getInstance().addEventExitPoint(node, toHexFirst4Bytes(topic1));
- MyCache.getInstance().addEventExitPoint(node, toHexFirst4Bytes(topic2));
- MyCache.getInstance().addEventExitPoint(node, toHexFirst4Bytes(topic3));
- MyCache.getInstance().addEventExitPoint(node, toHexFirst4Bytes(topic4));
- }
- }
- }
-
- /**
- * Converts a stack element to its hexadecimal representation and returns
- * only the first 4 bytes. If the value represents a special case (e.g.,
- * bottom, top, topNumeric), it returns a corresponding string.
- *
- * @param value The stack element to convert.
- *
- * @return A lowercase hexadecimal string of the first 4 bytes or a special
- * identifier if applicable.
- */
- public static String toHexFirst4Bytes(StackElement value) {
- if (value.isBottom())
- return "bottom";
- if (value.isTopNotJumpdest())
- return "topNotJumpdest";
- if (value.isTopNumeric())
- return "topNumeric";
- if (value.isTop())
- return "top";
-
- StringBuilder hex = new StringBuilder(toHex(value));
- while (hex.length() < 8)
- hex.insert(0, "0");
- return hex.toString().toLowerCase().substring(0, 8);
- }
-
- /**
- * Converts a stack element's numeric value to its full hexadecimal
- * representation.
- *
- * @param value The stack element to convert.
- *
- * @return A hexadecimal string representing the numeric value of the stack
- * element.
- */
- public static String toHex(StackElement value) {
- return it.unipr.analysis.Number.toBigInteger(value.getNumber()).toString(16);
- }
-}
\ No newline at end of file
diff --git a/src/main/java/it/unipr/analysis/contract/SmartContract.java b/src/main/java/it/unipr/analysis/contract/SmartContract.java
index 62f3d16ea..fb43c07f1 100644
--- a/src/main/java/it/unipr/analysis/contract/SmartContract.java
+++ b/src/main/java/it/unipr/analysis/contract/SmartContract.java
@@ -1,14 +1,10 @@
package it.unipr.analysis.contract;
+import it.unipr.EVMLiSA;
import it.unipr.cfg.EVMCFG;
import it.unipr.cfg.push.Push;
import it.unipr.frontend.EVMFrontend;
-import it.unipr.frontend.EVMLiSAFeatures;
-import it.unipr.frontend.EVMLiSATypeSystem;
import it.unipr.utils.*;
-import it.unive.lisa.LiSA;
-import it.unive.lisa.conf.LiSAConfiguration;
-import it.unive.lisa.program.Program;
import it.unive.lisa.program.cfg.statement.Statement;
import java.io.File;
import java.io.IOException;
@@ -32,6 +28,8 @@
public class SmartContract {
private static final Logger log = LogManager.getLogger(SmartContract.class);
+ private String _name;
+
/** Contract address on the Ethereum blockchain. */
private String _address;
@@ -74,6 +72,11 @@ public class SmartContract {
/** Event signatures extracted from the contract ABI. */
private Set _eventsSignature;
+ private Set _allFunctionsEntryPoints;
+
+ /** Execution time in milliseconds of the contract. */
+ private long _executionTime;
+
/**
* Constructs a new SmartContract with a generated address.
*/
@@ -90,6 +93,7 @@ public SmartContract() {
JSONManager.throwNewError("Unable to create output directory: " + outputDir);
System.exit(1);
}
+ this._executionTime = 0;
}
/**
@@ -108,6 +112,7 @@ public SmartContract(String address) {
}
this._address = address;
+ this._name = address;
Path outputDir = _workingDirectory.resolve(address);
this._abiFilePath = outputDir.resolve(address + ".abi");
this._bytecodeFilePath = outputDir.resolve(address + ".bytecode");
@@ -177,6 +182,7 @@ public SmartContract(String address) {
this._functionsSignature = ABIManager.parseFunctionsFromABI(this._abiFilePath);
this._eventsSignature = ABIManager.parseEventsFromABI(this._abiFilePath);
+ this._executionTime = 0;
}
/**
@@ -187,7 +193,6 @@ public SmartContract(String address) {
* @throws IllegalArgumentException If bytecodeFilePath is null.
*/
public SmartContract(Path bytecodeFilePath) {
- this();
if (bytecodeFilePath == null) {
log.error("Bytecode file path is null");
System.err.println(JSONManager.throwNewError("Bytecode file path is null"));
@@ -198,7 +203,18 @@ public SmartContract(Path bytecodeFilePath) {
System.exit(1);
}
+ this._name = getFileNameWithoutExtension(bytecodeFilePath);
+ this._address = getFileNameWithoutExtension(bytecodeFilePath);
+
Path outputDir = _workingDirectory.resolve(_address);
+ try {
+ Files.createDirectories(outputDir);
+ } catch (IOException e) {
+ log.error("Unable to create output directory: {}", outputDir);
+ JSONManager.throwNewError("Unable to create output directory: " + outputDir);
+ System.exit(1);
+ }
+
this._bytecodeFilePath = outputDir.resolve(_address + ".bytecode");
this._mnemonicBytecodeFilePath = outputDir.resolve(_address + ".opcode");
@@ -244,7 +260,15 @@ public SmartContract(Path bytecodeFilePath, Path abiFilePath) {
this._eventsSignature = ABIManager.parseEventsFromABI(abiFilePath);
}
- ///////////////////// GETTERs
+ /**
+ * Returns the contract name.
+ *
+ * @return The contract name.
+ */
+ public String getName() {
+ return this._name;
+ }
+
/**
* Returns the contract address.
*
@@ -299,6 +323,15 @@ public Path getWorkingDirectory() {
return _workingDirectory.resolve(this._address);
}
+ /**
+ * Returns the execution time for this contract.
+ *
+ * @return Execution time in milliseconds.
+ */
+ public long getExecutionTime() {
+ return _executionTime;
+ }
+
/**
* Returns the path of the mnemonic bytecode file.
*
@@ -359,10 +392,21 @@ public StatisticsObject getStatistics() {
* @return Vulnerabilities object.
*/
public VulnerabilitiesObject getVulnerabilities() {
- return _vulnerabilities;
+ return this._vulnerabilities = VulnerabilitiesObject.buildFromCFG(this._cfg);
+ }
+
+ /**
+ * Sets the contract name.
+ *
+ * @param name The contract name.
+ *
+ * @return This SmartContract instance for method chaining.
+ */
+ public SmartContract setName(String name) {
+ this._name = name;
+ return this;
}
- ///////////////////// SETTERs
/**
* Sets the contract address.
*
@@ -564,6 +608,18 @@ public static void setWorkingDirectory(Path workingDirectory) {
_workingDirectory = workingDirectory;
}
+ /**
+ * Sets the execution time for this contract.
+ *
+ * @param executionTime Execution time in milliseconds.
+ *
+ * @return This SmartContract instance for method chaining.
+ */
+ public SmartContract setExecutionTime(long executionTime) {
+ this._executionTime = executionTime;
+ return this;
+ }
+
/**
* Identifies and associates entry points for each function signature in the
* contract. Uses the selector from each signature to find matching Push
@@ -632,15 +688,15 @@ public void computeEventsExitPoints() {
return;
}
- // Setup configuration
- Program program = new Program(new EVMLiSAFeatures(), new EVMLiSATypeSystem());
- program.addCodeMember(this._cfg);
- LiSAConfiguration conf = LiSAConfigurationManager.createConfiguration(this);
- LiSA lisa = new LiSA(conf);
-
- EventsExitPointsComputer checker = new EventsExitPointsComputer();
- conf.semanticChecks.add(checker);
- lisa.run(program);
+ Set logStatements = _cfg.getAllLogX();
+ for (Signature signature : _eventsSignature) {
+ for (Statement eventEntryPoint : signature.getEntryPoints()) {
+ for (Statement logStatement : logStatements) {
+ if (_cfg.reachableFrom(eventEntryPoint, logStatement))
+ signature.addExitPoint(logStatement);
+ }
+ }
+ }
}
/**
@@ -648,11 +704,72 @@ public void computeEventsExitPoints() {
* the graph to a file in the contract's working directory.
*/
public void generateCFGWithBasicBlocks() {
+ if (_functionsSignature == null) {
+ log.warn("Unable to generate CFG with Basic Blocks (_functionsSignature is null)");
+ return;
+ }
+ if (_eventsSignature == null) {
+ log.warn("Unable to generate CFG with Basic Blocks (_eventsSignature is null)");
+ return;
+ }
+
Path dotFile = _workingDirectory.resolve(_address).resolve("CFG.dot");
DOTFileManager.generateDotGraph(JSONManager.basicBlocksToJson(this), dotFile.toString());
log.info("Generated CFG at {}", dotFile);
}
+ /**
+ * Retrieves all entry-point statements for every function in this contract.
+ * Caches the result on first invocation for efficiency.
+ *
+ * @return a set containing the entry-point statements of all functions
+ */
+ public Set getAllFunctionEntryPoints() {
+ if (_allFunctionsEntryPoints == null) {
+ Set result = new HashSet<>();
+ for (Signature signature : _functionsSignature)
+ result.addAll(signature.getEntryPoints());
+ _allFunctionsEntryPoints = result;
+ }
+ return _allFunctionsEntryPoints;
+ }
+
+ /**
+ * Determines the full function signature that corresponds to a given
+ * entry-point statement. If no matching function is found, returns
+ * "no-function-found".
+ *
+ * @param entryPoint the entry-point statement to look up
+ *
+ * @return the full signature of the function owning that entry-point, or
+ * "no-function-found" if none matches
+ */
+ public String getFunctionSignatureFromEntryPoint(Statement entryPoint) {
+ for (Signature signature : _functionsSignature)
+ for (Statement entry : signature.getEntryPoints())
+ if (entry.equals(entryPoint))
+ return signature.getFullSignature();
+ return "no-function-found";
+ }
+
+ /**
+ * Locates the function signature for an arbitrary statement by performing a
+ * reverse reachability search to its nearest entry-point, then mapping that
+ * entry-point back to its function signature.
+ *
+ * @param statement any statement within the contract
+ *
+ * @return the full signature of the containing function, or an empty string
+ * if no function context is found
+ */
+ public String getFunctionSignatureByStatement(Statement statement) {
+ Statement ep = _cfg.reachableFromReverse(statement, getAllFunctionEntryPoints());
+
+ if (getFunctionSignatureFromEntryPoint(ep) != null)
+ return getFunctionSignatureFromEntryPoint(ep);
+ return "";
+ }
+
/**
* Converts this SmartContract to a JSON object. Includes all fields of the
* contract, including bytecode, ABI, signatures, and analysis results.
@@ -663,9 +780,25 @@ public JSONObject toJson() {
JSONObject jsonObject = new JSONObject();
jsonObject.put("address", _address != null ? _address : new JSONArray());
- jsonObject.put("bytecode", _bytecode != null ? _bytecode : new JSONArray());
- jsonObject.put("mnemonic_bytecode", _mnemonicBytecode != null ? _mnemonicBytecode : new JSONArray());
- jsonObject.put("abi", _abi != null ? _abi : new JSONArray());
+ if (!EVMLiSA.isInTestMode()) {
+ jsonObject.put("bytecode", _bytecode != null ? _bytecode : new JSONArray());
+ jsonObject.put("mnemonic_bytecode", _mnemonicBytecode != null ? _mnemonicBytecode : new JSONArray());
+ jsonObject.put("abi", _abi != null ? _abi : new JSONArray());
+
+ jsonObject.put("basic_blocks",
+ _basicBlocks != null ? JSONManager.basicBlocksToJson(this) : new JSONArray());
+ JSONArray functionsArray = new JSONArray();
+ if (_functionsSignature != null && !_functionsSignature.isEmpty())
+ for (Signature signature : _functionsSignature)
+ functionsArray.put(signature.toJson());
+ jsonObject.put("functions_signature", !functionsArray.isEmpty() ? functionsArray : new JSONArray());
+
+ JSONArray eventsArray = new JSONArray();
+ if (_eventsSignature != null && !_eventsSignature.isEmpty())
+ for (Signature signature : _eventsSignature)
+ eventsArray.put(signature.toJson());
+ jsonObject.put("events_signature", !eventsArray.isEmpty() ? eventsArray : new JSONArray());
+ }
jsonObject.put("working_directory", _workingDirectory.toString());
@@ -678,23 +811,10 @@ public JSONObject toJson() {
jsonObject.put("vulnerabilities", _vulnerabilities != null ? _vulnerabilities.toJson() : new JSONArray());
- jsonObject.put("basic_blocks",
- _basicBlocks != null ? JSONManager.basicBlocksToJson(this) : new JSONArray());
-
jsonObject.put("basic_blocks_pc", _basicBlocks != null ? BasicBlock.basicBlocksToLongArrayToString(
BasicBlock.basicBlocksToLongArray(_basicBlocks)) : new JSONArray());
- JSONArray functionsArray = new JSONArray();
- if (_functionsSignature != null && !_functionsSignature.isEmpty())
- for (Signature signature : _functionsSignature)
- functionsArray.put(signature.toJson());
- jsonObject.put("functions_signature", !functionsArray.isEmpty() ? functionsArray : new JSONArray());
-
- JSONArray eventsArray = new JSONArray();
- if (_eventsSignature != null && !_eventsSignature.isEmpty())
- for (Signature signature : _eventsSignature)
- eventsArray.put(signature.toJson());
- jsonObject.put("events_signature", !eventsArray.isEmpty() ? eventsArray : new JSONArray());
+ jsonObject.put("execution_time", _executionTime);
return jsonObject;
}
@@ -723,4 +843,17 @@ public boolean toFile() {
}
return true;
}
+
+ /**
+ * Extracts the filename without extension from a given Path.
+ *
+ * @param path The file path
+ *
+ * @return The filename without extension
+ */
+ private static String getFileNameWithoutExtension(Path path) {
+ String fileName = path.getFileName().toString();
+ int lastDotIndex = fileName.lastIndexOf('.');
+ return (lastDotIndex == -1) ? fileName : fileName.substring(0, lastDotIndex);
+ }
}
\ No newline at end of file
diff --git a/src/main/java/it/unipr/analysis/taint/RandomnessDependencyAbstractDomain.java b/src/main/java/it/unipr/analysis/taint/RandomnessDependencyAbstractDomain.java
new file mode 100644
index 000000000..5bcb3a865
--- /dev/null
+++ b/src/main/java/it/unipr/analysis/taint/RandomnessDependencyAbstractDomain.java
@@ -0,0 +1,59 @@
+package it.unipr.analysis.taint;
+
+import it.unipr.cfg.*;
+import it.unive.lisa.program.cfg.statement.Statement;
+import it.unive.lisa.symbolic.value.Operator;
+import java.util.Set;
+
+public class RandomnessDependencyAbstractDomain extends TaintAbstractDomain {
+ private static final RandomnessDependencyAbstractDomain TOP = new RandomnessDependencyAbstractDomain(
+ createFilledArray(TaintAbstractDomain.STACK_LIMIT, TaintElement.BOTTOM), TaintElement.CLEAN);
+ private static final RandomnessDependencyAbstractDomain BOTTOM = new RandomnessDependencyAbstractDomain(null,
+ TaintElement.BOTTOM);
+
+ /**
+ * Builds an initial symbolic stack.
+ */
+ public RandomnessDependencyAbstractDomain() {
+ this(createFilledArray(STACK_LIMIT, TaintElement.BOTTOM), TaintElement.CLEAN);
+ }
+
+ /**
+ * Builds a taint abstract stack starting from a given stack and a list of
+ * elements that push taint.
+ *
+ * @param stack the stack of values
+ */
+ protected RandomnessDependencyAbstractDomain(TaintElement[] stack, TaintElement memory) {
+ super(stack, memory);
+ }
+
+ @Override
+ public boolean isTainted(Statement stmt) {
+ return stmt instanceof Timestamp
+ || stmt instanceof Blockhash
+ || stmt instanceof Difficulty
+ || stmt instanceof Balance;
+ }
+
+ @Override
+ public Set getSanitizedOpcode() {
+ return Set.of();
+ }
+
+ @Override
+ public RandomnessDependencyAbstractDomain top() {
+ return TOP;
+ }
+
+ @Override
+ public RandomnessDependencyAbstractDomain bottom() {
+ return BOTTOM;
+ }
+
+ @Override
+ public TaintAbstractDomain mk(TaintElement[] stack, TaintElement memory) {
+ return new RandomnessDependencyAbstractDomain(stack, memory);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/it/unipr/analysis/taint/TaintAbstractDomain.java b/src/main/java/it/unipr/analysis/taint/TaintAbstractDomain.java
index 0b5e53fe3..7a73c465d 100644
--- a/src/main/java/it/unipr/analysis/taint/TaintAbstractDomain.java
+++ b/src/main/java/it/unipr/analysis/taint/TaintAbstractDomain.java
@@ -8,6 +8,7 @@
import it.unive.lisa.analysis.lattices.Satisfiability;
import it.unive.lisa.analysis.value.ValueDomain;
import it.unive.lisa.program.cfg.ProgramPoint;
+import it.unive.lisa.program.cfg.statement.Statement;
import it.unive.lisa.symbolic.value.Constant;
import it.unive.lisa.symbolic.value.Identifier;
import it.unive.lisa.symbolic.value.Operator;
@@ -21,6 +22,21 @@
import java.util.Set;
import java.util.function.Predicate;
+/**
+ * An abstract domain for taint analysis of EVM smart contracts.
+ *
+ * This abstract class provides the framework to track and propagate taint
+ * information along the symbolic execution of EVM bytecode. It models a
+ * circular stack of {@link TaintElement} objects and an associated memory
+ * element. Concrete implementations should define how to determine whether a
+ * statement is tainted and provide a set of sanitizing opcodes, as well as
+ * instantiate new instances of the domain.
+ *
+ *
+ * @see TaintElement
+ * @see ValueDomain
+ * @see BaseLattice
+ */
public abstract class TaintAbstractDomain
implements ValueDomain, BaseLattice {
@@ -32,7 +48,7 @@ public abstract class TaintAbstractDomain
/**
* The abstract stack as a circular array.
*/
- private final TaintElement[] circularArray;
+ private final TaintElement[] stack;
/**
* The index representing the beginning of the logical stack in the circular
@@ -58,14 +74,15 @@ public abstract class TaintAbstractDomain
private final TaintElement memory;
/**
- * Builds a taint abstract stack starting from a given stack and a memory
- * element.
+ * Builds a taint abstract stack starting from a given stack and a list of
+ * elements that push taint. Builds a taint abstract stack starting from a
+ * given stack and a memory element.
*
- * @param circularArray the stack of values
- * @param memory the memory element
+ * @param stack the stack of values
+ * @param memory the memory element
*/
- protected TaintAbstractDomain(TaintElement[] circularArray, TaintElement memory) {
- this.circularArray = circularArray;
+ protected TaintAbstractDomain(TaintElement[] stack, TaintElement memory) {
+ this.stack = stack;
this.memory = memory;
this.head = 0;
this.tail = 0;
@@ -117,7 +134,7 @@ public TaintAbstractDomain smallStepSemantics(ValueExpression expression, Progra
case "PushOperator":
case "Push0Operator": {
TaintAbstractDomain resultStack = clone();
- if (this.getTaintedOpcode().contains(op))
+ if (this.isTainted((Statement) pp))
resultStack.push(TaintElement.TAINT);
else
resultStack.push(TaintElement.CLEAN);
@@ -162,7 +179,6 @@ public TaintAbstractDomain smallStepSemantics(ValueExpression expression, Progra
return resultStack;
}
-
case "CalldatacopyOperator": {
if (hasBottomUntil(3))
return bottom();
@@ -170,8 +186,8 @@ public TaintAbstractDomain smallStepSemantics(ValueExpression expression, Progra
TaintAbstractDomain resultStack = clone();
resultStack.popX(3);
- if (this.getTaintedOpcode().contains(op))
- return mk(resultStack.circularArray, TaintElement.TAINT);
+ if (this.isTainted((Statement) pp))
+ return mk(resultStack.stack, TaintElement.TAINT);
return resultStack;
}
@@ -188,7 +204,7 @@ public TaintAbstractDomain smallStepSemantics(ValueExpression expression, Progra
TaintAbstractDomain resultStack = clone();
TaintElement opnd1 = resultStack.pop();
- if (this.getTaintedOpcode().contains(op))
+ if (this.isTainted((Statement) pp))
resultStack.push(TaintElement.TAINT);
else
resultStack.push(TaintElement.semantics(opnd1));
@@ -217,8 +233,7 @@ else if (memory.isClean())
TaintElement value = resultStack.pop();
if (value.isTaint())
- return mk(resultStack.circularArray, TaintElement.TAINT);
-
+ return mk(resultStack.stack, TaintElement.TAINT);
else if (value.isClean())
return resultStack;
}
@@ -439,7 +454,7 @@ else if (value.isClean())
TaintElement offset = resultStack.pop();
TaintElement length = resultStack.pop();
- if (this.getTaintedOpcode().contains(op))
+ if (this.isTainted((Statement) pp))
resultStack.push(TaintElement.TAINT);
else
resultStack.push(TaintElement.semantics(value, offset, length));
@@ -454,7 +469,7 @@ else if (value.isClean())
TaintElement length = resultStack.pop();
TaintElement salt = resultStack.pop();
- if (this.getTaintedOpcode().contains(op))
+ if (this.isTainted((Statement) pp))
resultStack.push(TaintElement.TAINT);
else
resultStack.push(TaintElement.semantics(value, offset, length, salt));
@@ -473,7 +488,7 @@ else if (value.isClean())
TaintElement outOffset = resultStack.pop();
TaintElement outLength = resultStack.pop();
- if (this.getTaintedOpcode().contains(op))
+ if (this.isTainted((Statement) pp))
resultStack.push(TaintElement.TAINT);
else
resultStack
@@ -501,7 +516,7 @@ else if (value.isClean())
TaintElement outOffset = resultStack.pop();
TaintElement outLength = resultStack.pop();
- if (this.getTaintedOpcode().contains(op))
+ if (this.isTainted((Statement) pp))
resultStack.push(TaintElement.TAINT);
else
resultStack.push(TaintElement.semantics(gas, to, inOffset, inLength, outOffset, outLength));
@@ -540,7 +555,7 @@ else if (value.isClean())
TaintAbstractDomain resultStack = clone();
TaintElement address = resultStack.pop();
- if (this.getTaintedOpcode().contains(op))
+ if (this.isTainted((Statement) pp))
resultStack.push(TaintElement.TAINT);
else
resultStack.push(TaintElement.semantics(address));
@@ -568,7 +583,7 @@ else if (value.isClean())
TaintAbstractDomain resultStack = clone();
TaintElement address = resultStack.pop();
- if (this.getTaintedOpcode().contains(op))
+ if (this.isTainted((Statement) pp))
resultStack.push(TaintElement.TAINT);
else
resultStack.push(TaintElement.semantics(address));
@@ -581,10 +596,10 @@ else if (value.isClean())
throw new SemanticException("Unrecognized opcode: " + pp);
}
- private TaintAbstractDomain dupXoperator(int x, TaintAbstractDomain stack) {
- if (stack.isEmpty() || stack.hasBottomUntil(x))
+ private TaintAbstractDomain dupXoperator(int x, TaintAbstractDomain other) {
+ if (other.isEmpty() || other.hasBottomUntil(x))
return bottom();
- return stack.dupX(x);
+ return other.dupX(x);
}
private TaintAbstractDomain swapXoperator(int x, TaintAbstractDomain stack) {
@@ -618,9 +633,9 @@ public TaintAbstractDomain swapX(int x) {
int posX = (tail - x + STACK_LIMIT) % STACK_LIMIT;
int topIndex = (tail - 1 + STACK_LIMIT) % STACK_LIMIT;
TaintAbstractDomain copy = this.clone();
- TaintElement tmp = copy.circularArray[posX];
- copy.circularArray[posX] = copy.circularArray[topIndex];
- copy.circularArray[topIndex] = tmp;
+ TaintElement tmp = copy.stack[posX];
+ copy.stack[posX] = copy.stack[topIndex];
+ copy.stack[topIndex] = tmp;
return copy;
}
@@ -638,13 +653,13 @@ public TaintAbstractDomain dupX(int x) {
return bottom();
int posX = (tail - x + STACK_LIMIT) % STACK_LIMIT;
TaintAbstractDomain copy = this.clone();
- copy.push(circularArray[posX]);
+ copy.push(stack[posX]);
return copy;
}
@Override
public TaintAbstractDomain assume(ValueExpression expression, ProgramPoint src, ProgramPoint dest,
- SemanticOracle oracle) throws SemanticException {
+ SemanticOracle oracle) {
// nothing to do here
return this;
}
@@ -656,13 +671,13 @@ public boolean knowsIdentifier(Identifier id) {
}
@Override
- public TaintAbstractDomain forgetIdentifier(Identifier id) throws SemanticException {
+ public TaintAbstractDomain forgetIdentifier(Identifier id) {
// nothing to do here
return this;
}
@Override
- public TaintAbstractDomain forgetIdentifiersIf(Predicate test) throws SemanticException {
+ public TaintAbstractDomain forgetIdentifiersIf(Predicate test) {
// nothing to do here
return this;
}
@@ -693,7 +708,7 @@ public String toString() {
StringBuilder sb = new StringBuilder("[");
for (int i = 0; i < STACK_LIMIT; i++) {
int pos = (head + i) % STACK_LIMIT;
- sb.append(circularArray[pos]);
+ sb.append(stack[pos]);
if (i < STACK_LIMIT - 1)
sb.append(", ");
}
@@ -702,23 +717,29 @@ public String toString() {
}
@Override
- public TaintAbstractDomain pushScope(ScopeToken token) throws SemanticException {
+ public TaintAbstractDomain pushScope(ScopeToken token) {
// nothing to do here
return this;
}
@Override
- public TaintAbstractDomain popScope(ScopeToken token) throws SemanticException {
+ public TaintAbstractDomain popScope(ScopeToken token) {
// nothing to do here
return this;
}
+ protected static TaintElement[] createFilledArray(int size, TaintElement element) {
+ TaintElement[] array = new TaintElement[size];
+ Arrays.fill(array, element);
+ return array;
+ }
+
/**
* Computes the greatest lower bound (GLB) between this abstract domain and
* another.
*
* @param other the other domain
- *
+ *
* @return a new domain representing the greatest lower bound of the two
* domains
*/
@@ -736,7 +757,7 @@ public TaintAbstractDomain glbAux(TaintAbstractDomain other) throws SemanticExce
* another.
*
* @param other the other domain
- *
+ *
* @return a new domain representing the least upper bound of the two
* domains
*/
@@ -759,7 +780,7 @@ public TaintAbstractDomain lubAux(TaintAbstractDomain other) throws SemanticExce
public TaintElement get(int index) {
if (index < 0 || index >= STACK_LIMIT)
return TaintElement.BOTTOM;
- return circularArray[(head + index) % STACK_LIMIT];
+ return stack[(head + index) % STACK_LIMIT];
}
@Override
@@ -782,7 +803,7 @@ public boolean lessOrEqualAux(TaintAbstractDomain other) throws SemanticExceptio
* @param target the element to be pushed onto the stack
*/
public void push(TaintElement target) {
- circularArray[tail] = target;
+ stack[tail] = target;
tail = (tail + 1) % STACK_LIMIT;
head = (head + 1) % STACK_LIMIT;
}
@@ -800,26 +821,26 @@ public void push(TaintElement target) {
*/
public TaintElement pop() {
int topIndex = (tail - 1 + STACK_LIMIT) % STACK_LIMIT;
- TaintElement popped = circularArray[topIndex];
+ TaintElement popped = stack[topIndex];
head = (head - 1 + STACK_LIMIT) % STACK_LIMIT;
int bottomIndex = head;
int nextIndex = (head + 1) % STACK_LIMIT;
- if (circularArray[nextIndex].isTop())
- circularArray[bottomIndex] = TaintElement.TOP;
+ if (stack[nextIndex].isTop())
+ stack[bottomIndex] = TaintElement.TOP;
else
- circularArray[bottomIndex] = TaintElement.BOTTOM;
+ stack[bottomIndex] = TaintElement.BOTTOM;
tail = (tail - 1 + STACK_LIMIT) % STACK_LIMIT;
- circularArray[topIndex] = TaintElement.BOTTOM;
+ stack[topIndex] = TaintElement.BOTTOM;
return popped;
}
/**
* Checks whether between 0 and x-positions of the stack an element is
- * bottom. /** Checks whether between 0 and x-positions of the stack an
- * element is bottom.
+ * bottom. Checks whether between 0 and x-positions of the stack an element
+ * is bottom.
*
* @param x the position
*
@@ -829,7 +850,7 @@ public TaintElement pop() {
public boolean hasBottomUntil(int x) {
for (int i = 0; i < x; i++) {
int index = (tail - 1 - i + STACK_LIMIT) % STACK_LIMIT;
- if (circularArray[index].isBottom())
+ if (stack[index].isBottom())
return true;
}
return false;
@@ -837,7 +858,7 @@ public boolean hasBottomUntil(int x) {
@Override
public TaintAbstractDomain clone() {
- TaintElement[] newArray = circularArray.clone();
+ TaintElement[] newArray = stack.clone();
TaintAbstractDomain copy = mk(newArray, memory);
copy.head = this.head;
copy.tail = this.tail;
@@ -858,12 +879,12 @@ public boolean equals(Object obj) {
if (!memory.equals(other.memory))
return false;
- return Arrays.equals(this.circularArray, other.circularArray);
+ return Arrays.equals(this.stack, other.stack);
}
@Override
public int hashCode() {
- return Objects.hash(circularArray, memory);
+ return Objects.hash(stack, memory);
}
/**
@@ -872,11 +893,7 @@ public int hashCode() {
* @return the second element of this abstract stack
*/
public TaintElement getSecondElement() {
- if (isBottom())
- return TaintElement.BOTTOM;
- if (isTop())
- return TaintElement.TOP;
- return circularArray[(tail - 2 + STACK_LIMIT) % STACK_LIMIT];
+ return getElementAtPosition(2);
}
/**
@@ -885,11 +902,7 @@ public TaintElement getSecondElement() {
* @return the first element of this abstract stack
*/
public TaintElement getFirstElement() {
- if (isBottom())
- return TaintElement.BOTTOM;
- if (isTop())
- return TaintElement.TOP;
- return circularArray[(tail - 1 + STACK_LIMIT) % STACK_LIMIT];
+ return getElementAtPosition(1);
}
/**
@@ -903,11 +916,35 @@ public void popX(int pos) {
}
/**
- * Yields the set of opcodes that push taint elements.
+ * Retrieves the element from the stack at the specified position. The
+ * position is calculated from the top of the stack, where the top is at
+ * position 1. If the stack state is {@code BOTTOM},
+ * {@code TaintElement.BOTTOM} is returned. If the stack state is
+ * {@code TOP}, {@code TaintElement.TOP} is returned.
*
- * @return the set of opcodes that push taint elements
+ * @param position the position of the element to retrieve from the stack
+ *
+ * @return the element at the specified position in the stack
*/
- public abstract Set getTaintedOpcode();
+ public TaintElement getElementAtPosition(int position) {
+ if (position < 1 || position > STACK_LIMIT)
+ throw new IllegalArgumentException("Invalid position: " + position);
+ if (isBottom())
+ return TaintElement.BOTTOM;
+ else if (isTop())
+ return TaintElement.TOP;
+ return stack[(tail - position + STACK_LIMIT) % STACK_LIMIT];
+ }
+
+ /**
+ * Determines whether the given statement is tainted.
+ *
+ * @param stmt the statement to check
+ *
+ * @return {@code true} if the statement is tainted, {@code false}
+ * otherwise.
+ */
+ public abstract boolean isTainted(Statement stmt);
/**
* Yields the set of opcodes that sanitize the state.
@@ -920,11 +957,10 @@ public void popX(int pos) {
* Utility for creating a concrete instance of {@link TaintAbstractDomain}
* given the stack and the memory.
*
- * @param array the stack
+ * @param stack the stack
* @param memory the memory
*
* @return a new concrete instance of {@link TaintAbstractDomain}
*/
- public abstract TaintAbstractDomain mk(TaintElement[] array, TaintElement memory);
-
+ public abstract TaintAbstractDomain mk(TaintElement[] stack, TaintElement memory);
}
\ No newline at end of file
diff --git a/src/main/java/it/unipr/analysis/taint/TaintElement.java b/src/main/java/it/unipr/analysis/taint/TaintElement.java
index de55086c0..6c299bca0 100644
--- a/src/main/java/it/unipr/analysis/taint/TaintElement.java
+++ b/src/main/java/it/unipr/analysis/taint/TaintElement.java
@@ -2,11 +2,29 @@
import it.unive.lisa.analysis.BaseLattice;
import it.unive.lisa.analysis.Lattice;
-import it.unive.lisa.analysis.SemanticException;
import it.unive.lisa.util.representation.StringRepresentation;
import it.unive.lisa.util.representation.StructuredRepresentation;
import java.util.Objects;
+/**
+ * Represents an element in the taint abstract domain for EVM bytecode analysis.
+ *
+ * This class models a taint element using a simple byte value and defines
+ * several special constants:
+ *
+ * - TOP: Represents the highest abstract value, indicating an unknown
+ * or over-approximated value.
+ * - BOTTOM: Represents the lowest abstract value, indicating the
+ * absence of any information or an error state.
+ * - TAINT: Marks an element as tainted, meaning it carries untrusted
+ * or external data that may lead to vulnerabilities.
+ * - CLEAN: Marks an element as clean, meaning it is not tainted.
+ *
+ *
+ *
+ * @see BaseLattice
+ * @see Lattice
+ */
public class TaintElement implements BaseLattice {
public static final TaintElement TOP = new TaintElement((byte) 0);
@@ -71,20 +89,30 @@ public boolean isClean() {
}
@Override
- public TaintElement lubAux(TaintElement other) throws SemanticException {
+ public TaintElement lubAux(TaintElement other) {
return TOP;
}
@Override
- public boolean lessOrEqualAux(TaintElement other) throws SemanticException {
+ public boolean lessOrEqualAux(TaintElement other) {
return false;
}
@Override
- public TaintElement glbAux(TaintElement other) throws SemanticException {
+ public TaintElement glbAux(TaintElement other) {
return BOTTOM;
}
+ @Override
+ public boolean isTop() {
+ return this == TOP;
+ }
+
+ @Override
+ public boolean isBottom() {
+ return this == BOTTOM;
+ }
+
/**
* Check whether a set of {@link TaintElement}s contains at least one
* tainted element, if so returns taint. Otherwise, if there is at least a
@@ -112,6 +140,53 @@ else if (t == TOP)
return CLEAN;
}
+ /**
+ * Checks if any of the provided {@link TaintElement} objects is tainted or
+ * has the top value.
+ *
+ * @param elements the array of {@link TaintElement} objects to check
+ *
+ * @return {@code true} if at least one of the elements is tainted or has
+ * the top value, {@code false} otherwise
+ */
+ public static boolean isTaintedOrTop(TaintElement... elements) {
+ return isAtLeastOneTainted(elements) || isAtLeastOneTop(elements);
+ }
+
+ /**
+ * Checks if at least one of the provided {@link TaintElement} objects is
+ * tainted.
+ *
+ * @param elements the array of {@link TaintElement} objects to check
+ *
+ * @return {@code true} if at least one of the elements is tainted,
+ * {@code false} otherwise
+ */
+ public static boolean isAtLeastOneTainted(TaintElement... elements) {
+ for (TaintElement element : elements) {
+ if (element.isTaint())
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Checks if at least one of the provided {@link TaintElement} objects has
+ * the top value.
+ *
+ * @param elements the array of {@link TaintElement} objects to check
+ *
+ * @return {@code true} if at least one of the elements has the top value,
+ * {@code false} otherwise
+ */
+ public static boolean isAtLeastOneTop(TaintElement... elements) {
+ for (TaintElement element : elements) {
+ if (element.isTop())
+ return true;
+ }
+ return false;
+ }
+
@Override
public int hashCode() {
return Objects.hash(v);
diff --git a/src/main/java/it/unipr/analysis/taint/TimestampDependencyAbstractDomain.java b/src/main/java/it/unipr/analysis/taint/TimestampDependencyAbstractDomain.java
deleted file mode 100644
index 3af779d58..000000000
--- a/src/main/java/it/unipr/analysis/taint/TimestampDependencyAbstractDomain.java
+++ /dev/null
@@ -1,69 +0,0 @@
-package it.unipr.analysis.taint;
-
-import it.unipr.analysis.operator.BalanceOperator;
-import it.unipr.analysis.operator.BlockhashOperator;
-import it.unipr.analysis.operator.DifficultyOperator;
-import it.unipr.analysis.operator.TimestampOperator;
-import it.unive.lisa.symbolic.value.Operator;
-import java.util.*;
-
-public class TimestampDependencyAbstractDomain extends TaintAbstractDomain {
- private static final TimestampDependencyAbstractDomain TOP = new TimestampDependencyAbstractDomain(
- createFilledArray(TaintAbstractDomain.STACK_LIMIT, TaintElement.BOTTOM), TaintElement.CLEAN);
- private static final TimestampDependencyAbstractDomain BOTTOM = new TimestampDependencyAbstractDomain(null,
- TaintElement.BOTTOM);
-
- private static TaintElement[] createFilledArray(int size, TaintElement element) {
- TaintElement[] array = new TaintElement[size];
- Arrays.fill(array, element);
- return array;
- }
-
- /**
- * Builds an initial symbolic stack.
- */
- public TimestampDependencyAbstractDomain() {
- this(createFilledArray(STACK_LIMIT, TaintElement.BOTTOM), TaintElement.CLEAN);
- }
-
- /**
- * Builds a taint abstract stack starting from a given stack and a list of
- * elements that push taint.
- *
- * @param circularArray the stack of values
- */
- protected TimestampDependencyAbstractDomain(TaintElement[] circularArray, TaintElement memory) {
- super(circularArray, memory);
- }
-
- @Override
- public Set getTaintedOpcode() {
- Set taintedOpcode = new HashSet<>();
- taintedOpcode.add(TimestampOperator.INSTANCE);
- taintedOpcode.add(BlockhashOperator.INSTANCE);
- taintedOpcode.add(DifficultyOperator.INSTANCE);
- taintedOpcode.add(BalanceOperator.INSTANCE);
- return taintedOpcode;
- }
-
- @Override
- public Set getSanitizedOpcode() {
- return Set.of();
- }
-
- @Override
- public TimestampDependencyAbstractDomain top() {
- return TOP;
- }
-
- @Override
- public TimestampDependencyAbstractDomain bottom() {
- return BOTTOM;
- }
-
- @Override
- public TaintAbstractDomain mk(TaintElement[] array, TaintElement memory) {
- return new TimestampDependencyAbstractDomain(array, memory);
- }
-
-}
\ No newline at end of file
diff --git a/src/main/java/it/unipr/analysis/taint/TxOriginAbstractDomain.java b/src/main/java/it/unipr/analysis/taint/TxOriginAbstractDomain.java
index 6cf263c60..83fc3980e 100644
--- a/src/main/java/it/unipr/analysis/taint/TxOriginAbstractDomain.java
+++ b/src/main/java/it/unipr/analysis/taint/TxOriginAbstractDomain.java
@@ -1,9 +1,8 @@
package it.unipr.analysis.taint;
-import it.unipr.analysis.operator.OriginOperator;
+import it.unipr.cfg.Origin;
+import it.unive.lisa.program.cfg.statement.Statement;
import it.unive.lisa.symbolic.value.Operator;
-import java.util.Arrays;
-import java.util.Collections;
import java.util.Set;
public class TxOriginAbstractDomain extends TaintAbstractDomain {
@@ -12,12 +11,6 @@ public class TxOriginAbstractDomain extends TaintAbstractDomain {
createFilledArray(TaintAbstractDomain.STACK_LIMIT, TaintElement.BOTTOM), TaintElement.CLEAN);
private static final TxOriginAbstractDomain BOTTOM = new TxOriginAbstractDomain(null, TaintElement.BOTTOM);
- private static TaintElement[] createFilledArray(int size, TaintElement element) {
- TaintElement[] array = new TaintElement[size];
- Arrays.fill(array, element);
- return array;
- }
-
/**
* Builds an initial symbolic stack.
*/
@@ -29,15 +22,15 @@ public TxOriginAbstractDomain() {
* Builds a taint abstract stack starting from a given stack and a list of
* elements that push taint.
*
- * @param circularArray the stack of values
+ * @param stack the stack of values
*/
- protected TxOriginAbstractDomain(TaintElement[] circularArray, TaintElement memory) {
- super(circularArray, memory);
+ protected TxOriginAbstractDomain(TaintElement[] stack, TaintElement memory) {
+ super(stack, memory);
}
@Override
- public Set getTaintedOpcode() {
- return Collections.singleton(OriginOperator.INSTANCE);
+ public boolean isTainted(Statement stmt) {
+ return stmt instanceof Origin;
}
@Override
@@ -56,7 +49,7 @@ public TxOriginAbstractDomain bottom() {
}
@Override
- public TaintAbstractDomain mk(TaintElement[] array, TaintElement memory) {
- return new TxOriginAbstractDomain(array, memory);
+ public TaintAbstractDomain mk(TaintElement[] stack, TaintElement memory) {
+ return new TxOriginAbstractDomain(stack, memory);
}
}
diff --git a/src/main/java/it/unipr/cfg/EVMCFG.java b/src/main/java/it/unipr/cfg/EVMCFG.java
index a3810920f..c9767e1c6 100644
--- a/src/main/java/it/unipr/cfg/EVMCFG.java
+++ b/src/main/java/it/unipr/cfg/EVMCFG.java
@@ -38,12 +38,17 @@
public class EVMCFG extends CFG {
private static final Logger log = LogManager.getLogger(EVMCFG.class);
+ private Set _basicBlocks;
private Set jumpDestsNodes;
private Set jumpNodes;
private Set pushedJumps;
private Set sstores;
private Set jumpDestsNodesLocations;
- private Set basicBlocks;
+ public Set logxs;
+ public Set calls;
+ public Set externalData;
+ public Set jumpI;
+ public Set successfullyTerminationStatements;
/**
* Builds a EVMCFG starting from its description.
@@ -87,12 +92,137 @@ else if ((statement instanceof Jump) || (statement instanceof Jumpi)) {
}
/**
- * Returns a set of all the SSTORE statements in the CFG. SSTORE
+ * Returns a set of all the CALLDATA and CALLDATACOPY statements in the CFG.
+ *
+ * @return a set of all the CALLDATA and CALLDATACOPY statements in the CFG
+ */
+ public Set getExternalData() {
+ if (this.externalData == null) {
+ NodeList cfgNodeList = this.getNodeList();
+ Set externalData = new HashSet<>();
+
+ for (Statement statement : cfgNodeList.getNodes()) {
+ if (statement instanceof Calldataload
+ || statement instanceof Calldatacopy) {
+ externalData.add(statement);
+ }
+ }
+
+ return this.externalData = externalData;
+ }
+
+ return this.externalData;
+ }
+
+ /**
+ * Returns a set of all the SSTORE statements in the CFG.
*
* @return a set of all the SSTORE statements in the CFG
*/
public Set getAllSstore() {
- return sstores;
+ if (this.sstores == null) {
+ NodeList cfgNodeList = this.getNodeList();
+ Set sstores = new HashSet<>();
+
+ for (Statement statement : cfgNodeList.getNodes())
+ if (statement instanceof Sstore)
+ sstores.add(statement);
+
+ return this.sstores = sstores;
+ }
+
+ return this.sstores;
+ }
+
+ /**
+ * Returns a set of all the STOP and RETURN statements in the CFG.
+ *
+ * @return a set of all the STOP and RETURN statements in the CFG
+ */
+ public Set getAllSuccessfullyTerminationStatements() {
+ if (this.successfullyTerminationStatements == null) {
+ NodeList cfgNodeList = this.getNodeList();
+ Set successfullyTerminationStatements = new HashSet<>();
+
+ for (Statement statement : cfgNodeList.getNodes())
+ if (statement instanceof Stop
+ || statement instanceof Return)
+ successfullyTerminationStatements.add(statement);
+
+ return this.successfullyTerminationStatements = successfullyTerminationStatements;
+ }
+
+ return this.successfullyTerminationStatements;
+ }
+
+ /**
+ * Returns a set of all the CALL, STATICCALL and DELEGATECALL statements in
+ * the CFG.
+ *
+ * @return a set of all the CALL, STATICCALL and DELEGATECALL statements in
+ * the CFG
+ */
+ public Set getAllCall() {
+ if (this.calls == null) {
+ NodeList cfgNodeList = this.getNodeList();
+ Set calls = new HashSet<>();
+
+ for (Statement statement : cfgNodeList.getNodes()) {
+ if (statement instanceof Call) {
+ calls.add(statement);
+ } else if (statement instanceof Staticcall) {
+ calls.add(statement);
+ } else if (statement instanceof Delegatecall) {
+ calls.add(statement);
+ }
+ }
+
+ return this.calls = calls;
+ }
+
+ return this.calls;
+ }
+
+ public Set getAllJumpI() {
+ if (this.jumpI == null) {
+ NodeList cfgNodeList = this.getNodeList();
+ Set jumpI = new HashSet<>();
+
+ for (Statement statement : cfgNodeList.getNodes()) {
+ if (statement instanceof Jumpi) {
+ jumpI.add(statement);
+ }
+ }
+
+ return this.jumpI = jumpI;
+ }
+
+ return this.jumpI;
+ }
+
+ /**
+ * Returns a set of all the LOGx statements in the CFG.
+ *
+ * @return a set of all the LOGx statements in the CFG
+ */
+ public Set getAllLogX() {
+ if (logxs == null) {
+ NodeList cfgNodeList = this.getNodeList();
+ Set logxs = new HashSet<>();
+
+ for (Statement statement : cfgNodeList.getNodes()) {
+ if (statement instanceof Log1
+ || statement instanceof Log2
+ || statement instanceof Log3
+ || statement instanceof Log4) {
+ logxs.add(statement);
+ }
+ }
+
+ return this.logxs = logxs;
+ }
+
+ return logxs;
}
/**
@@ -128,6 +258,39 @@ public Set getAllJumps() {
return jumpNodes;
}
+ /**
+ * Retrieves all basic blocks in this control-flow graph (CFG). If the basic
+ * blocks have not been computed yet, they are generated.
+ *
+ * @return a set containing all {@link BasicBlock} instances in the CFG
+ */
+ public Set getAllBasicBlocks() {
+ if (this._basicBlocks == null)
+ this._basicBlocks = BasicBlock.getBasicBlocks(this);
+ return this._basicBlocks;
+ }
+
+ /**
+ * Finds the basic block that contains the given statement. If the basic
+ * blocks have not been computed yet, they are generated first.
+ *
+ * @param stmt the statement to search for within the basic blocks
+ *
+ * @return the {@link BasicBlock} containing the given statement, or
+ * {@code null} if not found
+ */
+ public BasicBlock getBasicBlock(Statement stmt) {
+ if (this._basicBlocks == null)
+ this._basicBlocks = BasicBlock.getBasicBlocks(this);
+
+ for (BasicBlock bb : this._basicBlocks)
+ for (Statement s : bb.getStatements())
+ if (s.equals(stmt))
+ return bb;
+
+ return null;
+ }
+
public int getOpcodeCount() {
// -1 for the return statement, that it does not correspond to an actual
// statement of the smart contract, but it is added by EVMLiSA
@@ -243,15 +406,89 @@ public void validate() throws ProgramValidationException {
*/
public boolean reachableFrom(Statement start, Statement target) {
String key = this.hashCode() + "" + start.hashCode() + "" + target.hashCode();
- if (MyCache.getInstance().existsInReachableFrom(key)) {
+
+ if (MyCache.getInstance().existsInReachableFrom(key))
return MyCache.getInstance().isReachableFrom(key);
- }
boolean result = dfs(start, target, new HashSet<>());
MyCache.getInstance().addReachableFrom(key, result);
return result;
}
+ public boolean reachableFromReverse(Statement start, Statement target) {
+ String key = this.hashCode() + "" + start.hashCode() + "" + target.hashCode() + "reverse";
+
+ if (MyCache.getInstance().existsInReachableFrom(key))
+ return MyCache.getInstance().isReachableFrom(key);
+
+ boolean result = dfsReverse(start, target, new HashSet<>());
+ MyCache.getInstance().addReachableFrom(key, result);
+ return result;
+ }
+
+ /**
+ * Attempts to find a reachable entrypoint statement by traversing the CFG
+ * in reverse (following ingoing edges) from the specified start node.
+ *
+ * @param start the node from which to begin the reverse search
+ * @param entrypoints the set of candidate entrypoint statements to detect
+ *
+ * @return the first entrypoint encountered, or null if none are reachable
+ */
+ public Statement reachableFromReverse(Statement start, Set entrypoints) {
+ return bfsReverse(start, entrypoints, new HashSet<>());
+ }
+
+ /**
+ * Performs a breadth‑first search on the reversed CFG, starting from a
+ * given node, to locate any of a set of entrypoint statements.
+ *
+ * @param start the initial node of the search
+ * @param entrypoints the set of target statements to find
+ * @param visited the set of already visited nodes (to prevent cycles)
+ *
+ * @return the first matching entrypoint found, or null if no entrypoint is
+ * reachable
+ */
+ private Statement bfsReverse(Statement start, Set entrypoints, Set visited) {
+ Queue queue = new LinkedList<>();
+ queue.offer(start);
+ visited.add(start);
+
+ while (!queue.isEmpty()) {
+ Statement current = queue.poll();
+
+ for (Edge edge : list.getIngoingEdges(current)) {
+ Statement next = edge.getSource();
+ if (visited.add(next)) {
+ queue.offer(next);
+ }
+ if (entrypoints.contains(next)) {
+ return next;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Determines if the target statement is reachable from the start statement
+ * across a cross-chain edge in the control flow graph (CFG).
+ *
+ * @param start the starting statement in the first control flow graph.
+ * @param target the target statement which may belong to a different
+ * control flow graph.
+ *
+ * @return true if the target statement is reachable from the start
+ * statement across a cross-chain edge; false otherwise.
+ */
+ public boolean reachableFromCrossingACrossChainEdge(Statement start, Statement target) {
+ if (start.getCFG().equals(target.getCFG()))
+ return false;
+ return reachableFrom(start, target);
+ }
+
/**
* Performs a depth-first search (DFS) to determine if the target statement
* is reachable from the start statement.
@@ -284,6 +521,60 @@ private boolean dfs(Statement start, Statement target, Set visited) {
return false;
}
+ /**
+ * Performs a breadth-first search (BFS) to determine if the target
+ * statement is reachable from the start statement.
+ *
+ * @param start The starting statement.
+ * @param target The target statement.
+ * @param visited A set of visited statements to avoid cycles.
+ *
+ * @return True if the target is reachable from the start, false otherwise.
+ */
+ private boolean bfs(Statement start, Statement target, Set visited) {
+ Queue queue = new LinkedList<>();
+ queue.offer(start);
+ visited.add(start);
+
+ while (!queue.isEmpty()) {
+ Statement current = queue.poll();
+ if (current.equals(target))
+ return true;
+
+ for (Edge edge : list.getOutgoingEdges(current)) {
+ Statement next = edge.getDestination();
+ if (visited.add(next)) { // add returns true if next was not
+ // already in visited
+ queue.offer(next);
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private boolean dfsReverse(Statement start, Statement target, Set visited) {
+ Deque stack = new ArrayDeque<>();
+ stack.push(start);
+
+ while (!stack.isEmpty()) {
+ Statement current = stack.pop();
+
+ if (current.equals(target))
+ return true;
+
+ if (visited.add(current)) {
+ for (Edge edge : list.getIngoingEdges(current)) {
+ Statement next = edge.getSource();
+ if (!visited.contains(next))
+ stack.push(next);
+ }
+ }
+ }
+
+ return false;
+ }
+
/**
* Finds the furthest reachable SSTORE statements from a given start
* statement using BFS.
@@ -328,9 +619,9 @@ public Set getFurthestSstores(Statement start) {
*/
public boolean reachableFromSequentially(Statement start, Statement target) {
String key = this.hashCode() + "" + start.hashCode() + "" + target.hashCode() + "sequentially";
- if (MyCache.getInstance().existsInReachableFrom(key)) {
+
+ if (MyCache.getInstance().existsInReachableFrom(key))
return MyCache.getInstance().isReachableFrom(key);
- }
boolean result = dfsSequential(start, target, new HashSet<>());
MyCache.getInstance().addReachableFrom(key, result);
@@ -413,98 +704,157 @@ public Set> findMatchingStatements(Statement start,
return matchingStatements;
}
- public boolean reachableFromCrossing(Statement start, Statement target, Set statements) {
- return dfsCrossing(start, target, new HashSet<>(), statements);
+ /**
+ * Determines if the target statement is reachable from the start statement
+ * without triggering any of the specified statement types.
+ *
+ * @param start the starting statement from which the reachability is
+ * checked
+ * @param target the target statement to check for reachability
+ * @param avoidTypes the types of statements to avoid during traversal
+ *
+ * @return true if the target statement is reachable from the start without
+ * triggering any of the specified statement types, false
+ * otherwise
+ */
+ public boolean reachableFromWithoutTypes(Statement start, Statement target, Set> avoidTypes) {
+ String key = this.hashCode() + "" + start.hashCode() + "" + target.hashCode() + "withouttypes"
+ + avoidTypes.hashCode();
+
+ if (MyCache.getInstance().existsInReachableFrom(key))
+ return MyCache.getInstance().isReachableFrom(key);
+
+ boolean result = dfsWithoutTypes(start, target, new HashSet<>(), avoidTypes);
+ MyCache.getInstance().addReachableFrom(key, result);
+ return result;
}
/**
- * Check whether all paths from {@code start} to {@code target} contain at
- * least one {@code statements} statement. This method explores all possible
- * paths using a depth-first search (DFS). If there is at least one path
- * where a required statement is missing, the method returns {@code false}.
- *
- * @param start The starting statement of the search.
- * @param target The target statement.
- * @param visited A set of visited statements to avoid cycles.
- * @param statements The set of statements that must be present in all
- * paths.
+ * Performs a depth-first search (DFS) to determine if a path exists from a
+ * start statement to a target statement in a graph, avoiding any paths that
+ * involve edges with a source node of the specified types.
*
- * @return {@code true} if all paths contain all statements, otherwise
- * {@code false}.
+ * @param start the starting statement for the DFS traversal
+ * @param target the target statement to reach during the DFS traversal
+ * @param visited a set of already visited statements to avoid cycles
+ * during the traversal
+ * @param avoidTypes the types of statements to avoid during traversal
+ *
+ * @return true if a path exists from the start statement to the target
+ * statement without traversing through edges originating from
+ * nodes of the specified types, false otherwise
*/
- private boolean dfsCrossing(Statement start, Statement target, Set visited, Set statements) {
- boolean foundTarget = false;
- Set pathStatements = new HashSet<>();
-
- Stack> stack = new Stack<>();
- stack.push(Collections.singletonList(start));
+ private boolean dfsWithoutTypes(Statement start,
+ Statement target,
+ Set visited,
+ Set> avoidTypes) {
+ Stack stack = new Stack<>();
+ boolean noVisitedLogs = true;
+ stack.push(start);
while (!stack.isEmpty()) {
- List path = stack.pop();
- Statement current = path.get(path.size() - 1);
-
- if (visited.contains(current)) {
- continue;
- }
+ Statement current = stack.pop();
- visited.add(current);
- pathStatements.add(current);
+ if (current.equals(target))
+ return noVisitedLogs;
- if (current.equals(target)) {
- foundTarget = true;
+ if (!visited.contains(current)) {
+ visited.add(current);
+ Collection outgoingEdges = list.getOutgoingEdges(current);
- // Check if this specific path contains at least a statement
- boolean contains = false;
- for (Statement statement : statements) {
- if (pathStatements.contains(statement)) {
- contains = true;
- break;
+ for (Edge edge : outgoingEdges) {
+ if (avoidTypes.stream().anyMatch(type -> type.isInstance(edge.getSource()))) {
+ noVisitedLogs = false;
+ continue;
}
- }
- if (!contains)
- return false;
- }
- Collection outgoingEdges = list.getOutgoingEdges(current);
- for (Edge edge : outgoingEdges) {
- Statement next = edge.getDestination();
- if (!visited.contains(next)) {
- List newPath = new ArrayList<>(path);
- newPath.add(next);
- stack.push(newPath);
+ Statement next = edge.getDestination();
+ if (!visited.contains(next))
+ stack.push(next);
}
}
}
-
- return foundTarget;
+ return noVisitedLogs;
}
/**
- * Checks if the target statement is reachable from the start statement
- * without traversing any Jumpi instructions.
+ * Retrieves all statements of the specified types that lie on any
+ * control‑flow path between two given statements in the CFG.
+ *
+ * This method performs a depth‑first traversal from the {@code start}
+ * statement until reaching the {@code target}. Along every explored path,
+ * it collects any intermediate statements whose runtime class matches one
+ * of the provided types.
*
- * @param start the starting statement
- * @param target the target statement
+ * @param start the entry point of the search
+ * @param target the termination point of the search
+ * @param getTypes a set of statement classes to filter and collect along
+ * the paths
*
- * @return true if the target is reachable without passing through Jumpi,
- * false otherwise
+ * @return a set of all matching statements encountered before reaching
+ * {@code target}, or an empty set if none are found
*/
- public boolean reachableFromWithoutJumpI(Statement start, Statement target) {
- return dfsWithoutJumpI(start, target, new HashSet<>());
+ public Set getStatementsInAPathWithTypes(
+ Statement start,
+ Statement target,
+ Set> getTypes) {
+
+ Set visited = new HashSet<>();
+ Set matchingStatements = new HashSet<>();
+ Stack stack = new Stack<>();
+ stack.push(start);
+
+ while (!stack.isEmpty()) {
+ Statement current = stack.pop();
+
+ if (current.equals(target))
+ return matchingStatements;
+
+ if (!visited.contains(current)) {
+ visited.add(current);
+
+ Collection outgoingEdges = list.getOutgoingEdges(current);
+
+ for (Edge edge : outgoingEdges) {
+ if (getTypes.stream().anyMatch(type -> type.isInstance(edge.getDestination())))
+ matchingStatements.add(edge.getDestination());
+ Statement next = edge.getDestination();
+ if (!visited.contains(next))
+ stack.push(next);
+ }
+ }
+ }
+
+ return matchingStatements;
}
/**
- * Performs a depth-first search (DFS) to determine if the target statement
- * is reachable from the start statement while avoiding Jumpi instructions.
+ * Determines if the 'target' statement is reachable from the 'start'
+ * statement while avoiding any statements in 'avoidStatements'. Results are
+ * cached to avoid repeated computations.
*
- * @param start the starting statement
- * @param target the target statement
- * @param visited the set of already visited statements
- *
- * @return true if the target is reachable without passing through Jumpi,
- * false otherwise
+ * @param start the starting statement for the search
+ * @param target the target statement to verify reachability
+ * @param avoidStatements the set of statements to ignore during traversal
+ *
+ * @return true if the 'target' is reachable from 'start' without going
+ * through any of the 'avoidStatements' it return false
+ * otherwise
*/
- private boolean dfsWithoutJumpI(Statement start, Statement target, Set visited) {
+ public boolean reachableFromWithoutStatements(Statement start, Statement target, Set avoidStatements) {
+ String key = this.hashCode() + "" + start.hashCode() + "" + target.hashCode() + "withoutstatements"
+ + avoidStatements.hashCode();
+
+ if (MyCache.getInstance().existsInReachableFrom(key))
+ return MyCache.getInstance().isReachableFrom(key);
+
+ boolean result = dfsWithoutStatements(start, target, new HashSet<>(), avoidStatements);
+ MyCache.getInstance().addReachableFrom(key, result);
+ return result;
+ }
+
+ private boolean dfsWithoutStatements(Statement start, Statement target, Set visited,
+ Set avoidStatements) {
Stack stack = new Stack<>();
stack.push(start);
@@ -520,7 +870,7 @@ private boolean dfsWithoutJumpI(Statement start, Statement target, Set outgoingEdges = list.getOutgoingEdges(current);
for (Edge edge : outgoingEdges) {
- if (edge.getSource() instanceof Jumpi)
+ if (avoidStatements.contains(edge.getDestination()))
continue;
Statement next = edge.getDestination();
if (!visited.contains(next))
diff --git a/src/main/java/it/unipr/checker/RandomnessDependencyChecker.java b/src/main/java/it/unipr/checker/RandomnessDependencyChecker.java
new file mode 100644
index 000000000..9f2512dda
--- /dev/null
+++ b/src/main/java/it/unipr/checker/RandomnessDependencyChecker.java
@@ -0,0 +1,141 @@
+package it.unipr.checker;
+
+import it.unipr.analysis.taint.TaintAbstractDomain;
+import it.unipr.analysis.taint.TaintElement;
+import it.unipr.cfg.*;
+import it.unipr.utils.MyCache;
+import it.unive.lisa.analysis.AnalysisState;
+import it.unive.lisa.analysis.AnalyzedCFG;
+import it.unive.lisa.analysis.SemanticException;
+import it.unive.lisa.analysis.SimpleAbstractState;
+import it.unive.lisa.analysis.heap.MonolithicHeap;
+import it.unive.lisa.analysis.nonrelational.value.TypeEnvironment;
+import it.unive.lisa.analysis.types.InferredTypes;
+import it.unive.lisa.checks.semantic.CheckToolWithAnalysisResults;
+import it.unive.lisa.checks.semantic.SemanticCheck;
+import it.unive.lisa.program.cfg.CFG;
+import it.unive.lisa.program.cfg.statement.Statement;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public class RandomnessDependencyChecker implements
+ SemanticCheck>> {
+
+ private static final Logger log = LogManager.getLogger(RandomnessDependencyChecker.class);
+
+ private static boolean isEnabled = false;
+
+ public static void enableChecker() {
+ isEnabled = true;
+ }
+
+ public static void disableChecker() {
+ isEnabled = false;
+ }
+
+ public static boolean isEnabled() {
+ return isEnabled;
+ }
+
+ @Override
+ public boolean visit(
+ CheckToolWithAnalysisResults<
+ SimpleAbstractState>> tool,
+ CFG graph, Statement node) {
+
+ EVMCFG cfg = ((EVMCFG) graph);
+
+ if (node instanceof Jump || node instanceof Jumpi || node instanceof Sstore
+ || node instanceof Sha3)
+ for (AnalyzedCFG>> result : tool.getResultOf(cfg)) {
+ AnalysisState>> analysisResult = null;
+
+ try {
+ analysisResult = result.getAnalysisStateBefore(node);
+ } catch (SemanticException e1) {
+ log.error("(RandomnessDependencyChecker): {}", e1.getMessage());
+ }
+
+ // Retrieve the symbolic stack from the analysis result
+ TaintAbstractDomain taintedStack = analysisResult.getState().getValueState();
+
+ // If the stack is bottom, the jump is definitely
+ // unreachable
+ if (taintedStack.isBottom())
+ // Nothing to do
+ continue;
+ else {
+ if (node instanceof Sha3
+ || node instanceof Sstore
+ || node instanceof Jumpi) {
+
+ if (TaintElement.isAtLeastOneTainted(taintedStack.getElementAtPosition(1),
+ taintedStack.getElementAtPosition(2)))
+ raiseWarning(node, tool, cfg);
+ else if (TaintElement.isAtLeastOneTop(taintedStack.getElementAtPosition(1),
+ taintedStack.getElementAtPosition(2)))
+ raisePossibleWarning(node, tool, cfg);
+
+ } else if (node instanceof Jump) {
+
+ if (TaintElement.isAtLeastOneTainted(taintedStack.getElementAtPosition(1)))
+ raiseWarning(node, tool, cfg);
+ else if (TaintElement.isAtLeastOneTop(taintedStack.getElementAtPosition(1)))
+ raisePossibleWarning(node, tool, cfg);
+
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Raises a warning indicating a randomness dependency vulnerability in the
+ * analyzed program.
+ *
+ * @param sink the statement causing the warning
+ * @param tool the analysis tool and results used for the check
+ * @param cfg the control flow graph where the warning is identified
+ */
+ private void raiseWarning(Statement sink, CheckToolWithAnalysisResults<
+ SimpleAbstractState>> tool,
+ EVMCFG cfg) {
+ ProgramCounterLocation sinkLoc = (ProgramCounterLocation) sink.getLocation();
+
+ log.warn("[DEFINITE] Randomness dependency vulnerability at pc {} (line {}).",
+ sinkLoc.getPc(),
+ sinkLoc.getSourceCodeLine());
+
+ String warn = "[DEFINITE] Randomness dependency vulnerability at pc "
+ + ((ProgramCounterLocation) sink.getLocation()).getSourceCodeLine();
+ tool.warn(warn);
+ MyCache.getInstance().addRandomnessDependencyWarning(cfg.hashCode(), warn);
+ }
+
+ /**
+ * Logs a possible randomness dependency vulnerability warning and updates
+ * the tool and cache with the detected issue.
+ *
+ * @param sink the statement where the potential vulnerability is detected
+ * @param tool the analysis tool containing the current state and analysis
+ * results
+ * @param cfg the control flow graph associated with the statement
+ */
+ private void raisePossibleWarning(Statement sink, CheckToolWithAnalysisResults<
+ SimpleAbstractState>> tool,
+ EVMCFG cfg) {
+ ProgramCounterLocation sinkLoc = (ProgramCounterLocation) sink.getLocation();
+
+ log.warn("[POSSIBLE] Randomness dependency vulnerability at pc {} (line {}).",
+ sinkLoc.getPc(),
+ sinkLoc.getSourceCodeLine());
+
+ String warn = "[POSSIBLE] Randomness dependency vulnerability at pc "
+ + ((ProgramCounterLocation) sink.getLocation()).getSourceCodeLine();
+ tool.warn(warn);
+ MyCache.getInstance().addPossibleRandomnessDependencyWarning(cfg.hashCode(), warn);
+ }
+}
diff --git a/src/main/java/it/unipr/checker/ReentrancyChecker.java b/src/main/java/it/unipr/checker/ReentrancyChecker.java
index 194adc994..4019b60f2 100644
--- a/src/main/java/it/unipr/checker/ReentrancyChecker.java
+++ b/src/main/java/it/unipr/checker/ReentrancyChecker.java
@@ -111,8 +111,11 @@ private void checkForReentrancy(Statement call, CheckToolWithAnalysisResults<
if (!ss2.equals(ss1) && cfg.reachableFromSequentially(ss1, ss2))
sstoreLoc = (ProgramCounterLocation) ss2.getLocation();
- log.debug("Reentrancy attack at {} at line no. {} coming from line {}", sstoreLoc.getPc(),
- sstoreLoc.getSourceCodeLine(), ((ProgramCounterLocation) call.getLocation()).getSourceCodeLine());
+ log.warn("Reentrancy attack at pc {} (line {}) coming from pc {} (line {}).",
+ sstoreLoc.getPc(),
+ sstoreLoc.getSourceCodeLine(),
+ ((ProgramCounterLocation) call.getLocation()).getPc(),
+ ((ProgramCounterLocation) call.getLocation()).getSourceCodeLine());
String warn = "Reentrancy attack at " + sstoreLoc.getPc();
tool.warn(warn);
MyCache.getInstance().addReentrancyWarning(cfg.hashCode(), warn);
diff --git a/src/main/java/it/unipr/checker/TimestampDependencyChecker.java b/src/main/java/it/unipr/checker/TimestampDependencyChecker.java
deleted file mode 100644
index 1c7861be0..000000000
--- a/src/main/java/it/unipr/checker/TimestampDependencyChecker.java
+++ /dev/null
@@ -1,124 +0,0 @@
-package it.unipr.checker;
-
-import it.unipr.analysis.taint.TaintAbstractDomain;
-import it.unipr.analysis.taint.TaintElement;
-import it.unipr.cfg.EVMCFG;
-import it.unipr.cfg.Jump;
-import it.unipr.cfg.Jumpi;
-import it.unipr.cfg.ProgramCounterLocation;
-import it.unipr.cfg.Return;
-import it.unipr.cfg.Sha3;
-import it.unipr.cfg.Sstore;
-import it.unipr.utils.MyCache;
-import it.unive.lisa.analysis.AnalysisState;
-import it.unive.lisa.analysis.AnalyzedCFG;
-import it.unive.lisa.analysis.SemanticException;
-import it.unive.lisa.analysis.SimpleAbstractState;
-import it.unive.lisa.analysis.heap.MonolithicHeap;
-import it.unive.lisa.analysis.nonrelational.value.TypeEnvironment;
-import it.unive.lisa.analysis.types.InferredTypes;
-import it.unive.lisa.checks.semantic.CheckToolWithAnalysisResults;
-import it.unive.lisa.checks.semantic.SemanticCheck;
-import it.unive.lisa.program.cfg.CFG;
-import it.unive.lisa.program.cfg.statement.Statement;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-public class TimestampDependencyChecker implements
- SemanticCheck>> {
-
- private static final Logger log = LogManager.getLogger(TimestampDependencyChecker.class);
-
- private static boolean isEnabled = false;
-
- public static void enableChecker() {
- isEnabled = true;
- }
-
- public static void disableChecker() {
- isEnabled = false;
- }
-
- public static boolean isEnabled() {
- return isEnabled;
- }
-
- @Override
- public boolean visit(
- CheckToolWithAnalysisResults<
- SimpleAbstractState>> tool,
- CFG graph, Statement node) {
-
- EVMCFG cfg = ((EVMCFG) graph);
-
- if (node instanceof Jump || node instanceof Return || node instanceof Jumpi || node instanceof Sstore
- || node instanceof Sha3)
- for (AnalyzedCFG>> result : tool.getResultOf(cfg)) {
- AnalysisState>> analysisResult = null;
-
- try {
- analysisResult = result.getAnalysisStateBefore(node);
- } catch (SemanticException e1) {
- log.error("(TimestampDependencyChecker): {}", e1.getMessage());
- }
-
- // Retrieve the symbolic stack from the analysis result
- TaintAbstractDomain taintedStack = analysisResult.getState().getValueState();
-
- // If the stack is bottom, the jump is definitely
- // unreachable
- if (taintedStack.isBottom())
- // Nothing to do
- continue;
- else {
- if (node instanceof Sha3 || node instanceof Sstore || node instanceof Jumpi
- || node instanceof Return) {
- if (checkTaintTwoPops(taintedStack))
- raiseWarning(node, tool, cfg);
- } else if (node instanceof Jump) {
- if (checkTaintOnePop(taintedStack))
- raiseWarning(node, tool, cfg);
- }
- }
- }
-
- return true;
- }
-
- // 2 pop()
- private boolean checkTaintTwoPops(TaintAbstractDomain taintedStack) {
- TaintElement firstStackElement = taintedStack.getFirstElement();
- TaintElement secondStackElement = taintedStack.getSecondElement();
- if (secondStackElement.isBottom() || secondStackElement.isBottom())
- return false;
- else if (firstStackElement.isTaint() || secondStackElement.isTaint())
- return true;
- return false;
- }
-
- // 1 pop()
- private boolean checkTaintOnePop(TaintAbstractDomain taintedStack) {
- TaintElement firstStackElement = taintedStack.getFirstElement();
-
- if (firstStackElement.isBottom())
- return false;
- else if (firstStackElement.isTaint())
- return true;
- return false;
- }
-
- private void raiseWarning(Statement sink, CheckToolWithAnalysisResults<
- SimpleAbstractState>> tool,
- EVMCFG cfg) {
- ProgramCounterLocation sinkLoc = (ProgramCounterLocation) sink.getLocation();
-
- log.debug("Timestamp attack at {} at line no. {}", sinkLoc.getPc(),
- sinkLoc.getSourceCodeLine());
-
- String warn = "Timestamp attack at " + ((ProgramCounterLocation) sink.getLocation()).getSourceCodeLine();
- tool.warn(warn);
- MyCache.getInstance().addTimestampDependencyWarning(cfg.hashCode(), warn);
- }
-}
diff --git a/src/main/java/it/unipr/checker/TxOriginChecker.java b/src/main/java/it/unipr/checker/TxOriginChecker.java
index cb9e3b1bd..4d83e9f63 100644
--- a/src/main/java/it/unipr/checker/TxOriginChecker.java
+++ b/src/main/java/it/unipr/checker/TxOriginChecker.java
@@ -60,33 +60,25 @@ public boolean visit(
}
// Retrieve the symbolic stack from the analysis result
- TaintAbstractDomain stack = analysisResult.getState().getValueState();
+ TaintAbstractDomain taintedStack = analysisResult.getState().getValueState();
// If the stack is bottom, the node is definitely
// unreachable
- if (stack.isBottom())
+ if (taintedStack.isBottom())
// Nothing to do
continue;
else {
- TaintElement firstElem = stack.getFirstElement();
- TaintElement secondElem = stack.getSecondElement();
- if (firstElem.isBottom() || secondElem.isBottom())
+ if (taintedStack.getElementAtPosition(1).isBottom()
+ || taintedStack.getElementAtPosition(2).isBottom())
// Nothing to do
continue;
else {
- // Checks if either first or second element in the
- // stack is tainted
- if (firstElem.isTaint() || secondElem.isTaint()) {
- ProgramCounterLocation jumploc = (ProgramCounterLocation) node.getLocation();
-
- log.debug("Tx. Origin attack at {} at line no. {}", jumploc.getPc(),
- jumploc.getSourceCodeLine());
-
- String warn = "TxOrigin attack at "
- + ((ProgramCounterLocation) node.getLocation()).getSourceCodeLine();
- tool.warn(warn);
- MyCache.getInstance().addTxOriginWarning(cfg.hashCode(), warn);
- }
+ if (TaintElement.isAtLeastOneTainted(taintedStack.getElementAtPosition(1),
+ taintedStack.getElementAtPosition(2)))
+ raiseWarning(node, tool, cfg);
+ else if (TaintElement.isAtLeastOneTop(taintedStack.getElementAtPosition(1),
+ taintedStack.getElementAtPosition(2)))
+ raisePossibleWarning(node, tool, cfg);
}
}
}
@@ -94,4 +86,35 @@ public boolean visit(
return true;
}
+
+ private void raiseWarning(Statement node, CheckToolWithAnalysisResults<
+ SimpleAbstractState>> tool,
+ EVMCFG cfg) {
+
+ ProgramCounterLocation nodeLocation = (ProgramCounterLocation) node.getLocation();
+
+ log.warn("[DEFINITE] Tx.Origin attack at pc {} (line {}).",
+ nodeLocation.getPc(),
+ nodeLocation.getSourceCodeLine());
+
+ String warn = "[DEFINITE] TxOrigin attack at "
+ + ((ProgramCounterLocation) node.getLocation()).getSourceCodeLine();
+ tool.warn(warn);
+ MyCache.getInstance().addTxOriginWarning(cfg.hashCode(), warn);
+ }
+
+ private void raisePossibleWarning(Statement node, CheckToolWithAnalysisResults<
+ SimpleAbstractState>> tool,
+ EVMCFG cfg) {
+
+ ProgramCounterLocation nodeLocation = (ProgramCounterLocation) node.getLocation();
+
+ log.warn("[POSSIBLE] Tx.Origin attack at pc {} (line {}).", nodeLocation.getPc(),
+ nodeLocation.getSourceCodeLine());
+
+ String warn = "[POSSIBLE] Tx.Origin attack at "
+ + ((ProgramCounterLocation) node.getLocation()).getSourceCodeLine();
+ tool.warn(warn);
+ MyCache.getInstance().addPossibleTxOriginWarning(cfg.hashCode(), warn);
+ }
}
\ No newline at end of file
diff --git a/src/main/java/it/unipr/utils/ABIManager.java b/src/main/java/it/unipr/utils/ABIManager.java
index d25c9d872..4606bbfb1 100644
--- a/src/main/java/it/unipr/utils/ABIManager.java
+++ b/src/main/java/it/unipr/utils/ABIManager.java
@@ -1,6 +1,5 @@
package it.unipr.utils;
-import it.unipr.analysis.contract.EventKnowledge;
import it.unipr.analysis.contract.Signature;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@@ -74,6 +73,7 @@ public static Set parseEventsFromABI(Path abi) {
}
private static Set parseABI(Path abi, String type) {
+ Set stateMutabilityForbidden = Set.of("view", "pure");
Set signatures = new HashSet<>();
String abiJson;
@@ -89,6 +89,12 @@ private static Set parseABI(Path abi, String type) {
for (int i = 0; i < abiArray.length(); i++) {
JSONObject obj = abiArray.getJSONObject(i);
if (obj.has("type") && obj.getString("type").equals(type)) {
+
+ /* We do not need to collect global variables */
+ if (obj.has("stateMutability")
+ && stateMutabilityForbidden.contains(obj.getString("stateMutability")))
+ continue;
+
String functionName = obj.getString("name");
JSONArray inputs = obj.getJSONArray("inputs");
JSONArray outputs = obj.optJSONArray("outputs");
@@ -118,8 +124,6 @@ private static Set parseABI(Path abi, String type) {
String selector = getFunctionSelector(fullSignature);
signatures.add(new Signature(functionName, type, paramTypes, outputTypes, fullSignature, selector));
-
-// log.debug("[{}] {} -> {}", type, fullSignature, selector);
}
}
return signatures;
@@ -210,11 +214,6 @@ public static void main(String[] args) {
log.info("Parsing events");
Set events = parseEventsFromABI(abi);
verifyFunctionSelectors(events, bytecode);
-
- log.info("Computing events knowledge");
- for (Signature event : events) {
- log.debug("{}: {}", event.getFullSignature(), EventKnowledge.getKnowledge(event.getName()));
- }
} catch (Exception e) {
log.error("Error reading ABI or bytecode file", e);
}
diff --git a/src/main/java/it/unipr/utils/EVMLiSAExecutor.java b/src/main/java/it/unipr/utils/EVMLiSAExecutor.java
new file mode 100644
index 000000000..6e74b6f4c
--- /dev/null
+++ b/src/main/java/it/unipr/utils/EVMLiSAExecutor.java
@@ -0,0 +1,227 @@
+package it.unipr.utils;
+
+import java.util.*;
+import java.util.concurrent.*;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public class EVMLiSAExecutor {
+ private static final Logger log = LogManager.getLogger(EVMLiSAExecutor.class);
+
+ private static int CORES = (Runtime.getRuntime().availableProcessors() > 1)
+ ? Runtime.getRuntime().availableProcessors() - 1
+ : 1;
+ private static ExecutorService _executor = Executors.newFixedThreadPool(CORES);
+
+ private static long tasksInQueue = 0;
+ private static long tasksExecuted = 0;
+ private static long tasksTimedOut = 0;
+
+ /**
+ * Submits a task for execution in the thread pool.
+ *
+ * @param task the task to be executed
+ *
+ * @return a Future representing the pending result of the task
+ */
+ public static Future> submit(Runnable task) {
+ return _executor.submit(new EVMLiSAExecutorTask(task));
+ }
+
+ /**
+ * Submits a list of tasks for execution in the thread pool.
+ *
+ * @param tasks the list of tasks to be executed
+ *
+ * @return a list of Future objects representing the pending results of the
+ * tasks
+ */
+ public static List> submitAll(List tasks) {
+ List> futures = new ArrayList<>();
+
+ for (Runnable task : tasks)
+ futures.add(submit(new EVMLiSAExecutorTask(task)));
+
+ return futures;
+ }
+
+ /**
+ * Executes a main task asynchronously and schedules additional tasks upon
+ * its completion.
+ *
+ * @param mainTask the main task to execute
+ * @param secondaryTasks additional tasks to execute after the main task
+ *
+ * @return a CompletableFuture that completes when all tasks are finished
+ */
+ public static CompletableFuture runAsync(Runnable mainTask, Runnable... secondaryTasks) {
+ return runAsync(mainTask, Set.of(secondaryTasks));
+ }
+
+ /**
+ * Executes a main task asynchronously and schedules additional tasks upon
+ * its completion.
+ *
+ * @param mainTask the main task to execute
+ * @param secondaryTasks additional tasks to execute after the main task
+ *
+ * @return a CompletableFuture that completes when all tasks are finished
+ */
+ public static CompletableFuture runAsync(Runnable mainTask, Set secondaryTasks) {
+ return CompletableFuture.runAsync(new EVMLiSAExecutorTask(mainTask), _executor)
+ .thenCompose(ignored -> {
+ List> checkerFutures = new ArrayList<>();
+ for (Runnable secondaryTask : secondaryTasks)
+ checkerFutures
+ .add(CompletableFuture.runAsync(new EVMLiSAExecutorTask(secondaryTask), _executor));
+ return CompletableFuture.allOf(checkerFutures.toArray(new CompletableFuture[0]));
+ });
+ }
+
+ /**
+ * Waits for the completion of all provided CompletableFutures.
+ *
+ * @param futures the list of CompletableFutures to wait for
+ */
+ public static void awaitCompletionCompletableFutures(List> futures) {
+ CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
+ }
+
+ /**
+ * Waits for the completion of all provided {@code CompletableFuture},
+ * applying a timeout to each future.
+ *
+ * @param futures the list of {@code CompletableFuture} instances to
+ * await
+ * @param timeout the maximum time to wait for each future to complete
+ * @param unit the time unit of the {@code timeout} parameter
+ */
+ public static void awaitCompletionCompletableFutures(List> futures, long timeout,
+ TimeUnit unit) {
+ CompletableFuture.allOf(futures.stream()
+ .map(f -> f.orTimeout(timeout, unit)
+ .exceptionally(ex -> {
+ log.warn("Future timed out after {} {}. Skipping this task.", timeout, unit);
+
+ synchronized (EVMLiSAExecutor.class) {
+ ++tasksTimedOut;
+ }
+
+ return null;
+ }))
+ .toArray(CompletableFuture[]::new)).join();
+ }
+
+ /**
+ * Waits for the completion of all provided {@code Future} objects.
+ *
+ * @param futures the list of {@code Future} objects to wait for
+ */
+ public static void awaitCompletionFutures(List> futures) {
+ for (Future> future : futures) {
+ try {
+ future.get();
+ } catch (ExecutionException e) {
+ System.err.println(JSONManager.throwNewError("Error during task execution: " + e.getMessage()));
+ System.exit(1);
+ } catch (InterruptedException ie) {
+ System.err.println(JSONManager.throwNewError("Interrupted during task execution: " + ie.getMessage()));
+ System.exit(1);
+ }
+ }
+ }
+
+ /**
+ * Waits for the completion of all provided futures with a timeout for each.
+ * If a future does not complete within the specified timeout, it is
+ * skipped.
+ *
+ * @param futures the list of futures to wait for
+ * @param timeout the maximum time to wait for each future
+ * @param unit the time unit of the timeout argument
+ */
+ public static void awaitCompletionFutures(List> futures, long timeout, TimeUnit unit) {
+ for (Future> future : futures) {
+ try {
+ future.get(timeout, unit);
+ } catch (TimeoutException te) {
+ log.warn("Future timed out after {} {}. Skipping this task.", timeout, unit);
+ } catch (ExecutionException e) {
+ System.err.println(JSONManager.throwNewError("Error during task execution: " + e.getMessage()));
+ System.exit(1);
+ } catch (InterruptedException ie) {
+ System.err.println(JSONManager.throwNewError("Interrupted during task execution: " + ie.getMessage()));
+ System.exit(1);
+ }
+ }
+ }
+
+ /**
+ * Shuts down the executor service, preventing new tasks from being
+ * submitted.
+ */
+ public static void shutdown() {
+ log.debug("Tasks submitted: {}.", tasksExecuted);
+ log.debug("Tasks timed out: {}.", tasksTimedOut);
+ log.info("Shutting down executor.");
+ _executor.shutdown();
+ }
+
+ public static void setCoresAvailable(int cores) {
+ if (cores > Runtime.getRuntime().availableProcessors())
+ cores = Runtime.getRuntime().availableProcessors() - 1;
+
+ if (CORES == cores)
+ return;
+
+ shutdown();
+ CORES = Math.max(cores, 1);
+ _executor = Executors.newFixedThreadPool(CORES);
+ }
+
+ public static int getCoresAvailable() {
+ return CORES;
+ }
+
+ /**
+ * A private static class that wraps a {@link Runnable} task and manages
+ * task execution tracking. It increments the count of tasks in the queue
+ * upon creation and decrements it upon completion.
+ */
+ private static class EVMLiSAExecutorTask implements Runnable {
+ private final Runnable task;
+
+ /**
+ * Creates a new {@code MyTask} instance wrapping the given task. It
+ * increments the count of tasks in the queue upon instantiation.
+ *
+ * @param task the {@code Runnable} task to be executed.
+ */
+ public EVMLiSAExecutorTask(Runnable task) {
+ this.task = task;
+
+ synchronized (EVMLiSAExecutor.class) {
+ ++tasksInQueue;
+ ++tasksExecuted;
+ }
+ }
+
+ /**
+ * Executes the wrapped task and updates the task queue count.
+ */
+ @Override
+ public void run() {
+ task.run();
+
+ synchronized (EVMLiSAExecutor.class) {
+ --tasksInQueue;
+ }
+
+ if (tasksInQueue % 10 == 0 || tasksInQueue < 100)
+ log.debug("{} tasks in progress, {} tasks in queue to be analyzed.",
+ ((tasksInQueue - CORES) > 0 ? CORES : tasksInQueue),
+ ((tasksInQueue - CORES) > 0 ? (tasksInQueue - CORES) : 0));
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/it/unipr/utils/JSONManager.java b/src/main/java/it/unipr/utils/JSONManager.java
index 06e2013a9..56bbb26e7 100644
--- a/src/main/java/it/unipr/utils/JSONManager.java
+++ b/src/main/java/it/unipr/utils/JSONManager.java
@@ -107,6 +107,15 @@ public static JSONObject aggregateSmartContractsToJson(List contr
* @return a JSON array representing the basic blocks
*/
public static JSONArray basicBlocksToJson(SmartContract contract) {
+ if (contract.getFunctionsSignature() == null) {
+ log.warn("Unable to generate CFG with Basic Blocks (contract.getFunctionsSignature() is null)");
+ return new JSONArray();
+ }
+ if (contract.getEventsSignature() == null) {
+ log.warn("Unable to generate CFG with Basic Blocks (contract.getEventsSignature() is null)");
+ return new JSONArray();
+ }
+
JSONArray blocksArray = new JSONArray();
for (BasicBlock block : contract.getBasicBlocks()) {
diff --git a/src/main/java/it/unipr/utils/LiSAConfigurationManager.java b/src/main/java/it/unipr/utils/LiSAConfigurationManager.java
index 44a497db8..0dac08fe7 100644
--- a/src/main/java/it/unipr/utils/LiSAConfigurationManager.java
+++ b/src/main/java/it/unipr/utils/LiSAConfigurationManager.java
@@ -9,6 +9,7 @@
import it.unive.lisa.conf.LiSAConfiguration;
import it.unive.lisa.interprocedural.ModularWorstCaseAnalysis;
import it.unive.lisa.interprocedural.callgraph.RTACallGraph;
+import java.nio.file.Path;
public class LiSAConfigurationManager {
@@ -47,10 +48,35 @@ public static LiSAConfiguration createConfiguration(SmartContract contract, bool
conf.jsonOutput = false;
if (dumpResults) {
- conf.analysisGraphs = LiSAConfiguration.GraphType.HTML;
+ conf.analysisGraphs = LiSAConfiguration.GraphType.DOT;
conf.jsonOutput = true;
}
return conf;
}
+
+ public static LiSAConfiguration createConfiguration(Path path, boolean dumpResults) {
+ LiSAConfiguration conf = new LiSAConfiguration();
+ conf.abstractState = new SimpleAbstractState<>(new MonolithicHeap(),
+ new EVMAbstractState(null),
+ new TypeEnvironment<>(new InferredTypes()));
+ conf.workdir = path.toString();
+ conf.interproceduralAnalysis = new ModularWorstCaseAnalysis<>();
+ conf.callGraph = new RTACallGraph();
+ conf.serializeResults = false;
+ conf.optimize = false;
+ conf.useWideningPoints = false;
+ conf.jsonOutput = false;
+
+ if (dumpResults) {
+ conf.analysisGraphs = LiSAConfiguration.GraphType.DOT;
+ conf.jsonOutput = true;
+ }
+
+ return conf;
+ }
+
+ public static LiSAConfiguration createConfiguration(Path path) {
+ return createConfiguration(path, false);
+ }
}
diff --git a/src/main/java/it/unipr/utils/MyCache.java b/src/main/java/it/unipr/utils/MyCache.java
index fba991a9b..54338b00a 100644
--- a/src/main/java/it/unipr/utils/MyCache.java
+++ b/src/main/java/it/unipr/utils/MyCache.java
@@ -2,12 +2,13 @@
import it.unipr.analysis.Number;
import it.unipr.analysis.StackElement;
-import it.unive.lisa.program.cfg.statement.Statement;
+import it.unipr.analysis.contract.Signature;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.apache.commons.collections4.map.LRUMap;
import org.apache.commons.lang3.tuple.Pair;
+import org.json.JSONArray;
/**
* Singleton class implementing a cache with an LRU (Least Recently Used)
@@ -17,16 +18,22 @@
*/
public class MyCache {
private static MyCache _instance = null;
+ private static long _timesUsed = 0;
+
private final LRUMap, StackElement> _map;
private final LRUMap _timeLostToGetStorage;
+ private final LRUMap _reachableFrom;
+
private final LRUMap> _reentrancyWarnings;
+
private final LRUMap> _txOriginWarnings;
- private final LRUMap _reachableFrom;
- private final LRUMap> _eventOrderWarnings;
- private final LRUMap> _uncheckedStateUpdateWarnings;
- private final LRUMap> _uncheckedExternalInfluenceWarnings;
- private final LRUMap> _timestampDependencyWarnings;
- public final LRUMap> _eventsExitPoints;
+ private final LRUMap> _possibleTxOriginWarnings;
+
+ private final LRUMap> _randomnessDependencyWarnings;
+ private final LRUMap> _possibleRandomnessDependencyWarnings;
+
+ private final LRUMap> _vulnerabilityPerFunction;
+ private final LRUMap> _mapEventsFunctions;
/**
* Retrieves the singleton instance of the cache.
@@ -40,23 +47,99 @@ public static MyCache getInstance() {
_instance = new MyCache();
}
}
+ ++_timesUsed;
return _instance;
}
+ /**
+ * Retrieves the number of times the cache has been used.
+ *
+ * @return the count of times the cache has been used as a long value
+ */
+ public static long getTimesUsed() {
+ return _timesUsed;
+ }
+
/**
* Private constructor to prevent instantiation.
*/
private MyCache() {
this._map = new LRUMap, StackElement>(500);
this._timeLostToGetStorage = new LRUMap(500);
- this._reentrancyWarnings = new LRUMap>(1000);
- this._txOriginWarnings = new LRUMap>(1000);
- this._reachableFrom = new LRUMap(2000);
- this._eventOrderWarnings = new LRUMap>(1000);
- this._uncheckedStateUpdateWarnings = new LRUMap>(1000);
- this._uncheckedExternalInfluenceWarnings = new LRUMap>(1000);
- this._eventsExitPoints = new LRUMap>(2000);
- this._timestampDependencyWarnings = new LRUMap>(1000);
+ this._reachableFrom = new LRUMap(5000);
+
+ this._reentrancyWarnings = new LRUMap>(5000);
+
+ this._txOriginWarnings = new LRUMap>(5000);
+ this._possibleTxOriginWarnings = new LRUMap>(5000);
+
+ this._randomnessDependencyWarnings = new LRUMap>(5000);
+ this._possibleRandomnessDependencyWarnings = new LRUMap>(5000);
+
+ this._vulnerabilityPerFunction = new LRUMap<>(10000);
+ this._mapEventsFunctions = new LRUMap<>(10000);
+ }
+
+ public void addMapEventsFunctions(Signature event, Signature function) {
+ synchronized (_mapEventsFunctions) {
+ _mapEventsFunctions
+ .computeIfAbsent(event, k -> Collections.synchronizedSet(new HashSet<>()))
+ .add(function);
+ }
+ }
+
+ public Set getMapEventsFunctions(Signature event) {
+ synchronized (_mapEventsFunctions) {
+ return (_mapEventsFunctions.get(event) != null)
+ ? _mapEventsFunctions.get(event)
+ : Set.of();
+ }
+ }
+
+ /**
+ * Records a vulnerability warning for a specific function identified by its
+ * key.
+ *
+ * This method ensures thread-safe access to the internal map, creating a
+ * new synchronized Set if none exists for the given key, and then adds the
+ * warning object to that set.
+ *
+ * @param key the identifier of the function (e.g., CFG hashcode or
+ * program counter)
+ * @param warning the vulnerability description or warning object to record
+ */
+ public void addVulnerabilityPerFunction(Integer key, Object warning) {
+ synchronized (_vulnerabilityPerFunction) {
+ _vulnerabilityPerFunction
+ .computeIfAbsent(key, k -> Collections.synchronizedSet(new HashSet<>()))
+ .add(warning);
+ }
+ }
+
+ /**
+ * Retrieves all recorded vulnerability warnings for a specific function.
+ *
+ * This method performs a thread-safe lookup in the internal map. If no
+ * warnings have been recorded for the given key, it returns an empty
+ * JSONArray; otherwise, it returns a JSONArray containing all stored
+ * warnings.
+ *
+ * @param key the identifier of the function whose warnings are requested
+ *
+ * @return a JSONArray of warning objects for the given function key, or an
+ * empty JSONArray if none are present
+ */
+ public JSONArray getVulnerabilityPerFunction(Integer key) {
+ synchronized (_vulnerabilityPerFunction) {
+ if (_vulnerabilityPerFunction.get(key) == null)
+ return new JSONArray();
+
+ JSONArray results = new JSONArray();
+ for (Object warning : _vulnerabilityPerFunction.get(key)) {
+ results.put(warning);
+ }
+ return results;
+ }
}
/**
@@ -77,7 +160,7 @@ public void put(Pair key, StackElement value)
*
* @param key the key, a {@link Pair} of {@link String} and
* {@link it.unipr.analysis.Number}.
- *
+ *
* @return the value associated with the key, or {@code null} if the key is
* not in the cache.
*/
@@ -151,7 +234,7 @@ public void addReentrancyWarning(Integer key, Object warning) {
*
* @param key the key identifying the smart contract or entity whose
* warnings are to be retrieved
- *
+ *
* @return the number of warnings associated with the key, or 0 if none
* exist
*/
@@ -161,157 +244,6 @@ public int getReentrancyWarnings(Integer key) {
}
}
- /**
- * Adds an event order warning for the specified key. If no warnings are
- * associated with the key, a new set is created and the warning is added to
- * it. This method is thread-safe.
- *
- * @param key the key identifying the smart contract or entity for which
- * the warning applies
- * @param warning the warning object to be added
- */
- public void addEventOrderWarning(Integer key, Object warning) {
- synchronized (_eventOrderWarnings) {
- _eventOrderWarnings
- .computeIfAbsent(key, k -> Collections.synchronizedSet(new HashSet<>()))
- .add(warning);
- }
- }
-
- /**
- * Retrieves the number of event order warnings associated with the
- * specified key. If no warnings are associated with the key, the method
- * returns 0. This method is thread-safe.
- *
- * @param key the key identifying the smart contract or entity whose
- * warnings are to be retrieved
- *
- * @return the number of warnings associated with the key, or 0 if none
- * exist
- */
- public int getEventOrderWarnings(Integer key) {
- synchronized (_eventOrderWarnings) {
- return (_eventOrderWarnings.get(key) != null) ? _eventOrderWarnings.get(key).size() : 0;
- }
- }
-
- /**
- * Adds an unchecked state update warning for the specified key. If no
- * warnings are associated with the key, a new set is created and the
- * warning is added to it. This method is thread-safe.
- *
- * @param key the key identifying the smart contract or entity for which
- * the warning applies
- * @param warning the warning object to be added
- */
- public void addUncheckedStateUpdateWarning(Integer key, Object warning) {
- synchronized (_uncheckedStateUpdateWarnings) {
- _uncheckedStateUpdateWarnings
- .computeIfAbsent(key, k -> Collections.synchronizedSet(new HashSet<>()))
- .add(warning);
- }
- }
-
- /**
- * Retrieves the number of unchecked state update warnings associated with
- * the specified key. If no warnings are associated with the key, the method
- * returns 0. This method is thread-safe.
- *
- * @param key the key identifying the smart contract or entity whose
- * warnings are to be retrieved
- *
- * @return the number of warnings associated with the key, or 0 if none
- * exist
- */
- public int getUncheckedStateUpdateWarnings(Integer key) {
- synchronized (_uncheckedStateUpdateWarnings) {
- return (_uncheckedStateUpdateWarnings.get(key) != null) ? _uncheckedStateUpdateWarnings.get(key).size() : 0;
- }
- }
-
- /**
- * Adds an unchecked external influence warning for the specified key. If no
- * warnings are associated with the key, a new set is created and the
- * warning is added to it. This method is thread-safe.
- *
- * @param key the key identifying the smart contract or entity for which
- * the warning applies
- * @param warning the warning object to be added
- */
- public void addUncheckedExternalInfluenceWarning(Integer key, Object warning) {
- synchronized (_uncheckedExternalInfluenceWarnings) {
- _uncheckedExternalInfluenceWarnings
- .computeIfAbsent(key, k -> Collections.synchronizedSet(new HashSet<>()))
- .add(warning);
- }
- }
-
- /**
- * Retrieves the number of unchecked external influence warnings associated
- * with the specified key. If no warnings are associated with the key, the
- * method returns 0. This method is thread-safe.
- *
- * @param key the key identifying the smart contract or entity whose
- * warnings are to be retrieved
- *
- * @return the number of warnings associated with the key, or 0 if none
- * exist
- */
- public int getUncheckedExternalInfluenceWarnings(Integer key) {
- synchronized (_uncheckedExternalInfluenceWarnings) {
- return (_uncheckedExternalInfluenceWarnings.get(key) != null)
- ? _uncheckedExternalInfluenceWarnings.get(key).size()
- : 0;
- }
- }
-
- /**
- * Adds an event exit point associated with a given statement. If the
- * statement does not already have an associated set of event exit points, a
- * new synchronized HashSet is created. This method is thread-safe.
- *
- * @param key The statement representing the exit point.
- * @param signature The event signature to associate with the statement.
- */
- public void addEventExitPoint(Statement key, String signature) {
- synchronized (_eventsExitPoints) {
- _eventsExitPoints
- .computeIfAbsent(key, k -> Collections.synchronizedSet(new HashSet<>()))
- .add(signature);
- }
- }
-
- /**
- * Retrieves the set of event exit points associated with a given statement.
- * If the statement has no associated exit points, an empty set is returned.
- * This method is thread-safe.
- *
- * @param key The statement whose event exit points are being queried.
- *
- * @return A set of event signatures associated with the given statement, or
- * an empty set if none exist.
- */
- public Set getEventExitPoints(Statement key) {
- synchronized (_eventsExitPoints) {
- return _eventsExitPoints.get(key) == null ? new HashSet<>() : _eventsExitPoints.get(key);
- }
- }
-
- /**
- * Checks whether there are event exit points associated with a given
- * statement.
- *
- * @param key The statement to check.
- *
- * @return {@code true} if the statement has associated event exit points,
- * {@code false} otherwise.
- */
- public boolean containsEventExitPoints(Statement key) {
- synchronized (_eventsExitPoints) {
- return _eventsExitPoints.get(key) != null;
- }
- }
-
/**
* Adds or updates the reachability status of a specific key in the
* reachability map.
@@ -330,10 +262,10 @@ public void addReachableFrom(String key, boolean isReachableFrom) {
* Checks if a specific key is marked as reachable in the reachability map.
*
* @param key the key representing the element to check
- *
+ *
* @return {@code true} if the key is marked as reachable, {@code false}
* otherwise
- *
+ *
* @throws NullPointerException if the key does not exist in the map
*/
public boolean isReachableFrom(String key) {
@@ -346,7 +278,7 @@ public boolean isReachableFrom(String key) {
* Checks if a specific key exists in the reachability map.
*
* @param key the key representing the element to check
- *
+ *
* @return {@code true} if the key exists in the map, {@code false}
* otherwise
*/
@@ -380,7 +312,7 @@ public void addTxOriginWarning(Integer key, Object warning) {
*
* @param key the key identifying the smart contract or entity whose
* warnings are to be retrieved
- *
+ *
* @return the number of warnings associated with the key, or 0 if none
* exist
*/
@@ -391,7 +323,7 @@ public int getTxOriginWarnings(Integer key) {
}
/**
- * Adds a timestamp dependency warning for the specified key. If no warnings
+ * Adds a possible tx origin warning for the specified key. If no warnings
* are associated with the key, a new set is created and the warning is
* added to it. This method is thread-safe.
*
@@ -399,28 +331,101 @@ public int getTxOriginWarnings(Integer key) {
* the warning applies
* @param warning the warning object to be added
*/
- public void addTimestampDependencyWarning(Integer key, Object warning) {
- synchronized (_timestampDependencyWarnings) {
- _timestampDependencyWarnings
+ public void addPossibleTxOriginWarning(Integer key, Object warning) {
+ synchronized (_possibleTxOriginWarnings) {
+ _possibleTxOriginWarnings
.computeIfAbsent(key, k -> Collections.synchronizedSet(new HashSet<>()))
.add(warning);
}
}
/**
- * Retrieves the number of timestamp dependency warnings associated with the
+ * Retrieves the number of possible tx origin warnings associated with the
* specified key. If no warnings are associated with the key, the method
* returns 0. This method is thread-safe.
*
* @param key the key identifying the smart contract or entity whose
* warnings are to be retrieved
+ *
+ * @return the number of possible warnings associated with the key, or 0 if
+ * none exist
+ */
+ public int getPossibleTxOriginWarnings(Integer key) {
+ synchronized (_possibleTxOriginWarnings) {
+ return (_possibleTxOriginWarnings.get(key) != null) ? _possibleTxOriginWarnings.get(key).size() : 0;
+ }
+ }
+
+ /**
+ * Adds a randomness dependency warning for the specified key. If no
+ * warnings are associated with the key, a new set is created and the
+ * warning is added to it. This method is thread-safe.
+ *
+ * @param key the key identifying the smart contract or entity for which
+ * the warning applies
+ * @param warning the warning object to be added
+ */
+ public void addRandomnessDependencyWarning(Integer key, Object warning) {
+ synchronized (_randomnessDependencyWarnings) {
+ _randomnessDependencyWarnings
+ .computeIfAbsent(key, k -> Collections.synchronizedSet(new HashSet<>()))
+ .add(warning);
+ }
+ }
+
+ /**
+ * Retrieves the number of randomness dependency warnings associated with
+ * the specified key. If no warnings are associated with the key, the method
+ * returns 0. This method is thread-safe.
*
+ * @param key the key identifying the smart contract or entity whose
+ * warnings are to be retrieved
+ *
* @return the number of warnings associated with the key, or 0 if none
* exist
*/
- public int getTimestampDependencyWarnings(Integer key) {
- synchronized (_timestampDependencyWarnings) {
- return (_timestampDependencyWarnings.get(key) != null) ? _timestampDependencyWarnings.get(key).size() : 0;
+ public int getRandomnessDependencyWarnings(Integer key) {
+ synchronized (_randomnessDependencyWarnings) {
+ return (_randomnessDependencyWarnings.get(key) != null) ? _randomnessDependencyWarnings.get(key).size() : 0;
+ }
+ }
+
+ /**
+ * Adds a warning indicating a possible randomness dependency to the
+ * internal collection. The method ensures thread safety during modification
+ * of warnings collection.
+ *
+ * @param key an integer key representing the category or type of the
+ * warning
+ * @param warning an object representing the warning message or detail to be
+ * added
+ */
+ public void addPossibleRandomnessDependencyWarning(Integer key, Object warning) {
+ synchronized (_possibleRandomnessDependencyWarnings) {
+ _possibleRandomnessDependencyWarnings
+ .computeIfAbsent(key, k -> Collections.synchronizedSet(new HashSet<>()))
+ .add(warning);
+ }
+ }
+
+ /**
+ * Retrieves the number of possible randomness dependency warnings
+ * associated with a given key. The method checks the internal map for the
+ * specified key and returns the size of the list of warnings if the key
+ * exists; otherwise, it returns zero.
+ *
+ * @param key the key used to identify the list of randomness dependency
+ * warnings in the internal map. It can be null.
+ *
+ * @return the number of randomness dependency warnings associated with the
+ * specified key. Returns 0 if the key is not present or there
+ * are no warnings associated with it.
+ */
+ public int getPossibleRandomnessDependencyWarnings(Integer key) {
+ synchronized (_possibleRandomnessDependencyWarnings) {
+ return (_possibleRandomnessDependencyWarnings.get(key) != null)
+ ? _possibleRandomnessDependencyWarnings.get(key).size()
+ : 0;
}
}
}
\ No newline at end of file
diff --git a/src/main/java/it/unipr/utils/VulnerabilitiesObject.java b/src/main/java/it/unipr/utils/VulnerabilitiesObject.java
index ff6d0ffdd..c675ef633 100644
--- a/src/main/java/it/unipr/utils/VulnerabilitiesObject.java
+++ b/src/main/java/it/unipr/utils/VulnerabilitiesObject.java
@@ -1,80 +1,56 @@
package it.unipr.utils;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
+import it.unipr.cfg.EVMCFG;
import org.json.JSONObject;
/**
- * Represents an object that stores information about potential smart contract
- * vulnerabilities, including reentrancy, timestamp dependency, and tx. origin
- * usage.
+ * The {@code VulnerabilitiesObject} class represents a data model to hold
+ * various security vulnerability scores within a system. It provides methods to
+ * manage and build these scores, including utilities for JSON representation.
*/
public class VulnerabilitiesObject {
- private static final Logger log = LogManager.getLogger(VulnerabilitiesObject.class);
-
private int reentrancy;
- private int timestamp;
+
+ private int randomness;
+ private int possibleRandomness;
+
private int txOrigin;
+ private int possibleTxOrigin;
+
private JSONObject json;
- /**
- * Creates a new {@code VulnerabilitiesObject} with default values (-1) for
- * all vulnerabilities.
- */
private VulnerabilitiesObject() {
- this.reentrancy = -1;
- this.timestamp = -1;
- this.txOrigin = -1;
+ this.reentrancy = 0;
+
+ this.randomness = 0;
+ this.possibleRandomness = 0;
+
+ this.txOrigin = 0;
+ this.possibleTxOrigin = 0;
+
this.json = new JSONObject();
}
- /**
- * Creates a new {@code VulnerabilitiesObject} with specified values.
- *
- * @param reentrancy the reentrancy vulnerability score
- * @param timestamp the timestamp dependency vulnerability score
- * @param txOrigin the tx.origin vulnerability score
- * @param json the JSON representation of the object
- */
- private VulnerabilitiesObject(int reentrancy, int timestamp, int txOrigin, JSONObject json) {
+ private VulnerabilitiesObject(int reentrancy,
+ int randomness, int possibleRandomness,
+ int txOrigin, int possibleTxOrigin,
+ JSONObject json) {
+
this.reentrancy = reentrancy;
- this.timestamp = timestamp;
- this.txOrigin = txOrigin;
- this.json = json;
- if (reentrancy != -1)
- this.json.put("reentrancy", this.reentrancy);
- if (timestamp != -1)
- this.json.put("timestamp_dependency", this.timestamp);
- if (txOrigin != -1)
- this.json.put("tx_origin", this.txOrigin);
- }
+ this.randomness = randomness;
+ this.possibleRandomness = possibleRandomness;
- /**
- * Returns the reentrancy vulnerability score.
- *
- * @return the reentrancy score
- */
- public int getReentrancy() {
- return reentrancy;
- }
+ this.txOrigin = txOrigin;
+ this.possibleTxOrigin = possibleTxOrigin;
- /**
- * Returns the timestamp dependency vulnerability score.
- *
- * @return the timestamp dependency score
- */
- public int getTimestamp() {
- return timestamp;
- }
+ this.json = json;
- /**
- * Returns the tx .origin vulnerability score.
- *
- * @return the tx. origin score
- */
- public int getTxOrigin() {
- return txOrigin;
+ this.json.put("reentrancy", this.reentrancy);
+ this.json.put("randomness_dependency", this.randomness);
+ this.json.put("randomness_dependency_possible", this.possibleRandomness);
+ this.json.put("tx_origin", this.txOrigin);
+ this.json.put("tx_origin_possible", this.possibleTxOrigin);
}
/**
@@ -99,14 +75,26 @@ public VulnerabilitiesObject reentrancy(int reentrancy) {
}
/**
- * Sets the timestamp dependency vulnerability score.
+ * Sets the randomness dependency vulnerability score.
+ *
+ * @param randomness the randomness dependency score
+ *
+ * @return the updated {@code VulnerabilitiesObject} instance
+ */
+ public VulnerabilitiesObject randomness(int randomness) {
+ this.randomness = randomness;
+ return this;
+ }
+
+ /**
+ * Sets the possible randomness dependency vulnerability score.
*
- * @param timestamp the timestamp dependency score
+ * @param possibleRandomness the possible randomness dependency score
*
* @return the updated {@code VulnerabilitiesObject} instance
*/
- public VulnerabilitiesObject timestamp(int timestamp) {
- this.timestamp = timestamp;
+ public VulnerabilitiesObject possibleRandomness(int possibleRandomness) {
+ this.possibleRandomness = possibleRandomness;
return this;
}
@@ -122,13 +110,54 @@ public VulnerabilitiesObject txOrigin(int txOrigin) {
return this;
}
+ /**
+ * Sets the possible tx. origin vulnerability score.
+ *
+ * @param possibleTxOrigin the tx. origin score
+ *
+ * @return the updated {@code VulnerabilitiesObject} instance
+ */
+ public VulnerabilitiesObject possibleTxOrigin(int possibleTxOrigin) {
+ this.possibleTxOrigin = possibleTxOrigin;
+ return this;
+ }
+
+ /**
+ * Builds a {@link VulnerabilitiesObject} from the given EVM control-flow
+ * graph (CFG). This method retrieves various vulnerability warnings from
+ * the cache based on the CFG's hash code and compiles them into a
+ * {@link VulnerabilitiesObject}.
+ *
+ * @param cfg the EVM control-flow graph from which to extract vulnerability
+ * data
+ *
+ * @return a {@link VulnerabilitiesObject} containing detected
+ * vulnerabilities
+ */
+ public static VulnerabilitiesObject buildFromCFG(EVMCFG cfg) {
+ return VulnerabilitiesObject.newVulnerabilitiesObject()
+ .reentrancy(
+ MyCache.getInstance().getReentrancyWarnings(cfg.hashCode()))
+ .txOrigin(MyCache.getInstance().getTxOriginWarnings(cfg.hashCode()))
+ .possibleTxOrigin(MyCache.getInstance()
+ .getPossibleTxOriginWarnings(cfg.hashCode()))
+ .randomness(MyCache.getInstance()
+ .getRandomnessDependencyWarnings(cfg.hashCode()))
+ .possibleRandomness(MyCache.getInstance()
+ .getPossibleRandomnessDependencyWarnings(cfg.hashCode()))
+ .build();
+ }
+
/**
* Builds a new {@code VulnerabilitiesObject} with the specified values.
*
* @return a new {@code VulnerabilitiesObject} instance
*/
public VulnerabilitiesObject build() {
- return new VulnerabilitiesObject(reentrancy, timestamp, txOrigin, json);
+ return new VulnerabilitiesObject(reentrancy,
+ randomness, possibleRandomness,
+ txOrigin, possibleTxOrigin,
+ json);
}
/**
@@ -150,14 +179,4 @@ public String toString() {
return toJson().toString(4);
}
- /**
- * Logs the vulnerability scores of the given {@code VulnerabilitiesObject}.
- *
- * @param vulnerabilities the vulnerabilities object to log
- */
- public static void printVulnerabilities(VulnerabilitiesObject vulnerabilities) {
- log.info("Reentrancy: {}", vulnerabilities.getReentrancy());
- log.info("Timestamp dependency: {}", vulnerabilities.getTimestamp());
- log.info("Tx.Origin: {}", vulnerabilities.getTxOrigin());
- }
}
\ No newline at end of file
diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml
index 4ac48d0d6..151325058 100644
--- a/src/main/resources/log4j2.xml
+++ b/src/main/resources/log4j2.xml
@@ -2,7 +2,7 @@
-
+
@@ -13,4 +13,4 @@
-
\ No newline at end of file
+
diff --git a/src/test/java/it/unipr/analysis/cron/semantics/TimestampDependencyAbstractSemanticsTest.java b/src/test/java/it/unipr/analysis/cron/semantics/RandomnessDependencyAbstractSemanticsTest.java
similarity index 97%
rename from src/test/java/it/unipr/analysis/cron/semantics/TimestampDependencyAbstractSemanticsTest.java
rename to src/test/java/it/unipr/analysis/cron/semantics/RandomnessDependencyAbstractSemanticsTest.java
index c9eae4b12..ed629534e 100644
--- a/src/test/java/it/unipr/analysis/cron/semantics/TimestampDependencyAbstractSemanticsTest.java
+++ b/src/test/java/it/unipr/analysis/cron/semantics/RandomnessDependencyAbstractSemanticsTest.java
@@ -2,8 +2,8 @@
import it.unipr.analysis.cron.CronConfiguration;
import it.unipr.analysis.cron.EVMBytecodeAnalysisExecutor;
+import it.unipr.analysis.taint.RandomnessDependencyAbstractDomain;
import it.unipr.analysis.taint.TaintAbstractDomain;
-import it.unipr.analysis.taint.TimestampDependencyAbstractDomain;
import it.unive.lisa.AnalysisSetupException;
import it.unive.lisa.analysis.SimpleAbstractState;
import it.unive.lisa.analysis.heap.MonolithicHeap;
@@ -20,7 +20,7 @@
* as if, if-else, while, etc. Operations with orphan jumps, marked as NPBJ (No
* Push Before Jump), are also tested in specific test cases.
*/
-public class TimestampDependencyAbstractSemanticsTest extends EVMBytecodeAnalysisExecutor {
+public class RandomnessDependencyAbstractSemanticsTest extends EVMBytecodeAnalysisExecutor {
private static final boolean GENERATE_CFG_FOR_ALL_TESTS = false;
/**
@@ -52,7 +52,7 @@ private static CronConfiguration createConfiguration(String testDir, String subD
}
conf.abstractState = new SimpleAbstractState>(
- new MonolithicHeap(), new TimestampDependencyAbstractDomain(),
+ new MonolithicHeap(), new RandomnessDependencyAbstractDomain(),
new TypeEnvironment<>(new InferredTypes()));
conf.callGraph = new RTACallGraph();
conf.interproceduralAnalysis = new ModularWorstCaseAnalysis<>();
diff --git a/src/test/resources/log4j2.xml b/src/test/resources/log4j2.xml
index e13c8e911..5f31aa362 100644
--- a/src/test/resources/log4j2.xml
+++ b/src/test/resources/log4j2.xml
@@ -2,7 +2,7 @@
-
+