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}