001package daikon;
002
003import java.io.LineNumberReader;
004import java.io.ObjectStreamException;
005import java.io.Serializable;
006import java.io.StreamTokenizer;
007import java.io.StringReader;
008import java.util.ArrayList;
009import java.util.HashMap;
010import java.util.HashSet;
011import java.util.LinkedHashSet;
012import java.util.List;
013import org.checkerframework.checker.interning.qual.Interned;
014import org.checkerframework.checker.lock.qual.GuardSatisfied;
015import org.checkerframework.checker.nullness.qual.Nullable;
016import org.checkerframework.dataflow.qual.Pure;
017import org.checkerframework.dataflow.qual.SideEffectFree;
018import org.plumelib.util.Intern;
019import org.plumelib.util.StringsPlume;
020
021/**
022 * Represents the type of a variable, for its declared type, dtrace file representation, and
023 * internal representations. ProgLangTypes are interned, so they can be == compared.
024 */
025
026// I could also consider using Class; however:
027//  * that ties this to a Java front end, as Class can't represent types of
028//    (say) C variables.  (not a compelling problem)
029//  * that loads the class, which requies that all classes available at
030//    run time be available at inference time.  (not a compelling problem)
031//  * Class does not represent inheritance (but I can do that myself);
032//    and see isAssignableFrom, which might do all I need.
033//  * Class has no "dimensions" field.  isArray() exists, however, as does
034//    getComponentType().  I can always determine the number of dimensions
035//    from the number of leading "[" in the name, and dimensions is only
036//    really needed for parse_value.  parse_value can do literal checks and
037//    elementType() can use the name, I suppose.  Or maybe try to
038//    instantiate something, though that seems dicier.
039
040// Problem:  this doesn't currently represent inheritance, coercability, or
041// other relationships over the base types.
042
043// integral_types = ("int", "char", "float", "double", "integral", "boolean")
044// known_types = integral_types + ("pointer", "address")
045
046public final @Interned class ProglangType implements Serializable {
047  static final long serialVersionUID = 20020122L;
048
049  /** Maps from a base type name to its ProglangTypes and arrays with that base. */
050  private static HashMap<@Interned String, List<ProglangType>> all_known_types = new HashMap<>();
051
052  /**
053   * The set of (interned) names of classes that implement java.util.List. For a Java class, this is
054   * a {@code @BinaryName}, but when Daikon is processing programs written in other languages, it
055   * can be arbitrary.
056   */
057  public static HashSet<String> list_implementors = new LinkedHashSet<>();
058
059  /**
060   * If true, treat 32 bit values whose high bit is on, as a negative number (rather than as a 32
061   * bit unsigned).
062   */
063  public static boolean dkconfig_convert_to_signed = false;
064
065  static {
066    // XXX for now, hard-code these list-implementing types. We should
067    // make the front-end dump the language-specific ones into .decls.
068    list_implementors.add("java.util.List");
069    list_implementors.add("java.util.AbstractList");
070    list_implementors.add("java.util.Vector");
071    list_implementors.add("java.util.ArrayList");
072    list_implementors.add("java.util.AbstractSequentialList");
073    list_implementors.add("java.util.LinkedList");
074    list_implementors.add("java.util.Stack");
075  }
076
077  // For a Java class, this is a binary name.  When Daikon is processing
078  // other languages, the format is arbitrary.
079  private @Interned String base; // interned name of base type
080
081  public @Interned String base() {
082    return base;
083  }
084
085  /** Number of dimensions. Zero for a non-array. */
086  private int dimensions;
087
088  /**
089   * Return the number of dimensions (zero for a non-array).
090   *
091   * @return the number of dimensions
092   */
093  public int dimensions() {
094    return dimensions;
095  }
096
097  @Pure
098  public boolean isArray() {
099    return dimensions > 0;
100  }
101
102  /**
103   * No public constructor: use parse() instead to get a canonical representation. basetype should
104   * be interned.
105   *
106   * @param basetype the base type
107   * @param dimensions the number of dimensions
108   */
109  @SuppressWarnings("super.invocation") // never called twice with the same arguments
110  private ProglangType(@Interned String basetype, int dimensions) {
111    assert basetype == basetype.intern();
112    this.base = basetype;
113    this.dimensions = dimensions;
114  }
115
116  /**
117   * This can't be a constructor because it returns a canonical representation (that can be compared
118   * with ==), not necessarily a new object.
119   *
120   * @param rep the name of the type, optionally suffixed by (possibly multiple) "[]"
121   */
122  public static ProglangType parse(String rep) {
123    String new_base = rep;
124    int dims = 0;
125    while (new_base.endsWith("[]")) {
126      dims++;
127      new_base = new_base.substring(0, new_base.length() - 2);
128    }
129    return intern(new_base.intern(), dims);
130  }
131
132  /**
133   * Like parse, but normalizes representation types (such as converting "float" to "double"), in
134   * order to return real file representation types even if the file contains something slightly
135   * different than the prescribed format.
136   */
137  public static ProglangType rep_parse(String rep) {
138    ProglangType candidate = parse(rep);
139    if ((candidate.base == "address") || (candidate.base == "pointer")) { // interned
140      return intern("hashcode", candidate.dimensions);
141    } else if (candidate.base == "float") { // interned
142      return intern("double", candidate.dimensions);
143    } else if (candidate.base == "string") { // interned
144      return intern("java.lang.String", candidate.dimensions);
145    } else {
146      return candidate;
147    }
148  }
149
150  /** Convert a file representation type to an internal representation type. */
151  public ProglangType fileTypeToRepType() {
152    if ((base == BASE_HASHCODE)
153        || (base == BASE_BOOLEAN)
154        || (base == BASE_LONG)
155        || (base == BASE_LONG_LONG)
156        || (base == BASE_SHORT)) {
157      return intern(BASE_INT, dimensions);
158    }
159    return this;
160  }
161
162  /**
163   * For serialization; indicates which object to return instead of the one that was just read from
164   * the file. This obviates the need to write a readObject method that interns the interned fields
165   * (just "base").
166   */
167  public Object readResolve() throws ObjectStreamException {
168    return intern(base.intern(), dimensions);
169  }
170
171  // Is this necessary?  It will be inherited from Object.
172  // @EnsuresNonNullIf(result=true, expression="#1")
173  // public boolean equals(@Nullable Object o) {
174  //   return this == o;
175  // }
176
177  // THIS CODE IS A HOT SPOT (~33% of run time) [as of January 2002].
178  /**
179   * Searches for an array type with the given base and number of dimensions.
180   *
181   * @param t_base the base type; must be interned
182   * @param t_dims the number of dimensions
183   * @return an array type with the given base and number of dimensions, or null
184   */
185  private static @Nullable ProglangType find(@Interned String t_base, int t_dims) {
186    // Disabled for performance reasons! this assertion is sound though:
187    //    assert t_base == t_base.intern();
188
189    // the string maps us to a vec of all ProglangTypes with that base
190    List<ProglangType> v = all_known_types.get(t_base);
191    if (v == null) {
192      return null;
193    }
194
195    // now search for the right dimension
196    for (ProglangType candidate : v) {
197      if (candidate.dimensions() == t_dims) {
198        return candidate;
199      }
200    }
201
202    return null;
203  }
204
205  // private ProglangType intern() {
206  //   return ProglangType.intern(this);
207  // }
208
209  // We shouldn't even get to the point of interning; we should not
210  // have created the ProglangType if it isn't canonical.
211  // private static ProglangType intern(ProglangType t) {
212  //   ProglangType candidate = find(t.base, t.dimensions);
213  //   if (candidate != null)
214  //     return candidate;
215  //   all_known_types.add(t);
216  //   return t;
217  // }
218
219  private static ProglangType intern(@Interned String t_base, int t_dims) {
220    // Disabled for performance reasons! this assertion is sound though:
221    //    assert t_base == t_base.intern();
222    ProglangType existing = find(t_base, t_dims);
223    if (existing != null) {
224      return existing;
225    }
226    @SuppressWarnings("interning") // test above did not find one, so the new one is interned
227    @Interned ProglangType result = new ProglangType(t_base, t_dims);
228
229    List<ProglangType> v =
230        all_known_types.computeIfAbsent(t_base, __ -> new ArrayList<ProglangType>());
231
232    v.add(result);
233
234    return result;
235  }
236
237  /**
238   * Returns the type of elements of this. They may themselves be arrays if this is
239   * multidimensional.
240   */
241  public ProglangType elementType(@GuardSatisfied ProglangType this) {
242    // Presume that if there are no dimensions, this must be a list of
243    // objects.  Callers should really find this out from other information
244    // in the variable, but this will old code that relied on the pseudo
245    // dimensions of lists to work
246    if (dimensions == 0) {
247      return OBJECT;
248    }
249    assert base == base.intern() : "Uninterned base " + base;
250    return ProglangType.intern(base, dimensions - 1);
251  }
252
253  // All these variables are public because the way to check the base of an
254  // array is type.base() == ProglangType.BASE_CHAR.
255
256  // Primitive types
257  static final @Interned String BASE_BOOLEAN = "boolean";
258  static final @Interned String BASE_BYTE = "byte";
259  static final @Interned String BASE_CHAR = "char";
260  static final @Interned String BASE_DOUBLE = "double";
261  static final @Interned String BASE_FLOAT = "float";
262  static final @Interned String BASE_INT = "int";
263  static final @Interned String BASE_LONG = "long";
264  static final @Interned String BASE_LONG_LONG = "long long int";
265  static final @Interned String BASE_SHORT = "short";
266
267  // Nonprimitive types
268  static final @Interned String BASE_OBJECT = "java.lang.Object";
269  static final @Interned String BASE_STRING = "java.lang.String";
270  static final @Interned String BASE_INTEGER = "java.lang.Integer";
271  // "hashcode", "address", and "pointer" are identical;
272  // "hashcode" is preferred.
273  static final @Interned String BASE_HASHCODE = "hashcode";
274  // static final @Interned String BASE_ADDRESS = "address";
275  // static final @Interned String BASE_POINTER = "pointer";
276
277  // avoid duplicate allocations
278  // No need for the Integer versions; use Long instead.
279  // static final @Interned Integer IntegerZero = Intern.internedInteger(0);
280  // static final @Interned Integer IntegerOne = Intern.internedInteger(1);
281  static final @Interned Long LongZero = Intern.internedLong(0);
282  static final @Interned Long LongOne = Intern.internedLong(1);
283  static final @Interned Double DoubleZero = Intern.internedDouble(0);
284  static final @Interned Double DoubleNaN = Intern.internedDouble(Double.NaN);
285  static final @Interned Double DoublePositiveInfinity =
286      Intern.internedDouble(Double.POSITIVE_INFINITY);
287  static final @Interned Double DoubleNegativeInfinity =
288      Intern.internedDouble(Double.NEGATIVE_INFINITY);
289
290  /*
291   *  Now that all other static initialisers are done, it is safe to
292   *  construct some instances, also statically.  Note that these are
293   *  down here to delay their instantiation until after we have
294   *  created (e.g.) the list_implementors object, etc.  */
295
296  // Use == to compare, because ProglangType objects are interned.
297  public static final ProglangType INT = ProglangType.intern("int", 0);
298  public static final ProglangType LONG_PRIMITIVE = ProglangType.intern("long", 0);
299  public static final ProglangType DOUBLE = ProglangType.intern("double", 0);
300  public static final ProglangType CHAR = ProglangType.intern("char", 0);
301  public static final ProglangType STRING = ProglangType.intern("java.lang.String", 0);
302  public static final ProglangType INT_ARRAY = ProglangType.intern("int", 1);
303  public static final ProglangType LONG_PRIMITIVE_ARRAY = ProglangType.intern("long", 1);
304  public static final ProglangType DOUBLE_ARRAY = ProglangType.intern("double", 1);
305  public static final ProglangType CHAR_ARRAY = ProglangType.intern("char", 1);
306  public static final ProglangType STRING_ARRAY = ProglangType.intern("java.lang.String", 1);
307  public static final ProglangType CHAR_ARRAY_ARRAY = ProglangType.intern("char", 2);
308
309  public static final ProglangType INTEGER = ProglangType.intern("java.lang.Integer", 0);
310  public static final ProglangType LONG_OBJECT = ProglangType.intern("java.lang.Long", 0);
311
312  public static final ProglangType OBJECT = ProglangType.intern("java.lang.Object", 0);
313
314  public static final ProglangType BOOLEAN = ProglangType.intern("boolean", 0);
315  public static final ProglangType HASHCODE = ProglangType.intern("hashcode", 0);
316  public static final ProglangType BOOLEAN_ARRAY = ProglangType.intern("boolean", 1);
317  public static final ProglangType HASHCODE_ARRAY = ProglangType.intern("hashcode", 1);
318
319  /**
320   * Like Long.parseLong(), but transform large unsigned longs (as from C's unsigned long long) into
321   * the corresponding negative Java longs. Also handles hex values that begin with 0x.
322   */
323  @SuppressWarnings("ConstantOverflow")
324  private static long myParseLong(String value) {
325    if ((value.length() == 20 && value.charAt(0) == '1')
326        || (value.length() == 19
327            && value.charAt(0) == '9'
328            && value.compareTo("9223372036854775808") >= 0)) {
329      // Oops, we got a large unsigned long, which Java, having
330      // only signed longs, will refuse to parse. We'll have to
331      // turn it into the corresponding negative long value.
332      String rest; // A substring that will be a valid positive long
333      long subtracted; // The amount we effectively subtracted to make it
334      if (value.length() == 20) {
335        rest = value.substring(1);
336        subtracted = 100000L * 100000 * 100000 * 10000; // 10^19; overflows
337      } else {
338        rest = value.substring(1);
339        subtracted = 9L * 100000 * 100000 * 100000 * 1000; // 9*10^18
340      }
341      return Long.parseLong(rest) + subtracted;
342    } else {
343      long val;
344      if ((value.length() > 2) && (value.charAt(0) == '0') && (value.charAt(1) == 'x')) {
345        val = Long.parseLong(value.substring(2), 16);
346      } else {
347        val = Long.parseLong(value);
348      }
349      // presume that 32 bit values are signed
350      if (dkconfig_convert_to_signed
351          && (((val & 0x80000000L) == 0x80000000L) && ((val & 0xFFFFFFFF00000000L) == 0))) {
352        // long orig = val;
353        val |= 0xFFFFFFFF00000000L;
354        // System.out.printf("Warning: converted %d to %d%n", orig, val);
355      }
356      return val;
357    }
358  }
359
360  /**
361   * Given a string representation of a value (of the type represented by this ProglangType), return
362   * the (canonicalized) interpretation of that value.
363   *
364   * <p>This ProglangType (the method receiver) is assumed to be a representation type, not an
365   * arbitrary type in the underlying programming language.
366   *
367   * <p>If the type is an array and there are any nonsensical elements in the array, the entire
368   * array is considered to be nonsensical (indicated by returning null). This is not really
369   * correct, but it is a reasonable path to take for now. (jhp, Feb 12, 2005)
370   */
371  public final @Nullable @Interned Object parse_value(
372      String value, LineNumberReader reader, String filename) {
373    // System.out.println(format() + ".parse(\"" + value + "\")");
374
375    switch (dimensions) {
376      case 0:
377        return parse_value_scalar(value, reader, filename);
378      case 1:
379        return parse_value_array_1d(value, reader, filename);
380      case 2:
381        return parse_value_array_2d(value, reader, filename);
382      default:
383        throw new Error("Can't parse a value of type " + format());
384    }
385  }
386
387  public final @Nullable @Interned Object parse_value_scalar(
388      String value, LineNumberReader reader, String filename) {
389    // System.out.println(format() + ".parse(\"" + value + "\")");
390
391    assert dimensions == 0;
392
393    if (base == BASE_STRING) {
394      if (value.equals("null")) {
395        return null;
396      }
397      // assert value.startsWith("\"") && value.endsWith("\"");
398      if (value.startsWith("\"") && value.endsWith("\"")) {
399        value = value.substring(1, value.length() - 1);
400      } else {
401        // Unfortunately, there is not a convenient way to communicate what
402        // the variable name is, which would make the error message even
403        // more specific.
404        if (!value.startsWith("\"")) {
405          System.out.printf(
406              "Warning: unquoted string value at %s line %d: %s%n",
407              filename, reader.getLineNumber(), value);
408        } else {
409          assert !value.endsWith("\"");
410          System.out.printf(
411              "Warning: unterminated string value at %s line %d: %s%n",
412              filename, reader.getLineNumber(), value);
413        }
414        System.out.printf(
415            "Proceeding anyway.  Please report a bug in the tool that made the data trace file.");
416      }
417      value = StringsPlume.unescapeJava(value);
418      return value.intern();
419    } else if (base == BASE_CHAR) {
420      // This will fail if the character is output as an integer
421      // (as I believe the C front end does).
422      char c;
423      if (value.length() == 1) {
424        c = value.charAt(0);
425      } else if ((value.length() == 2) && (value.charAt(0) == '\\')) {
426        c = StringsPlume.unescapeJava(value).charAt(0);
427      } else if ((value.length() == 4) && (value.charAt(0) == '\\')) {
428        Byte b = Byte.decode("0" + value.substring(1));
429        return Intern.internedLong(b.longValue());
430      } else {
431        throw new IllegalArgumentException("Bad character: " + value);
432      }
433      return Intern.internedLong((int) c);
434    }
435    // When parse_value is called from FileIO.read_ppt_decl, we have
436    // not set file_rep_type. Hence, rep_type is still file_rep_type
437    // and BASE_BOOLEAN is legal.
438    else if ((base == BASE_INT) || (base == BASE_BOOLEAN)) {
439      // File rep type might be int, boolean, or hashcode.
440      // If we had the declared type, we could do error-checking here.
441      // (Example:  no hashcode should be negative, nor any boolean > 1.)
442      if (value.equals("nonsensical")) {
443        return null;
444      }
445      if (value.equals("false") || value.equals("0")) {
446        return LongZero;
447      }
448      if (value.equals("true") || value.equals("1")) {
449        return LongOne;
450      }
451      if (value.equals("null")) {
452        return LongZero;
453      }
454      return Intern.internedLong(myParseLong(value));
455    } else if (base == BASE_DOUBLE) {
456      // Must ignore case, because dfej outputs "NaN", while dfec
457      // outputs "nan".  dfec outputs "nan", because this string
458      // comes from the C++ library.
459      if (value.equalsIgnoreCase("NaN")) {
460        return DoubleNaN;
461      }
462      if (value.equalsIgnoreCase("Infinity") || value.equals("inf")) {
463        return DoublePositiveInfinity;
464      }
465      if (value.equalsIgnoreCase("-Infinity") || value.equals("-inf")) {
466        return DoubleNegativeInfinity;
467      }
468      return Intern.internedDouble(value);
469    } else if ((base == BASE_HASHCODE)
470        || (base == BASE_LONG)
471        || (base == BASE_LONG_LONG)
472        || (base == BASE_SHORT)) {
473      throw new Error("not a rep type: illegal base type " + base);
474    } else {
475      throw new Error("unrecognized base type " + base);
476    }
477  }
478
479  public final @Nullable @Interned Object parse_value_array_1d(
480      String value, LineNumberReader reader, String filename) {
481    // System.out.println(format() + ".parse(\"" + value + "\")");
482
483    String value_orig = value; // we will side-effect the parameter
484
485    // variable is an array
486    assert dimensions == 1;
487
488    value = value.trim();
489
490    // Deal with [] surrounding array output (permits
491    // distinguishing between null and an array containing just null).
492    if (value.startsWith("[") && value.endsWith("]")) {
493      value = value.substring(1, value.length() - 1).trim();
494    } else {
495      throw new IllegalArgumentException("Array values must be enlosed in square brackets");
496    }
497
498    // Elements of value_strings can be null only if base == BASE_STRING.
499    // [I think they can never be null.  -MDE]
500    String[] value_strings;
501    if (value.length() == 0) {
502      value_strings = new String[0];
503    } else if (base == BASE_STRING) {
504      // This properly handles strings containing embedded spaces.
505      List<@Nullable String> v = new ArrayList<>();
506      StreamTokenizer parser = new StreamTokenizer(new StringReader(value));
507      parser.quoteChar('\"');
508      try {
509        while (parser.nextToken() != StreamTokenizer.TT_EOF) {
510          if (parser.ttype == '\"') {
511            v.add(parser.sval);
512          } else if (parser.ttype == StreamTokenizer.TT_WORD) {
513            assert parser.sval != null
514                : "@AssumeAssertion(nullness): dependent: representation invariant of"
515                    + " StreamTokenizer";
516            if (parser.sval.equals("nonsensical")) {
517              return null;
518            }
519            assert parser.sval.equals("null");
520            v.add(null);
521          } else if (parser.ttype == StreamTokenizer.TT_NUMBER) {
522            v.add(Integer.toString((int) parser.nval));
523          } else {
524            System.out.printf(
525                "Warning: at %s line %d%n"
526                    + "  bad ttype %c [int=%d] while parsing %s%n"
527                    + "  Proceeding with value 'null'%n",
528                filename, reader.getLineNumber(), (char) parser.ttype, parser.ttype, value_orig);
529            v.add(null);
530          }
531        }
532      } catch (Exception e) {
533        throw new Error(e);
534      }
535      // Avoid nullness warnings about elements of value_strings
536      @SuppressWarnings("nullness")
537      String[] value_strings_result = v.toArray(new @Nullable String[0]);
538      value_strings = value_strings_result;
539    } else {
540      value_strings = Global.ws_regexp.split(value);
541    }
542    int len = value_strings.length;
543
544    // This big if ... else should deal with all the primitive types --
545    // or at least all the ones that can be rep_types.
546    // ("long" and "short" cannot be rep_types; for simplicity, variables
547    // declared as long or short have the "int" rep_type.)
548    if (base == BASE_INT) {
549      long[] result = new long[len];
550      for (int i = 0; i < len; i++) {
551        if (value_strings[i].equals("nonsensical")) {
552          return null;
553        } else if (value_strings[i].equals("null")) result[i] = 0;
554        else if (value_strings[i].equals("false")) result[i] = 0;
555        else if (value_strings[i].equals("true")) result[i] = 1;
556        else {
557          result[i] = myParseLong(value_strings[i]);
558        }
559      }
560      return Intern.intern(result);
561    } else if (base == BASE_DOUBLE) {
562      double[] result = new double[len];
563      for (int i = 0; i < len; i++) {
564        if (value_strings[i].equals("nonsensical")) {
565          return null;
566        } else if (value_strings[i].equals("null")) {
567          result[i] = 0;
568        } else if (value_strings[i].equalsIgnoreCase("NaN")) {
569          result[i] = Double.NaN;
570        } else if (value_strings[i].equalsIgnoreCase("Infinity")
571            || value_strings[i].equals("inf")) {
572          result[i] = Double.POSITIVE_INFINITY;
573        } else if (value_strings[i].equalsIgnoreCase("-Infinity")
574            || value_strings[i].equals("-inf")) {
575          result[i] = Double.NEGATIVE_INFINITY;
576        } else {
577          result[i] = Double.parseDouble(value_strings[i]);
578        }
579      }
580      return Intern.intern(result);
581    } else if (base == BASE_STRING) {
582      // First, intern each String in the array ...
583      @Interned String[] value_strings_elts_interned = Intern.internStrings(value_strings);
584      // ... then, intern the entire array, and return it
585      return Intern.intern(value_strings_elts_interned);
586    } else {
587      throw new Error("Can't yet parse array of base type " + base);
588    }
589
590    // This is a more general technique; but when will we need
591    // such generality?
592    // // not elementType() because that interns; here, there is no
593    // // need to do the work of interning (I think)
594    // ProglangType elt_type = elementType();
595    // Object[] result = new Object[len];
596    // for (int i = 0; i<len; i++)
597    //   result[i] = ***;
598
599  }
600
601  public final @Nullable @Interned Object parse_value_array_2d(
602      String value, LineNumberReader reader, String filename) {
603    // System.out.println(format() + ".parse(\"" + value + "\")");
604
605    assert dimensions == 2;
606    if (base == BASE_CHAR) {
607      // Array of strings
608      throw new Error("To implement");
609      // value = tuple(eval(value));
610    } else {
611      throw new Error("Can't parse a value of type " + format());
612    }
613  }
614
615  public boolean baseIsPrimitive() {
616    return ((base == BASE_BOOLEAN)
617        || (base == BASE_BYTE)
618        || (base == BASE_CHAR)
619        || (base == BASE_DOUBLE)
620        || (base == BASE_FLOAT)
621        || (base == BASE_INT)
622        || (base == BASE_LONG)
623        || (base == BASE_LONG_LONG)
624        || (base == BASE_SHORT));
625  }
626
627  @Pure
628  public boolean isPrimitive() {
629    return (dimensions == 0) && baseIsPrimitive();
630  }
631
632  // Does not include boolean.  Is that intentional?  (If it were added,
633  // we would need to change isIndex().)
634  public boolean baseIsIntegral() {
635    return ((base == BASE_BYTE)
636        || (base == BASE_CHAR)
637        || (base == BASE_INT)
638        || (base == BASE_LONG)
639        || (base == BASE_LONG_LONG)
640        || (base == BASE_SHORT)
641        || (base == BASE_INTEGER));
642  }
643
644  @Pure
645  public boolean isIntegral() {
646    return (dimensions == 0) && baseIsIntegral();
647  }
648
649  // More efficient than elementType().isIntegral()
650  public boolean elementIsIntegral() {
651    return (dimensions == 1) && baseIsIntegral();
652  }
653
654  public boolean elementIsFloat() {
655    return (dimensions == 1) && baseIsFloat();
656  }
657
658  public boolean elementIsString() {
659    return (dimensions == 1) && baseIsString();
660  }
661
662  // Return true if this variable is sensible as an array index.
663  @Pure
664  public boolean isIndex() {
665    return isIntegral();
666  }
667
668  @Pure
669  public boolean isScalar() {
670    // For reptypes, checking against INT is sufficient, rather than
671    // calling isIntegral().
672    return isIntegral() || (this == HASHCODE) || (this == BOOLEAN);
673  }
674
675  public boolean baseIsScalar() {
676    return baseIsIntegral() || (base == BASE_BOOLEAN) || (base == BASE_HASHCODE);
677  }
678
679  public boolean baseIsBoolean() {
680    return base == BASE_BOOLEAN;
681  }
682
683  public boolean baseIsFloat() {
684    return (base == BASE_DOUBLE) || (base == BASE_FLOAT);
685  }
686
687  @Pure
688  public boolean isFloat() {
689    return (dimensions == 0) && baseIsFloat();
690  }
691
692  /**
693   * Return true if this is java.lang.Object.
694   *
695   * @return true if this is java.lang.Object
696   */
697  @Pure
698  public boolean isObject() {
699    return (dimensions == 0) && baseIsObject();
700  }
701
702  /**
703   * Return true if the base (the final element type) is a reference type rather than integer,
704   * float, or boolean.
705   *
706   * @return true if the base is Object
707   */
708  public boolean baseIsObject() {
709    return !baseIsIntegral() && !baseIsFloat() && !(base == BASE_BOOLEAN);
710  }
711
712  public boolean baseIsString() {
713    return base == BASE_STRING;
714  }
715
716  @Pure
717  public boolean isString() {
718    return (dimensions == 0) && baseIsString();
719  }
720
721  public boolean baseIsHashcode() {
722    return (base == BASE_HASHCODE);
723  }
724
725  @Pure
726  public boolean isHashcode() {
727    return (dimensions == 0) && baseIsHashcode();
728  }
729
730  /** Does this type represent a pointer? Should only be applied to file_rep types. */
731  @Pure
732  public boolean isPointerFileRep() {
733    return base == BASE_HASHCODE;
734  }
735
736  /**
737   * Return true if these two types can be sensibly compared to one another, or if one can be cast
738   * to the other. For instance, int is castable to long, but boolean is not castable to float, and
739   * int is not castable to int[]. This is a reflexive relationship, but not a transitive one
740   * because it might not be true for two children of a superclass, even though it's true for the
741   * superclass.
742   */
743  public boolean comparableOrSuperclassEitherWay(ProglangType other) {
744    if (this == other) { // ProglangType objects are interned
745      return true;
746    }
747    if (this.dimensions != other.dimensions) {
748      return false;
749    }
750    boolean thisIntegral = this.baseIsIntegral();
751    boolean otherIntegral = other.baseIsIntegral();
752    if (thisIntegral && otherIntegral) {
753      return true;
754    }
755    // Make Object castable to everything, except booleans
756    if (((this.base == BASE_OBJECT) && other.baseIsObject()) // interned strings
757        || ((other.base == BASE_OBJECT) && baseIsObject())) { // interned strings
758      return true;
759    }
760
761    return false;
762  }
763
764  /**
765   * Return true if these two types can be sensibly compared to one another, and if non-integral,
766   * whether this could be a superclass of other. A List is comparableOrSuperclassOf to a ArrayList,
767   * but not the other way around. This is a transitive method, but not reflexive.
768   */
769  public boolean comparableOrSuperclassOf(ProglangType other) {
770    if (this == other) { // ProglangType objects are interned
771      return true;
772    }
773    if (this.dimensions != other.dimensions) {
774      return false;
775    }
776    boolean thisIntegral = this.baseIsIntegral();
777    boolean otherIntegral = other.baseIsIntegral();
778    if (thisIntegral && otherIntegral) {
779      return true;
780    }
781    // Make Object castable to everything, except booleans
782    if ((this.base == BASE_OBJECT) && other.baseIsObject()) { // interned strings
783      return true;
784    }
785
786    return false;
787  }
788
789  // For Java programs, a @BinaryName.
790  @SideEffectFree
791  public String format(@GuardSatisfied ProglangType this) {
792    if (dimensions == 0) {
793      return base;
794    }
795
796    StringBuilder sb = new StringBuilder();
797    sb.append(base);
798    for (int i = 0; i < dimensions; i++) {
799      sb.append("[]");
800    }
801    return sb.toString();
802  }
803
804  // For Java programs, a @BinaryName.
805  @SideEffectFree
806  @Override
807  public String toString(@GuardSatisfied ProglangType this) {
808    return format();
809  }
810
811  /**
812   * Returns whether or not this declared type is a function pointer Only valid if the front end
813   * marks the function pointer with the name '*func'.
814   */
815  @Pure
816  public boolean is_function_pointer() {
817    assert base == base.intern();
818    return base == "*func"; // interned
819  }
820}