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