001package daikon.simplify; 002 003import static java.nio.charset.StandardCharsets.UTF_8; 004import static java.util.logging.Level.INFO; 005 006import java.io.BufferedReader; 007import java.io.Closeable; 008import java.io.File; 009import java.io.FileOutputStream; 010import java.io.IOException; 011import java.io.InputStream; 012import java.io.InputStreamReader; 013import java.io.PrintStream; 014import java.util.ArrayList; 015import java.util.List; 016import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; 017import org.checkerframework.checker.initialization.qual.UnknownInitialization; 018import org.checkerframework.checker.lock.qual.GuardSatisfied; 019import org.checkerframework.checker.lock.qual.GuardedBy; 020import org.checkerframework.checker.lock.qual.Holding; 021import org.checkerframework.checker.mustcall.qual.MustCall; 022import org.checkerframework.checker.mustcall.qual.Owning; 023import org.checkerframework.checker.nullness.qual.MonotonicNonNull; 024import org.checkerframework.checker.nullness.qual.NonNull; 025import org.checkerframework.checker.nullness.qual.Nullable; 026 027/** 028 * A session is a channel to the Simplify theorem-proving tool. Once a session is started, commands 029 * may be applied to the session to make queries and manipulate its state. 030 */ 031@MustCall("close") public class Session implements Closeable { 032 /** 033 * A non-negative integer, representing the largest number of iterations for which Simplify should 034 * be allowed to run on any single conjecture before giving up. Larger values may cause Simplify 035 * to run longer, but will increase the number of invariants that can be recognized as redundant. 036 * The default value is small enough to keep Simplify from running for more than a few seconds on 037 * any one conjecture, allowing it to verify most simple facts without getting bogged down in long 038 * searches. A value of 0 means not to bound the number of iterations at all, though see also the 039 * {@code simplify_timeout} parameter.. 040 */ 041 public static int dkconfig_simplify_max_iterations = 1000; 042 043 /** 044 * A non-negative integer, representing the longest time period (in seconds) Simplify should be 045 * allowed to run on any single conjecture before giving up. Larger values may cause Simplify to 046 * run longer, but will increase the number of invariants that can be recognized as redundant. 047 * Roughly speaking, the time spent in Simplify will be bounded by this value, times the number of 048 * invariants generated, though it can be much less. A value of 0 means to not bound Simplify at 049 * all by time, though also see the option {@code simplify_max_iterations}. Beware that using this 050 * option might make Daikon's output depend on the speed of the machine it's run on. 051 */ 052 public static int dkconfig_simplify_timeout = 0; 053 054 /** 055 * Positive values mean to print extra indications as each candidate invariant is passed to 056 * Simplify during the {@code --suppress_redundant} check. If the value is 1 or higher, a hyphen 057 * will be printed when each invariant is passed to Simplify, and then replaced by a {@code T} if 058 * the invariant was redundant, {@code F} if it was not found to be, and {@code ?} if Simplify 059 * gave up because of a time limit. If the value is 2 or higher, a {@code <} or {@code >} will 060 * also be printed for each invariant that is pushed onto or popped from from Simplify's 061 * assumption stack. This option is mainly intended for debugging purposes, but can also provide 062 * something to watch when Simplify takes a long time. 063 */ 064 public static int dkconfig_verbose_progress = 0; 065 066 /** 067 * Boolean. If true, the input to the Simplify theorem prover will also be directed to a file 068 * named simplifyN.in (where N is a number starting from 0) in the current directory. Simplify's 069 * operation can then be reproduced with a command like {@code Simplify -nosc <simplify0.in}. This 070 * is intended primarily for debugging when Simplify fails. 071 */ 072 public static boolean dkconfig_trace_input = false; 073 074 /** non-null if dkconfig_trace_input==true */ 075 private final @Owning @MonotonicNonNull PrintStream trace_file; 076 077 /** A unique identifier for creating unique filenames for trace files. */ 078 private static int trace_count = 0; 079 080 /* package */ final Process process; 081 private final PrintStream input; 082 private final BufferedReader output; 083 084 /** 085 * Starts a new Simplify process, which runs concurrently; I/O with this process will block. 086 * Initializes the simplify environment for interaction. Use {@code Cmd} objects to interact with 087 * this Session. 088 */ 089 public Session() { 090 // Note that this local variable shadows `this.trace_file`. 091 PrintStream trace_file = null; 092 try { 093 List<String> newEnv = new ArrayList<>(); 094 if (dkconfig_simplify_max_iterations != 0) { 095 newEnv.add("PROVER_KILL_ITER=" + dkconfig_simplify_max_iterations); 096 } 097 if (dkconfig_simplify_timeout != 0) { 098 newEnv.add("PROVER_KILL_TIME=" + dkconfig_simplify_timeout); 099 } 100 String[] envArray = newEnv.toArray(new String[] {}); 101 SessionManager.debugln("Session: exec"); 102 // -nosc: don't compute or print invalid context 103 String simplifyPath; 104 if (System.getProperty("simplify.path") == null) { 105 simplifyPath = "Simplify"; 106 } else { 107 simplifyPath = System.getProperty("simplify.path"); 108 } 109 process = java.lang.Runtime.getRuntime().exec(new String[] {simplifyPath, "-nosc"}, envArray); 110 SessionManager.debugln("Session: exec ok"); 111 112 if (dkconfig_trace_input) { 113 File f; 114 while ((f = new File("simplify" + trace_count + ".in")).exists()) { 115 trace_count++; 116 } 117 trace_file = new PrintStream(new FileOutputStream(f)); 118 } 119 this.trace_file = trace_file; 120 121 // set up command stream 122 PrintStream tmp_input = new PrintStream(process.getOutputStream()); 123 input = tmp_input; 124 125 // set up result stream 126 @NonNull InputStream is = process.getInputStream(); 127 output = new BufferedReader(new InputStreamReader(is, UTF_8)); 128 129 // turn off prompting 130 SessionManager.debugln("Session: prompt off"); 131 sendLine("(PROMPT_OFF)"); 132 SessionManager.debugln("Session: eat prompt"); 133 // eat first (and only, because we turn it off) prompt 134 String expect = ">\t"; 135 byte[] buf = new byte[expect.length()]; 136 int pos = is.read(buf); 137 assert pos != -1 : "Prompt exected, stream ended"; 138 String actual = new String(buf, 0, pos, UTF_8); 139 assert expect.equals(actual) : "Prompt expected, got '" + actual + "'"; 140 141 } catch (Exception | AssertionError e) { 142 if (trace_file != null) { 143 try { 144 trace_file.close(); 145 } catch (Exception closeException) { 146 e.addSuppressed(closeException); 147 } 148 } 149 throw new SimplifyError(e); 150 } 151 } 152 153 /* package access */ void sendLine( 154 @UnknownInitialization(Session.class) @GuardSatisfied Session this, String s) { 155 if (dkconfig_trace_input) { 156 assert trace_file != null 157 : "@AssumeAssertion(nullness): dependent: trace_file is non-null (set in constructor) if" 158 + " dkconfig_trace_input is true"; 159 trace_file.println(s); 160 } 161 input.println(s); 162 input.flush(); 163 } 164 165 @Holding("this") 166 /* package access */ 167 @Nullable String readLine(@GuardSatisfied Session this) throws IOException { 168 return output.readLine(); 169 } 170 171 /** Releases the resources held by this. */ 172 @EnsuresCalledMethods(value = "trace_file", methods = "close") 173 @Override 174 public void close(@GuardSatisfied Session this) { 175 process.destroy(); 176 assert dkconfig_trace_input == (trace_file != null) 177 : "@AssumeAssertion(nullness): conditional: trace_file is non-null if" 178 + " dkconfig_trace_input==true"; 179 if (trace_file != null) { 180 trace_file.close(); 181 } 182 } 183 184 /** 185 * for testing and playing around, not for real use 186 * 187 * @param args command-line arguments 188 */ 189 public static void main(String[] args) { 190 daikon.LogHelper.setupLogs(INFO); 191 try (@GuardedBy("<self>") Session s = new Session()) { 192 193 CmdCheck cc; 194 195 cc = new CmdCheck("(EQ 1 1)"); 196 cc.apply(s); 197 assert cc.valid; 198 199 cc = new CmdCheck("(EQ 1 2)"); 200 cc.apply(s); 201 assert !cc.valid; 202 203 cc = new CmdCheck("(EQ x z)"); 204 cc.apply(s); 205 assert !cc.valid; 206 207 CmdAssume a = new CmdAssume("(AND (EQ x y) (EQ y z))"); 208 a.apply(s); 209 210 cc.apply(s); 211 assert cc.valid; 212 213 CmdUndoAssume.single.apply(s); 214 215 cc.apply(s); 216 assert !cc.valid; 217 } 218 } 219}