package sqlancer;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import sqlancer.common.query.Query;
public class StatementReducer, O extends DBMSSpecificOptions>, C extends SQLancerDBConnection>
implements Reducer {
private final DatabaseProvider provider;
private boolean observedChange;
private int partitionNum;
private long currentReduceSteps;
private long currentReduceTime;
private long maxReduceSteps;
private long maxReduceTime;
Instant timeOfReductionBegins;
public StatementReducer(DatabaseProvider provider) {
this.provider = provider;
}
private boolean hasNotReachedLimit(long curr, long limit) {
if (limit == MainOptions.NO_REDUCE_LIMIT) {
return true;
}
return curr < limit;
}
@SuppressWarnings("unchecked")
@Override
public void reduce(G state, Reproducer reproducer, G newGlobalState) throws Exception {
maxReduceTime = state.getOptions().getMaxStatementReduceTime();
maxReduceSteps = state.getOptions().getMaxStatementReduceSteps();
List> knownToReproduceBugStatements = new ArrayList<>();
for (Query> stat : state.getState().getStatements()) {
knownToReproduceBugStatements.add((Query) stat);
}
// System.out.println("Starting query:");
// Main.StateLogger logger = newGlobalState.getLogger();
// printQueries(knownToReproduceBugStatements);
// System.out.println();
if (knownToReproduceBugStatements.size() <= 1) {
return;
}
timeOfReductionBegins = Instant.now();
currentReduceSteps = 0;
currentReduceTime = 0;
partitionNum = 2;
while (knownToReproduceBugStatements.size() >= 2 && hasNotReachedLimit(currentReduceSteps, maxReduceSteps)
&& hasNotReachedLimit(currentReduceTime, maxReduceTime)) {
observedChange = false;
knownToReproduceBugStatements = tryReduction(state, reproducer, newGlobalState,
knownToReproduceBugStatements);
if (!observedChange) {
if (partitionNum == knownToReproduceBugStatements.size()) {
break;
}
// increase the search granularity
partitionNum = Math.min(partitionNum * 2, knownToReproduceBugStatements.size());
}
}
// System.out.println("Reduced query:");
// printQueries(knownToReproduceBugStatements);
newGlobalState.getState().setStatements(new ArrayList<>(knownToReproduceBugStatements));
newGlobalState.getLogger().logReduced(newGlobalState.getState());
}
private List> tryReduction(G state, // NOPMD
Reproducer reproducer, G newGlobalState, List> knownToReproduceBugStatements) throws Exception {
List> statements = knownToReproduceBugStatements;
int start = 0;
int subLength = statements.size() / partitionNum;
while (start < statements.size()) {
// newStatements = candidate[:start] + candidate[start+subLength:]
// in other word, remove [start, start+subLength) from candidates
try (C con2 = provider.createDatabase(newGlobalState)) {
newGlobalState.setConnection(con2);
List> candidateStatements = new ArrayList<>(statements);
int endPoint = Math.min(start + subLength, candidateStatements.size());
candidateStatements.subList(start, endPoint).clear();
newGlobalState.getState().setStatements(new ArrayList<>(candidateStatements));
for (Query s : candidateStatements) {
try {
s.execute(newGlobalState);
} catch (Throwable ignoredException) {
// ignore
}
}
try {
if (reproducer.bugStillTriggers(newGlobalState)) {
observedChange = true;
statements = candidateStatements;
partitionNum = Math.max(partitionNum - 1, 2);
// reproducer.outputHook((SQLite3GlobalState) newGlobalState);
newGlobalState.getLogger().logReduced(newGlobalState.getState());
break;
}
} catch (Throwable ignoredException) {
}
} catch (Exception e) {
e.printStackTrace();
}
currentReduceSteps++;
Instant currentInstant = Instant.now();
currentReduceTime = Duration.between(timeOfReductionBegins, currentInstant).getSeconds();
if (!hasNotReachedLimit(currentReduceSteps, maxReduceSteps)
|| !hasNotReachedLimit(currentReduceTime, maxReduceTime)) {
return statements;
}
start = start + subLength;
}
return statements;
}
@SuppressWarnings("unused")
private void printQueries(List> statements) {
System.out.println("===============================");
for (Query> q : statements) {
System.out.println(q.getLogString());
}
System.out.println("===============================");
}
}