/*
* ProGuard -- shrinking, optimization, obfuscation, and preverification
* of Java bytecode.
*
* Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package proguard.evaluation.value;
import proguard.classfile.*;
import proguard.classfile.util.ClassUtil;
import proguard.classfile.visitor.ClassCollector;
import java.util.*;
/**
* This class represents a partially evaluated reference value. It has a type
* and a flag that indicates whether the value could be null
. If
* the type is null
, the value is null
.
*
* @author Eric Lafortune
*/
public class ReferenceValue extends Category1Value
{
private static final boolean DEBUG = false;
protected final String type;
protected final Clazz referencedClass;
protected final boolean mayBeNull;
/**
* Creates a new ReferenceValue.
*/
public ReferenceValue(String type,
Clazz referencedClass,
boolean mayBeNull)
{
this.type = type;
this.referencedClass = referencedClass;
this.mayBeNull = mayBeNull;
}
/**
* Returns the type.
*/
public String getType()
{
return type;
}
/**
* Returns the class that is referenced by the type.
*/
public Clazz getReferencedClass()
{
return referencedClass;
}
// Basic unary methods.
/**
* Returns whether the type is null
.
*/
public int isNull()
{
return type == null ? ALWAYS :
mayBeNull ? MAYBE :
NEVER;
}
/**
* Returns whether the type is an instance of the given type.
*/
public int instanceOf(String otherType, Clazz otherReferencedClass)
{
String thisType = this.type;
// If this type is null, it is never an instance of any class.
if (thisType == null)
{
return NEVER;
}
// Start taking into account the type dimensions.
int thisDimensionCount = ClassUtil.internalArrayTypeDimensionCount(thisType);
int otherDimensionCount = ClassUtil.internalArrayTypeDimensionCount(otherType);
int commonDimensionCount = Math.min(thisDimensionCount, otherDimensionCount);
// Strip any common array prefixes.
thisType = thisType.substring(commonDimensionCount);
otherType = otherType.substring(commonDimensionCount);
// If either stripped type is a primitive type, we can tell right away.
if (commonDimensionCount > 0 &&
(ClassUtil.isInternalPrimitiveType(thisType.charAt(0)) ||
ClassUtil.isInternalPrimitiveType(otherType.charAt(0))))
{
return !thisType.equals(otherType) ? NEVER :
mayBeNull ? MAYBE :
ALWAYS;
}
// Strip the class type prefix and suffix of this type, if any.
if (thisDimensionCount == commonDimensionCount)
{
thisType = ClassUtil.internalClassNameFromClassType(thisType);
}
// Strip the class type prefix and suffix of the other type, if any.
if (otherDimensionCount == commonDimensionCount)
{
otherType = ClassUtil.internalClassNameFromClassType(otherType);
}
// If this type is an array type, and the other type is not
// java.lang.Object, java.lang.Cloneable, or java.io.Serializable,
// this type can never be an instance.
if (thisDimensionCount > otherDimensionCount &&
!ClassUtil.isInternalArrayInterfaceName(otherType))
{
return NEVER;
}
// If the other type is an array type, and this type is not
// java.lang.Object, java.lang.Cloneable, or java.io.Serializable,
// this type can never be an instance.
if (thisDimensionCount < otherDimensionCount &&
!ClassUtil.isInternalArrayInterfaceName(thisType))
{
return NEVER;
}
// If this type may be null, it might not be an instance of any class.
if (mayBeNull)
{
return MAYBE;
}
// If this type is equal to the other type, or if the other type is
// java.lang.Object, this type is always an instance.
if (thisType.equals(otherType) ||
ClassConstants.INTERNAL_NAME_JAVA_LANG_OBJECT.equals(otherType))
{
return ALWAYS;
}
// If this type is an array type, it's ok.
if (thisDimensionCount > otherDimensionCount)
{
return ALWAYS;
}
// If the other type is an array type, it might be ok.
if (thisDimensionCount < otherDimensionCount)
{
return MAYBE;
}
// If the value extends the type, we're sure.
return referencedClass != null &&
otherReferencedClass != null &&
referencedClass.extendsOrImplements(otherReferencedClass) ?
ALWAYS :
MAYBE;
}
/**
* Returns the length of the array, assuming this type is an array.
*/
public IntegerValue arrayLength(ValueFactory valueFactory)
{
return valueFactory.createIntegerValue();
}
/**
* Returns the value of the array at the given index, assuming this type
* is an array.
*/
public Value arrayLoad(IntegerValue integerValue, ValueFactory valueFactory)
{
return
type == null ? ValueFactory.REFERENCE_VALUE_NULL :
!ClassUtil.isInternalArrayType(type) ? ValueFactory.REFERENCE_VALUE_JAVA_LANG_OBJECT_MAYBE_NULL :
valueFactory.createValue(type.substring(1),
referencedClass,
true);
}
// Basic binary methods.
/**
* Returns the generalization of this ReferenceValue and the given other
* ReferenceValue.
*/
public ReferenceValue generalize(ReferenceValue other)
{
// If both types are identical, the generalization is the same too.
if (this.equals(other))
{
return this;
}
String thisType = this.type;
String otherType = other.type;
// If both types are nul, the generalization is null too.
if (thisType == null && otherType == null)
{
return ValueFactory.REFERENCE_VALUE_NULL;
}
// If this type is null, the generalization is the other type, maybe null.
if (thisType == null)
{
return other.generalizeMayBeNull(true);
}
// If the other type is null, the generalization is this type, maybe null.
if (otherType == null)
{
return this.generalizeMayBeNull(true);
}
boolean mayBeNull = this.mayBeNull || other.mayBeNull;
// If the two types are equal, the generalization remains the same, maybe null.
if (thisType.equals(otherType))
{
return this.generalizeMayBeNull(mayBeNull);
}
// Start taking into account the type dimensions.
int thisDimensionCount = ClassUtil.internalArrayTypeDimensionCount(thisType);
int otherDimensionCount = ClassUtil.internalArrayTypeDimensionCount(otherType);
int commonDimensionCount = Math.min(thisDimensionCount, otherDimensionCount);
if (thisDimensionCount == otherDimensionCount)
{
// See if we can take into account the referenced classes.
Clazz thisReferencedClass = this.referencedClass;
Clazz otherReferencedClass = other.referencedClass;
if (thisReferencedClass != null &&
otherReferencedClass != null)
{
if (thisReferencedClass.extendsOrImplements(otherReferencedClass))
{
return other.generalizeMayBeNull(mayBeNull);
}
if (otherReferencedClass.extendsOrImplements(thisReferencedClass))
{
return this.generalizeMayBeNull(mayBeNull);
}
// Collect the superclasses and interfaces of this class.
Set thisSuperClasses = new HashSet();
thisReferencedClass.hierarchyAccept(false, true, true, false,
new ClassCollector(thisSuperClasses));
// Collect the superclasses and interfaces of the other class.
Set otherSuperClasses = new HashSet();
otherReferencedClass.hierarchyAccept(false, true, true, false,
new ClassCollector(otherSuperClasses));
if (DEBUG)
{
System.out.println("ReferenceValue.generalize this ["+thisReferencedClass.getName()+"] with other ["+otherReferencedClass.getName()+"]");
System.out.println(" This super classes: "+thisSuperClasses);
System.out.println(" Other super classes: "+otherSuperClasses);
}
// Find the common superclasses.
thisSuperClasses.retainAll(otherSuperClasses);
if (DEBUG)
{
System.out.println(" Common super classes: "+thisSuperClasses);
}
// Find a class that is a subclass of all common superclasses,
// or that at least has the maximum number of common superclasses.
Clazz commonClazz = null;
int maximumSuperClassCount = -1;
// Go over all common superclasses to find it. In case of
// multiple subclasses, keep the lowest one alphabetically,
// in order to ensure that the choice is deterministic.
Iterator commonSuperClasses = thisSuperClasses.iterator();
while (commonSuperClasses.hasNext())
{
Clazz commonSuperClass = (Clazz)commonSuperClasses.next();
int superClassCount = superClassCount(commonSuperClass, thisSuperClasses);
if (maximumSuperClassCount < superClassCount ||
(maximumSuperClassCount == superClassCount &&
commonClazz != null &&
commonClazz.getName().compareTo(commonSuperClass.getName()) > 0))
{
commonClazz = commonSuperClass;
maximumSuperClassCount = superClassCount;
}
}
if (commonClazz == null)
{
throw new IllegalArgumentException("Can't find common super class of ["+thisType+"] and ["+otherType+"]");
}
if (DEBUG)
{
System.out.println(" Best common class: ["+commonClazz.getName()+"]");
}
// TODO: Handle more difficult cases, with multiple global subclasses.
return new ReferenceValue(commonDimensionCount == 0 ?
commonClazz.getName() :
ClassUtil.internalArrayTypeFromClassName(commonClazz.getName(),
commonDimensionCount),
commonClazz,
mayBeNull);
}
}
else if (thisDimensionCount > otherDimensionCount)
{
// See if the other type is an interface type of arrays.
if (ClassUtil.isInternalArrayInterfaceName(ClassUtil.internalClassNameFromClassType(otherType)))
{
return other.generalizeMayBeNull(mayBeNull);
}
}
else if (thisDimensionCount < otherDimensionCount)
{
// See if this type is an interface type of arrays.
if (ClassUtil.isInternalArrayInterfaceName(ClassUtil.internalClassNameFromClassType(thisType)))
{
return this.generalizeMayBeNull(mayBeNull);
}
}
// Reduce the common dimension count if either type is an array of
// primitives type of this dimension.
if (commonDimensionCount > 0 &&
(ClassUtil.isInternalPrimitiveType(otherType.charAt(commonDimensionCount))) ||
ClassUtil.isInternalPrimitiveType(thisType.charAt(commonDimensionCount)))
{
commonDimensionCount--;
}
// Fall back on a basic Object or array of Objects type.
return commonDimensionCount == 0 ?
mayBeNull ?
ValueFactory.REFERENCE_VALUE_JAVA_LANG_OBJECT_MAYBE_NULL :
ValueFactory.REFERENCE_VALUE_JAVA_LANG_OBJECT_NOT_NULL :
new ReferenceValue(ClassUtil.internalArrayTypeFromClassName(ClassConstants.INTERNAL_NAME_JAVA_LANG_OBJECT,
commonDimensionCount),
null,
mayBeNull);
}
/**
* Returns if the number of superclasses of the given class in the given
* set of classes.
*/
private int superClassCount(Clazz subClass, Set classes)
{
int count = 0;
Iterator iterator = classes.iterator();
while (iterator.hasNext())
{
Clazz clazz = (Clazz)iterator.next();
if (subClass.extendsOrImplements(clazz))
{
count++;
}
}
//System.out.println("ReferenceValue.superClassCount: ["+subClass.getName()+"]: "+count);
return count;
}
/**
* Returns whether this ReferenceValue is equal to the given other
* ReferenceValue.
* @return NEVER
, MAYBE
, or ALWAYS
.
*/
public int equal(ReferenceValue other)
{
return this.type == null && other.type == null ? ALWAYS : MAYBE;
}
// Derived unary methods.
/**
* Returns whether this ReferenceValue is not null
.
* @return NEVER
, MAYBE
, or ALWAYS
.
*/
public final int isNotNull()
{
return -isNull();
}
/**
* Returns the generalization of this ReferenceValue and the given other
* ReferenceValue.
*/
private ReferenceValue generalizeMayBeNull(boolean mayBeNull)
{
return this.mayBeNull || !mayBeNull ?
this :
new ReferenceValue(this.type, this.referencedClass, true);
}
// Derived binary methods.
/**
* Returns whether this ReferenceValue and the given ReferenceValue are different.
* @return NEVER
, MAYBE
, or ALWAYS
.
*/
public final int notEqual(ReferenceValue other)
{
return -equal(other);
}
// Implementations for Value.
public final ReferenceValue referenceValue()
{
return this;
}
public final Value generalize(Value other)
{
return this.generalize(other.referenceValue());
}
public boolean isParticular()
{
return type == null;
}
public final int computationalType()
{
return TYPE_REFERENCE;
}
public final String internalType()
{
return
type == null ? ClassConstants.INTERNAL_TYPE_JAVA_LANG_OBJECT :
ClassUtil.isInternalArrayType(type) ? type :
ClassConstants.INTERNAL_TYPE_CLASS_START +
type +
ClassConstants.INTERNAL_TYPE_CLASS_END;
}
// Implementations for Object.
public boolean equals(Object object)
{
if (this == object)
{
return true;
}
if (object == null ||
this.getClass() != object.getClass())
{
return false;
}
ReferenceValue other = (ReferenceValue)object;
return this.type == null ? other.type == null :
(this.mayBeNull == other.mayBeNull &&
this.type.equals(other.type));
}
public int hashCode()
{
return this.getClass().hashCode() ^
(type == null ? 0 : type.hashCode() ^ (mayBeNull ? 0 : 1));
}
public String toString()
{
return type == null ?
"null" :
type + (referencedClass == null ? "?" : "") + (mayBeNull ? "" : "!");
}
}