/*
* Copyright (c) 2011, IETR/INSA of Rennes
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of the IETR/INSA of Rennes nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
* WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
package net.sf.orcc.tools.classifier.smt;
import static net.sf.orcc.OrccActivator.getDefault;
import static net.sf.orcc.preferences.PreferenceConstants.P_SOLVER;
import static net.sf.orcc.preferences.PreferenceConstants.P_SOLVER_OPTIONS;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.Reader;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.sf.orcc.OrccRuntimeException;
import net.sf.orcc.df.Action;
import net.sf.orcc.df.Actor;
import net.sf.orcc.df.Pattern;
import net.sf.orcc.df.Port;
import net.sf.orcc.util.FilesManager;
import net.sf.orcc.util.OrccLogger;
import net.sf.orcc.util.OrccUtil;
import net.sf.orcc.util.sexp.SExp;
import net.sf.orcc.util.sexp.SExpList;
import net.sf.orcc.util.sexp.SExpParser;
import net.sf.orcc.util.sexp.SExpSymbol;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
/**
* This class defines an interface to an SMT solver compatible with SMT-LIB v2.
*
* @author Matthieu Wipliez
*
*/
public class SmtSolver {
static boolean hasFailed;
private class SmtSolverOutputProcessor implements Runnable {
private Reader reader;
public SmtSolverOutputProcessor(InputStream in) {
reader = new InputStreamReader(in);
}
/**
* Parse the assertion encoded as the given list s-expression.
*
* @param list
* a list s-expression
*/
private Object getExpression(SExp exp) {
if (exp.isSymbol()) {
SExpSymbol symbol = (SExpSymbol) exp;
if ("true".equals(symbol.getContents())) {
return true;
} else if ("false".equals(symbol.getContents())) {
return false;
}
} else if (exp.isList()) {
SExpList list = (SExpList) exp;
if (list.size() == 2) {
SExpSymbol bv = list.getSymbol(1);
// remove "bv" from the symbol
String contents = bv.getContents().substring(2);
BigInteger value = new BigInteger(contents, 16);
return value;
}
}
return null;
}
@Override
public void run() {
hasFailed = false;
StringBuilder builder = new StringBuilder();
try {
char[] cbuf = new char[8192];
int n = reader.read(cbuf);
while (n > 0) {
builder.append(cbuf, 0, n);
n = reader.read(cbuf);
}
} catch (IOException ioe) {
ioe.printStackTrace();
} finally {
try {
reader.close();
} catch (IOException e) {
}
}
if (builder.toString().contains("error")) {
OrccLogger.warnln("Solving of actor " + actor.getName() + ":");
hasFailed = true;
String error[] = builder.toString().split("\n");
for (int i = 0; i < error.length; i++) {
if (!error[i].equals("sat")) {
OrccLogger.traceln(error[i]);
}
}
return;
}
SExpParser parser = new SExpParser(builder.toString());
SExp exp = parser.read();
SExpSymbol symbol = (SExpSymbol) exp;
satisfied = "sat".equals(symbol.getContents());
// parse assertions (if there are any)
if (satisfied && action != null && ports != null) {
exp = parser.read();
if (exp != null && exp.isList()) {
SExpList list = (SExpList) exp;
Pattern pattern = action.getPeekPattern();
int index = 0;
for (Port port : ports) {
exp = list.get(index);
index++;
Object value;
int numTokens = pattern.getNumTokens(port);
if (numTokens > 1) {
List<Object> values = new ArrayList<Object>();
SExpList portList = (SExpList) exp;
for (int i = 0; i < numTokens; i++) {
SExp subExpr = portList.get(i);
values.add(getExpression(subExpr));
}
value = values.toArray();
} else {
value = getExpression(exp);
}
assertions.put(port.getName(), value);
}
}
}
}
}
private Action action;
private Actor actor;
private Map<String, Object> assertions;
private List<String> options;
private IFolder output;
private List<Port> ports;
private boolean satisfied;
private String solver;
/**
* Creates a new solver. The solver writes SMT-LIB files that are given the
* name of the actor.
*
* @param actor
* the actor
*/
public SmtSolver(Actor actor) {
assertions = new HashMap<String, Object>();
this.actor = actor;
IFile file = actor.getFile();
output = OrccUtil.getOutputFolder(file.getProject());
String solverPath = getDefault().getPreference(P_SOLVER, "");
solverPath = FilesManager.sanitize(solverPath);
if (!solverPath.isEmpty()) {
File solverFile = new File(solverPath);
if (solverFile.exists()) {
if(solverFile.canExecute()) {
solver = solverPath;
} else {
throw new OrccRuntimeException("solver " + solverPath
+ " is not executable");
}
} else {
throw new OrccRuntimeException("solver executable not found!");
}
} else {
throw new OrccRuntimeException("path of solver is empty!");
}
String strOptions = getDefault().getPreference(P_SOLVER_OPTIONS, "");
options = Arrays.asList(strOptions.split(" "));
}
/**
* Checks if the given script is satisfiable.
*
* @param script
* an SMT script
* @return <code>true</code> if the given script is satisfiable
*/
public boolean checkSat(SmtScript script) {
return checkSat(script, null, null);
}
public boolean hasFailed() {
return hasFailed;
}
/**
* Checks if the given script is satisfiable, and retrieve the value of the
* tokens present on the control ports.
*
* @param script
* an SMT script
* @param action
* if present, will get the tokens read on these ports
* @param ports
* the list of control ports
* @return <code>true</code> if the given script is satisfiable
*/
public boolean checkSat(SmtScript script, Action action, List<Port> ports) {
try {
this.action = action;
this.ports = ports;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
PrintStream ps = new PrintStream(bos);
for (String command : script.getCommands()) {
ps.println(command);
}
if (action != null && ports != null) {
ps.print("(get-value (");
Pattern pattern = action.getPeekPattern();
for (Port port : ports) {
int numTokens = pattern.getNumTokens(port);
if (numTokens > 1) {
ps.print("(");
for (int i = 0; i < numTokens; i++) {
ps.print("(select " + port.getName() + " (_ bv" + i
+ " 32)) ");
}
ps.print(")");
} else {
ps.print("(select " + port.getName() + " (_ bv0 32)) ");
}
}
ps.println("))");
}
ps.close();
File file = new File(output.getLocation().toOSString(), actor.getSimpleName() + "_"
+ System.currentTimeMillis() + ".smt2");
FilesManager.writeFile(bos.toString(), file);
launchSolver(file);
} catch (Exception e) {
throw new OrccRuntimeException("could not execute solver", e);
}
return satisfied;
}
/**
* Returns the assertions as a map between ports and associated values.
*
* @return the assertions as a map between ports and associated values
*/
public Map<String, Object> getAssertions() {
return assertions;
}
/**
* Launch the solver on the given file.
*
* @param file
* a file
* @throws IOException
*/
private void launchSolver(File file) throws IOException {
List<String> allOptions = new ArrayList<String>(options.size() + 2);
allOptions.add(solver);
allOptions.addAll(options);
allOptions.add(file.getCanonicalPath());
ProcessBuilder pb = new ProcessBuilder(allOptions);
final Process process = pb.start();
// Output error message
SmtSolverOutputProcessor processor = new SmtSolverOutputProcessor(
process.getInputStream());
Thread thErr = new Thread() {
public void run() {
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getErrorStream()));
try {
String line = reader.readLine();
while (line != null) {
OrccLogger.traceln(line);
line = reader.readLine();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
};
thErr.start();
Thread thread = new Thread(processor);
thread.start();
try {
process.waitFor();
// wait for all output to be processed
thread.join();
thErr.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}