001package daikon;
002
003import java.io.IOException;
004import java.io.ObjectStreamException;
005import java.io.Reader;
006import java.io.Serializable;
007import java.io.StreamTokenizer;
008import java.io.StringReader;
009import java.util.ArrayList;
010import java.util.HashMap;
011import java.util.Map;
012import java.util.Objects;
013import java.util.logging.Level;
014import java.util.logging.Logger;
015import org.checkerframework.checker.interning.qual.Interned;
016import org.checkerframework.checker.lock.qual.GuardSatisfied;
017import org.checkerframework.checker.nullness.qual.EnsuresKeyForIf;
018import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
019import org.checkerframework.checker.nullness.qual.KeyFor;
020import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
021import org.checkerframework.checker.nullness.qual.Nullable;
022import org.checkerframework.dataflow.qual.Pure;
023import org.checkerframework.dataflow.qual.SideEffectFree;
024
025/**
026 * Represents additional information about a VarInfo that front ends tell Daikon. For example,
027 * whether order matters in a collection. This is immutable and interned.
028 */
029public final class VarInfoAux implements Cloneable, Serializable {
030  static final long serialVersionUID = 20020614L;
031
032  /** General debug tracer. */
033  public static final Logger debug = Logger.getLogger("daikon.VarInfoAux");
034
035  public static final String TRUE = "true";
036  public static final String FALSE = "false";
037
038  // The below are all the possible keys for the map, and values tend to be
039  // binary.  So could we make it a packed binary array?
040
041  // All of the keys below should be @KeyFor("this.map") but that isn't a valid expression.
042  // See https://tinyurl.com/cfissue/877
043
044  /**
045   * Whether the elements in this collection are all the meaningful elements, or whether there is a
046   * null at the end of this collection that ends the collection.
047   */
048  public static final String NULL_TERMINATING = "nullTerminating";
049
050  /**
051   * Whether this variable is a parameter to a method, or derived from a parameter to a method. By
052   * default, if p is a parameter, then some EXIT invariants related to p aren't printed. However,
053   * this does not affect the computation of invariants.
054   *
055   * <p>Frontends are responsible for setting if p is a parameter and if p.a is a parameter. In
056   * Java, p.a is not a parameter, whereas in IOA, it is.
057   */
058  public static final String IS_PARAM = "isParam";
059
060  /** Whether repeated elements can exist in this collection. */
061  public static final String HAS_DUPLICATES = "hasDuplicates";
062
063  /** Whether order matters. */
064  public static final String HAS_ORDER = "hasOrder";
065
066  /** Whether taking the size of this matters. */
067  public static final String HAS_SIZE = "hasSize";
068
069  /** Whether null has a special meaning for this variable or its members. */
070  public static final String HAS_NULL = "hasNull";
071
072  /** Indicates the minimum size of the vector, if there's any. */
073  public static final String MINIMUM_LENGTH = "minlength";
074
075  /** Indicates the maximum size of the vector, if there's any. */
076  public static final String MAXIMUM_LENGTH = "maxlength";
077
078  /** Indicates the minimum value of the scalar variable or the vector elements, if there's any. */
079  public static final String MINIMUM_VALUE = "minvalue";
080
081  /** Indicates the maximum value of the scalar variable or the vector elements, if there's any. */
082  public static final String MAXIMUM_VALUE = "maxvalue";
083
084  /**
085   * Indicates the valid values (using string representation) for the elements of the vector, if
086   * there's any. Values are enclosed in square brackets, and each element is quoted separately,
087   * e.g.: ["a" "b"]. Parsing will be done upon call of the getList() method.
088   *
089   * @see #getList(String)
090   */
091  public static final String VALID_VALUES = "validvalues";
092
093  /**
094   * Whether this variable is an inline structure. By default, a variable is a reference to a
095   * structure (class). If it is an inlined structure (or array), it doesn't make sense to look for
096   * invariants over its hashcode. Front ends include references to inlined structures as variables
097   * because some tools that follow daikon need other information about the variable.
098   */
099  public static final String IS_STRUCT = "isStruct";
100
101  /** Whether this variable is known to be non-null, such as "this" in a Java program. */
102  public static final String IS_NON_NULL = "isNonNull";
103
104  /**
105   * Java-specific. The package name of the class that declares this variable, if the variable is a
106   * field. If it's not a field of some class, the value of this key is "no_package_name_string".
107   */
108  public static final String PACKAGE_NAME = "declaringClassPackageName";
109
110  /** See {@link #PACKAGE_NAME}. */
111  public static final String NO_PACKAGE_NAME = "no_package_name_string";
112
113  /** Interned default options. */
114  private static @Interned VarInfoAux theDefault = new VarInfoAux().intern();
115
116  /** Contains the actual hashMap for this. */
117  @SuppressWarnings("serial")
118  private Map<@Interned String, @Interned String> map;
119
120  /** Whether this is interned. */
121  private boolean isInterned = false;
122
123  /** Make the default map here. */
124  private VarInfoAux() {
125    HashMap<@Interned String, @Interned String> defaultMap = new HashMap<>();
126    // The following are default values.
127    defaultMap.put(HAS_DUPLICATES, TRUE);
128    defaultMap.put(HAS_ORDER, TRUE);
129    defaultMap.put(HAS_SIZE, TRUE);
130    defaultMap.put(HAS_NULL, TRUE);
131    defaultMap.put(NULL_TERMINATING, TRUE);
132    defaultMap.put(IS_PARAM, FALSE);
133    defaultMap.put(PACKAGE_NAME, NO_PACKAGE_NAME);
134    defaultMap.put(IS_STRUCT, FALSE);
135    defaultMap.put(IS_NON_NULL, FALSE);
136    this.map = defaultMap;
137    this.isInterned = false;
138  }
139
140  /**
141   * Create a new VarInfoAux with default options.
142   *
143   * @param map the map from property names to values
144   */
145  private VarInfoAux(Map<@Interned String, @Interned String> map) {
146    this.map = map;
147    this.isInterned = false;
148  }
149
150  /**
151   * Return an interned VarInfoAux that represents a given string. Elements are separated by commas,
152   * in the form:
153   *
154   * <p>x = a, "a key" = "a value"
155   *
156   * <p>Parse allow for quoted elements. White space to the left and right of keys and values do not
157   * matter, but inbetween does.
158   */
159  public static @Interned VarInfoAux parse(String inString) throws IOException {
160    Reader inStringReader = new StringReader(inString);
161    StreamTokenizer tok = new StreamTokenizer(inStringReader);
162    tok.resetSyntax();
163    tok.wordChars(0, Integer.MAX_VALUE);
164    tok.quoteChar('\"');
165    tok.whitespaceChars(' ', ' ');
166    tok.ordinaryChar('[');
167    tok.ordinaryChar(']');
168    tok.ordinaryChars(',', ',');
169    tok.ordinaryChars('=', '=');
170    @SuppressWarnings("serial")
171    Map<@Interned String, @Interned String> map = theDefault.map;
172
173    String key = "";
174    String value = "";
175    boolean seenEqual = false;
176    boolean insideVector = false;
177    for (int tokInfo = tok.nextToken();
178        tokInfo != StreamTokenizer.TT_EOF;
179        tokInfo = tok.nextToken()) {
180      @SuppressWarnings("interning") // initialization-checking pattern
181      boolean mapUnchanged = (map == theDefault.map);
182      if (mapUnchanged) {
183        // We use default values if none are specified.  We initialize
184        // here rather than above to save time when there are no tokens.
185
186        map = new HashMap<>(theDefault.map);
187      }
188
189      @Interned String token;
190      if (tok.ttype == StreamTokenizer.TT_WORD || tok.ttype == '\"') {
191        assert tok.sval != null
192            : "@AssumeAssertion(nullness): representation invariant of StreamTokenizer";
193        token = tok.sval.trim().intern();
194      } else {
195        token = ((char) tok.ttype + "").intern();
196      }
197
198      debug.fine("Token info: " + tokInfo + " " + token);
199
200      if (token == "[") { // interned
201        if (!seenEqual) {
202          throw new IOException("Aux option did not contain an '='");
203        }
204        if (insideVector) {
205          throw new IOException("Vectors cannot be nested in an aux option");
206        }
207        if (value.length() > 0) {
208          throw new IOException("Cannot mix scalar and vector values");
209        }
210
211        insideVector = true;
212        value = "";
213      } else if (token == "]") { // interned
214        if (!insideVector) {
215          throw new IOException("']' without preceding '['");
216        }
217        insideVector = false;
218      } else if (token == ",") { // interned
219        if (!seenEqual) {
220          throw new IOException("Aux option did not contain an '='");
221        }
222        if (insideVector) {
223          throw new IOException("',' cannot be used inside a vector");
224        }
225        map.put(key.intern(), value.intern());
226        key = "";
227        value = "";
228        seenEqual = false;
229      } else if (token == "=") { // interned
230        if (seenEqual) {
231          throw new IOException("Aux option contained more than one '='");
232        }
233        if (insideVector) {
234          throw new IOException("'=' cannot be used inside a vector");
235        }
236        seenEqual = true;
237      } else {
238        if (!seenEqual) {
239          key = (key + " " + token).trim();
240        } else if (insideVector) {
241          value = value + " \"" + token.trim() + "\"";
242        } else {
243          value = (value + " " + token).trim();
244        }
245      }
246    }
247
248    if (seenEqual) {
249      map.put(key.intern(), value.intern());
250    }
251
252    // Interning
253    VarInfoAux result = new VarInfoAux(map).intern();
254    assert interningMap != null
255        : "@AssumeAssertion(nullness):  application invariant:  postcondition of intern(), which"
256            + " was just called";
257    if (debug.isLoggable(Level.FINE)) {
258      debug.fine("New parse " + result);
259      debug.fine("Intern table size: " + interningMap.size());
260    }
261    return result;
262  }
263
264  /**
265   * Create a new VarInfoAux with default options.
266   *
267   * @return a new VarInfoAux with default options
268   */
269  public static @Interned VarInfoAux getDefault() {
270    return theDefault;
271  }
272
273  /** Map for interning. */
274  private static @MonotonicNonNull Map<VarInfoAux, @Interned VarInfoAux> interningMap =
275      // Static fields might not be initialized before static methods (which
276      // call instance methods) are called, so don't bother to initialize here.
277      null;
278
279  /** Special handler for deserialization. */
280  private @Interned Object readResolve() throws ObjectStreamException {
281    isInterned = false;
282    Map<@Interned String, @Interned String> newMap = new HashMap<>();
283    for (String key : map.keySet()) {
284      newMap.put(key.intern(), map.get(key).intern());
285    }
286    map = newMap;
287    return this.intern();
288  }
289
290  /** Creates and returns a copy of this. */
291  // Default implementation to quiet Findbugs.
292  @SideEffectFree
293  @Override
294  public VarInfoAux clone(@GuardSatisfied VarInfoAux this) throws CloneNotSupportedException {
295    VarInfoAux result = (VarInfoAux) super.clone();
296    result.isInterned = false;
297    return result;
298  }
299
300  @SideEffectFree
301  @Override
302  public String toString(@GuardSatisfied VarInfoAux this) {
303    return map.toString();
304  }
305
306  @Pure
307  @Override
308  public int hashCode(@GuardSatisfied VarInfoAux this) {
309    return map.hashCode();
310  }
311
312  @EnsuresNonNullIf(result = true, expression = "#1")
313  @Pure
314  @Override
315  public boolean equals(@GuardSatisfied VarInfoAux this, @GuardSatisfied @Nullable Object o) {
316    if (o instanceof VarInfoAux) {
317      return equalsVarInfoAux((VarInfoAux) o);
318    } else {
319      return false;
320    }
321  }
322
323  @Pure
324  public boolean equalsVarInfoAux(@GuardSatisfied VarInfoAux this, @GuardSatisfied VarInfoAux o) {
325    return this.map.equals(o.map);
326  }
327
328  // Two variables should not be put in the same equality set unless they have the same flags.  For
329  // example, suppose that variable "this" is known to be non-null, but it is initially equal to
330  // another variable.  Then NonZero will not be instantiated over "this", and when the equality set
331  // is broken, there will be no NonZero invariant to copy to the other variable.  We only need to
332  // check equality for every aux field that might affect methods such as instantiate_ok.
333  @SuppressWarnings("keyfor") // https://tinyurl.com/cfissue/877
334  @Pure
335  public boolean equals_for_instantiation(
336      @GuardSatisfied VarInfoAux this, @GuardSatisfied VarInfoAux o) {
337    return this.getValue(HAS_DUPLICATES).equals(o.getValue(HAS_DUPLICATES))
338        && this.getValue(HAS_ORDER).equals(o.getValue(HAS_ORDER))
339        && this.getValue(HAS_SIZE).equals(o.getValue(HAS_SIZE))
340        && this.getValue(HAS_NULL).equals(o.getValue(HAS_NULL))
341        && Objects.equals(this.getValueOrNull(MINIMUM_LENGTH), o.getValueOrNull(MINIMUM_LENGTH))
342        && Objects.equals(this.getValueOrNull(MAXIMUM_LENGTH), o.getValueOrNull(MAXIMUM_LENGTH))
343        && Objects.equals(this.getValueOrNull(MINIMUM_VALUE), o.getValueOrNull(MINIMUM_VALUE))
344        && Objects.equals(this.getValueOrNull(MAXIMUM_VALUE), o.getValueOrNull(MAXIMUM_VALUE))
345        && Objects.equals(this.getValueOrNull(VALID_VALUES), o.getValueOrNull(VALID_VALUES))
346        && this.getValue(IS_STRUCT).equals(o.getValue(IS_STRUCT))
347        && this.getValue(IS_NON_NULL).equals(o.getValue(IS_NON_NULL));
348  }
349
350  /**
351   * Returns canonical representation of this. Doesn't need to be called by outside classes because
352   * these are always interned.
353   */
354  @SuppressWarnings({"interning", "cast"}) // intern method
355  private @Interned VarInfoAux intern() {
356
357    for (@Interned String key : map.keySet()) {
358      assert key == key.intern();
359      assert map.get(key) == map.get(key).intern();
360    }
361
362    if (this.isInterned) {
363      return (@Interned VarInfoAux) this; // cast is redundant (except in JSR 308)
364    }
365
366    // Necessary because various static methods call intern(), possibly before static field
367    // interningMap's initializer would be executed.
368    if (interningMap == null) {
369      interningMap = new HashMap<>();
370    }
371
372    @Interned VarInfoAux result;
373    if (interningMap.containsKey(this)) {
374      result = interningMap.get(this);
375    } else {
376      // Intern values in map
377      interningMap.put(this, (@Interned VarInfoAux) this); // cast is redundant (except in JSR 308)
378      result = (@Interned VarInfoAux) this; // cast is redundant (except in JSR 308)
379      this.isInterned = true;
380    }
381    return result;
382  }
383
384  /**
385   * Returns the integer value associated with a key, assuming it is defined. It is recommended to
386   * check that it is defined first with {@link #hasValue(String)}.
387   *
388   * @throws RuntimeException if the key is not defined
389   * @throws NumberFormatException if the value of the key cannot be parsed as an integer
390   * @see #hasValue(String)
391   */
392  @Pure
393  public int getInt(@KeyFor("this.map") String key) {
394    if (!hasValue(key)) {
395      throw new RuntimeException(String.format("Key '%s' is not defined", key));
396    }
397    return Integer.parseInt(getValue(key));
398  }
399
400  /**
401   * Returns the string array associated with a key, assuming it is defined. It is recommended to
402   * check that it is defined first with {@link #hasValue(String)}.
403   *
404   * @throws RuntimeException if the key is not defined
405   * @see #hasValue(String)
406   */
407  public String[] getList(@KeyFor("this.map") String key) {
408    try {
409      if (!hasValue(key)) {
410        throw new RuntimeException(String.format("Key '%s' is not defined", key));
411      }
412      final String sValue = getValue(key);
413      StreamTokenizer tok = new StreamTokenizer(new StringReader(sValue));
414      tok.quoteChar('"');
415      tok.whitespaceChars(' ', ' ');
416      ArrayList<String> lValues = new ArrayList<>();
417
418      int tokInfo = tok.nextToken();
419      while (tokInfo != StreamTokenizer.TT_EOF) {
420        if (tok.ttype != '"') {
421          continue;
422        }
423        assert tok.sval != null
424            : "@AssumeAssertion(nullness)"; // tok.type == '"' guarantees not null
425        lValues.add(tok.sval.trim());
426        tokInfo = tok.nextToken();
427      }
428      return lValues.toArray(new String[] {});
429    } catch (IOException ex) {
430      throw new RuntimeException(String.format("Parsing for key '%s' failed", key), ex);
431    }
432  }
433
434  /** Returns the value for the given key, which must be present in the map. */
435  public String getValue(@GuardSatisfied VarInfoAux this, @KeyFor("this.map") String key) {
436    return map.get(key);
437  }
438
439  /** Returns the value for the given key, or null if it is not present. */
440  public @Nullable String getValueOrNull(@GuardSatisfied VarInfoAux this, String key) {
441    return map.get(key);
442  }
443
444  /** Return {@code true} if the value for the given key is defined, and {@code false} otherwise. */
445  @Pure
446  @EnsuresKeyForIf(result = true, expression = "#1", map = "map")
447  public boolean hasValue(String key) {
448    return map.containsKey(key);
449  }
450
451  public boolean getFlag(@KeyFor("this.map") String key) {
452    assert map.containsKey(key);
453    Object value = map.get(key);
454    assert value == TRUE || value == FALSE;
455    return value.equals(TRUE);
456  }
457
458  /** Return a new VarInfoAux with the desired value set. Does not modify this. */
459  public @Interned VarInfoAux setValue(String key, String value) {
460    HashMap<@Interned String, @Interned String> newMap = new HashMap<>(this.map);
461    newMap.put(key.intern(), value.intern());
462    return new VarInfoAux(newMap).intern();
463  }
464
465  /**
466   * Converts the integer {@code value} to a String before invoking {@link #setValue(String,
467   * String)}.
468   */
469  public VarInfoAux setInt(String key, int value) {
470    return setValue(key, Integer.toString(value));
471  }
472
473  /**
474   * See {@link #NULL_TERMINATING}.
475   *
476   * @see #NULL_TERMINATING
477   */
478  @SuppressWarnings("keyfor") // NULL_TERMINATING is always a key
479  @Pure
480  public boolean nullTerminating() {
481    return getFlag(NULL_TERMINATING);
482  }
483
484  /**
485   * See {@link #IS_PARAM}.
486   *
487   * @see #IS_PARAM
488   */
489  @SuppressWarnings("keyfor") // IS_PARAM is always a key
490  @Pure
491  public boolean isParam() {
492    return getFlag(IS_PARAM);
493  }
494
495  /**
496   * See {@link #PACKAGE_NAME}.
497   *
498   * @see #PACKAGE_NAME
499   */
500  @SuppressWarnings("keyfor") // PACKAGE_NAME is always a key
501  @Pure
502  public boolean packageName() {
503    return getFlag(PACKAGE_NAME);
504  }
505
506  /**
507   * See {@link #HAS_DUPLICATES}.
508   *
509   * @see #HAS_DUPLICATES
510   */
511  @SuppressWarnings("keyfor") // HAS_DUPLICATES is always a key
512  @Pure
513  public boolean hasDuplicates() {
514    return getFlag(HAS_DUPLICATES);
515  }
516
517  /**
518   * See {@link #HAS_ORDER}.
519   *
520   * @see #HAS_ORDER
521   */
522  @SuppressWarnings("keyfor") // HAS_ORDER is always a key
523  @Pure
524  public boolean hasOrder() {
525    return getFlag(HAS_ORDER);
526  }
527
528  /**
529   * See {@link #HAS_SIZE}.
530   *
531   * @see #HAS_SIZE
532   */
533  @SuppressWarnings("keyfor") // HAS_SIZE is always a key
534  @Pure
535  public boolean hasSize() {
536    return getFlag(HAS_SIZE);
537  }
538
539  /**
540   * See {@link #HAS_NULL}.
541   *
542   * @see #HAS_NULL
543   */
544  @SuppressWarnings("keyfor") // HAS_NULL is always a key
545  @Pure
546  public boolean hasNull() {
547    return getFlag(HAS_NULL);
548  }
549
550  /**
551   * See {@link #IS_STRUCT}.
552   *
553   * @see #IS_STRUCT
554   */
555  @SuppressWarnings("keyfor") // IS_STRUCT is always a key
556  @Pure
557  public boolean isStruct() {
558    return getFlag(IS_STRUCT);
559  }
560
561  /**
562   * See {@link #IS_NON_NULL}.
563   *
564   * @see #IS_NON_NULL
565   */
566  @SuppressWarnings("keyfor") // IS_NON_NULL is always a key
567  @Pure
568  public boolean isNonNull() {
569    return getFlag(IS_NON_NULL);
570  }
571}