/*
* Javassist, a Java-bytecode translator toolkit.
* Copyright (C) 1999- Shigeru Chiba. All Rights Reserved.
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. Alternatively, the contents of this file may be used under
* the terms of the GNU Lesser General Public License Version 2.1 or later,
* or the Apache License Version 2.0.
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*/
package scouter.javassist.bytecode.analysis;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.Map;
import scouter.javassist.ClassPool;
import scouter.javassist.CtClass;
import scouter.javassist.NotFoundException;
import scouter.javassist.bytecode.analysis.MultiArrayType;
import scouter.javassist.bytecode.analysis.MultiType;
/**
* Represents a JVM type in data-flow analysis. This abstraction is necessary since
* a JVM type not only includes all normal Java types, but also a few special types
* that are used by the JVM internally. See the static field types on this class for
* more info on these special types.
*
* All primitive and special types reuse the same instance, so identity comparison can
* be used when examining them. Normal java types must use {@link #equals(Object)} to
* compare type instances.
*
* In most cases, applications which consume this API, only need to call {@link #getCtClass()}
* to obtain the needed type information.
*
* @author Jason T. Greene
*/
public class Type {
private final CtClass clazz;
private final boolean special;
private static final Map prims = new IdentityHashMap();
/** Represents the double primitive type */
public static final Type DOUBLE = new Type(CtClass.doubleType);
/** Represents the boolean primitive type */
public static final Type BOOLEAN = new Type(CtClass.booleanType);
/** Represents the long primitive type */
public static final Type LONG = new Type(CtClass.longType);
/** Represents the char primitive type */
public static final Type CHAR = new Type(CtClass.charType);
/** Represents the byte primitive type */
public static final Type BYTE = new Type(CtClass.byteType);
/** Represents the short primitive type */
public static final Type SHORT = new Type(CtClass.shortType);
/** Represents the integer primitive type */
public static final Type INTEGER = new Type(CtClass.intType);
/** Represents the float primitive type */
public static final Type FLOAT = new Type(CtClass.floatType);
/** Represents the void primitive type */
public static final Type VOID = new Type(CtClass.voidType);
/**
* Represents an unknown, or null type. This occurs when aconst_null is used.
* It is important not to treat this type as java.lang.Object, since a null can
* be assigned to any reference type. The analyzer will replace these with
* an actual known type if it can be determined by a merged path with known type
* information. If this type is encountered on a frame then it is guaranteed to
* be null, and the type information is simply not available. Any attempts to
* infer the type, without further information from the compiler would be a guess.
*/
public static final Type UNINIT = new Type(null);
/**
* Represents an internal JVM return address, which is used by the RET
* instruction to return to a JSR that invoked the subroutine.
*/
public static final Type RETURN_ADDRESS = new Type(null, true);
/** A placeholder used by the analyzer for the second word position of a double-word type */
public static final Type TOP = new Type(null, true);
/**
* Represents a non-accessible value. Code cannot access the value this type
* represents. It occurs when bytecode reuses a local variable table
* position with non-mergable types. An example would be compiled code which
* uses the same position for a primitive type in one branch, and a reference type
* in another branch.
*/
public static final Type BOGUS = new Type(null, true);
/** Represents the java.lang.Object reference type */
public static final Type OBJECT = lookupType("java.lang.Object");
/** Represents the java.io.Serializable reference type */
public static final Type SERIALIZABLE = lookupType("java.io.Serializable");
/** Represents the java.lang.Coneable reference type */
public static final Type CLONEABLE = lookupType("java.lang.Cloneable");
/** Represents the java.lang.Throwable reference type */
public static final Type THROWABLE = lookupType("java.lang.Throwable");
static {
prims.put(CtClass.doubleType, DOUBLE);
prims.put(CtClass.longType, LONG);
prims.put(CtClass.charType, CHAR);
prims.put(CtClass.shortType, SHORT);
prims.put(CtClass.intType, INTEGER);
prims.put(CtClass.floatType, FLOAT);
prims.put(CtClass.byteType, BYTE);
prims.put(CtClass.booleanType, BOOLEAN);
prims.put(CtClass.voidType, VOID);
}
/**
* Obtain the Type for a given class. If the class is a primitive,
* the the unique type instance for the primitive will be returned.
* Otherwise a new Type instance representing the class is returned.
*
* @param clazz The java class
* @return a type instance for this class
*/
public static Type get(CtClass clazz) {
Type type = (Type)prims.get(clazz);
return type != null ? type : new Type(clazz);
}
private static Type lookupType(String name) {
try {
return new Type(ClassPool.getDefault().get(name));
} catch (NotFoundException e) {
throw new RuntimeException(e);
}
}
Type(CtClass clazz) {
this(clazz, false);
}
private Type(CtClass clazz, boolean special) {
this.clazz = clazz;
this.special = special;
}
// Used to indicate a merge internally triggered a change
boolean popChanged() {
return false;
}
/**
* Gets the word size of this type. Double-word types, such as long and double
* will occupy two positions on the local variable table or stack.
*
* @return the number of words needed to hold this type
*/
public int getSize() {
return clazz == CtClass.doubleType || clazz == CtClass.longType || this == TOP ? 2 : 1;
}
/**
* Returns the class this type represents. If the type is special, null will be returned.
*
* @return the class for this type, or null if special
*/
public CtClass getCtClass() {
return clazz;
}
/**
* Returns whether or not this type is a normal java reference, i.e. it is or extends java.lang.Object.
*
* @return true if a java reference, false if a primitive or special
*/
public boolean isReference() {
return !special && (clazz == null || !clazz.isPrimitive());
}
/**
* Returns whether or not the type is special. A special type is one that is either used
* for internal tracking, or is only used internally by the JVM.
*
* @return true if special, false if not
*/
public boolean isSpecial() {
return special;
}
/**
* Returns whether or not this type is an array.
*
* @return true if an array, false if not
*/
public boolean isArray() {
return clazz != null && clazz.isArray();
}
/**
* Returns the number of dimensions of this array. If the type is not an
* array zero is returned.
*
* @return zero if not an array, otherwise the number of array dimensions.
*/
public int getDimensions() {
if (!isArray()) return 0;
String name = clazz.getName();
int pos = name.length() - 1;
int count = 0;
while (name.charAt(pos) == ']' ) {
pos -= 2;
count++;
}
return count;
}
/**
* Returns the array component if this type is an array. If the type
* is not an array null is returned.
*
* @return the array component if an array, otherwise null
*/
public Type getComponent() {
if (this.clazz == null || !this.clazz.isArray())
return null;
CtClass component;
try {
component = this.clazz.getComponentType();
} catch (NotFoundException e) {
throw new RuntimeException(e);
}
Type type = (Type)prims.get(component);
return (type != null) ? type : new Type(component);
}
/**
* Determines whether this type is assignable, to the passed type.
* A type is assignable to another if it is either the same type, or
* a sub-type.
*
* @param type the type to test assignability to
* @return true if this is assignable to type, otherwise false
*/
public boolean isAssignableFrom(Type type) {
if (this == type)
return true;
if ((type == UNINIT && isReference()) || this == UNINIT && type.isReference())
return true;
if (type instanceof MultiType)
return ((MultiType)type).isAssignableTo(this);
if (type instanceof MultiArrayType)
return ((MultiArrayType)type).isAssignableTo(this);
// Primitives and Special types must be identical
if (clazz == null || clazz.isPrimitive())
return false;
try {
return type.clazz.subtypeOf(clazz);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Finds the common base type, or interface which both this and the specified
* type can be assigned. If there is more than one possible answer, then a {@link MultiType},
* or a {@link MultiArrayType} is returned. Multi-types have special rules,
* and successive merges and assignment tests on them will alter their internal state,
* as well as other multi-types they have been merged with. This method is used by
* the data-flow analyzer to merge the type state from multiple branches.
*
* @param type the type to merge with
* @return the merged type
*/
public Type merge(Type type) {
if (type == this)
return this;
if (type == null)
return this;
if (type == Type.UNINIT)
return this;
if (this == Type.UNINIT)
return type;
// Unequal primitives and special types can not be merged
if (! type.isReference() || ! this.isReference())
return BOGUS;
// Centralize merging of multi-interface types
if (type instanceof MultiType)
return type.merge(this);
if (type.isArray() && this.isArray())
return mergeArray(type);
try {
return mergeClasses(type);
} catch (NotFoundException e) {
throw new RuntimeException(e);
}
}
Type getRootComponent(Type type) {
while (type.isArray())
type = type.getComponent();
return type;
}
private Type createArray(Type rootComponent, int dims) {
if (rootComponent instanceof MultiType)
return new MultiArrayType((MultiType) rootComponent, dims);
String name = arrayName(rootComponent.clazz.getName(), dims);
Type type;
try {
type = Type.get(getClassPool(rootComponent).get(name));
} catch (NotFoundException e) {
throw new RuntimeException(e);
}
return type;
}
String arrayName(String component, int dims) {
// Using char[] since we have no StringBuilder in JDK4, and StringBuffer is slow.
// Although, this is more efficient even if we did have one.
int i = component.length();
int size = i + dims * 2;
char[] string = new char[size];
component.getChars(0, i, string, 0);
while (i < size) {
string[i++] = '[';
string[i++] = ']';
}
component = new String(string);
return component;
}
private ClassPool getClassPool(Type rootComponent) {
ClassPool pool = rootComponent.clazz.getClassPool();
return pool != null ? pool : ClassPool.getDefault();
}
private Type mergeArray(Type type) {
Type typeRoot = getRootComponent(type);
Type thisRoot = getRootComponent(this);
int typeDims = type.getDimensions();
int thisDims = this.getDimensions();
// Array commponents can be merged when the dimensions are equal
if (typeDims == thisDims) {
Type mergedComponent = thisRoot.merge(typeRoot);
// If the components can not be merged (a primitive component mixed with a different type)
// then Object is the common type.
if (mergedComponent == Type.BOGUS)
return Type.OBJECT;
return createArray(mergedComponent, thisDims);
}
Type targetRoot;
int targetDims;
if (typeDims < thisDims) {
targetRoot = typeRoot;
targetDims = typeDims;
} else {
targetRoot = thisRoot;
targetDims = thisDims;
}
// Special case, arrays are cloneable and serializable, so prefer them when dimensions differ
if (eq(CLONEABLE.clazz, targetRoot.clazz) || eq(SERIALIZABLE.clazz, targetRoot.clazz))
return createArray(targetRoot, targetDims);
return createArray(OBJECT, targetDims);
}
private static CtClass findCommonSuperClass(CtClass one, CtClass two) throws NotFoundException {
CtClass deep = one;
CtClass shallow = two;
CtClass backupShallow = shallow;
CtClass backupDeep = deep;
// Phase 1 - Find the deepest hierarchy, set deep and shallow correctly
for (;;) {
// In case we get lucky, and find a match early
if (eq(deep, shallow) && deep.getSuperclass() != null)
return deep;
CtClass deepSuper = deep.getSuperclass();
CtClass shallowSuper = shallow.getSuperclass();
if (shallowSuper == null) {
// right, now reset shallow
shallow = backupShallow;
break;
}
if (deepSuper == null) {
// wrong, swap them, since deep is now useless, its our tmp before we swap it
deep = backupDeep;
backupDeep = backupShallow;
backupShallow = deep;
deep = shallow;
shallow = backupShallow;
break;
}
deep = deepSuper;
shallow = shallowSuper;
}
// Phase 2 - Move deepBackup up by (deep end - deep)
for (;;) {
deep = deep.getSuperclass();
if (deep == null)
break;
backupDeep = backupDeep.getSuperclass();
}
deep = backupDeep;
// Phase 3 - The hierarchy positions are now aligned
// The common super class is easy to find now
while (!eq(deep, shallow)) {
deep = deep.getSuperclass();
shallow = shallow.getSuperclass();
}
return deep;
}
private Type mergeClasses(Type type) throws NotFoundException {
CtClass superClass = findCommonSuperClass(this.clazz, type.clazz);
// If its Object, then try and find a common interface(s)
if (superClass.getSuperclass() == null) {
Map interfaces = findCommonInterfaces(type);
if (interfaces.size() == 1)
return new Type((CtClass) interfaces.values().iterator().next());
if (interfaces.size() > 1)
return new MultiType(interfaces);
// Only Object is in common
return new Type(superClass);
}
// Check for a common interface that is not on the found supertype
Map commonDeclared = findExclusiveDeclaredInterfaces(type, superClass);
if (commonDeclared.size() > 0) {
return new MultiType(commonDeclared, new Type(superClass));
}
return new Type(superClass);
}
private Map findCommonInterfaces(Type type) {
Map typeMap = getAllInterfaces(type.clazz, null);
Map thisMap = getAllInterfaces(this.clazz, null);
return findCommonInterfaces(typeMap, thisMap);
}
private Map findExclusiveDeclaredInterfaces(Type type, CtClass exclude) {
Map typeMap = getDeclaredInterfaces(type.clazz, null);
Map thisMap = getDeclaredInterfaces(this.clazz, null);
Map excludeMap = getAllInterfaces(exclude, null);
Iterator i = excludeMap.keySet().iterator();
while (i.hasNext()) {
Object intf = i.next();
typeMap.remove(intf);
thisMap.remove(intf);
}
return findCommonInterfaces(typeMap, thisMap);
}
Map findCommonInterfaces(Map typeMap, Map alterMap) {
Iterator i = alterMap.keySet().iterator();
while (i.hasNext()) {
if (! typeMap.containsKey(i.next()))
i.remove();
}
// Reduce to subinterfaces
// This does not need to be recursive since we make a copy,
// and that copy contains all super types for the whole hierarchy
i = new ArrayList(alterMap.values()).iterator();
while (i.hasNext()) {
CtClass intf = (CtClass) i.next();
CtClass[] interfaces;
try {
interfaces = intf.getInterfaces();
} catch (NotFoundException e) {
throw new RuntimeException(e);
}
for (int c = 0; c < interfaces.length; c++)
alterMap.remove(interfaces[c].getName());
}
return alterMap;
}
Map getAllInterfaces(CtClass clazz, Map map) {
if (map == null)
map = new HashMap();
if (clazz.isInterface())
map.put(clazz.getName(), clazz);
do {
try {
CtClass[] interfaces = clazz.getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
CtClass intf = interfaces[i];
map.put(intf.getName(), intf);
getAllInterfaces(intf, map);
}
clazz = clazz.getSuperclass();
} catch (NotFoundException e) {
throw new RuntimeException(e);
}
} while (clazz != null);
return map;
}
Map getDeclaredInterfaces(CtClass clazz, Map map) {
if (map == null)
map = new HashMap();
if (clazz.isInterface())
map.put(clazz.getName(), clazz);
CtClass[] interfaces;
try {
interfaces = clazz.getInterfaces();
} catch (NotFoundException e) {
throw new RuntimeException(e);
}
for (int i = 0; i < interfaces.length; i++) {
CtClass intf = interfaces[i];
map.put(intf.getName(), intf);
getDeclaredInterfaces(intf, map);
}
return map;
}
public boolean equals(Object o) {
if (! (o instanceof Type))
return false;
return o.getClass() == getClass() && eq(clazz, ((Type)o).clazz);
}
static boolean eq(CtClass one, CtClass two) {
return one == two || (one != null && two != null && one.getName().equals(two.getName()));
}
public String toString() {
if (this == BOGUS)
return "BOGUS";
if (this == UNINIT)
return "UNINIT";
if (this == RETURN_ADDRESS)
return "RETURN ADDRESS";
if (this == TOP)
return "TOP";
return clazz == null ? "null" : clazz.getName();
}
}