Home | History | Annotate | Download | only in value
      1 /*
      2  * ProGuard -- shrinking, optimization, obfuscation, and preverification
      3  *             of Java bytecode.
      4  *
      5  * Copyright (c) 2002-2009 Eric Lafortune (eric (at) graphics.cornell.edu)
      6  *
      7  * This program is free software; you can redistribute it and/or modify it
      8  * under the terms of the GNU General Public License as published by the Free
      9  * Software Foundation; either version 2 of the License, or (at your option)
     10  * any later version.
     11  *
     12  * This program is distributed in the hope that it will be useful, but WITHOUT
     13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     14  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
     15  * more details.
     16  *
     17  * You should have received a copy of the GNU General Public License along
     18  * with this program; if not, write to the Free Software Foundation, Inc.,
     19  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
     20  */
     21 package proguard.evaluation.value;
     22 
     23 import proguard.classfile.*;
     24 import proguard.classfile.util.ClassUtil;
     25 import proguard.classfile.visitor.ClassCollector;
     26 
     27 import java.util.*;
     28 
     29 /**
     30  * This class represents a partially evaluated reference value. It has a type
     31  * and a flag that indicates whether the value could be <code>null</code>. If
     32  * the type is <code>null</code>, the value is <code>null</code>.
     33  *
     34  * @author Eric Lafortune
     35  */
     36 public class ReferenceValue extends Category1Value
     37 {
     38     private static final boolean DEBUG = false;
     39 
     40 
     41     protected final String  type;
     42     protected final Clazz   referencedClass;
     43     protected final boolean mayBeNull;
     44 
     45 
     46     /**
     47      * Creates a new ReferenceValue.
     48      */
     49     public ReferenceValue(String  type,
     50                           Clazz   referencedClass,
     51                           boolean mayBeNull)
     52     {
     53         this.type            = type;
     54         this.referencedClass = referencedClass;
     55         this.mayBeNull       = mayBeNull;
     56     }
     57 
     58 
     59     /**
     60      * Returns the type.
     61      */
     62     public String getType()
     63     {
     64         return type;
     65     }
     66 
     67 
     68     /**
     69      * Returns the class that is referenced by the type.
     70      */
     71     public Clazz getReferencedClass()
     72     {
     73         return referencedClass;
     74     }
     75 
     76 
     77     // Basic unary methods.
     78 
     79     /**
     80      * Returns whether the type is <code>null</code>.
     81      */
     82     public int isNull()
     83     {
     84         return type == null ? ALWAYS :
     85                mayBeNull    ? MAYBE  :
     86                               NEVER;
     87     }
     88 
     89 
     90     /**
     91      * Returns whether the type is an instance of the given type.
     92      */
     93     public int instanceOf(String otherType, Clazz otherReferencedClass)
     94     {
     95         String thisType = this.type;
     96 
     97         // If this type is null, it is never an instance of any class.
     98         if (thisType == null)
     99         {
    100             return NEVER;
    101         }
    102 
    103         // Start taking into account the type dimensions.
    104         int thisDimensionCount   = ClassUtil.internalArrayTypeDimensionCount(thisType);
    105         int otherDimensionCount  = ClassUtil.internalArrayTypeDimensionCount(otherType);
    106         int commonDimensionCount = Math.min(thisDimensionCount, otherDimensionCount);
    107 
    108         // Strip any common array prefixes.
    109         thisType  = thisType.substring(commonDimensionCount);
    110         otherType = otherType.substring(commonDimensionCount);
    111 
    112         // If either stripped type is a primitive type, we can tell right away.
    113         if (commonDimensionCount > 0 &&
    114             (ClassUtil.isInternalPrimitiveType(thisType.charAt(0)) ||
    115              ClassUtil.isInternalPrimitiveType(otherType.charAt(0))))
    116         {
    117             return !thisType.equals(otherType) ? NEVER :
    118                    mayBeNull                   ? MAYBE :
    119                                                  ALWAYS;
    120         }
    121 
    122         // Strip the class type prefix and suffix of this type, if any.
    123         if (thisDimensionCount == commonDimensionCount)
    124         {
    125             thisType = ClassUtil.internalClassNameFromClassType(thisType);
    126         }
    127 
    128         // Strip the class type prefix and suffix of the other type, if any.
    129         if (otherDimensionCount == commonDimensionCount)
    130         {
    131             otherType = ClassUtil.internalClassNameFromClassType(otherType);
    132         }
    133 
    134         // If this type is an array type, and the other type is not
    135         // java.lang.Object, java.lang.Cloneable, or java.io.Serializable,
    136         // this type can never be an instance.
    137         if (thisDimensionCount > otherDimensionCount &&
    138             !ClassUtil.isInternalArrayInterfaceName(otherType))
    139         {
    140             return NEVER;
    141         }
    142 
    143         // If the other type is an array type, and this type is not
    144         // java.lang.Object, java.lang.Cloneable, or java.io.Serializable,
    145         // this type can never be an instance.
    146         if (thisDimensionCount < otherDimensionCount &&
    147             !ClassUtil.isInternalArrayInterfaceName(thisType))
    148         {
    149             return NEVER;
    150         }
    151 
    152         // If this type may be null, it might not be an instance of any class.
    153         if (mayBeNull)
    154         {
    155             return MAYBE;
    156         }
    157 
    158         // If this type is equal to the other type, or if the other type is
    159         // java.lang.Object, this type is always an instance.
    160         if (thisType.equals(otherType) ||
    161             ClassConstants.INTERNAL_NAME_JAVA_LANG_OBJECT.equals(otherType))
    162         {
    163             return ALWAYS;
    164         }
    165 
    166         // If this type is an array type, it's ok.
    167         if (thisDimensionCount > otherDimensionCount)
    168         {
    169             return ALWAYS;
    170         }
    171 
    172         // If the other type is an array type, it might be ok.
    173         if (thisDimensionCount < otherDimensionCount)
    174         {
    175             return MAYBE;
    176         }
    177 
    178         // If the value extends the type, we're sure.
    179         return referencedClass      != null &&
    180                otherReferencedClass != null &&
    181                referencedClass.extendsOrImplements(otherReferencedClass) ?
    182                    ALWAYS :
    183                    MAYBE;
    184     }
    185 
    186 
    187     /**
    188      * Returns the length of the array, assuming this type is an array.
    189      */
    190     public IntegerValue arrayLength(ValueFactory valueFactory)
    191     {
    192         return valueFactory.createIntegerValue();
    193     }
    194 
    195 
    196     /**
    197      * Returns the value of the array at the given index, assuming this type
    198      * is an array.
    199      */
    200     public Value arrayLoad(IntegerValue integerValue, ValueFactory valueFactory)
    201     {
    202         return
    203             type == null                         ? ValueFactory.REFERENCE_VALUE_NULL                        :
    204             !ClassUtil.isInternalArrayType(type) ? ValueFactory.REFERENCE_VALUE_JAVA_LANG_OBJECT_MAYBE_NULL :
    205                                                    valueFactory.createValue(type.substring(1),
    206                                                                             referencedClass,
    207                                                                             true);
    208     }
    209 
    210 
    211     // Basic binary methods.
    212 
    213     /**
    214      * Returns the generalization of this ReferenceValue and the given other
    215      * ReferenceValue.
    216      */
    217     public ReferenceValue generalize(ReferenceValue other)
    218     {
    219         // If both types are identical, the generalization is the same too.
    220         if (this.equals(other))
    221         {
    222             return this;
    223         }
    224 
    225         String thisType  = this.type;
    226         String otherType = other.type;
    227 
    228         // If both types are nul, the generalization is null too.
    229         if (thisType == null && otherType == null)
    230         {
    231             return ValueFactory.REFERENCE_VALUE_NULL;
    232         }
    233 
    234         // If this type is null, the generalization is the other type, maybe null.
    235         if (thisType == null)
    236         {
    237             return other.generalizeMayBeNull(true);
    238         }
    239 
    240         // If the other type is null, the generalization is this type, maybe null.
    241         if (otherType == null)
    242         {
    243             return this.generalizeMayBeNull(true);
    244         }
    245 
    246         boolean mayBeNull = this.mayBeNull || other.mayBeNull;
    247 
    248         // If the two types are equal, the generalization remains the same, maybe null.
    249         if (thisType.equals(otherType))
    250         {
    251             return this.generalizeMayBeNull(mayBeNull);
    252         }
    253 
    254         // Start taking into account the type dimensions.
    255         int thisDimensionCount   = ClassUtil.internalArrayTypeDimensionCount(thisType);
    256         int otherDimensionCount  = ClassUtil.internalArrayTypeDimensionCount(otherType);
    257         int commonDimensionCount = Math.min(thisDimensionCount, otherDimensionCount);
    258 
    259         if (thisDimensionCount == otherDimensionCount)
    260         {
    261             // See if we can take into account the referenced classes.
    262             Clazz thisReferencedClass  = this.referencedClass;
    263             Clazz otherReferencedClass = other.referencedClass;
    264 
    265             if (thisReferencedClass  != null &&
    266                 otherReferencedClass != null)
    267             {
    268                 if (thisReferencedClass.extendsOrImplements(otherReferencedClass))
    269                 {
    270                     return other.generalizeMayBeNull(mayBeNull);
    271                 }
    272 
    273                 if (otherReferencedClass.extendsOrImplements(thisReferencedClass))
    274                 {
    275                     return this.generalizeMayBeNull(mayBeNull);
    276                 }
    277 
    278                 // Collect the superclasses and interfaces of this class.
    279                 Set thisSuperClasses = new HashSet();
    280                 thisReferencedClass.hierarchyAccept(false, true, true, false,
    281                                                     new ClassCollector(thisSuperClasses));
    282 
    283                 // Collect the superclasses and interfaces of the other class.
    284                 Set otherSuperClasses = new HashSet();
    285                 otherReferencedClass.hierarchyAccept(false, true, true, false,
    286                                                      new ClassCollector(otherSuperClasses));
    287 
    288                 if (DEBUG)
    289                 {
    290                     System.out.println("ReferenceValue.generalize this ["+thisReferencedClass.getName()+"] with other ["+otherReferencedClass.getName()+"]");
    291                     System.out.println("  This super classes:  "+thisSuperClasses);
    292                     System.out.println("  Other super classes: "+otherSuperClasses);
    293                 }
    294 
    295                 // Find the common superclasses.
    296                 thisSuperClasses.retainAll(otherSuperClasses);
    297 
    298                 if (DEBUG)
    299                 {
    300                     System.out.println("  Common super classes: "+thisSuperClasses);
    301                 }
    302 
    303                 // Find a class that is a subclass of all common superclasses,
    304                 // or that at least has the maximum number of common superclasses.
    305                 Clazz commonClazz = null;
    306 
    307                 int maximumSuperClassCount = -1;
    308 
    309                 // Go over all common superclasses to find it. In case of
    310                 // multiple subclasses, keep the lowest one alphabetically,
    311                 // in order to ensure that the choice is deterministic.
    312                 Iterator commonSuperClasses = thisSuperClasses.iterator();
    313                 while (commonSuperClasses.hasNext())
    314                 {
    315                     Clazz commonSuperClass = (Clazz)commonSuperClasses.next();
    316 
    317                     int superClassCount = superClassCount(commonSuperClass, thisSuperClasses);
    318                     if (maximumSuperClassCount < superClassCount ||
    319                         (maximumSuperClassCount == superClassCount &&
    320                          commonClazz != null                       &&
    321                          commonClazz.getName().compareTo(commonSuperClass.getName()) > 0))
    322                     {
    323                         commonClazz            = commonSuperClass;
    324                         maximumSuperClassCount = superClassCount;
    325                     }
    326                 }
    327 
    328                 if (commonClazz == null)
    329                 {
    330                     throw new IllegalArgumentException("Can't find common super class of ["+thisType+"] and ["+otherType+"]");
    331                 }
    332 
    333                 if (DEBUG)
    334                 {
    335                     System.out.println("  Best common class: ["+commonClazz.getName()+"]");
    336                 }
    337 
    338                 // TODO: Handle more difficult cases, with multiple global subclasses.
    339 
    340                 return new ReferenceValue(commonDimensionCount == 0 ?
    341                                               commonClazz.getName() :
    342                                               ClassUtil.internalArrayTypeFromClassName(commonClazz.getName(),
    343                                                                                        commonDimensionCount),
    344                                           commonClazz,
    345                                           mayBeNull);
    346             }
    347         }
    348         else if (thisDimensionCount > otherDimensionCount)
    349         {
    350             // See if the other type is an interface type of arrays.
    351             if (ClassUtil.isInternalArrayInterfaceName(ClassUtil.internalClassNameFromClassType(otherType)))
    352             {
    353                 return other.generalizeMayBeNull(mayBeNull);
    354             }
    355         }
    356         else if (thisDimensionCount < otherDimensionCount)
    357         {
    358             // See if this type is an interface type of arrays.
    359             if (ClassUtil.isInternalArrayInterfaceName(ClassUtil.internalClassNameFromClassType(thisType)))
    360             {
    361                 return this.generalizeMayBeNull(mayBeNull);
    362             }
    363         }
    364 
    365         // Reduce the common dimension count if either type is an array of
    366         // primitives type of this dimension.
    367         if (commonDimensionCount > 0 &&
    368             (ClassUtil.isInternalPrimitiveType(otherType.charAt(commonDimensionCount))) ||
    369              ClassUtil.isInternalPrimitiveType(thisType.charAt(commonDimensionCount)))
    370         {
    371             commonDimensionCount--;
    372         }
    373 
    374         // Fall back on a basic Object or array of Objects type.
    375         return commonDimensionCount == 0 ?
    376             mayBeNull ?
    377                 ValueFactory.REFERENCE_VALUE_JAVA_LANG_OBJECT_MAYBE_NULL :
    378                 ValueFactory.REFERENCE_VALUE_JAVA_LANG_OBJECT_NOT_NULL   :
    379             new ReferenceValue(ClassUtil.internalArrayTypeFromClassName(ClassConstants.INTERNAL_NAME_JAVA_LANG_OBJECT,
    380                                                                         commonDimensionCount),
    381                                null,
    382                                mayBeNull);
    383     }
    384 
    385 
    386     /**
    387      * Returns if the number of superclasses of the given class in the given
    388      * set of classes.
    389      */
    390     private int superClassCount(Clazz subClass, Set classes)
    391     {
    392         int count = 0;
    393 
    394         Iterator iterator = classes.iterator();
    395 
    396         while (iterator.hasNext())
    397         {
    398             Clazz clazz = (Clazz)iterator.next();
    399             if (subClass.extendsOrImplements(clazz))
    400             {
    401                 count++;
    402             }
    403         }
    404 
    405         //System.out.println("ReferenceValue.superClassCount: ["+subClass.getName()+"]: "+count);
    406 
    407         return count;
    408     }
    409 
    410 
    411     /**
    412      * Returns whether this ReferenceValue is equal to the given other
    413      * ReferenceValue.
    414      * @return <code>NEVER</code>, <code>MAYBE</code>, or <code>ALWAYS</code>.
    415      */
    416     public int equal(ReferenceValue other)
    417     {
    418         return this.type  == null && other.type == null ? ALWAYS : MAYBE;
    419     }
    420 
    421 
    422     // Derived unary methods.
    423 
    424     /**
    425      * Returns whether this ReferenceValue is not <code>null</code>.
    426      * @return <code>NEVER</code>, <code>MAYBE</code>, or <code>ALWAYS</code>.
    427      */
    428     public final int isNotNull()
    429     {
    430         return -isNull();
    431     }
    432 
    433 
    434     /**
    435      * Returns the generalization of this ReferenceValue and the given other
    436      * ReferenceValue.
    437      */
    438     private ReferenceValue generalizeMayBeNull(boolean mayBeNull)
    439     {
    440         return this.mayBeNull || !mayBeNull ?
    441             this :
    442             new ReferenceValue(this.type, this.referencedClass, true);
    443     }
    444 
    445 
    446     // Derived binary methods.
    447 
    448     /**
    449      * Returns whether this ReferenceValue and the given ReferenceValue are different.
    450      * @return <code>NEVER</code>, <code>MAYBE</code>, or <code>ALWAYS</code>.
    451      */
    452     public final int notEqual(ReferenceValue other)
    453     {
    454         return -equal(other);
    455     }
    456 
    457 
    458     // Implementations for Value.
    459 
    460     public final ReferenceValue referenceValue()
    461     {
    462         return this;
    463     }
    464 
    465     public final Value generalize(Value other)
    466     {
    467         return this.generalize(other.referenceValue());
    468     }
    469 
    470     public boolean isParticular()
    471     {
    472         return type == null;
    473     }
    474 
    475     public final int computationalType()
    476     {
    477         return TYPE_REFERENCE;
    478     }
    479 
    480     public final String internalType()
    481     {
    482         return
    483             type == null                        ? ClassConstants.INTERNAL_TYPE_JAVA_LANG_OBJECT :
    484             ClassUtil.isInternalArrayType(type) ? type                                          :
    485                                                   ClassConstants.INTERNAL_TYPE_CLASS_START +
    486                                                   type +
    487                                                   ClassConstants.INTERNAL_TYPE_CLASS_END;
    488     }
    489 
    490 
    491     // Implementations for Object.
    492 
    493     public boolean equals(Object object)
    494     {
    495         if (this == object)
    496         {
    497             return true;
    498         }
    499 
    500         if (object == null ||
    501             this.getClass() != object.getClass())
    502         {
    503             return false;
    504         }
    505 
    506         ReferenceValue other = (ReferenceValue)object;
    507         return this.type == null ? other.type == null :
    508                                    (this.mayBeNull == other.mayBeNull &&
    509                                     this.type.equals(other.type));
    510     }
    511 
    512 
    513     public int hashCode()
    514     {
    515         return this.getClass().hashCode() ^
    516                (type == null ? 0 : type.hashCode() ^ (mayBeNull ? 0 : 1));
    517     }
    518 
    519 
    520     public String toString()
    521     {
    522         return type == null ?
    523             "null" :
    524             type + (referencedClass == null ? "?" : "") + (mayBeNull ? "" : "!");
    525     }
    526 }
    527