1 19 20 package edu.umd.cs.findbugs.detect; 21 22 23 import edu.umd.cs.findbugs.*; 24 import edu.umd.cs.findbugs.ba.AnalysisContext; 25 import edu.umd.cs.findbugs.ba.ClassContext; 26 import edu.umd.cs.findbugs.ba.XFactory; 27 import edu.umd.cs.findbugs.ba.XMethod; 28 import edu.umd.cs.findbugs.visitclass.PreorderVisitor; 29 import java.util.*; 30 import java.util.regex.*; 31 import org.apache.bcel.Repository; 32 import org.apache.bcel.classfile.*; 33 import org.apache.bcel.classfile.Deprecated; 34 35 public class Naming extends PreorderVisitor implements Detector { 36 String baseClassName; 37 boolean classIsPublicOrProtected; 38 39 public static boolean definedIn(JavaClass clazz, XMethod m) { 40 for(Method m2 : clazz.getMethods()) 41 if (m.getName().equals(m2.getName()) && m.getSignature().equals(m2.getSignature()) && m.isStatic() == m2.isStatic()) return true; 42 return false; 43 } 44 45 public static boolean confusingMethodNames(XMethod m1, XMethod m2) { 46 if (m1.isStatic() != m2.isStatic()) return false; 47 if (m1.getClassName().equals(m2.getClassName())) return false; 48 49 if (m1.getName().equalsIgnoreCase(m2.getName()) 50 && !m1.getName().equals(m2.getName()) 51 && m1.getSignature().equals(m2.getSignature())) return true; 52 if (m1.getSignature().equals(m2.getSignature())) return false; 53 if (removePackageNamesFromSignature(m1.getSignature()).equals(removePackageNamesFromSignature(m2.getSignature()))) 54 return true; 55 return false; 56 } 57 58 HashMap<String , HashSet<String >> canonicalToTrueMapping 60 = new HashMap<String , HashSet<String >>(); 61 HashMap<String , HashSet<XMethod>> canonicalToXMethod 63 = new HashMap<String , HashSet<XMethod>>(); 64 65 HashSet<String > visited = new HashSet<String >(); 66 67 private BugReporter bugReporter; 68 69 public Naming(BugReporter bugReporter) { 70 this.bugReporter = bugReporter; 71 } 72 73 public void visitClassContext(ClassContext classContext) { 74 classContext.getJavaClass().accept(this); 75 } 76 77 private boolean checkSuper(XMethod m, HashSet<XMethod> others) { 78 for (XMethod m2 : others) { 79 try { 80 if (confusingMethodNames(m, m2) 81 && Repository.instanceOf(m.getClassName(), m2.getClassName())) { 82 int priority = HIGH_PRIORITY; 83 try { 84 JavaClass clazz = Repository.lookupClass(m.getClassName()); 85 if (definedIn(clazz, m2)) 86 priority+=2; 87 for(JavaClass i : clazz.getAllInterfaces()) 88 if (definedIn(i, m)) 89 priority+=2; 90 for(JavaClass s : clazz.getSuperClasses()) 91 if (definedIn(s, m)) 92 priority+=2; 93 } catch (ClassNotFoundException e) { 94 priority++; 95 AnalysisContext.reportMissingClass(e); 96 } 97 String pattern = "NM_VERY_CONFUSING"; 98 if (priority == HIGH_PRIORITY && AnalysisContext.currentXFactory().isCalled(m)) priority = NORMAL_PRIORITY; 99 else if (priority > NORMAL_PRIORITY && m.getSignature().equals(m2.getSignature())) { 100 pattern = "NM_VERY_CONFUSING_INTENTIONAL"; 101 priority = NORMAL_PRIORITY; 102 } 103 XFactory xFactory = AnalysisContext.currentXFactory(); 104 if (xFactory.getDeprecated().contains(m) || xFactory.getDeprecated().contains(m2)) priority++; 105 106 bugReporter.reportBug(new BugInstance(this, pattern, priority) 107 .addClass(m.getClassName()) 108 .addMethod(m) 109 .addClass(m2.getClassName()) 110 .addMethod(m2)); 111 return true; 112 } 113 } catch (ClassNotFoundException e) { 114 AnalysisContext.reportMissingClass(e); 115 } 116 } 117 return false; 118 } 119 120 private boolean checkNonSuper(XMethod m, HashSet<XMethod> others) { 121 for (XMethod m2 : others) { 122 if (confusingMethodNames(m,m2)) { 123 bugReporter.reportBug(new BugInstance(this, "NM_CONFUSING", LOW_PRIORITY) 124 .addClass(m.getClassName()) 125 .addMethod(m) 126 .addClass(m2.getClassName()) 127 .addMethod(m2)); 128 return true; 129 } 130 } 131 return false; 132 } 133 134 135 public void report() { 136 137 canonicalNameIterator: 138 for (String allSmall : canonicalToTrueMapping.keySet()) { 139 HashSet<String > s = canonicalToTrueMapping.get(allSmall); 140 if (s.size() <= 1) 141 continue; 142 HashSet<XMethod> conflictingMethods = canonicalToXMethod.get(allSmall); 143 for (Iterator<XMethod> j = conflictingMethods.iterator(); j.hasNext();) { 144 if (checkSuper(j.next(), conflictingMethods)) 145 j.remove(); 146 } 147 for (XMethod conflictingMethod : conflictingMethods) { 148 if (checkNonSuper(conflictingMethod, conflictingMethods)) 149 continue canonicalNameIterator; 150 } 151 } 152 } 153 154 @Override 155 public void visitJavaClass(JavaClass obj) { 156 if (obj.isInterface()) return; 157 String name = obj.getClassName(); 158 if (!visited.add(name)) return; 159 try { 160 JavaClass supers[] = Repository.getSuperClasses(obj); 161 for (JavaClass aSuper : supers) { 162 visitJavaClass(aSuper); 163 } 164 } catch (ClassNotFoundException e) { 165 } 167 super.visitJavaClass(obj); 168 } 169 170 @Override 171 public void visit(JavaClass obj) { 172 String name = obj.getClassName(); 173 String [] parts = name.split("[$+.]"); 174 baseClassName = parts[parts.length - 1]; 175 classIsPublicOrProtected = obj.isPublic() || obj.isProtected(); 176 if (baseClassName.length() == 1) return; 177 if(Character.isLetter(baseClassName.charAt(0)) 178 && !Character.isUpperCase(baseClassName.charAt(0)) 179 && baseClassName.indexOf("_") ==-1 180 ) 181 bugReporter.reportBug(new BugInstance(this, 182 "NM_CLASS_NAMING_CONVENTION", 183 classIsPublicOrProtected 184 ? NORMAL_PRIORITY 185 : LOW_PRIORITY 186 ) 187 .addClass(this)); 188 if (name.endsWith("Exception") 189 && (!obj.getSuperclassName().endsWith("Exception")) 190 && (!obj.getSuperclassName().endsWith("Error")) 191 && (!obj.getSuperclassName().endsWith("Throwable"))) { 192 bugReporter.reportBug(new BugInstance(this, 193 "NM_CLASS_NOT_EXCEPTION", 194 NORMAL_PRIORITY ) 195 .addClass(this)); 196 } 197 198 super.visit(obj); 199 } 200 201 @Override 202 public void visit(Field obj) { 203 if (getFieldName().length() == 1) return; 204 205 if (!obj.isFinal() 206 && Character.isLetter(getFieldName().charAt(0)) 207 && !Character.isLowerCase(getFieldName().charAt(0)) 208 && getFieldName().indexOf("_") == -1 209 && Character.isLetter(getFieldName().charAt(1)) 210 && Character.isLowerCase(getFieldName().charAt(1))) { 211 bugReporter.reportBug(new BugInstance(this, 212 "NM_FIELD_NAMING_CONVENTION", 213 classIsPublicOrProtected 214 && (obj.isPublic() || obj.isProtected()) 215 ? NORMAL_PRIORITY 216 : LOW_PRIORITY) 217 .addClass(this) 218 .addVisitedField(this) 219 ); 220 } 221 } 222 private final static Pattern sigType = Pattern.compile("L([^;]*/)?([^/]+;)"); 223 private boolean isInnerClass(JavaClass obj) { 224 for(Field f : obj.getFields()) 225 if (f.getName().startsWith("this$")) return true; 226 return false; 227 } 228 private boolean markedAsNotUsable(Method obj) { 229 for(Attribute a : obj.getAttributes()) 230 if (a instanceof Deprecated ) return true; 231 Code code = obj.getCode(); 232 if (code == null) return false; 233 byte [] codeBytes = code.getCode(); 234 if (codeBytes.length > 1 && codeBytes.length < 10) { 235 int lastOpcode = codeBytes[codeBytes.length-1] & 0xff; 236 if (lastOpcode != ATHROW) return false; 237 for(int b : codeBytes) 238 if ((b & 0xff) == RETURN) return false; 239 return true; 240 } 241 return false; 242 } 243 @Override 244 public void visit(Method obj) { 245 String mName = getMethodName(); 246 if (mName.length() == 1) return; 247 if (Character.isLetter(mName.charAt(0)) 248 && !Character.isLowerCase(mName.charAt(0)) 249 && Character.isLetter(mName.charAt(1)) 250 && Character.isLowerCase(mName.charAt(1)) 251 && mName.indexOf("_") == -1 ) 252 bugReporter.reportBug(new BugInstance(this, 253 "NM_METHOD_NAMING_CONVENTION", 254 classIsPublicOrProtected 255 && (obj.isPublic() || obj.isProtected()) 256 ? NORMAL_PRIORITY 257 : LOW_PRIORITY) 258 .addClassAndMethod(this)); 259 String sig = getMethodSig(); 260 if (mName.equals(baseClassName) && sig.equals("()V")) { 261 Code code = obj.getCode(); 262 if (code != null && !markedAsNotUsable(obj)) { 263 int priority = NORMAL_PRIORITY; 264 byte [] codeBytes = code.getCode(); 265 if (codeBytes.length > 1) 266 priority--; 267 if (!obj.isPrivate() && getThisClass().isPublic()) 268 priority--; 269 bugReporter.reportBug( new BugInstance(this, "NM_METHOD_CONSTRUCTOR_CONFUSION", priority).addClassAndMethod(this).lowerPriorityIfDeprecated()); 270 return; 271 } 272 } 273 274 if (obj.isAbstract()) return; 275 if (obj.isPrivate()) return; 276 277 if (mName.equals("equal") && sig.equals("(Ljava/lang/Object;)Z")) { 278 bugReporter.reportBug(new BugInstance(this, "NM_BAD_EQUAL", HIGH_PRIORITY) 279 .addClassAndMethod(this).lowerPriorityIfDeprecated()); 280 return; 281 } 282 if (mName.equals("hashcode") && sig.equals("()I")) { 283 bugReporter.reportBug(new BugInstance(this, "NM_LCASE_HASHCODE", HIGH_PRIORITY) 284 .addClassAndMethod(this).lowerPriorityIfDeprecated()); 285 return; 286 } 287 if (mName.equals("tostring") && sig.equals("()Ljava/lang/String;")) { 288 bugReporter.reportBug(new BugInstance(this, "NM_LCASE_TOSTRING", HIGH_PRIORITY) 289 .addClassAndMethod(this).lowerPriorityIfDeprecated()); 290 return; 291 } 292 293 294 if (obj.isPrivate() 295 || obj.isStatic() 296 || mName.equals("<init>") 297 ) 298 return; 299 300 String trueName = mName + sig; 301 String sig2 = removePackageNamesFromSignature(sig); 302 String allSmall = mName.toLowerCase() + sig2; 303 304 305 XMethod xm = XFactory.createXMethod(this); 306 { 307 HashSet<String > s = canonicalToTrueMapping.get(allSmall); 308 if (s == null) { 309 s = new HashSet<String >(); 310 canonicalToTrueMapping.put(allSmall, s); 311 } 312 s.add(trueName); 313 } 314 { 315 HashSet<XMethod> s = canonicalToXMethod.get(allSmall); 316 if (s == null) { 317 s = new HashSet<XMethod>(); 318 canonicalToXMethod.put(allSmall, s); 319 } 320 s.add(xm); 321 } 322 323 } 324 325 private static String removePackageNamesFromSignature(String sig) { 326 int end = sig.indexOf(")"); 327 Matcher m = sigType.matcher(sig.substring(0,end)); 328 return m.replaceAll("L$2") + sig.substring(end); 329 } 330 331 332 } 333 | Popular Tags |