-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathreduce.py
executable file
·179 lines (144 loc) · 7.22 KB
/
reduce.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
import os
import subprocess
# Function to run the test command and check for bug presence
def run_test(cmd, bug_output):
"""
Executes the provided command to run the PHP test and checks
if the expected bug output or any sanitizer error appears.
"""
# Run the command and capture the output
try:
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, encoding='iso-8859-1', timeout=10)
except:
return False
# Check if the bug output or any sanitizer errors are in the stdout/stderr
if not (bug_output in result.stdout or bug_output in result.stderr) and \
("LeakSanitizer" not in result.stdout and "LeakSanitizer" not in result.stderr):
# If another sanitizer message shows up, print the error
if "Sanitizer" in result.stdout or "Sanitizer" in result.stderr:
print("Other error messages found:")
print(result.stdout)
print(result.stderr)
# Uncomment below if you want to pause for input when this happens
# input()
# Return True if the bug output is found in the test results
return bug_output in result.stdout or bug_output in result.stderr
# Function to minimize the test case by removing lines
def minimize_testcase(lines, bug_output, testpath, reproduce_cmd):
print("reducing .. it may cost some times")
"""
Minimizes the test case by iteratively removing lines and checking
if the bug still reproduces. Uses a stepwise approach for efficiency.
"""
n = len(lines)
step = max(n // 2, 1) # Start with removing half of the lines at a time
init_step = step
# Reduce the number of lines step by step
while step > 0:
print(f"Current step: {step}")
# Try removing 'step' lines at a time
for i in range(0, n, step):
temp_lines = lines[:i] + lines[i+step:]
with open(testpath, "w") as f:
f.write("\n".join(temp_lines))
# If the bug reproduces, accept this as the minimized version
if run_test(reproduce_cmd, bug_output) or run_test(reproduce_cmd, bug_output) or run_test(reproduce_cmd, bug_output):
lines = temp_lines
n = len(lines)
break
else:
step //= 2 # If no further reduction is found, reduce step size
return lines, init_step
# Function for further minimizing by removing multiple lines at a time
def further_minimize_testcase(lines, bug_output, testpath, reproduce_cmd):
"""
Further minimizes the test case by removing 2 to 5 lines at a time
and checking if the bug still reproduces.
"""
n = len(lines)
# Try removing 2 to 5 lines at a time
for count in range(2, 6):
# print(f"Trying to remove {count} lines at a time.")
# Try removing 'count' lines from each part of the test case
for i in range(n - count + 1):
temp_lines = lines[:i] + lines[i+count:]
with open(testpath, "w") as f:
f.write("\n".join(temp_lines))
# If the bug reproduces, accept this as the minimized version
if run_test(reproduce_cmd, bug_output) or run_test(reproduce_cmd, bug_output) or run_test(reproduce_cmd, bug_output):
lines = temp_lines
n = len(lines)
break
return lines
def reduce_php(testpath, phppath, config, bug_output):
reproduce_cmd = f'{phppath} {config} {testpath}'
# Initial test to verify if the reproduce command triggers the bug
if not run_test(reproduce_cmd, bug_output) and not run_test(reproduce_cmd, bug_output) and not run_test(reproduce_cmd, bug_output):
return "bug not reproduced when reducing", "bug not reproduced when reducing"
else:
while True:
# Read the original test file lines
with open(testpath, "r") as f:
lines = f.readlines()
# Strip any extra whitespace or newlines
lines = [line.strip() for line in lines]
# Begin minimizing the test case by removing lines
minimized_lines, init_step = minimize_testcase(lines, bug_output, testpath, reproduce_cmd)
# Further minimize by removing multiple lines at once
further_minimized_lines = further_minimize_testcase(minimized_lines, bug_output, testpath, reproduce_cmd)
# Restore the original test case in the file
with open(testpath, "w") as f:
f.write("\n".join(further_minimized_lines))
n = len(further_minimized_lines)
step = max(n // 2, 1)
if step==init_step:
print("reducing php finished")
break
reducedphp = "\n".join(further_minimized_lines)
# Initialize reduced_config with the full configuration
reduced_config = config
while True:
# Split the configuration into individual options
test_config = reduced_config.split(' -d ')
# Remove any empty strings resulting from the split
test_config = [c for c in test_config if c != '']
# Store the length to check for changes after iteration
before_reduced_config_len = len(reduced_config)
# Flag to check if a shorter configuration is found
found_shorter_config = False
# Iterate over a copy of the list to avoid modifying it during iteration
for i in range(len(test_config)):
# Create a new configuration without the current option
test_config_copy = test_config[:i] + test_config[i+1:]
# Reconstruct the configuration string
if test_config_copy:
configstr = ' -d ' + ' -d '.join(test_config_copy)
else:
configstr = ''
# Build the command to test
test_cmd = f'{phppath} {configstr} {testpath}'
# Run the test to see if the bug still occurs
if run_test(test_cmd, bug_output) or run_test(test_cmd, bug_output) or run_test(test_cmd, bug_output):
# Update reduced_config if the bug still occurs
reduced_config = configstr
found_shorter_config = True
# Break to restart the while loop with the new reduced_config
break
# If no shorter configuration is found, exit the loop
if not found_shorter_config:
break
return reducedphp, reduced_config
if __name__ == "__main__":
# Define the path to the test PHP file, you need to move the php to the tmp
# best to also copy all dependencies to /tmp for reproduce
testpath = "/tmp/test.php"
# default php path
phppath = "/home/phpfuzz/WorkSpace/flowfusion/php-src/sapi/cli/php"
# Configuration options for the PHP test run
config = '-d "zend_extension=/home/phpfuzz/WorkSpace/flowfusion/php-src/modules/opcache.so" -d "opcache.enable=1" -d "opcache.enable_cli=1" -d "opcache.jit=1254"'
# The expected bug output that we are trying to reproduce
# if sanitizers' alerts
bug_output = 'Sanitizer'
# if assertion failure
bug_output = 'core dumped'
print(reduce_php(testpath, phppath, config, bug_output))