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