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}