001package daikon.simplify; 002 003import daikon.inv.Invariant; 004import java.io.Closeable; 005import java.util.ArrayList; 006import java.util.HashSet; 007import java.util.List; 008import java.util.NavigableSet; 009import java.util.Set; 010import java.util.Stack; 011import java.util.TreeSet; 012import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; 013import org.checkerframework.checker.initialization.qual.UnknownInitialization; 014import org.checkerframework.checker.lock.qual.GuardSatisfied; 015import org.checkerframework.checker.mustcall.qual.CreatesMustCallFor; 016import org.checkerframework.checker.mustcall.qual.MustCall; 017import org.checkerframework.checker.mustcall.qual.Owning; 018import org.checkerframework.checker.nullness.qual.EnsuresNonNull; 019 020/** 021 * A stack of Lemmas that shadows the stack of assumptions that Simplify keeps. Keeping this stack 022 * is necessary if we're to be able to restart Simplify from where we left off after it hangs, but 023 * it's also a convenient place to hang routines that any Simplify client can use. 024 */ 025@SuppressWarnings("JdkObsolete") // Stack has methods that ArrayDeque lacks, such as elementAt() 026@MustCall("close") public class LemmaStack implements Closeable { 027 /** 028 * Boolean. Controls Daikon's response when inconsistent invariants are discovered while running 029 * Simplify. If false, Daikon will give up on using Simplify for that program point. If true, 030 * Daikon will try to find a small subset of the invariants that cause the contradiction and avoid 031 * them, to allow processing to continue. For more information, see the section on troubleshooting 032 * contradictory invariants in the Daikon manual. 033 */ 034 public static boolean dkconfig_remove_contradictions = true; 035 036 /** 037 * Boolean. Controls Daikon's response when inconsistent invariants are discovered while running 038 * Simplify. If true, Daikon will print an error message to the standard error stream listing the 039 * contradictory invariants. This is mainly intended for debugging Daikon itself, but can 040 * sometimes be helpful in tracing down other problems. For more information, see the section on 041 * troubleshooting contradictory invariants in the Daikon manual. 042 */ 043 public static boolean dkconfig_print_contradictions = false; 044 045 /** 046 * Boolean. If true, ask Simplify to check a simple proposition after each assumption is pushed, 047 * providing an opportunity to wait for output from Simplify and potentially receive error 048 * messages about the assumption. When false, long sequences of assumptions may be pushed in a 049 * row, so that by the time an error message arrives, it's not clear which input caused the error. 050 * Of course, Daikon's input to Simplify isn't supposed to cause errors, so this option should 051 * only be needed for debugging. 052 */ 053 public static boolean dkconfig_synchronous_errors = false; 054 055 private Stack<Lemma> lemmas; 056 private @Owning SessionManager session; 057 058 /** Tell Simplify to assume a lemma, which should already be on our stack. */ 059 private void assume(@UnknownInitialization(LemmaStack.class) LemmaStack this, Lemma lemma) 060 throws TimeoutException { 061 session.request(new CmdAssume(lemma.formula)); 062 } 063 064 /** Assume a list of lemmas. */ 065 private void assumeAll(@UnknownInitialization(LemmaStack.class) LemmaStack this, List<Lemma> invs) 066 throws TimeoutException { 067 for (Lemma lem : invs) { 068 assume(lem); 069 } 070 } 071 072 /** Pop a lemma off Simplify's stack. */ 073 private void unAssume() { 074 try { 075 session.request(CmdUndoAssume.single); 076 } catch (TimeoutException e) { 077 throw new Error("Unexpected timeout on (BG_POP)"); 078 } 079 } 080 081 /** 082 * Pop a bunch of lemmas off Simplify's stack. Since it's a stack, it only works to unassume the 083 * things you most recently assumed, but we aren't smart enough to check that. 084 * 085 * @param invs the lemmas to pop off Simplify's stack 086 */ 087 private void unAssumeAll(List<Lemma> invs) { 088 for (@SuppressWarnings("UnusedVariable") Lemma lem : invs) { 089 unAssume(); 090 } 091 } 092 093 /** Try to start Simplify. */ 094 @CreatesMustCallFor("this") 095 @EnsuresNonNull("session") 096 private void startProver(@UnknownInitialization LemmaStack this) throws SimplifyError { 097 SessionManager session_try = SessionManager.attemptProverStartup(); 098 if (session != null) { 099 try { 100 session.close(); 101 } catch (Exception e) { 102 throw new SimplifyError(e); 103 } 104 } 105 if (session_try != null) { 106 session = session_try; 107 } else { 108 throw new SimplifyError("Couldn't start Simplify"); 109 } 110 } 111 112 /** Try to restart Simplify back where we left off, after killing it. */ 113 @CreatesMustCallFor("this") 114 private void restartProver(@UnknownInitialization(LemmaStack.class) LemmaStack this) 115 throws SimplifyError { 116 startProver(); 117 try { 118 assumeAll(lemmas); 119 } catch (TimeoutException e) { 120 throw new SimplifyError("Simplify restart timed out"); 121 } 122 } 123 124 /** Create a new LemmaStack. */ 125 public LemmaStack() throws SimplifyError { 126 startProver(); 127 lemmas = new Stack<Lemma>(); 128 if (daikon.inv.Invariant.dkconfig_simplify_define_predicates) { 129 pushLemmas(Lemma.lemmasList()); 130 } 131 } 132 133 /** Pop a lemma from our and Simplify's stacks. */ 134 public void popLemma() { 135 unAssume(); 136 lemmas.pop(); 137 } 138 139 /** 140 * Push an assumption onto our and Simplify's stacks. 141 * 142 * @param lem the assumption 143 * @return true if success, false if Simplify times out 144 */ 145 @SuppressWarnings("builder:reset.not.owning") // only resets conditionally, on exception path 146 public boolean pushLemma(@UnknownInitialization(LemmaStack.class) LemmaStack this, Lemma lem) 147 throws SimplifyError { 148 SimpUtil.assert_well_formed(lem.formula); 149 try { 150 assume(lem); 151 lemmas.push(lem); 152 if (dkconfig_synchronous_errors) { 153 // The following debugging code causes us to flush all our input 154 // to Simplify after each lemma, and is useful to figure out 155 // which lemma an error message refers to. 156 try { 157 checkString("(AND)"); 158 } catch (SimplifyError err) { 159 System.err.println("Error after pushing " + lem.summarize() + " " + lem.formula); 160 throw err; 161 } 162 } 163 164 return true; 165 } catch (TimeoutException e) { 166 restartProver(); 167 return false; 168 } 169 } 170 171 /** Push a vector of assumptions onto our and Simplify's stacks. */ 172 public void pushLemmas( 173 @UnknownInitialization(LemmaStack.class) LemmaStack this, List<Lemma> newLemmas) 174 throws SimplifyError { 175 for (Lemma lem : newLemmas) { 176 pushLemma(lem); 177 } 178 } 179 180 /** 181 * Ask Simplify whether a string is a valid statement, given our assumptions. Returns 'T' if 182 * Simplify says yes, 'F' if Simplify says no, or '?' if we have to kill Simplify because it won't 183 * answer. 184 * 185 * @param str the string to check 186 * @return 'T' if Simplify says yes, 'F' if Simplify says no, or '?' if Simplify does not answer 187 */ 188 @SuppressWarnings("builder:reset.not.owning") // only resets conditionally, on exception path 189 private char checkString(@UnknownInitialization(LemmaStack.class) LemmaStack this, String str) 190 throws SimplifyError { 191 SimpUtil.assert_well_formed(str); 192 CmdCheck cc = new CmdCheck(str); 193 try { 194 session.request(cc); 195 } catch (TimeoutException e) { 196 restartProver(); 197 return '?'; 198 } 199 if (cc.unknown) { 200 return '?'; 201 } 202 return cc.valid ? 'T' : 'F'; 203 } 204 205 /** 206 * Ask Simplify whether a lemma is valid, given our assumptions. Returns 'T' if Simplify says yes, 207 * 'F' if Simplify says no, or '?' if we have to kill Simplify because it won't answer. 208 */ 209 public char checkLemma(Lemma lemma) throws SimplifyError { 210 return checkString(lemma.formula); 211 } 212 213 /** 214 * Ask Simplify whether the assumptions we've pushed so far are contradictory. Returns 'T' if 215 * Simplify says yes, 'F' if Simplify says no, or '?' if we have to kill Simplify because it won't 216 * answer. 217 */ 218 public char checkForContradiction() throws SimplifyError { 219 return checkString("(OR)"); // s/b always false 220 } 221 222 /** 223 * Return true if all the invariants in invs[i] in invs[] not between min and max (inclusive) for 224 * which excluded[i] is false, together imply the formula conseq. 225 */ 226 private boolean allExceptImply(Lemma[] invs, boolean[] excluded, int min, int max, String conseq) 227 throws TimeoutException { 228 int assumed = 0; 229 for (int i = 0; i < invs.length; i++) { 230 if (!excluded[i] && (i < min || i > max)) { 231 assume(invs[i]); 232 assumed++; 233 } 234 } 235 boolean valid = checkString(conseq) != 'F'; 236 for (int i = 0; i < assumed; i++) { 237 unAssume(); 238 } 239 return valid; 240 } 241 242 /** Return true if all the elements of bools between min and max (inclusive) are true. */ 243 private static boolean allTrue(boolean[] bools, int min, int max) { 244 for (int i = min; i <= max; i++) { 245 if (!bools[i]) { 246 return false; 247 } 248 } 249 return true; 250 } 251 252 /** 253 * Find a subset of invs[] that imply consequence, such that no subset of that set does. Note that 254 * we may not return the smallest such set. The set is currently returned in the same order as the 255 * invariants appeared in invs[]. 256 */ 257 private List<Lemma> minimizeAssumptions(Lemma[] invs, String consequence) 258 throws TimeoutException { 259 boolean[] excluded = new boolean[invs.length]; 260 261 for (int size = invs.length / 2; size > 1; size /= 2) { 262 for (int start = 0; start < invs.length; start += size) { 263 int end = Math.min(start + size - 1, invs.length - 1); 264 if (!allTrue(excluded, start, end) 265 && allExceptImply(invs, excluded, start, end, consequence)) { 266 for (int i = start; i <= end; i++) { 267 excluded[i] = true; 268 } 269 } 270 } 271 } 272 273 boolean reduced; 274 do { 275 reduced = false; 276 for (int i = 0; i < invs.length; i++) { 277 if (!excluded[i]) { 278 if (allExceptImply(invs, excluded, i, i, consequence)) { 279 excluded[i] = true; 280 reduced = true; 281 } 282 } 283 } 284 } while (reduced); 285 List<Lemma> new_invs = new ArrayList<>(); 286 for (int i = 0; i < invs.length; i++) { 287 if (!excluded[i]) { 288 new_invs.add(invs[i]); 289 } 290 } 291 return new_invs; 292 } 293 294 private static List<Lemma> filterByClass( 295 List<Lemma> lems, Set<Class<? extends Invariant>> blacklist) { 296 List<Lemma> new_lems = new ArrayList<>(); 297 for (Lemma lem : lems) { 298 Class<? extends Invariant> cls = lem.invClass(); 299 if (cls != null && !blacklist.contains(cls)) { 300 new_lems.add(lem); 301 } 302 } 303 return new_lems; 304 } 305 306 private void minimizeClasses_rec( 307 String result, 308 List<Lemma> lems, 309 Set<Class<? extends Invariant>> exclude, 310 Set<Set<Class<? extends Invariant>>> black, 311 Set<Set<Class<? extends Invariant>>> gray, 312 Set<Set<Class<? extends Invariant>>> found) 313 throws TimeoutException { 314 for (Set<Class<? extends Invariant>> known : found) { 315 // If known and exclude are disjoint, return 316 Set<Class<? extends Invariant>> exclude2 = new HashSet<Class<? extends Invariant>>(exclude); 317 exclude2.retainAll(known); 318 if (exclude2.isEmpty()) { 319 return; 320 } 321 } 322 int mark = markLevel(); 323 List<Lemma> filtered = filterByClass(lems, exclude); 324 pushLemmas(filtered); 325 boolean holds = checkString(result) == 'T'; 326 popToMark(mark); 327 if (holds) { 328 List<Lemma> mini = minimizeAssumptions(filtered.toArray(new Lemma[0]), result); 329 Set<Class<? extends Invariant>> used = new HashSet<Class<? extends Invariant>>(); 330 for (Lemma mlem : mini) { 331 Class<? extends Invariant> c = mlem.invClass(); 332 if (c != null) { 333 used.add(c); 334 } 335 } 336 for (Lemma mlem : mini) { 337 System.err.println(mlem.summarize()); 338 System.err.println(mlem.formula); 339 } 340 System.err.println("-----------------------------------"); 341 System.err.println(result); 342 System.err.println(); 343 344 found.add(used); 345 for (Class<? extends Invariant> c : used) { 346 Set<Class<? extends Invariant>> step = new HashSet<Class<? extends Invariant>>(exclude); 347 step.add(c); 348 if (!black.contains(step) && !gray.contains(step)) { 349 gray.add(step); 350 minimizeClasses_rec(result, lems, step, black, gray, found); 351 } 352 } 353 } 354 black.add(exclude); 355 } 356 357 public List<Set<Class<? extends Invariant>>> minimizeClasses(String result) { 358 List<Lemma> assumptions = new ArrayList<>(lemmas); 359 List<Set<Class<? extends Invariant>>> found = new ArrayList<>(); 360 try { 361 unAssumeAll(lemmas); 362 if (checkString(result) == 'F') { 363 Set<Class<? extends Invariant>> exclude = new HashSet<Class<? extends Invariant>>(); 364 Set<Set<Class<? extends Invariant>>> black = new HashSet<Set<Class<? extends Invariant>>>(); 365 Set<Set<Class<? extends Invariant>>> gray = new HashSet<Set<Class<? extends Invariant>>>(); 366 Set<Set<Class<? extends Invariant>>> found_set = 367 new HashSet<Set<Class<? extends Invariant>>>(); 368 minimizeClasses_rec(result, assumptions, exclude, black, gray, found_set); 369 found.addAll(found_set); 370 } 371 assumeAll(lemmas); 372 } catch (TimeoutException e) { 373 throw new Error(); 374 } 375 return found; 376 } 377 378 /** 379 * Return a minimal set of assumptions from the stack that imply a given string. 380 * 381 * @param str the expression to make true 382 * @return a minimal set of assumptions from the stack that imply the given string 383 */ 384 @SuppressWarnings("builder:reset.not.owning") // only resets conditionally, on exception path 385 private List<Lemma> minimizeReasons(String str) throws SimplifyError { 386 assert checkString(str) == 'T'; 387 unAssumeAll(lemmas); 388 List<Lemma> result; 389 try { 390 Lemma[] lemmaAry = lemmas.toArray(new Lemma[0]); 391 // shuffle(lemmaAry, new Random()); 392 result = minimizeAssumptions(lemmaAry, str); 393 assumeAll(lemmas); 394 } catch (TimeoutException e) { 395 System.err.println("Minimzation timed out"); 396 restartProver(); 397 return lemmas; 398 } 399 return result; 400 } 401 402 /** 403 * Return a set of contradictory assumptions from the stack (as a vector of Lemmas) which are 404 * minimal in the sense that no proper subset of them are contradictory as far as Simplify can 405 * tell. 406 */ 407 public List<Lemma> minimizeContradiction() throws SimplifyError { 408 return minimizeReasons("(OR)"); 409 } 410 411 /** 412 * Return a set of assumptions from the stack (as a vector of Lemmas) that imply the given Lemma 413 * and which are minimal in the sense that no proper subset of them imply it as far as Simplify 414 * can tell. 415 */ 416 public List<Lemma> minimizeProof(Lemma lem) throws SimplifyError { 417 return minimizeReasons(lem.formula); 418 } 419 420 /** 421 * Remove some lemmas from the stack, such that our set of assumptions is no longer contradictory. 422 * This is not a very principled thing to do, but it's better than just giving up. The approach is 423 * relatively slow, trying not to remove too many lemmas. 424 */ 425 public void removeContradiction() throws SimplifyError { 426 do { 427 List<Lemma> problems = minimizeContradiction(); 428 if (problems.size() == 0) { 429 throw new SimplifyError("Minimization failed"); 430 } 431 Lemma bad = problems.get(problems.size() - 1); 432 removeLemma(bad); 433 System.err.print("x"); 434 } while (checkForContradiction() == 'T'); 435 } 436 437 /** 438 * Search for the given lemma in the stack, and then remove it from both our stack and Simplify's. 439 * This is rather inefficient. 440 */ 441 public void removeLemma(Lemma bad) throws SimplifyError { 442 unAssumeAll(lemmas); 443 int spliceOut = -1; 444 for (int i = 0; i < lemmas.size(); i++) { 445 Lemma lem = lemmas.elementAt(i); 446 @SuppressWarnings("interning") // value in list 447 boolean isBad = (lem == bad); 448 if (isBad) { 449 spliceOut = i; 450 } else { 451 try { 452 assume(lem); 453 } catch (TimeoutException e) { 454 throw new SimplifyError("Timeout in contradiction removal"); 455 } 456 } 457 } 458 assert spliceOut != -1; 459 lemmas.removeElementAt(spliceOut); 460 } 461 462 /** Blow away everything on our stack and Simplify's. */ 463 public void clear() { 464 unAssumeAll(lemmas); 465 lemmas.removeAllElements(); 466 } 467 468 /** 469 * Return a reference to the current position on the lemma stack. If, after pushing some stuff, 470 * you want to get back here, pass the mark to popToMark(). This will only work if you use these 471 * routines in a stack-disciplined way, of course. In particular, beware that 472 * removeContradiction() invalidates marks, since it can remove a lemma from anywhere on the 473 * stack. 474 */ 475 public int markLevel() { 476 return lemmas.size(); 477 } 478 479 /** Pop off lemmas from the stack until its level matches mark. */ 480 public void popToMark(int mark) { 481 while (lemmas.size() > mark) popLemma(); 482 } 483 484 /** 485 * Convenience method to print a vector of lemmas, in both their human-readable and Simplify 486 * forms. 487 */ 488 public static void printLemmas(java.io.PrintStream out, List<Lemma> v) { 489 for (Lemma lem : v) { 490 out.println(lem.summarize()); 491 out.println(" " + lem.formula); 492 } 493 } 494 495 /** Dump the state of the stack to a file, for debugging manually in Simplify. */ 496 public void dumpLemmas(java.io.PrintStream out) { 497 for (Lemma lem : lemmas) { 498 out.println("(BG_PUSH " + lem.formula + ")"); 499 } 500 } 501 502 private static NavigableSet<Long> ints_seen = new TreeSet<>(); 503 504 /** Keep track that we've seen this number in formulas, for the sake of pushOrdering. */ 505 public static void noticeInt(long i) { 506 ints_seen.add(i); 507 } 508 509 public static void clearInts() { 510 ints_seen = new TreeSet<Long>(); 511 } 512 513 /** 514 * Integers smaller in absolute value than this will be printed directly. Larger integers will be 515 * printed abstractly (see Invariant.simplify_format_long and a comment there for details). 516 */ 517 public static final long SMALL_INTEGER = 32000; 518 519 /** For all the integers we've seen, tell Simplify about the ordering between them. */ 520 public void pushOrdering() throws SimplifyError { 521 long last_long = Long.MIN_VALUE; 522 for (Long ll : ints_seen) { 523 long l = ll.longValue(); 524 if (l == Long.MIN_VALUE) { 525 continue; 526 } 527 assert l != last_long; 528 String formula = 529 "(< " + SimpUtil.formatInteger(last_long) + " " + SimpUtil.formatInteger(l) + ")"; 530 Lemma lem = new Lemma(last_long + " < " + l, formula); 531 pushLemma(lem); 532 if (l > -SMALL_INTEGER && l < SMALL_INTEGER) { 533 // Only give the concrete value for "small" integers. 534 String eq_formula = "(EQ " + l + " " + SimpUtil.formatInteger(l) + ")"; 535 Lemma eq_lem = new Lemma(l + " == " + l, eq_formula); 536 pushLemma(eq_lem); 537 } 538 last_long = l; 539 } 540 } 541 542 /** Releases resources held by this. */ 543 @SuppressWarnings("builder:contracts.postcondition") // performed on a local alias, not the field 544 @EnsuresCalledMethods(value = "session", methods = "close") 545 @Override 546 public void close(@GuardSatisfied LemmaStack this) { 547 // this.session should be effectively final in that it refers 548 // to the same value throughout the execution of this method. 549 // Unfortunately, the Lock Checker cannot verify this, 550 // so a final local variable is used to satisfy the Lock Checker's 551 // requirement that all variables used as locks be final or 552 // effectively final. If a bug exists whereby this.session 553 // is not effectively final, this would unfortunately mask that error. 554 final SessionManager session = this.session; 555 session.close(); 556 synchronized (session) { 557 session.notifyAll(); 558 } 559 } 560}