| /* |
| * Copyright (C) 2007 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.dx.rop.type; |
| |
| import com.android.dx.command.dexer.Main; |
| import com.android.dx.util.Hex; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentMap; |
| |
| /** |
| * Representation of a value type, such as may appear in a field, in a |
| * local, on a stack, or in a method descriptor. Instances of this |
| * class are generally interned and may be usefully compared with each |
| * other using {@code ==}. |
| */ |
| public final class Type implements TypeBearer, Comparable<Type> { |
| /** |
| * Intern table for instances. |
| * |
| * <p>The initial capacity is based on a medium-size project. |
| */ |
| private static final ConcurrentMap<String, Type> internTable = |
| new ConcurrentHashMap<>(10_000, 0.75f, Main.CONCURRENCY_LEVEL); |
| |
| |
| /** basic type constant for {@code void} */ |
| public static final int BT_VOID = 0; |
| |
| /** basic type constant for {@code boolean} */ |
| public static final int BT_BOOLEAN = 1; |
| |
| /** basic type constant for {@code byte} */ |
| public static final int BT_BYTE = 2; |
| |
| /** basic type constant for {@code char} */ |
| public static final int BT_CHAR = 3; |
| |
| /** basic type constant for {@code double} */ |
| public static final int BT_DOUBLE = 4; |
| |
| /** basic type constant for {@code float} */ |
| public static final int BT_FLOAT = 5; |
| |
| /** basic type constant for {@code int} */ |
| public static final int BT_INT = 6; |
| |
| /** basic type constant for {@code long} */ |
| public static final int BT_LONG = 7; |
| |
| /** basic type constant for {@code short} */ |
| public static final int BT_SHORT = 8; |
| |
| /** basic type constant for {@code Object} */ |
| public static final int BT_OBJECT = 9; |
| |
| /** basic type constant for a return address */ |
| public static final int BT_ADDR = 10; |
| |
| /** count of basic type constants */ |
| public static final int BT_COUNT = 11; |
| |
| /** {@code non-null;} instance representing {@code boolean} */ |
| public static final Type BOOLEAN = new Type("Z", BT_BOOLEAN); |
| |
| /** {@code non-null;} instance representing {@code byte} */ |
| public static final Type BYTE = new Type("B", BT_BYTE); |
| |
| /** {@code non-null;} instance representing {@code char} */ |
| public static final Type CHAR = new Type("C", BT_CHAR); |
| |
| /** {@code non-null;} instance representing {@code double} */ |
| public static final Type DOUBLE = new Type("D", BT_DOUBLE); |
| |
| /** {@code non-null;} instance representing {@code float} */ |
| public static final Type FLOAT = new Type("F", BT_FLOAT); |
| |
| /** {@code non-null;} instance representing {@code int} */ |
| public static final Type INT = new Type("I", BT_INT); |
| |
| /** {@code non-null;} instance representing {@code long} */ |
| public static final Type LONG = new Type("J", BT_LONG); |
| |
| /** {@code non-null;} instance representing {@code short} */ |
| public static final Type SHORT = new Type("S", BT_SHORT); |
| |
| /** {@code non-null;} instance representing {@code void} */ |
| public static final Type VOID = new Type("V", BT_VOID); |
| |
| /** {@code non-null;} instance representing a known-{@code null} */ |
| public static final Type KNOWN_NULL = new Type("<null>", BT_OBJECT); |
| |
| /** {@code non-null;} instance representing a subroutine return address */ |
| public static final Type RETURN_ADDRESS = new Type("<addr>", BT_ADDR); |
| |
| |
| /** |
| * {@code non-null;} instance representing |
| * {@code java.lang.annotation.Annotation} |
| */ |
| public static final Type ANNOTATION = |
| new Type("Ljava/lang/annotation/Annotation;", BT_OBJECT); |
| |
| /** {@code non-null;} instance representing {@code java.lang.Class} */ |
| public static final Type CLASS = new Type("Ljava/lang/Class;", BT_OBJECT); |
| |
| /** {@code non-null;} instance representing {@code java.lang.Cloneable} */ |
| public static final Type CLONEABLE = new Type("Ljava/lang/Cloneable;", BT_OBJECT); |
| |
| /** {@code non-null;} instance representing {@code java.lang.invoke.MethodHandle} */ |
| public static final Type METHOD_HANDLE = new Type("Ljava/lang/invoke/MethodHandle;", BT_OBJECT); |
| |
| /** {@code non-null;} instance representing {@code java.lang.Object} */ |
| public static final Type OBJECT = new Type("Ljava/lang/Object;", BT_OBJECT); |
| |
| /** {@code non-null;} instance representing {@code java.io.Serializable} */ |
| public static final Type SERIALIZABLE = new Type("Ljava/io/Serializable;", BT_OBJECT); |
| |
| /** {@code non-null;} instance representing {@code java.lang.String} */ |
| public static final Type STRING = new Type("Ljava/lang/String;", BT_OBJECT); |
| |
| /** {@code non-null;} instance representing {@code java.lang.Throwable} */ |
| public static final Type THROWABLE = new Type("Ljava/lang/Throwable;", BT_OBJECT); |
| |
| /** |
| * {@code non-null;} instance representing {@code java.lang.Boolean}; the |
| * suffix on the name helps disambiguate this from the instance |
| * representing a primitive type |
| */ |
| public static final Type BOOLEAN_CLASS = new Type("Ljava/lang/Boolean;", BT_OBJECT); |
| |
| /** |
| * {@code non-null;} instance representing {@code java.lang.Byte}; the |
| * suffix on the name helps disambiguate this from the instance |
| * representing a primitive type |
| */ |
| public static final Type BYTE_CLASS = new Type("Ljava/lang/Byte;", BT_OBJECT); |
| |
| /** |
| * {@code non-null;} instance representing {@code java.lang.Character}; the |
| * suffix on the name helps disambiguate this from the instance |
| * representing a primitive type |
| */ |
| public static final Type CHARACTER_CLASS = new Type("Ljava/lang/Character;", BT_OBJECT); |
| |
| /** |
| * {@code non-null;} instance representing {@code java.lang.Double}; the |
| * suffix on the name helps disambiguate this from the instance |
| * representing a primitive type |
| */ |
| public static final Type DOUBLE_CLASS = new Type("Ljava/lang/Double;", BT_OBJECT); |
| |
| /** |
| * {@code non-null;} instance representing {@code java.lang.Float}; the |
| * suffix on the name helps disambiguate this from the instance |
| * representing a primitive type |
| */ |
| public static final Type FLOAT_CLASS = new Type("Ljava/lang/Float;", BT_OBJECT); |
| |
| /** |
| * {@code non-null;} instance representing {@code java.lang.Integer}; the |
| * suffix on the name helps disambiguate this from the instance |
| * representing a primitive type |
| */ |
| public static final Type INTEGER_CLASS = new Type("Ljava/lang/Integer;", BT_OBJECT); |
| |
| /** |
| * {@code non-null;} instance representing {@code java.lang.Long}; the |
| * suffix on the name helps disambiguate this from the instance |
| * representing a primitive type |
| */ |
| public static final Type LONG_CLASS = new Type("Ljava/lang/Long;", BT_OBJECT); |
| |
| /** |
| * {@code non-null;} instance representing {@code java.lang.Short}; the |
| * suffix on the name helps disambiguate this from the instance |
| * representing a primitive type |
| */ |
| public static final Type SHORT_CLASS = new Type("Ljava/lang/Short;", BT_OBJECT); |
| |
| /** |
| * {@code non-null;} instance representing {@code java.lang.Void}; the |
| * suffix on the name helps disambiguate this from the instance |
| * representing a primitive type |
| */ |
| public static final Type VOID_CLASS = new Type("Ljava/lang/Void;", BT_OBJECT); |
| |
| /** {@code non-null;} instance representing {@code boolean[]} */ |
| public static final Type BOOLEAN_ARRAY = new Type("[" + BOOLEAN.descriptor, BT_OBJECT); |
| |
| /** {@code non-null;} instance representing {@code byte[]} */ |
| public static final Type BYTE_ARRAY = new Type("[" + BYTE.descriptor, BT_OBJECT); |
| |
| /** {@code non-null;} instance representing {@code char[]} */ |
| public static final Type CHAR_ARRAY = new Type("[" + CHAR.descriptor, BT_OBJECT); |
| |
| /** {@code non-null;} instance representing {@code double[]} */ |
| public static final Type DOUBLE_ARRAY = new Type("[" + DOUBLE.descriptor, BT_OBJECT); |
| |
| /** {@code non-null;} instance representing {@code float[]} */ |
| public static final Type FLOAT_ARRAY = new Type("[" + FLOAT.descriptor, BT_OBJECT);; |
| |
| /** {@code non-null;} instance representing {@code int[]} */ |
| public static final Type INT_ARRAY = new Type("[" + INT.descriptor, BT_OBJECT); |
| |
| /** {@code non-null;} instance representing {@code long[]} */ |
| public static final Type LONG_ARRAY = new Type("[" + LONG.descriptor, BT_OBJECT); |
| |
| /** {@code non-null;} instance representing {@code Object[]} */ |
| public static final Type OBJECT_ARRAY = new Type("[" + OBJECT.descriptor, BT_OBJECT); |
| |
| /** {@code non-null;} instance representing {@code short[]} */ |
| public static final Type SHORT_ARRAY = new Type("[" + SHORT.descriptor, BT_OBJECT); |
| |
| static { |
| initInterns(); |
| } |
| |
| /** |
| * Put the constant fields, including primitive types in to the intern table. |
| * |
| * <p>Must be called after the types are initialized above. |
| */ |
| private static void initInterns() { |
| putIntern(BOOLEAN); |
| putIntern(BYTE); |
| putIntern(CHAR); |
| putIntern(DOUBLE); |
| putIntern(FLOAT); |
| putIntern(INT); |
| putIntern(LONG); |
| putIntern(SHORT); |
| /* |
| * Note: VOID isn't put in the intern table, since it's special and |
| * shouldn't be found by a normal call to intern(). |
| */ |
| |
| putIntern(ANNOTATION); |
| putIntern(CLASS); |
| putIntern(CLONEABLE); |
| putIntern(METHOD_HANDLE); |
| putIntern(OBJECT); |
| putIntern(SERIALIZABLE); |
| putIntern(STRING); |
| putIntern(THROWABLE); |
| putIntern(BOOLEAN_CLASS); |
| putIntern(BYTE_CLASS); |
| putIntern(CHARACTER_CLASS); |
| putIntern(DOUBLE_CLASS); |
| putIntern(FLOAT_CLASS); |
| putIntern(INTEGER_CLASS); |
| putIntern(LONG_CLASS); |
| putIntern(SHORT_CLASS); |
| putIntern(VOID_CLASS); |
| |
| // Array types |
| putIntern(BOOLEAN_ARRAY); |
| putIntern(BYTE_ARRAY); |
| putIntern(CHAR_ARRAY); |
| putIntern(DOUBLE_ARRAY); |
| putIntern(FLOAT_ARRAY); |
| putIntern(INT_ARRAY); |
| putIntern(LONG_ARRAY); |
| putIntern(OBJECT_ARRAY); |
| putIntern(SHORT_ARRAY); |
| } |
| |
| |
| /** {@code non-null;} field descriptor for the type */ |
| private final String descriptor; |
| |
| /** |
| * basic type corresponding to this type; one of the |
| * {@code BT_*} constants |
| */ |
| private final int basicType; |
| |
| /** |
| * {@code >= -1;} for an uninitialized type, bytecode index that this |
| * instance was allocated at; {@code Integer.MAX_VALUE} if it |
| * was an incoming uninitialized instance; {@code -1} if this |
| * is an <i>inititialized</i> instance |
| */ |
| private final int newAt; |
| |
| /** |
| * {@code null-ok;} the internal-form class name corresponding to |
| * this type, if calculated; only valid if {@code this} is a |
| * reference type and additionally not a return address |
| */ |
| private String className; |
| |
| /** |
| * {@code null-ok;} the type corresponding to an array of this type, if |
| * calculated |
| */ |
| private Type arrayType; |
| |
| /** |
| * {@code null-ok;} the type corresponding to elements of this type, if |
| * calculated; only valid if {@code this} is an array type |
| */ |
| private Type componentType; |
| |
| /** |
| * {@code null-ok;} the type corresponding to the initialized version of |
| * this type, if this instance is in fact an uninitialized type |
| */ |
| private Type initializedType; |
| |
| /** |
| * Returns the unique instance corresponding to the type with the |
| * given descriptor. See vmspec-2 sec4.3.2 for details on the |
| * field descriptor syntax. This method does <i>not</i> allow |
| * {@code "V"} (that is, type {@code void}) as a valid |
| * descriptor. |
| * |
| * @param descriptor {@code non-null;} the descriptor |
| * @return {@code non-null;} the corresponding instance |
| * @throws IllegalArgumentException thrown if the descriptor has |
| * invalid syntax |
| */ |
| public static Type intern(String descriptor) { |
| Type result = internTable.get(descriptor); |
| |
| if (result != null) { |
| return result; |
| } |
| |
| char firstChar; |
| try { |
| firstChar = descriptor.charAt(0); |
| } catch (IndexOutOfBoundsException ex) { |
| // Translate the exception. |
| throw new IllegalArgumentException("descriptor is empty"); |
| } catch (NullPointerException ex) { |
| // Elucidate the exception. |
| throw new NullPointerException("descriptor == null"); |
| } |
| |
| if (firstChar == '[') { |
| /* |
| * Recursively strip away array markers to get at the underlying |
| * type, and build back on to form the result. |
| */ |
| result = intern(descriptor.substring(1)); |
| return result.getArrayType(); |
| } |
| |
| /* |
| * If the first character isn't '[' and it wasn't found in the |
| * intern cache, then it had better be the descriptor for a class. |
| */ |
| |
| int length = descriptor.length(); |
| if ((firstChar != 'L') || |
| (descriptor.charAt(length - 1) != ';')) { |
| throw new IllegalArgumentException("bad descriptor: " + descriptor); |
| } |
| |
| /* |
| * Validate the characters of the class name itself. Note that |
| * vmspec-2 does not have a coherent definition for valid |
| * internal-form class names, and the definition here is fairly |
| * liberal: A name is considered valid as long as it doesn't |
| * contain any of '[' ';' '.' '(' ')', and it has no more than one |
| * '/' in a row, and no '/' at either end. |
| */ |
| |
| int limit = (length - 1); // Skip the final ';'. |
| for (int i = 1; i < limit; i++) { |
| char c = descriptor.charAt(i); |
| switch (c) { |
| case '[': |
| case ';': |
| case '.': |
| case '(': |
| case ')': { |
| throw new IllegalArgumentException("bad descriptor: " + descriptor); |
| } |
| case '/': { |
| if ((i == 1) || |
| (i == (length - 1)) || |
| (descriptor.charAt(i - 1) == '/')) { |
| throw new IllegalArgumentException("bad descriptor: " + descriptor); |
| } |
| break; |
| } |
| } |
| } |
| |
| result = new Type(descriptor, BT_OBJECT); |
| return putIntern(result); |
| } |
| |
| /** |
| * Returns the unique instance corresponding to the type with the |
| * given descriptor, allowing {@code "V"} to return the type |
| * for {@code void}. Other than that one caveat, this method |
| * is identical to {@link #intern}. |
| * |
| * @param descriptor {@code non-null;} the descriptor |
| * @return {@code non-null;} the corresponding instance |
| * @throws IllegalArgumentException thrown if the descriptor has |
| * invalid syntax |
| */ |
| public static Type internReturnType(String descriptor) { |
| try { |
| if (descriptor.equals("V")) { |
| // This is the one special case where void may be returned. |
| return VOID; |
| } |
| } catch (NullPointerException ex) { |
| // Elucidate the exception. |
| throw new NullPointerException("descriptor == null"); |
| } |
| |
| return intern(descriptor); |
| } |
| |
| /** |
| * Returns the unique instance corresponding to the type of the |
| * class with the given name. Calling this method is equivalent to |
| * calling {@code intern(name)} if {@code name} begins |
| * with {@code "["} and calling {@code intern("L" + name + ";")} |
| * in all other cases. |
| * |
| * @param name {@code non-null;} the name of the class whose type |
| * is desired |
| * @return {@code non-null;} the corresponding type |
| * @throws IllegalArgumentException thrown if the name has |
| * invalid syntax |
| */ |
| public static Type internClassName(String name) { |
| if (name == null) { |
| throw new NullPointerException("name == null"); |
| } |
| |
| if (name.startsWith("[")) { |
| return intern(name); |
| } |
| |
| return intern('L' + name + ';'); |
| } |
| |
| /** |
| * Constructs an instance corresponding to an "uninitialized type." |
| * This is a private constructor; use one of the public static |
| * methods to get instances. |
| * |
| * @param descriptor {@code non-null;} the field descriptor for the type |
| * @param basicType basic type corresponding to this type; one of the |
| * {@code BT_*} constants |
| * @param newAt {@code >= -1;} allocation bytecode index |
| */ |
| private Type(String descriptor, int basicType, int newAt) { |
| if (descriptor == null) { |
| throw new NullPointerException("descriptor == null"); |
| } |
| |
| if ((basicType < 0) || (basicType >= BT_COUNT)) { |
| throw new IllegalArgumentException("bad basicType"); |
| } |
| |
| if (newAt < -1) { |
| throw new IllegalArgumentException("newAt < -1"); |
| } |
| |
| this.descriptor = descriptor; |
| this.basicType = basicType; |
| this.newAt = newAt; |
| this.arrayType = null; |
| this.componentType = null; |
| this.initializedType = null; |
| } |
| |
| /** |
| * Constructs an instance corresponding to an "initialized type." |
| * This is a private constructor; use one of the public static |
| * methods to get instances. |
| * |
| * @param descriptor {@code non-null;} the field descriptor for the type |
| * @param basicType basic type corresponding to this type; one of the |
| * {@code BT_*} constants |
| */ |
| private Type(String descriptor, int basicType) { |
| this(descriptor, basicType, -1); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public boolean equals(Object other) { |
| if (this == other) { |
| /* |
| * Since externally-visible types are interned, this check |
| * helps weed out some easy cases. |
| */ |
| return true; |
| } |
| |
| if (!(other instanceof Type)) { |
| return false; |
| } |
| |
| return descriptor.equals(((Type) other).descriptor); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public int hashCode() { |
| return descriptor.hashCode(); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public int compareTo(Type other) { |
| return descriptor.compareTo(other.descriptor); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public String toString() { |
| return descriptor; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public String toHuman() { |
| switch (basicType) { |
| case BT_VOID: return "void"; |
| case BT_BOOLEAN: return "boolean"; |
| case BT_BYTE: return "byte"; |
| case BT_CHAR: return "char"; |
| case BT_DOUBLE: return "double"; |
| case BT_FLOAT: return "float"; |
| case BT_INT: return "int"; |
| case BT_LONG: return "long"; |
| case BT_SHORT: return "short"; |
| case BT_OBJECT: break; |
| default: return descriptor; |
| } |
| |
| if (isArray()) { |
| return getComponentType().toHuman() + "[]"; |
| } |
| |
| // Remove the "L...;" around the type and convert "/" to ".". |
| return getClassName().replace("/", "."); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public Type getType() { |
| return this; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public Type getFrameType() { |
| switch (basicType) { |
| case BT_BOOLEAN: |
| case BT_BYTE: |
| case BT_CHAR: |
| case BT_INT: |
| case BT_SHORT: { |
| return INT; |
| } |
| } |
| |
| return this; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public int getBasicType() { |
| return basicType; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public int getBasicFrameType() { |
| switch (basicType) { |
| case BT_BOOLEAN: |
| case BT_BYTE: |
| case BT_CHAR: |
| case BT_INT: |
| case BT_SHORT: { |
| return BT_INT; |
| } |
| } |
| |
| return basicType; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public boolean isConstant() { |
| return false; |
| } |
| |
| /** |
| * Gets the descriptor. |
| * |
| * @return {@code non-null;} the descriptor |
| */ |
| public String getDescriptor() { |
| return descriptor; |
| } |
| |
| /** |
| * Gets the name of the class this type corresponds to, in internal |
| * form. This method is only valid if this instance is for a |
| * normal reference type (that is, a reference type and |
| * additionally not a return address). |
| * |
| * @return {@code non-null;} the internal-form class name |
| */ |
| public String getClassName() { |
| if (className == null) { |
| if (!isReference()) { |
| throw new IllegalArgumentException("not an object type: " + |
| descriptor); |
| } |
| |
| if (descriptor.charAt(0) == '[') { |
| className = descriptor; |
| } else { |
| className = descriptor.substring(1, descriptor.length() - 1); |
| } |
| } |
| |
| return className; |
| } |
| |
| /** |
| * Gets the category. Most instances are category 1. {@code long} |
| * and {@code double} are the only category 2 types. |
| * |
| * @see #isCategory1 |
| * @see #isCategory2 |
| * @return the category |
| */ |
| public int getCategory() { |
| switch (basicType) { |
| case BT_LONG: |
| case BT_DOUBLE: { |
| return 2; |
| } |
| } |
| |
| return 1; |
| } |
| |
| /** |
| * Returns whether or not this is a category 1 type. |
| * |
| * @see #getCategory |
| * @see #isCategory2 |
| * @return whether or not this is a category 1 type |
| */ |
| public boolean isCategory1() { |
| switch (basicType) { |
| case BT_LONG: |
| case BT_DOUBLE: { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Returns whether or not this is a category 2 type. |
| * |
| * @see #getCategory |
| * @see #isCategory1 |
| * @return whether or not this is a category 2 type |
| */ |
| public boolean isCategory2() { |
| switch (basicType) { |
| case BT_LONG: |
| case BT_DOUBLE: { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Gets whether this type is "intlike." An intlike type is one which, when |
| * placed on a stack or in a local, is automatically converted to an |
| * {@code int}. |
| * |
| * @return whether this type is "intlike" |
| */ |
| public boolean isIntlike() { |
| switch (basicType) { |
| case BT_BOOLEAN: |
| case BT_BYTE: |
| case BT_CHAR: |
| case BT_INT: |
| case BT_SHORT: { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Gets whether this type is a primitive type. All types are either |
| * primitive or reference types. |
| * |
| * @return whether this type is primitive |
| */ |
| public boolean isPrimitive() { |
| switch (basicType) { |
| case BT_BOOLEAN: |
| case BT_BYTE: |
| case BT_CHAR: |
| case BT_DOUBLE: |
| case BT_FLOAT: |
| case BT_INT: |
| case BT_LONG: |
| case BT_SHORT: |
| case BT_VOID: { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Gets whether this type is a normal reference type. A normal |
| * reference type is a reference type that is not a return |
| * address. This method is just convenient shorthand for |
| * {@code getBasicType() == Type.BT_OBJECT}. |
| * |
| * @return whether this type is a normal reference type |
| */ |
| public boolean isReference() { |
| return (basicType == BT_OBJECT); |
| } |
| |
| /** |
| * Gets whether this type is an array type. If this method returns |
| * {@code true}, then it is safe to use {@link #getComponentType} |
| * to determine the component type. |
| * |
| * @return whether this type is an array type |
| */ |
| public boolean isArray() { |
| return (descriptor.charAt(0) == '['); |
| } |
| |
| /** |
| * Gets whether this type is an array type or is a known-null, and |
| * hence is compatible with array types. |
| * |
| * @return whether this type is an array type |
| */ |
| public boolean isArrayOrKnownNull() { |
| return isArray() || equals(KNOWN_NULL); |
| } |
| |
| /** |
| * Gets whether this type represents an uninitialized instance. An |
| * uninitialized instance is what one gets back from the {@code new} |
| * opcode, and remains uninitialized until a valid constructor is |
| * invoked on it. |
| * |
| * @return whether this type is "uninitialized" |
| */ |
| public boolean isUninitialized() { |
| return (newAt >= 0); |
| } |
| |
| /** |
| * Gets the bytecode index at which this uninitialized type was |
| * allocated. This returns {@code Integer.MAX_VALUE} if this |
| * type is an uninitialized incoming parameter (i.e., the |
| * {@code this} of an {@code <init>} method) or |
| * {@code -1} if this type is in fact <i>initialized</i>. |
| * |
| * @return {@code >= -1;} the allocation bytecode index |
| */ |
| public int getNewAt() { |
| return newAt; |
| } |
| |
| /** |
| * Gets the initialized type corresponding to this instance, but only |
| * if this instance is in fact an uninitialized object type. |
| * |
| * @return {@code non-null;} the initialized type |
| */ |
| public Type getInitializedType() { |
| if (initializedType == null) { |
| throw new IllegalArgumentException("initialized type: " + |
| descriptor); |
| } |
| |
| return initializedType; |
| } |
| |
| /** |
| * Gets the type corresponding to an array of this type. |
| * |
| * @return {@code non-null;} the array type |
| */ |
| public Type getArrayType() { |
| if (arrayType == null) { |
| arrayType = putIntern(new Type('[' + descriptor, BT_OBJECT)); |
| } |
| |
| return arrayType; |
| } |
| |
| /** |
| * Gets the component type of this type. This method is only valid on |
| * array types. |
| * |
| * @return {@code non-null;} the component type |
| */ |
| public Type getComponentType() { |
| if (componentType == null) { |
| if (descriptor.charAt(0) != '[') { |
| throw new IllegalArgumentException("not an array type: " + |
| descriptor); |
| } |
| componentType = intern(descriptor.substring(1)); |
| } |
| |
| return componentType; |
| } |
| |
| /** |
| * Returns a new interned instance which is identical to this one, except |
| * it is indicated as uninitialized and allocated at the given bytecode |
| * index. This instance must be an initialized object type. |
| * |
| * @param newAt {@code >= 0;} the allocation bytecode index |
| * @return {@code non-null;} an appropriately-constructed instance |
| */ |
| public Type asUninitialized(int newAt) { |
| if (newAt < 0) { |
| throw new IllegalArgumentException("newAt < 0"); |
| } |
| |
| if (!isReference()) { |
| throw new IllegalArgumentException("not a reference type: " + |
| descriptor); |
| } |
| |
| if (isUninitialized()) { |
| /* |
| * Dealing with uninitialized types as a starting point is |
| * a pain, and it's not clear that it'd ever be used, so |
| * just disallow it. |
| */ |
| throw new IllegalArgumentException("already uninitialized: " + |
| descriptor); |
| } |
| |
| /* |
| * Create a new descriptor that is unique and shouldn't conflict |
| * with "normal" type descriptors |
| */ |
| String newDesc = 'N' + Hex.u2(newAt) + descriptor; |
| Type result = new Type(newDesc, BT_OBJECT, newAt); |
| result.initializedType = this; |
| return putIntern(result); |
| } |
| |
| /** |
| * Puts the given instance in the intern table if it's not already |
| * there. If a conflicting value is already in the table, then leave it. |
| * Return the interned value. |
| * |
| * @param type {@code non-null;} instance to make interned |
| * @return {@code non-null;} the actual interned object |
| */ |
| private static Type putIntern(Type type) { |
| Type result = internTable.putIfAbsent(type.getDescriptor(), type); |
| return result != null ? result : type; |
| } |
| |
| public static void clearInternTable() { |
| internTable.clear(); |
| initInterns(); |
| } |
| } |