/* * This file is part of the OpenJML project. * Author: David R. Cok */ package org.jmlspecs.openjml.utils; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.Reader; import java.io.Writer; import org.jmlspecs.annotation.NonNull; import org.jmlspecs.annotation.Nullable; import org.jmlspecs.openjml.proverinterface.ProverException; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Log; import com.sun.tools.javac.util.Log.WriterKind; public class ExternalProcessNoThread implements IExternalProcess { /** A debugging flag - 0 = show nothing; 1 = show errors; 2 = show something; 3 = show everything */ // Value should be 1 for ordinary operation // @edu.umd.cs.findbugs.annotations.SuppressWarnings("MS_SHOULD_BE_FINAL") static public int showCommunication = 1; public Context context; public Log log; public String[] app; public @Nullable String prompt; public ExternalProcessNoThread(Context context, @Nullable String prompt, String[] app) { this.context = context; this.log = Log.instance(context); this.app = app; this.prompt = prompt; } public void restartProver() throws ProverException { kill(); start(); } /** The stream connection to send information to the prover process. */ //@ invariant process != null ==> toProver != null; protected Writer toProver; /** The stream connection to read information from the prover process. */ //@ invariant process != null ==> fromProver != null; protected Reader fromProver; /** The error stream connection to read information from the prover process. */ //@ invariant process != null ==> errors != null; protected Reader errors; protected Process process; public String[] app() { return app; } /** Does the startup work */ public void start() throws ProverException { String[] app = app(); if (app == null) { throw new ProverException("No path to the executable found; specify it using -Dopenjml.prover.cvc3"); } else { java.io.File f = new java.io.File(app[0]); if (!f.exists()) log.getWriter(WriterKind.NOTICE).println("Does not appear to exist: " + app()[0]); //if (!f.exists()) throw new ProverException("The specified executable does not appear to exist: " + app[0]); } try { process = Runtime.getRuntime().exec(app); } catch (IOException e) { process = null; throw new ProverException("Failed to launch prover process: " + app()[0] + " " + e); } // TODO: assess performance of using buffered readers/writers toProver = new BufferedWriter(new OutputStreamWriter(process.getOutputStream())); fromProver = new BufferedReader(new InputStreamReader(process.getInputStream())); errors = new InputStreamReader(process.getErrorStream()); //eatPrompt(false); } public void kill() { process.destroy(); process = null; } /** Does the actual work of sending information to the prover process. You * need to call eatPrompt for each newline you send. This method does not * add any newlines to the supplied String. * @param s the String to send * @throws ProverException if something goes wrong */ public void send(String s) throws ProverException { if (showCommunication >= 2) { log.getWriter(WriterKind.NOTICE).print("SENDING ["+s.length()+ "]" + s); // ss has a newline so we only use print here log.getWriter(WriterKind.NOTICE).flush(); } try { // The number 2000 here is arbitrary - it is just a significant // amount to send at once, breaking up long inputs so that the // prover process has a chance to catch up. Not sure it is or // should be needed, but it seemed to help avoid deadlocks at one // time. final int gulp = 2000; if (s.length() > gulp) { int i = 0; for (; i< s.length()-gulp; i+= gulp) { toProver.append(s.substring(i,i+gulp)); try { Thread.sleep(1); } catch (Exception e) {} } toProver.append(s.substring(i)); } else { toProver.append(s); } toProver.flush(); } catch (IOException e) { throw new ProverException("Failed to write to prover: (" + s.length() + " chars) " + e); } } public String eatPrompt() throws ProverException { return eatPrompt(true); } /** A buffer to hold input */ /*@ non_null */ protected char[] cbuf = new char[3000000]; /** Returns the prover-specific prompt string that the eatPrompt method * should look for. The string may not contain any CR/NL characters. * @return the prompt string */ public @NonNull String prompt() { return prompt; } @Override public int readToCompletion() throws ProverException { // We read characters until the process terminates // FIXME - need a better way to read both inputs // FIXME - this probably can be made a lot more efficient // boolean interactive = true; // char[] prompt = prompt().toCharArray(); try { // if (interactive) { // int offset = 0; // String s = ""; // while (errors.ready()) { // int n = errors.read(cbuf,offset,cbuf.length-offset); // if (n < 0) throw new ProverException("Prover died"); // if (n == 0) break; // offset += n; // } // if (offset > 0) { // log.getWriter(WriterKind.NOTICE).println("ERROR: " + String.valueOf(cbuf,0,offset)); // } // int truncated = 0; // while (true) { // There is always a prompt to read, so it is OK to block // // until it is read. That gives the prover process time to // // do its processing. // //log.getWriter(WriterKind.NOTICE).println(" ... LISTENING"); // int n = fromProver.read(cbuf,offset,cbuf.length-offset); // if (n < 0) { // int off = 0; // while (errors.ready()) { // int nn = errors.read(cbuf,off,cbuf.length-off); // if (nn < 0) throw new ProverException("Prover died-eStream"); // if (nn == 0) break; // off += nn; // } // String serr = String.valueOf(cbuf,0,off); // if (!serr.startsWith("searching")) log.getWriter(WriterKind.NOTICE).println("ERROR STREAM ON DEATH: " + serr); // throw new ProverException("Prover died"); // } // offset += n; // // if (endsWith(offset,prompt)) break; // if (offset > cbuf.length-1000) { // if (s.length() > 280000) { // // excessive length // truncated += offset; // } else { // s = s + String.valueOf(cbuf,0,offset); // log.getWriter(WriterKind.NOTICE).println("BUFFER FULL " + s.length()); // } // offset = 0; // } // } // if (truncated > 0) { // log.getWriter(WriterKind.NOTICE).println("OUTPUT LENGTH " + s.length() + truncated); // throw new ProverException("Excessive output: " + s.length() + truncated); // } // s = s + String.valueOf(cbuf,0,offset); // offset = 0; // if (errors.ready()) { // while (errors.ready()) { // int n = errors.read(cbuf,offset,cbuf.length-offset); // if (n < 0) throw new ProverException("Prover died"); // if (n == 0) break; // offset += n; // } // if (offset > 0) { // String errorString = new String(cbuf,0,offset); // if (!errorString.startsWith("\nWARNING") && // !errorString.startsWith("CVC3 (version") && // !errorString.startsWith("searching")) { // if (showCommunication >= 1) log.getWriter(WriterKind.NOTICE).println("HEARD ERROR: " + errorString); // throw new ProverException("Prover error message: " + errorString); // } else { // if (showCommunication >= 3) log.getWriter(WriterKind.NOTICE).println("HEARD ERROR: " + errorString); // } // } // } // if (showCommunication >= 3) log.getWriter(WriterKind.NOTICE).println("HEARD: " + s); // return s; // } else { // In non-interactive mode, there may be no input at all // We sleep briefly, hoping that the target process will have time to put out any output try { Thread.sleep(1); } catch (Exception e) { /* No action needed */ } int offset = 0; if (true) { // FIXME - true or false? // TODO: Problem: When the prover produces a counterexample, it does not always do so promptly. // So the loop below tends to exit before all (or any) counterexample information is retrieved. do { int n = fromProver.read(cbuf,offset,cbuf.length-offset); if (n < 0) { return process.exitValue(); } offset += n; } while (fromProver.ready()); } else { while (fromProver.ready()) { int n = fromProver.read(cbuf,offset,cbuf.length-offset); if (n < 0) { throw new ProverException("Prover died"); } offset += n; } } String s = new String(cbuf,0,offset); offset = 0; if (errors.ready()) { while (errors.ready()) { int n = errors.read(cbuf,offset,cbuf.length-offset); if (n < 0) throw new ProverException("Prover died"); if (n == 0) break; offset += n; } if (offset > 0) { String errorString = new String(cbuf,0,offset); if (!errorString.startsWith("\nWARNING") && !errorString.startsWith("CVC3 (version") && !errorString.startsWith("searching")) { if (showCommunication >= 1) log.getWriter(WriterKind.NOTICE).println("HEARD ERROR: " + errorString); throw new ProverException("Prover error message: " + errorString); } else { if (showCommunication >= 3) log.getWriter(WriterKind.NOTICE).println("HEARD ERROR: " + errorString); } } } if (showCommunication >= 3) Log.instance(context).getWriter(WriterKind.NOTICE).println("HEARD: " + s); // } } catch (IOException e) { throw new ProverException("IO Error on reading from prover: " + e); } return -2; } protected String eatPrompt(boolean wait) throws ProverException { // We read characters until we get to the prompt sequence "> " // that marks the end of the input. Be careful // that sequence is not elsewhere in the input as well. // FIXME - need a better way to read both inputs // FIXME - this probably can be made a lot more efficient boolean interactive = true; char[] prompt = prompt().toCharArray(); try { if (interactive) { int offset = 0; String s = ""; while (errors.ready()) { int n = errors.read(cbuf,offset,cbuf.length-offset); if (n < 0) throw new ProverException("Prover died"); if (n == 0) break; offset += n; } if (offset > 0) { log.getWriter(WriterKind.NOTICE).println("ERROR: " + String.valueOf(cbuf,0,offset)); } int truncated = 0; while (true) { // There is always a prompt to read, so it is OK to block // until it is read. That gives the prover process time to // do its processing. //log.getWriter(WriterKind.NOTICE).println(" ... LISTENING"); int n = fromProver.read(cbuf,offset,cbuf.length-offset); if (n < 0) { int off = 0; while (errors.ready()) { int nn = errors.read(cbuf,off,cbuf.length-off); if (nn < 0) throw new ProverException("Prover died-eStream"); if (nn == 0) break; off += nn; } String serr = String.valueOf(cbuf,0,off); if (!serr.startsWith("searching")) log.getWriter(WriterKind.NOTICE).println("ERROR STREAM ON DEATH: " + serr); throw new ProverException("Prover died"); } offset += n; if (endsWith(offset,prompt)) break; if (offset > cbuf.length-1000) { if (s.length() > 280000) { // excessive length truncated += offset; } else { s = s + String.valueOf(cbuf,0,offset); log.getWriter(WriterKind.NOTICE).println("BUFFER FULL " + s.length()); } offset = 0; } } if (truncated > 0) { log.getWriter(WriterKind.NOTICE).println("OUTPUT LENGTH " + s.length() + truncated); throw new ProverException("Excessive output: " + s.length() + truncated); } s = s + String.valueOf(cbuf,0,offset); offset = 0; if (errors.ready()) { while (errors.ready()) { int n = errors.read(cbuf,offset,cbuf.length-offset); if (n < 0) throw new ProverException("Prover died"); if (n == 0) break; offset += n; } if (offset > 0) { String errorString = new String(cbuf,0,offset); if (!errorString.startsWith("\nWARNING") && !errorString.startsWith("CVC3 (version") && !errorString.startsWith("searching")) { if (showCommunication >= 1) log.getWriter(WriterKind.NOTICE).println("HEARD ERROR: " + errorString); throw new ProverException("Prover error message: " + errorString); } else { if (showCommunication >= 3) log.getWriter(WriterKind.NOTICE).println("HEARD ERROR: " + errorString); } } } if (showCommunication >= 3) log.getWriter(WriterKind.NOTICE).println("HEARD: " + s); return s; } else { // In non-interactive mode, there may be no input at all // We sleep briefly, hoping that the target process will have time to put out any output try { Thread.sleep(1); } catch (Exception e) { /* No action needed */ } int offset = 0; if (wait) { // TODO: Problem: When the prover produces a counterexample, it does not always do so promptly. // So the loop below tends to exit before all (or any) counterexample information is retrieved. do { int n = fromProver.read(cbuf,offset,cbuf.length-offset); if (n < 0) { throw new ProverException("Prover died"); } offset += n; } while (fromProver.ready()); } else { while (fromProver.ready()) { int n = fromProver.read(cbuf,offset,cbuf.length-offset); if (n < 0) { throw new ProverException("Prover died"); } offset += n; } } String s = new String(cbuf,0,offset); offset = 0; if (errors.ready()) { while (errors.ready()) { int n = errors.read(cbuf,offset,cbuf.length-offset); if (n < 0) throw new ProverException("Prover died"); if (n == 0) break; offset += n; } if (offset > 0) { String errorString = new String(cbuf,0,offset); if (!errorString.startsWith("\nWARNING") && !errorString.startsWith("CVC3 (version") && !errorString.startsWith("searching")) { if (showCommunication >= 1) log.getWriter(WriterKind.NOTICE).println("HEARD ERROR: " + errorString); throw new ProverException("Prover error message: " + errorString); } else { if (showCommunication >= 3) log.getWriter(WriterKind.NOTICE).println("HEARD ERROR: " + errorString); } } } if (showCommunication >= 3) Log.instance(context).getWriter(WriterKind.NOTICE).println("HEARD: " + s); return s; } } catch (IOException e) { throw new ProverException("IO Error on reading from prover: " + e); } } protected boolean endsWith(int offset, char[] prompt) { int k = offset - prompt.length; if (k < 0) return false; for (int i=0; i < prompt.length; i++) { if (cbuf[k+i] != prompt[i]) return false; } return true; } }