package sqlancer;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import sqlancer.common.query.Query;
import sqlancer.common.query.SQLQueryAdapter;
import sqlancer.transformations.RemoveClausesOfSelect;
import sqlancer.transformations.RemoveColumnsOfSelect;
import sqlancer.transformations.RemoveElementsOfExpressionList;
import sqlancer.transformations.RemoveRowsOfInsert;
import sqlancer.transformations.RemoveUnions;
import sqlancer.transformations.RoundDoubleConstant;
import sqlancer.transformations.SimplifyConstant;
import sqlancer.transformations.SimplifyExpressions;
import sqlancer.transformations.Transformation;
public class ASTBasedReducer, O extends DBMSSpecificOptions>, C extends SQLancerDBConnection>
implements Reducer {
private final DatabaseProvider provider;
@SuppressWarnings("unused")
private G state;
private G newGlobalState;
private Reproducer reproducer;
private List> reducedStatements;
// statement after reduction.
public ASTBasedReducer(DatabaseProvider provider) {
this.provider = provider;
}
@SuppressWarnings("unchecked")
private void updateStatements(String queryString, int index) {
boolean couldAffectSchema = queryString.contains("CREATE TABLE") || queryString.contains("EXPLAIN");
reducedStatements.set(index, (Query) new SQLQueryAdapter(queryString, couldAffectSchema));
}
@SuppressWarnings("unchecked")
@Override
public void reduce(G state, Reproducer reproducer, G newGlobalState) throws Exception {
this.state = state;
this.newGlobalState = newGlobalState;
this.reproducer = reproducer;
long maxReduceTime = state.getOptions().getMaxStatementReduceTime();
long maxReduceSteps = state.getOptions().getMaxStatementReduceSteps();
List> initialBugInducingStatements = state.getState().getStatements();
newGlobalState.getState().setStatements(new ArrayList<>(initialBugInducingStatements));
List transformations = new ArrayList<>();
transformations.add(new RemoveUnions());
transformations.add(new RemoveClausesOfSelect());
transformations.add(new RemoveRowsOfInsert());
transformations.add(new RemoveColumnsOfSelect());
transformations.add(new RemoveElementsOfExpressionList());
transformations.add(new SimplifyExpressions());
transformations.add(new SimplifyConstant());
transformations.add(new RoundDoubleConstant());
Transformation.setBugJudgement(() -> {
try {
return this.bugStillTriggers();
} catch (Exception ignored) {
}
return false;
});
boolean observeChange;
reducedStatements = new ArrayList<>();
for (Query> query : initialBugInducingStatements) {
reducedStatements.add((Query) query);
}
Instant startTime = Instant.now();
reduceProcess: do {
observeChange = false;
for (Transformation t : transformations) {
for (int i = 0; i < reducedStatements.size(); i++) {
Instant currentTime = Instant.now();
if (maxReduceTime != MainOptions.NO_REDUCE_LIMIT
&& Duration.between(startTime, currentTime).getSeconds() >= maxReduceTime) {
break reduceProcess;
}
if (maxReduceSteps != MainOptions.NO_REDUCE_LIMIT
&& Transformation.getReduceSteps() >= maxReduceSteps) {
break reduceProcess;
}
Query> query = reducedStatements.get(i);
boolean initFlag = t.init(query.getQueryString());
int index = i;
t.setStatementChangedCallBack((statementString) -> {
updateStatements(statementString, index);
});
if (!initFlag) {
newGlobalState.getLogger()
.logReducer("warning: failed parsing the statement at transformer : " + t);
continue;
}
t.apply();
observeChange |= t.changed();
}
}
} while (observeChange);
newGlobalState.getState().setStatements(new ArrayList<>(reducedStatements));
newGlobalState.getLogger().logReduced(newGlobalState.getState());
}
public boolean bugStillTriggers() throws Exception {
try (C con2 = provider.createDatabase(newGlobalState)) {
newGlobalState.setConnection(con2);
List> candidateStatements = new ArrayList<>(reducedStatements);
newGlobalState.getState().setStatements(new ArrayList<>(candidateStatements));
for (Query s : candidateStatements) {
try {
s.execute(newGlobalState);
} catch (Throwable ignoredException) {
// ignore
}
}
try {
if (reproducer.bugStillTriggers(newGlobalState)) {
newGlobalState.getLogger().logReduced(newGlobalState.getState());
return true;
}
} catch (Throwable ignoredException) {
}
}
return false;
}
}