1 package edu.umd.cs.findbugs.detect; 2 3 import java.util.BitSet ; 4 import java.util.HashSet ; 5 import java.util.Iterator ; 6 import java.util.Set ; 7 8 import org.apache.bcel.Constants; 9 import org.apache.bcel.Repository; 10 import org.apache.bcel.classfile.Attribute; 11 import org.apache.bcel.classfile.JavaClass; 12 import org.apache.bcel.classfile.Method; 13 import org.apache.bcel.classfile.Synthetic; 14 import org.apache.bcel.generic.CHECKCAST; 15 import org.apache.bcel.generic.ConstantPoolGen; 16 import org.apache.bcel.generic.INSTANCEOF; 17 import org.apache.bcel.generic.Instruction; 18 import org.apache.bcel.generic.InstructionHandle; 19 import org.apache.bcel.generic.InvokeInstruction; 20 import org.apache.bcel.generic.MethodGen; 21 import org.apache.bcel.generic.ObjectType; 22 import org.apache.bcel.generic.ReferenceType; 23 import org.apache.bcel.generic.Type; 24 import org.apache.bcel.generic.TypedInstruction; 25 26 import edu.umd.cs.findbugs.DeepSubtypeAnalysis; 27 import edu.umd.cs.findbugs.BugAccumulator; 28 import edu.umd.cs.findbugs.BugInstance; 29 import edu.umd.cs.findbugs.BugReporter; 30 import edu.umd.cs.findbugs.Detector; 31 import edu.umd.cs.findbugs.SourceLineAnnotation; 32 import edu.umd.cs.findbugs.SystemProperties; 33 import edu.umd.cs.findbugs.ba.CFG; 34 import edu.umd.cs.findbugs.ba.CFGBuilderException; 35 import edu.umd.cs.findbugs.ba.ClassContext; 36 import edu.umd.cs.findbugs.ba.DataflowAnalysisException; 37 import edu.umd.cs.findbugs.ba.Location; 38 import edu.umd.cs.findbugs.ba.MethodUnprofitableException; 39 import edu.umd.cs.findbugs.ba.npe.IsNullValue; 40 import edu.umd.cs.findbugs.ba.npe.IsNullValueDataflow; 41 import edu.umd.cs.findbugs.ba.npe.IsNullValueFrame; 42 import edu.umd.cs.findbugs.ba.type.NullType; 43 import edu.umd.cs.findbugs.ba.type.TopType; 44 import edu.umd.cs.findbugs.ba.type.TypeDataflow; 45 import edu.umd.cs.findbugs.ba.type.TypeFrame; 46 import edu.umd.cs.findbugs.ba.vna.ValueNumber; 47 import edu.umd.cs.findbugs.ba.vna.ValueNumberDataflow; 48 import edu.umd.cs.findbugs.ba.vna.ValueNumberFrame; 49 50 public class FindBadCast2 implements Detector { 51 52 private BugReporter bugReporter; 53 54 private Set <String > concreteCollectionClasses = new HashSet <String >(); 55 56 private Set <String > abstractCollectionClasses = new HashSet <String >(); 57 private Set <String > veryAbstractCollectionClasses = new HashSet <String >(); 58 59 private static final boolean DEBUG = SystemProperties.getBoolean("bc.debug"); 60 61 public FindBadCast2(BugReporter bugReporter) { 62 this.bugReporter = bugReporter; 63 veryAbstractCollectionClasses.add("java.util.Collection"); 64 veryAbstractCollectionClasses.add("java.util.Iterable"); 65 abstractCollectionClasses.add("java.util.Collection"); 66 abstractCollectionClasses.add("java.util.List"); 67 abstractCollectionClasses.add("java.util.Set"); 68 abstractCollectionClasses.add("java.util.SortedSet"); 69 abstractCollectionClasses.add("java.util.SortedMap"); 70 abstractCollectionClasses.add("java.util.Map"); 71 concreteCollectionClasses.add("java.util.LinkedHashMap"); 72 concreteCollectionClasses.add("java.util.LinkedHashSet"); 73 concreteCollectionClasses.add("java.util.HashMap"); 74 concreteCollectionClasses.add("java.util.HashSet"); 75 concreteCollectionClasses.add("java.util.TreeMap"); 76 concreteCollectionClasses.add("java.util.TreeSet"); 77 concreteCollectionClasses.add("java.util.ArrayList"); 78 concreteCollectionClasses.add("java.util.LinkedList"); 79 concreteCollectionClasses.add("java.util.Hashtable"); 80 concreteCollectionClasses.add("java.util.Vector"); 81 } 82 83 public void visitClassContext(ClassContext classContext) { 84 JavaClass javaClass = classContext.getJavaClass(); 85 Method[] methodList = javaClass.getMethods(); 86 87 for (Method method : methodList) { 88 if (method.getCode() == null) 89 continue; 90 91 try { 92 analyzeMethod(classContext, method); 93 } catch (MethodUnprofitableException e) { 94 assert true; } catch (CFGBuilderException e) { 96 String msg = "Detector " + this.getClass().getName() 97 + " caught exception while analyzing " + javaClass.getClassName() + "." + method.getName() + " : " + method.getSignature(); 98 bugReporter.logError(msg , e); 99 } catch (DataflowAnalysisException e) { 100 String msg = "Detector " + this.getClass().getName() 101 + " caught exception while analyzing " + javaClass.getClassName() + "." + method.getName() + " : " + method.getSignature(); 102 bugReporter.logError(msg, e); 103 } 104 } 105 } 106 107 public boolean prescreen(ClassContext classContext, Method method) { 108 BitSet bytecodeSet = classContext.getBytecodeSet(method); 109 return bytecodeSet != null && (bytecodeSet.get(Constants.CHECKCAST) 110 || bytecodeSet.get(Constants.INSTANCEOF)); 111 } 112 113 private boolean isSynthetic(Method m) { 114 Attribute[] attrs = m.getAttributes(); 115 for (Attribute attr : attrs) { 116 if (attr instanceof Synthetic) 117 return true; 118 } 119 return false; 120 } 121 private Set <ValueNumber> getParameterValueNumbers(ClassContext classContext, Method method, CFG cfg ) throws DataflowAnalysisException, CFGBuilderException { 122 ValueNumberDataflow vnaDataflow = classContext.getValueNumberDataflow(method); 123 ValueNumberFrame vnaFrameAtEntry = vnaDataflow.getStartFact(cfg 124 .getEntry()); 125 Set <ValueNumber> paramValueNumberSet = new HashSet <ValueNumber>(); 126 int firstParam = method.isStatic() ? 0 : 1; 127 for (int i = firstParam; i < vnaFrameAtEntry.getNumLocals(); ++i) { 128 paramValueNumberSet.add(vnaFrameAtEntry.getValue(i)); 129 } 130 return paramValueNumberSet; 131 } 132 private void analyzeMethod(ClassContext classContext, Method method) 133 throws CFGBuilderException, DataflowAnalysisException { 134 if (isSynthetic(method) || !prescreen(classContext, method)) 135 return; 136 BugAccumulator accumulator = new BugAccumulator(bugReporter); 137 138 139 CFG cfg = classContext.getCFG(method); 140 TypeDataflow typeDataflow = classContext.getTypeDataflow(method); 141 IsNullValueDataflow isNullDataflow = classContext.getIsNullValueDataflow(method); 142 Set <ValueNumber> paramValueNumberSet = null; 143 144 ValueNumberDataflow vnaDataflow = null; 145 146 ConstantPoolGen cpg = classContext.getConstantPoolGen(); 147 MethodGen methodGen = classContext.getMethodGen(method); 148 if (methodGen == null) return; 149 String methodName = methodGen.getClassName() + "." 150 + methodGen.getName(); 151 String sourceFile = classContext.getJavaClass().getSourceFileName(); 152 if (DEBUG) { 153 System.out.println("Checking " + methodName); 154 } 155 156 Set <SourceLineAnnotation> haveInstanceOf = new HashSet <SourceLineAnnotation>(); 157 Set <SourceLineAnnotation> haveCast = new HashSet <SourceLineAnnotation>(); 158 Set <SourceLineAnnotation> haveMultipleInstanceOf = new HashSet <SourceLineAnnotation>(); 159 Set <SourceLineAnnotation> haveMultipleCast = new HashSet <SourceLineAnnotation>(); 160 for (Iterator <Location> i = cfg.locationIterator(); i.hasNext();) { 161 Location location = i.next(); 162 InstructionHandle handle = location.getHandle(); 163 Instruction ins = handle.getInstruction(); 164 165 if (!(ins instanceof CHECKCAST) && !(ins instanceof INSTANCEOF)) 166 continue; 167 168 SourceLineAnnotation sourceLineAnnotation = SourceLineAnnotation 169 .fromVisitedInstruction(classContext, methodGen, sourceFile, handle); 170 if (ins instanceof CHECKCAST) { 171 if (!haveCast.add(sourceLineAnnotation)) 172 haveMultipleCast.add(sourceLineAnnotation); 173 } else { 174 if (!haveInstanceOf.add(sourceLineAnnotation)) 175 haveMultipleInstanceOf.add(sourceLineAnnotation); 176 } 177 } 178 for (Iterator <Location> i = cfg.locationIterator(); i.hasNext();) { 179 Location location = i.next(); 180 181 InstructionHandle handle = location.getHandle(); 182 int pc = handle.getPosition(); 183 Instruction ins = handle.getInstruction(); 184 185 if (!(ins instanceof CHECKCAST) && !(ins instanceof INSTANCEOF)) 186 continue; 187 if (handle.getNext() == null) continue; 188 Instruction nextIns = handle.getNext().getInstruction(); 189 190 boolean isCast = ins instanceof CHECKCAST; 191 String kind = isCast ? "checkedCast" : "instanceof"; 192 int occurrences = cfg.getLocationsContainingInstructionWithOffset( 193 pc).size(); 194 boolean split = occurrences > 1; 195 IsNullValueFrame nullFrame = isNullDataflow.getFactAtLocation(location); 196 if (!nullFrame.isValid()) continue; 197 IsNullValue operandNullness = nullFrame.getTopValue(); 198 if (DEBUG) { 199 System.out 200 .println(kind + " at pc: " + pc + " in " + methodName); 201 System.out.println(" occurrences: " + occurrences); 202 System.out.println("XXX: " + operandNullness); 203 204 } 205 206 if (split && !isCast) { 207 continue; 209 } 210 211 TypeFrame frame = typeDataflow.getFactAtLocation(location); 212 if (!frame.isValid()) { 213 continue; 215 } 216 217 Type operandType = frame.getTopValue(); 218 if (operandType.equals(TopType.instance())) { 219 continue; 221 } 222 boolean operandTypeIsExact = frame.isExact(frame.getStackLocation(0)); 223 Type castType = ((TypedInstruction) ins).getType(cpg); 224 225 if (!(castType instanceof ReferenceType)) { 226 continue; 228 } 229 String castSig = castType.getSignature(); 230 231 if (operandType.equals(NullType.instance()) || operandNullness.isDefinitelyNull()) { 232 SourceLineAnnotation sourceLineAnnotation = SourceLineAnnotation 233 .fromVisitedInstruction(classContext, methodGen, sourceFile, handle); 234 String castName = castSig.substring(1, castSig.length() - 1) 235 .replace('/', '.'); 236 if (!isCast) accumulator.accumulateBug(new BugInstance(this, 237 "NP_NULL_INSTANCEOF", NORMAL_PRIORITY) 238 .addClassAndMethod(methodGen, sourceFile) 239 .addClass(castName), sourceLineAnnotation); 240 continue; 241 242 } 243 if (!(operandType instanceof ReferenceType)) { 244 continue; 246 } 247 ReferenceType refType = (ReferenceType) operandType; 248 249 250 if (refType.equals(castType)) { 251 continue; 253 } 254 255 String refSig = refType.getSignature(); 256 String castSig2 = castSig; 257 String refSig2 = refSig; 258 while (castSig2.charAt(0) == '[' && refSig2.charAt(0) == '[') { 259 castSig2 = castSig2.substring(1); 260 refSig2 = refSig2.substring(1); 261 } 262 263 264 SourceLineAnnotation sourceLineAnnotation = SourceLineAnnotation 265 .fromVisitedInstruction(classContext, methodGen, sourceFile, handle); 266 267 if (refSig2.charAt(0) != 'L' || castSig2.charAt(0) != 'L') { 268 if ( castSig2.charAt(0) == '[' && (refSig2.equals("Ljava/io/Serializable;") 269 || refSig2.equals("Ljava/lang/Object;") 270 || refSig2.equals("Ljava/lang/Cloneable;"))) continue; 271 if ( refSig2.charAt(0) == '[' && (castSig2.equals("Ljava/io/Serializable;") 272 || castSig2.equals("Ljava/lang/Object;") 273 || castSig2.equals("Ljava/lang/Cloneable;"))) continue; 274 bugReporter.reportBug( 275 new BugInstance(this, 276 "BC_IMPOSSIBLE_CAST", HIGH_PRIORITY ) 277 .addClassAndMethod(methodGen, sourceFile) 278 .addType(refSig) 279 .addType(castSig) 280 .addSourceLine(sourceLineAnnotation)); 281 continue; 282 } 283 284 if (refSig2.equals("Ljava/lang/Object;") &!operandTypeIsExact) { 285 continue; 286 } 287 if (false && isCast && haveMultipleCast.contains(sourceLineAnnotation) 288 || !isCast 289 && haveMultipleInstanceOf.contains(sourceLineAnnotation)) { 290 continue; 292 } 293 String castName = castSig2.substring(1, castSig2.length() - 1) 294 .replace('/', '.'); 295 String refName = refSig2.substring(1, refSig2.length() - 1) 296 .replace('/', '.'); 297 298 if (vnaDataflow == null) 299 vnaDataflow = classContext 300 .getValueNumberDataflow(method); 301 ValueNumberFrame vFrame = vnaDataflow.getFactAtLocation(location); 302 if (paramValueNumberSet == null) 303 paramValueNumberSet = getParameterValueNumbers(classContext, method, cfg); 304 boolean isParameter = paramValueNumberSet.contains(vFrame 305 .getTopValue()); 306 try { 307 JavaClass castJavaClass = Repository.lookupClass(castName); 308 JavaClass refJavaClass = Repository.lookupClass(refName); 309 boolean upcast = Repository.instanceOf(refJavaClass, 310 castJavaClass); 311 if (upcast) { 312 if (!isCast) 313 accumulator.accumulateBug(new BugInstance(this, 314 "BC_VACUOUS_INSTANCEOF", NORMAL_PRIORITY) 315 .addClassAndMethod(methodGen, sourceFile) 316 317 .addType(refSig) 318 .addType(castSig) 319 ,sourceLineAnnotation); 320 } else { 321 boolean downcast = Repository.instanceOf(castJavaClass, 322 refJavaClass); 323 324 if (refName.equals("java.lang.Object") &!operandTypeIsExact) continue; 325 double rank = 0.0; 326 boolean castToConcreteCollection = concreteCollectionClasses.contains(castName) 327 && abstractCollectionClasses.contains(refName); 328 boolean castToAbstractCollection = 329 abstractCollectionClasses.contains(castName) 330 && veryAbstractCollectionClasses.contains(refName); 331 332 if (!operandTypeIsExact) { 333 rank = DeepSubtypeAnalysis.deepInstanceOf(refJavaClass, 334 castJavaClass); 335 if (castToConcreteCollection 336 && rank > 0.6) 337 rank = (rank + 0.6) /2; 338 else if (castToAbstractCollection 339 && rank > 0.3) 340 rank = (rank + 0.3) /2; 341 } 342 343 344 if (false) 345 System.out.println("Rank:\t" + rank + "\t" + refName 346 + "\t" + castName); 347 boolean completeInformation = (!castJavaClass.isInterface() && !refJavaClass 348 .isInterface()) 349 || refJavaClass.isFinal() 350 || castJavaClass.isFinal(); 351 if (DEBUG) { 352 System.out.println("cast from " + refName + " to " 353 + castName); 354 System.out.println(" is downcast: " + downcast); 355 System.out.println(" operand type is exact: " + operandTypeIsExact); 356 357 System.out.println(" complete information: " 358 + completeInformation); 359 System.out.println(" isParameter: " 360 + vFrame.getTopValue()); 361 System.out.println(" score: " + rank); 362 } 363 if (!downcast && completeInformation || operandTypeIsExact) 364 bugReporter.reportBug(new BugInstance(this, 365 isCast ? "BC_IMPOSSIBLE_CAST" 366 : "BC_IMPOSSIBLE_INSTANCEOF", 367 isCast ? HIGH_PRIORITY : NORMAL_PRIORITY) 368 .addClassAndMethod(methodGen, sourceFile) 369 370 .addType(refSig) 371 .addType(castSig) 372 .addSourceLine(sourceLineAnnotation)); 373 else if (isCast && rank < 0.9) { 374 375 int priority = NORMAL_PRIORITY; 376 377 if (rank > 0.75) 378 priority += 2; 379 else if (rank > 0.5) 380 priority += 1; 381 else if (rank > 0.25) 382 priority += 0; 383 else 384 priority--; 385 386 if (DEBUG) 387 System.out.println(" priority a: " + priority); 388 if (methodGen.getClassName().startsWith(refName) 389 || methodGen.getClassName().startsWith(castName)) 390 priority += 1; 391 if (DEBUG) 392 System.out.println(" priority b: " + priority); 393 if (castJavaClass.isInterface() && !castToAbstractCollection) 394 priority++; 395 if (DEBUG) 396 System.out.println(" priority c: " + priority); 397 if (castToConcreteCollection 398 && veryAbstractCollectionClasses.contains(refName)) 399 priority--; 400 if (DEBUG) 401 System.out.println(" priority d: " + priority); 402 if (priority <= LOW_PRIORITY 403 && !castToAbstractCollection 404 && !castToConcreteCollection 405 && (refJavaClass.isInterface() || refJavaClass 406 .isAbstract())) 407 priority++; 408 if (DEBUG) 409 System.out.println(" priority e: " + priority); 410 if (DEBUG) 411 System.out.println(" ref name: " + refName); 412 if (methodGen.getName().equals("compareTo")) 413 priority++; 414 else if (methodGen.isPublic() && isParameter) 415 priority--; 416 if (DEBUG) 417 System.out.println(" priority h: " + priority); 418 if (priority < HIGH_PRIORITY) 419 priority = HIGH_PRIORITY; 420 if (priority <= LOW_PRIORITY) { 421 String bug = "BC_UNCONFIRMED_CAST"; 422 if (castToConcreteCollection) 423 bug = "BC_BAD_CAST_TO_CONCRETE_COLLECTION"; 424 else if (castToAbstractCollection) 425 bug = "BC_BAD_CAST_TO_ABSTRACT_COLLECTION"; 426 427 accumulator.accumulateBug(new BugInstance(this, bug, priority) 428 .addClassAndMethod(methodGen, sourceFile) 429 .addType(refSig) 430 .addType(castSig), 431 sourceLineAnnotation 432 ); 433 } 434 435 } 436 437 } 438 } catch (ClassNotFoundException e) { 439 } 440 } 441 accumulator.reportAccumulatedBugs(); 442 } 443 444 public void report() { 445 } 446 447 } 448 | Popular Tags |