/* * FindBugs - Find bugs in Java programs * Copyright (C) 2003-2005 University of Maryland * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package edu.umd.cs.findbugs.detect; import java.math.BigDecimal; import java.util.Iterator; import org.apache.bcel.classfile.Attribute; import org.apache.bcel.classfile.Code; import org.apache.bcel.classfile.CodeException; import org.apache.bcel.classfile.Constant; import org.apache.bcel.classfile.ConstantClass; import org.apache.bcel.classfile.ConstantDouble; import org.apache.bcel.classfile.ConstantInteger; import org.apache.bcel.classfile.ConstantLong; import org.apache.bcel.classfile.ConstantPool; import org.apache.bcel.classfile.ConstantValue; import org.apache.bcel.classfile.Field; import org.apache.bcel.classfile.JavaClass; import org.apache.bcel.classfile.Method; import org.apache.bcel.classfile.Synthetic; import org.apache.bcel.generic.ObjectType; import org.apache.bcel.generic.ReferenceType; import org.apache.bcel.generic.Type; import edu.umd.cs.findbugs.BugAccumulator; import edu.umd.cs.findbugs.BugInstance; import edu.umd.cs.findbugs.BugReporter; import edu.umd.cs.findbugs.ClassAnnotation; import edu.umd.cs.findbugs.IntAnnotation; import edu.umd.cs.findbugs.JavaVersion; import edu.umd.cs.findbugs.LocalVariableAnnotation; import edu.umd.cs.findbugs.MethodAnnotation; import edu.umd.cs.findbugs.OpcodeStack; import edu.umd.cs.findbugs.SourceLineAnnotation; import edu.umd.cs.findbugs.StringAnnotation; import edu.umd.cs.findbugs.SystemProperties; import edu.umd.cs.findbugs.OpcodeStack.Item; import edu.umd.cs.findbugs.ba.AnalysisContext; import edu.umd.cs.findbugs.ba.CFGBuilderException; import edu.umd.cs.findbugs.ba.DataflowAnalysisException; import edu.umd.cs.findbugs.ba.Hierarchy; import edu.umd.cs.findbugs.ba.ObjectTypeFactory; import edu.umd.cs.findbugs.ba.SignatureParser; import edu.umd.cs.findbugs.ba.XMethod; import edu.umd.cs.findbugs.ba.type.TypeDataflow; import edu.umd.cs.findbugs.bcel.OpcodeStackDetector; import edu.umd.cs.findbugs.classfile.ClassDescriptor; import edu.umd.cs.findbugs.classfile.DescriptorFactory; import edu.umd.cs.findbugs.core.Priorities; import edu.umd.cs.findbugs.util.ClassName; import edu.umd.cs.findbugs.util.Util; public class DumbMethods extends OpcodeStackDetector { private static final ObjectType CONDITION_TYPE = ObjectTypeFactory.getInstance("java.util.concurrent.locks.Condition"); private final BugReporter bugReporter; private boolean sawCurrentTimeMillis; private BugInstance gcInvocationBugReport; private int gcInvocationPC; private CodeException[] exceptionTable; /* * private boolean sawLDCEmptyString; */ private String primitiveObjCtorSeen; private boolean ctorSeen; private boolean prevOpcodeWasReadLine; private int prevOpcode; private boolean isPublicStaticVoidMain; private boolean isEqualsObject; private boolean sawInstanceofCheck; private boolean reportedBadCastInEquals; private int sawCheckForNonNegativeSignedByte; private int sinceBufferedInputStreamReady; private int randomNextIntState; private boolean checkForBitIorofSignedByte; private final boolean jdk15ChecksEnabled; private final BugAccumulator accumulator; private static final int MICROS_PER_DAY_OVERFLOWED_AS_INT = 24 * 60 * 60 * 1000 * 1000; public DumbMethods(BugReporter bugReporter) { this.bugReporter = bugReporter; accumulator = new BugAccumulator(bugReporter); jdk15ChecksEnabled = JavaVersion.getRuntimeVersion().isSameOrNewerThan(JavaVersion.JAVA_1_5); } boolean isSynthetic; @Override public void visit(JavaClass obj) { String superclassName = obj.getSuperclassName(); isSynthetic = superclassName.equals("java.rmi.server.RemoteStub"); Attribute[] attributes = obj.getAttributes(); if (attributes != null) { for (Attribute a : attributes) { if (a instanceof Synthetic) { isSynthetic = true; } } } } @Override public void visitAfter(JavaClass obj) { accumulator.reportAccumulatedBugs(); } public static boolean isTestMethod(Method method) { return method.getName().startsWith("test"); } @Override public void visit(Field field) { ConstantValue value = field.getConstantValue(); if (value == null) return; Constant c = getConstantPool().getConstant(value.getConstantValueIndex()); if (c instanceof ConstantLong && ((ConstantLong)c).getBytes() == MICROS_PER_DAY_OVERFLOWED_AS_INT && SystemProperties.getBoolean("report_TESTING_pattern_in_standard_detectors")) { bugReporter.reportBug( new BugInstance(this, "TESTING", HIGH_PRIORITY).addClass(this).addField(this) .addString("Did you mean MICROS_PER_DAY") .addInt(MICROS_PER_DAY_OVERFLOWED_AS_INT) .describe(IntAnnotation.INT_VALUE)); } } @Override public void visit(Method method) { String cName = getDottedClassName(); // System.out.println(getFullyQualifiedMethodName()); isPublicStaticVoidMain = method.isPublic() && method.isStatic() && getMethodName().equals("main") || cName.toLowerCase().indexOf("benchmark") >= 0; prevOpcodeWasReadLine = false; Code code = method.getCode(); if (code != null) { this.exceptionTable = code.getExceptionTable(); } if (this.exceptionTable == null) { this.exceptionTable = new CodeException[0]; } primitiveObjCtorSeen = null; ctorSeen = false; randomNextIntState = 0; checkForBitIorofSignedByte = false; isEqualsObject = getMethodName().equals("equals") && getMethodSig().equals("(Ljava/lang/Object;)Z") && !method.isStatic(); sawInstanceofCheck = false; reportedBadCastInEquals = false; freshRandomOnTos = false; sinceBufferedInputStreamReady = 100000; sawCheckForNonNegativeSignedByte = -1000; } int opcodesSincePendingAbsoluteValueBug; BugInstance pendingAbsoluteValueBug; SourceLineAnnotation pendingAbsoluteValueBugSourceLine; boolean freshRandomOnTos = false; boolean freshRandomOneBelowTos = false; @Override public void sawOpcode(int seen) { // System.out.printf("%3d %12s %s%n", getPC(), OPCODE_NAMES[seen], // stack); if (seen == LDC || seen == LDC_W || seen == LDC2_W) { Constant c = getConstantRefOperand(); if (SystemProperties.getBoolean("report_TESTING_pattern_in_standard_detectors") && (c instanceof ConstantInteger && ((ConstantInteger) c).getBytes() == MICROS_PER_DAY_OVERFLOWED_AS_INT || c instanceof ConstantLong && ((ConstantLong) c).getBytes() == MICROS_PER_DAY_OVERFLOWED_AS_INT)) { BugInstance bug = new BugInstance(this, "TESTING", HIGH_PRIORITY).addClassAndMethod(this) .addString("Did you mean MICROS_PER_DAY").addInt(MICROS_PER_DAY_OVERFLOWED_AS_INT) .describe(IntAnnotation.INT_VALUE); accumulator.accumulateBug(bug, this); } } if (seen == LCMP) { OpcodeStack.Item left = stack.getStackItem(1); OpcodeStack.Item right = stack.getStackItem(0); checkForCompatibleLongComparison(left, right); checkForCompatibleLongComparison(right, left); } if (stack.getStackDepth() >= 2) switch (seen) { case IF_ICMPEQ: case IF_ICMPNE: case IF_ICMPLE: case IF_ICMPGE: case IF_ICMPLT: case IF_ICMPGT: OpcodeStack.Item item0 = stack.getStackItem(0); OpcodeStack.Item item1 = stack.getStackItem(1); if (item0.getConstant() instanceof Integer) { OpcodeStack.Item tmp = item0; item0 = item1; item1 = tmp; } Object constant1 = item1.getConstant(); XMethod returnValueOf = item0.getReturnValueOf(); if (constant1 instanceof Integer && returnValueOf != null && returnValueOf.getName().equals("getYear") && (returnValueOf.getClassName().equals("java.util.Date") || returnValueOf.getClassName().equals( "java.sql.Date"))) { int year = (Integer) constant1; if (year > 1900 && SystemProperties.getBoolean("report_TESTING_pattern_in_standard_detectors")) accumulator.accumulateBug( new BugInstance(this, "TESTING", HIGH_PRIORITY).addClassAndMethod(this) .addString("Comparison of getYear does understand that it returns year-1900") .addMethod(returnValueOf).describe(MethodAnnotation.METHOD_CALLED).addInt(year) .describe(IntAnnotation.INT_VALUE), this); } } // System.out.printf("%4d %10s: %s\n", getPC(), OPCODE_NAMES[seen], // stack); if (seen == IFLT && stack.getStackDepth() > 0 && stack.getStackItem(0).getSpecialKind() == OpcodeStack.Item.SIGNED_BYTE) { sawCheckForNonNegativeSignedByte = getPC(); } if (pendingAbsoluteValueBug != null) { if (opcodesSincePendingAbsoluteValueBug == 0) { opcodesSincePendingAbsoluteValueBug++; } else { if (seen == IREM) { OpcodeStack.Item top = stack.getStackItem(0); Object constantValue = top.getConstant(); if (constantValue instanceof Number && Util.isPowerOfTwo(((Number) constantValue).intValue())) { pendingAbsoluteValueBug.setPriority(Priorities.LOW_PRIORITY); } } if (false) try { pendingAbsoluteValueBug.addString(OPCODE_NAMES[getPrevOpcode(1)] + ":" + OPCODE_NAMES[seen] + ":" + OPCODE_NAMES[getNextOpcode()]); } catch (Exception e) { pendingAbsoluteValueBug.addString(OPCODE_NAMES[getPrevOpcode(1)] + ":" + OPCODE_NAMES[seen]); } accumulator.accumulateBug(pendingAbsoluteValueBug, pendingAbsoluteValueBugSourceLine); pendingAbsoluteValueBug = null; pendingAbsoluteValueBugSourceLine = null; } } if (seen == INVOKESTATIC && getClassConstantOperand().equals("org/easymock/EasyMock") && (getNameConstantOperand().equals("replay") || getNameConstantOperand().equals("verify") || getNameConstantOperand() .startsWith("reset")) && getSigConstantOperand().equals("([Ljava/lang/Object;)V") && getPrevOpcode(1) == ANEWARRAY && getPrevOpcode(2) == ICONST_0) accumulator.accumulateBug(new BugInstance(this, "DMI_VACUOUS_CALL_TO_EASYMOCK_METHOD", NORMAL_PRIORITY) .addClassAndMethod(this).addCalledMethod(this), this); if ((seen == INVOKESTATIC || seen == INVOKEVIRTUAL || seen == INVOKESPECIAL || seen == INVOKEINTERFACE) && getSigConstantOperand().indexOf("Ljava/lang/Runnable;") >= 0) { SignatureParser parser = new SignatureParser(getSigConstantOperand()); int count = 0; for (Iterator<String> i = parser.parameterSignatureIterator(); i.hasNext(); count++) { String parameter = i.next(); if (parameter.equals("Ljava/lang/Runnable;")) { OpcodeStack.Item item = stack.getStackItem(parser.getNumParameters() - 1 - count); if ("Ljava/lang/Thread;".equals(item.getSignature())) { accumulator.accumulateBug(new BugInstance(this, "DMI_THREAD_PASSED_WHERE_RUNNABLE_EXPECTED", NORMAL_PRIORITY).addClassAndMethod(this).addCalledMethod(this), this); } } } } if (prevOpcode == I2L && seen == INVOKESTATIC && getClassConstantOperand().equals("java/lang/Double") && getNameConstantOperand().equals("longBitsToDouble")) { accumulator.accumulateBug(new BugInstance(this, "DMI_LONG_BITS_TO_DOUBLE_INVOKED_ON_INT", HIGH_PRIORITY) .addClassAndMethod(this).addCalledMethod(this), this); } if (seen == INVOKEVIRTUAL && getClassConstantOperand().equals("java/util/Random") && (freshRandomOnTos || freshRandomOneBelowTos)) { accumulator.accumulateBug(new BugInstance(this, "DMI_RANDOM_USED_ONLY_ONCE", HIGH_PRIORITY).addClassAndMethod(this) .addCalledMethod(this), this); } freshRandomOneBelowTos = freshRandomOnTos && isRegisterLoad(); freshRandomOnTos = seen == INVOKESPECIAL && getClassConstantOperand().equals("java/util/Random") && getNameConstantOperand().equals("<init>"); if ((seen == INVOKEVIRTUAL && getClassConstantOperand().equals("java/util/HashMap") && getNameConstantOperand().equals( "get")) || (seen == INVOKEINTERFACE && getClassConstantOperand().equals("java/util/Map") && getNameConstantOperand() .equals("get")) || (seen == INVOKEVIRTUAL && getClassConstantOperand().equals("java/util/HashSet") && getNameConstantOperand() .equals("contains")) || (seen == INVOKEINTERFACE && getClassConstantOperand().equals("java/util/Set") && getNameConstantOperand() .equals("contains"))) { OpcodeStack.Item top = stack.getStackItem(0); if (top.getSignature().equals("Ljava/net/URL;")) { accumulator.accumulateBug(new BugInstance(this, "DMI_COLLECTION_OF_URLS", HIGH_PRIORITY).addClassAndMethod(this), this); } } /** * Since you can change the number of core threads for a scheduled * thread pool executor, disabling this for now */ if (false && seen == INVOKESPECIAL && getClassConstantOperand().equals("java/util/concurrent/ScheduledThreadPoolExecutor") && getNameConstantOperand().equals("<init>")) { int arguments = getNumberArguments(getSigConstantOperand()); OpcodeStack.Item item = stack.getStackItem(arguments - 1); Object value = item.getConstant(); if (value instanceof Integer && ((Integer) value).intValue() == 0) accumulator.accumulateBug(new BugInstance(this, "DMI_SCHEDULED_THREAD_POOL_EXECUTOR_WITH_ZERO_CORE_THREADS", HIGH_PRIORITY).addClassAndMethod(this), this); } if (seen == INVOKEVIRTUAL && getClassConstantOperand().equals("java/util/concurrent/ScheduledThreadPoolExecutor") && getNameConstantOperand().equals("setMaximumPoolSize")) { accumulator.accumulateBug(new BugInstance(this, "DMI_FUTILE_ATTEMPT_TO_CHANGE_MAXPOOL_SIZE_OF_SCHEDULED_THREAD_POOL_EXECUTOR", HIGH_PRIORITY) .addClassAndMethod(this), this); } if (isEqualsObject && !reportedBadCastInEquals) { if (seen == INVOKEVIRTUAL && getNameConstantOperand().equals("isInstance") && getClassConstantOperand().equals("java/lang/Class")) { OpcodeStack.Item item = stack.getStackItem(0); if (item.getRegisterNumber() == 1) { sawInstanceofCheck = true; } } else if (seen == INSTANCEOF || seen == INVOKEVIRTUAL && getNameConstantOperand().equals("getClass") && getSigConstantOperand().equals("()Ljava/lang/Class;")) { OpcodeStack.Item item = stack.getStackItem(0); if (item.getRegisterNumber() == 1) { sawInstanceofCheck = true; } } else if (seen == INVOKESPECIAL && getNameConstantOperand().equals("equals") && getSigConstantOperand().equals("(Ljava/lang/Object;)Z")) { OpcodeStack.Item item0 = stack.getStackItem(0); OpcodeStack.Item item1 = stack.getStackItem(1); if (item1.getRegisterNumber() + item0.getRegisterNumber() == 1) { sawInstanceofCheck = true; } } else if (seen == CHECKCAST && !sawInstanceofCheck) { OpcodeStack.Item item = stack.getStackItem(0); if (item.getRegisterNumber() == 1) { if (getSizeOfSurroundingTryBlock(getPC()) == Integer.MAX_VALUE) { accumulator.accumulateBug(new BugInstance(this, "BC_EQUALS_METHOD_SHOULD_WORK_FOR_ALL_OBJECTS", NORMAL_PRIORITY).addClassAndMethod(this), this); } reportedBadCastInEquals = true; } } } { boolean foundVacuousComparison = false; if (seen == IF_ICMPGT || seen == IF_ICMPLE) { OpcodeStack.Item rhs = stack.getStackItem(0); Object rhsConstant = rhs.getConstant(); if (rhsConstant instanceof Integer && ((Integer) rhsConstant).intValue() == Integer.MAX_VALUE) { foundVacuousComparison = true; } OpcodeStack.Item lhs = stack.getStackItem(1); Object lhsConstant = lhs.getConstant(); if (lhsConstant instanceof Integer && ((Integer) lhsConstant).intValue() == Integer.MIN_VALUE) { foundVacuousComparison = true; } } if (seen == IF_ICMPLT || seen == IF_ICMPGE) { OpcodeStack.Item rhs = stack.getStackItem(0); Object rhsConstant = rhs.getConstant(); if (rhsConstant instanceof Integer && ((Integer) rhsConstant).intValue() == Integer.MIN_VALUE) { foundVacuousComparison = true; } OpcodeStack.Item lhs = stack.getStackItem(1); Object lhsConstant = lhs.getConstant(); if (lhsConstant instanceof Integer && ((Integer) lhsConstant).intValue() == Integer.MAX_VALUE) { foundVacuousComparison = true; } } if (foundVacuousComparison) { accumulator.accumulateBug(new BugInstance(this, "INT_VACUOUS_COMPARISON", getBranchOffset() < 0 ? HIGH_PRIORITY : NORMAL_PRIORITY).addClassAndMethod(this), this); } } if (seen == INVOKESTATIC && ClassName.isMathClass(getClassConstantOperand()) && getNameConstantOperand().equals("abs") ) { OpcodeStack.Item item0 = stack.getStackItem(0); int special = item0.getSpecialKind(); if (special == OpcodeStack.Item.RANDOM_INT) { pendingAbsoluteValueBug = new BugInstance(this, "RV_ABSOLUTE_VALUE_OF_RANDOM_INT", HIGH_PRIORITY) .addClassAndMethod(this); pendingAbsoluteValueBugSourceLine = SourceLineAnnotation.fromVisitedInstruction(this); opcodesSincePendingAbsoluteValueBug = 0; } else if (special == OpcodeStack.Item.HASHCODE_INT) { pendingAbsoluteValueBug = new BugInstance(this, "RV_ABSOLUTE_VALUE_OF_HASHCODE", HIGH_PRIORITY) .addClassAndMethod(this); pendingAbsoluteValueBugSourceLine = SourceLineAnnotation.fromVisitedInstruction(this); opcodesSincePendingAbsoluteValueBug = 0; } } try { int stackLoc = stackEntryThatMustBeNonnegative(seen); if (stackLoc >= 0) { OpcodeStack.Item tos = stack.getStackItem(stackLoc); switch (tos.getSpecialKind()) { case OpcodeStack.Item.HASHCODE_INT_REMAINDER: accumulator.accumulateBug(new BugInstance(this, "RV_REM_OF_HASHCODE", HIGH_PRIORITY).addClassAndMethod(this), this); break; case OpcodeStack.Item.RANDOM_INT: case OpcodeStack.Item.RANDOM_INT_REMAINDER: accumulator.accumulateBug( new BugInstance(this, "RV_REM_OF_RANDOM_INT", HIGH_PRIORITY).addClassAndMethod(this), this); break; } } if (seen == IREM) { OpcodeStack.Item item0 = stack.getStackItem(0); Object constant0 = item0.getConstant(); if (constant0 instanceof Integer && ((Integer) constant0).intValue() == 1) { accumulator.accumulateBug(new BugInstance(this, "INT_BAD_REM_BY_1", HIGH_PRIORITY).addClassAndMethod(this), this); } } if (stack.getStackDepth() >= 1 && (seen == LOOKUPSWITCH || seen == TABLESWITCH)) { OpcodeStack.Item item0 = stack.getStackItem(0); if (item0.getSpecialKind() == OpcodeStack.Item.SIGNED_BYTE) { int[] switchLabels = getSwitchLabels(); int[] switchOffsets = getSwitchOffsets(); for (int i = 0; i < switchLabels.length; i++) { int v = switchLabels[i]; if (v <= -129 || v >= 128) { accumulator.accumulateBug(new BugInstance(this, "INT_BAD_COMPARISON_WITH_SIGNED_BYTE", HIGH_PRIORITY) .addClassAndMethod(this).addInt(v).describe(IntAnnotation.INT_VALUE), SourceLineAnnotation.fromVisitedInstruction(this, getPC() + switchOffsets[i])); } } } } // check for use of signed byte where is it assumed it can be out of // the -128...127 range if (stack.getStackDepth() >= 2) { switch (seen) { case IF_ICMPEQ: case IF_ICMPNE: case IF_ICMPLT: case IF_ICMPLE: case IF_ICMPGE: case IF_ICMPGT: OpcodeStack.Item item0 = stack.getStackItem(0); OpcodeStack.Item item1 = stack.getStackItem(1); int seen2 = seen; if (item0.getConstant() != null) { OpcodeStack.Item tmp = item0; item0 = item1; item1 = tmp; switch (seen) { case IF_ICMPLT: seen2 = IF_ICMPGT; break; case IF_ICMPGE: seen2 = IF_ICMPLE; break; case IF_ICMPGT: seen2 = IF_ICMPLT; break; case IF_ICMPLE: seen2 = IF_ICMPGE; break; } } Object constant1 = item1.getConstant(); if (item0.getSpecialKind() == OpcodeStack.Item.SIGNED_BYTE && constant1 instanceof Number) { int v1 = ((Number) constant1).intValue(); if (v1 <= -129 || v1 >= 128 || v1 == 127 && !(seen2 == IF_ICMPEQ || seen2 == IF_ICMPNE )) { int priority = HIGH_PRIORITY; if (v1 == 127) { switch (seen2) { case IF_ICMPGT: // 127 > x priority = LOW_PRIORITY; break; case IF_ICMPGE: // 127 >= x : always true priority = NORMAL_PRIORITY; break; case IF_ICMPLT: // 127 < x : never true priority = NORMAL_PRIORITY; break; case IF_ICMPLE: // 127 <= x priority = LOW_PRIORITY; break; } } else if (v1 == 128) { switch (seen2) { case IF_ICMPGT: // 128 > x; always true priority = NORMAL_PRIORITY; break; case IF_ICMPGE: // 128 >= x priority = HIGH_PRIORITY; break; case IF_ICMPLT: // 128 < x priority = HIGH_PRIORITY; break; case IF_ICMPLE: // 128 <= x; never true priority = NORMAL_PRIORITY; break; } } else if (v1 <= -129) { priority = NORMAL_PRIORITY; } if (getPC() - sawCheckForNonNegativeSignedByte < 10) priority++; accumulator.accumulateBug(new BugInstance(this, "INT_BAD_COMPARISON_WITH_SIGNED_BYTE", priority) .addClassAndMethod(this).addInt(v1).describe(IntAnnotation.INT_VALUE), this); } } else if (item0.getSpecialKind() == OpcodeStack.Item.NON_NEGATIVE && constant1 instanceof Number) { int v1 = ((Number) constant1).intValue(); if (v1 < 0) { accumulator.accumulateBug(new BugInstance(this, "INT_BAD_COMPARISON_WITH_NONNEGATIVE_VALUE", HIGH_PRIORITY).addClassAndMethod(this).addInt(v1).describe(IntAnnotation.INT_VALUE), this); } } } } switch (seen) { case IAND: case LAND: case IOR: case LOR: case IXOR: case LXOR: long badValue = (seen == IAND || seen == LAND) ? -1 : 0; OpcodeStack.Item rhs = stack.getStackItem(0); OpcodeStack.Item lhs = stack.getStackItem(1); int prevOpcode = getPrevOpcode(1); int prevPrevOpcode = getPrevOpcode(2); if (rhs.hasConstantValue(badValue) && (prevOpcode == LDC || prevOpcode == ICONST_0 || prevOpcode == ICONST_M1 || prevOpcode == LCONST_0) && prevPrevOpcode != GOTO) reportVacuousBitOperation(seen, lhs); } if (checkForBitIorofSignedByte && seen != I2B) { String pattern = (prevOpcode == LOR || prevOpcode == IOR) ? "BIT_IOR_OF_SIGNED_BYTE" : "BIT_ADD_OF_SIGNED_BYTE"; int priority = (prevOpcode == LOR || prevOpcode == LADD) ? HIGH_PRIORITY : NORMAL_PRIORITY; accumulator.accumulateBug(new BugInstance(this, pattern, priority).addClassAndMethod(this), this); checkForBitIorofSignedByte = false; } else if ((seen == IOR || seen == LOR || seen == IADD || seen == LADD) && stack.getStackDepth() >= 2) { OpcodeStack.Item item0 = stack.getStackItem(0); OpcodeStack.Item item1 = stack.getStackItem(1); int special0 = item0.getSpecialKind(); int special1 = item1.getSpecialKind(); if (special0 == OpcodeStack.Item.SIGNED_BYTE && special1 == OpcodeStack.Item.LOW_8_BITS_CLEAR && !item1.hasConstantValue(256) || special0 == OpcodeStack.Item.LOW_8_BITS_CLEAR && !item0.hasConstantValue(256) && special1 == OpcodeStack.Item.SIGNED_BYTE) { checkForBitIorofSignedByte = true; } else { checkForBitIorofSignedByte = false; } } else { checkForBitIorofSignedByte = false; } if (prevOpcodeWasReadLine && sinceBufferedInputStreamReady >= 100 && seen == INVOKEVIRTUAL && getClassConstantOperand().equals("java/lang/String") && getSigConstantOperand().startsWith("()")) { accumulator.accumulateBug( new BugInstance(this, "NP_IMMEDIATE_DEREFERENCE_OF_READLINE", NORMAL_PRIORITY).addClassAndMethod(this), this); } if (seen == INVOKEVIRTUAL && getClassConstantOperand().equals("java/io/BufferedReader") && getNameConstantOperand().equals("ready") && getSigConstantOperand().equals("()Z")) { sinceBufferedInputStreamReady = 0; } else { sinceBufferedInputStreamReady++; } prevOpcodeWasReadLine = (seen == INVOKEVIRTUAL || seen == INVOKEINTERFACE) && getNameConstantOperand().equals("readLine") && getSigConstantOperand().equals("()Ljava/lang/String;"); // System.out.println(randomNextIntState + " " + OPCODE_NAMES[seen] // + " " + getMethodName()); switch (randomNextIntState) { case 0: if (seen == INVOKEVIRTUAL && getClassConstantOperand().equals("java/util/Random") && getNameConstantOperand().equals("nextDouble") || seen == INVOKESTATIC && ClassName.isMathClass(getClassConstantOperand()) && getNameConstantOperand().equals("random")) { randomNextIntState = 1; } break; case 1: if (seen == D2I) { accumulator.accumulateBug(new BugInstance(this, "RV_01_TO_INT", HIGH_PRIORITY).addClassAndMethod(this), this); randomNextIntState = 0; } else if (seen == DMUL) randomNextIntState = 4; else if (seen == LDC2_W && getConstantRefOperand() instanceof ConstantDouble && ((ConstantDouble) getConstantRefOperand()).getBytes() == Integer.MIN_VALUE) randomNextIntState = 0; else randomNextIntState = 2; break; case 2: if (seen == I2D) { randomNextIntState = 3; } else if (seen == DMUL) { randomNextIntState = 4; } else { randomNextIntState = 0; } break; case 3: if (seen == DMUL) { randomNextIntState = 4; } else { randomNextIntState = 0; } break; case 4: if (seen == D2I) { accumulator.accumulateBug( new BugInstance(this, "DM_NEXTINT_VIA_NEXTDOUBLE", NORMAL_PRIORITY).addClassAndMethod(this), this); } randomNextIntState = 0; break; default: throw new IllegalStateException(); } if (isPublicStaticVoidMain && seen == INVOKEVIRTUAL && getClassConstantOperand().startsWith("javax/swing/") && (getNameConstantOperand().equals("show") && getSigConstantOperand().equals("()V") || getNameConstantOperand().equals("pack") && getSigConstantOperand().equals("()V") || getNameConstantOperand() .equals("setVisible") && getSigConstantOperand().equals("(Z)V"))) { accumulator.accumulateBug( new BugInstance(this, "SW_SWING_METHODS_INVOKED_IN_SWING_THREAD", LOW_PRIORITY).addClassAndMethod(this), this); } // if ((seen == INVOKEVIRTUAL) // && getClassConstantOperand().equals("java/lang/String") // && getNameConstantOperand().equals("substring") // && getSigConstantOperand().equals("(I)Ljava/lang/String;") // && stack.getStackDepth() > 1) { // OpcodeStack.Item item = stack.getStackItem(0); // Object o = item.getConstant(); // if (o != null && o instanceof Integer) { // int v = ((Integer) o).intValue(); // if (v == 0) // accumulator.accumulateBug(new BugInstance(this, // "DMI_USELESS_SUBSTRING", NORMAL_PRIORITY) // .addClassAndMethod(this) // .addSourceLine(this)); // } // } if ((seen == INVOKEVIRTUAL) && getNameConstantOperand().equals("isAnnotationPresent") && getSigConstantOperand().equals("(Ljava/lang/Class;)Z") && stack.getStackDepth() > 0) { OpcodeStack.Item item = stack.getStackItem(0); Object value = item.getConstant(); if (value instanceof String) { String annotationClassName = (String) value; boolean lacksClassfileRetention = AnalysisContext.currentAnalysisContext().getAnnotationRetentionDatabase() .lacksRuntimeRetention(annotationClassName.replace('/', '.')); if (lacksClassfileRetention) { ClassDescriptor annotationClass = DescriptorFactory.createClassDescriptor(annotationClassName); accumulator.accumulateBug( new BugInstance(this, "DMI_ANNOTATION_IS_NOT_VISIBLE_TO_REFLECTION", HIGH_PRIORITY) .addClassAndMethod(this).addCalledMethod(this).addClass(annotationClass) .describe(ClassAnnotation.ANNOTATION_ROLE), this); } } } if ((seen == INVOKEVIRTUAL) && getNameConstantOperand().equals("next") && getSigConstantOperand().equals("()Ljava/lang/Object;") && getMethodName().equals("hasNext") && getMethodSig().equals("()Z") && stack.getStackDepth() > 0) { OpcodeStack.Item item = stack.getStackItem(0); accumulator.accumulateBug(new BugInstance(this, "DMI_CALLING_NEXT_FROM_HASNEXT", item.isInitialParameter() && item.getRegisterNumber() == 0 ? NORMAL_PRIORITY : LOW_PRIORITY).addClassAndMethod(this) .addCalledMethod(this), this); } if ((seen == INVOKESPECIAL) && getClassConstantOperand().equals("java/lang/String") && getNameConstantOperand().equals("<init>") && getSigConstantOperand().equals("(Ljava/lang/String;)V")) { accumulator.accumulateBug(new BugInstance(this, "DM_STRING_CTOR", NORMAL_PRIORITY).addClassAndMethod(this), this); } if (seen == INVOKESTATIC && getClassConstantOperand().equals("java/lang/System") && getNameConstantOperand().equals("runFinalizersOnExit") || seen == INVOKEVIRTUAL && getClassConstantOperand().equals("java/lang/Runtime") && getNameConstantOperand().equals("runFinalizersOnExit")) { accumulator.accumulateBug( new BugInstance(this, "DM_RUN_FINALIZERS_ON_EXIT", HIGH_PRIORITY).addClassAndMethod(this), this); } if ((seen == INVOKESPECIAL) && getClassConstantOperand().equals("java/lang/String") && getNameConstantOperand().equals("<init>") && getSigConstantOperand().equals("()V")) { accumulator.accumulateBug(new BugInstance(this, "DM_STRING_VOID_CTOR", NORMAL_PRIORITY).addClassAndMethod(this), this); } if (!isPublicStaticVoidMain && seen == INVOKESTATIC && getClassConstantOperand().equals("java/lang/System") && getNameConstantOperand().equals("exit") && !getMethodName().equals("processWindowEvent") && !getMethodName().startsWith("windowClos") && getMethodName().indexOf("exit") == -1 && getMethodName().indexOf("Exit") == -1 && getMethodName().indexOf("crash") == -1 && getMethodName().indexOf("Crash") == -1 && getMethodName().indexOf("die") == -1 && getMethodName().indexOf("Die") == -1 && getMethodName().indexOf("main") == -1) { accumulator.accumulateBug(new BugInstance(this, "DM_EXIT", getMethod().isStatic() ? LOW_PRIORITY : NORMAL_PRIORITY).addClassAndMethod(this), SourceLineAnnotation.fromVisitedInstruction(this)); } if (((seen == INVOKESTATIC && getClassConstantOperand().equals("java/lang/System")) || (seen == INVOKEVIRTUAL && getClassConstantOperand() .equals("java/lang/Runtime"))) && getNameConstantOperand().equals("gc") && getSigConstantOperand().equals("()V") && !getDottedClassName().startsWith("java.lang") && !getMethodName().startsWith("gc") && !getMethodName().endsWith("gc")) { if (gcInvocationBugReport == null) { // System.out.println("Saw call to GC"); if (isPublicStaticVoidMain) { // System.out.println("Skipping GC complaint in main method"); return; } if (isTestMethod(getMethod())) { return; } // Just save this report in a field; it will be flushed // IFF there were no calls to System.currentTimeMillis(); // in the method. gcInvocationBugReport = new BugInstance(this, "DM_GC", HIGH_PRIORITY).addClassAndMethod(this).addSourceLine( this); gcInvocationPC = getPC(); // System.out.println("GC invocation at pc " + PC); } } if (!isSynthetic && (seen == INVOKESPECIAL) && getClassConstantOperand().equals("java/lang/Boolean") && getNameConstantOperand().equals("<init>") && !getClassName().equals("java/lang/Boolean")) { int majorVersion = getThisClass().getMajor(); if (majorVersion >= MAJOR_1_4) { accumulator.accumulateBug(new BugInstance(this, "DM_BOOLEAN_CTOR", NORMAL_PRIORITY).addClassAndMethod(this), this); } } if ((seen == INVOKESTATIC) && getClassConstantOperand().equals("java/lang/System") && (getNameConstantOperand().equals("currentTimeMillis") || getNameConstantOperand().equals("nanoTime"))) { sawCurrentTimeMillis = true; } if ((seen == INVOKEVIRTUAL) && getClassConstantOperand().equals("java/lang/String") && getNameConstantOperand().equals("toString") && getSigConstantOperand().equals("()Ljava/lang/String;")) { accumulator .accumulateBug(new BugInstance(this, "DM_STRING_TOSTRING", LOW_PRIORITY).addClassAndMethod(this), this); } if ((seen == INVOKEVIRTUAL) && getClassConstantOperand().equals("java/lang/String") && (getNameConstantOperand().equals("toUpperCase") || getNameConstantOperand().equals("toLowerCase")) && getSigConstantOperand().equals("()Ljava/lang/String;")) { accumulator.accumulateBug(new BugInstance(this, "DM_CONVERT_CASE", LOW_PRIORITY).addClassAndMethod(this), this); } if ((seen == INVOKESPECIAL) && getNameConstantOperand().equals("<init>")) { String cls = getClassConstantOperand(); String sig = getSigConstantOperand(); if ((cls.equals("java/lang/Integer") && sig.equals("(I)V")) || (cls.equals("java/lang/Float") && sig.equals("(F)V")) || (cls.equals("java/lang/Double") && sig.equals("(D)V")) || (cls.equals("java/lang/Long") && sig.equals("(J)V")) || (cls.equals("java/lang/Byte") && sig.equals("(B)V")) || (cls.equals("java/lang/Character") && sig.equals("(C)V")) || (cls.equals("java/lang/Short") && sig.equals("(S)V")) || (cls.equals("java/lang/Boolean") && sig.equals("(Z)V"))) { primitiveObjCtorSeen = cls; } else { primitiveObjCtorSeen = null; } } else if ((primitiveObjCtorSeen != null) && (seen == INVOKEVIRTUAL) && getNameConstantOperand().equals("toString") && getClassConstantOperand().equals(primitiveObjCtorSeen) && getSigConstantOperand().equals("()Ljava/lang/String;")) { accumulator.accumulateBug( new BugInstance(this, "DM_BOXED_PRIMITIVE_TOSTRING", LOW_PRIORITY).addClassAndMethod(this), this); primitiveObjCtorSeen = null; } else { primitiveObjCtorSeen = null; } if ((seen == INVOKESPECIAL) && getNameConstantOperand().equals("<init>")) { ctorSeen = true; } else if (ctorSeen && (seen == INVOKEVIRTUAL) && getClassConstantOperand().equals("java/lang/Object") && getNameConstantOperand().equals("getClass") && getSigConstantOperand().equals("()Ljava/lang/Class;")) { accumulator.accumulateBug(new BugInstance(this, "DM_NEW_FOR_GETCLASS", LOW_PRIORITY).addClassAndMethod(this), this); ctorSeen = false; } else { ctorSeen = false; } if (jdk15ChecksEnabled && (seen == INVOKEVIRTUAL) && isMonitorWait(getNameConstantOperand(), getSigConstantOperand())) { checkMonitorWait(); } if ((seen == INVOKESPECIAL) && getNameConstantOperand().equals("<init>") && getClassConstantOperand().equals("java/lang/Thread")) { String sig = getSigConstantOperand(); if (sig.equals("()V") || sig.equals("(Ljava/lang/String;)V") || sig.equals("(Ljava/lang/ThreadGroup;Ljava/lang/String;)V")) { OpcodeStack.Item invokedOn = stack.getItemMethodInvokedOn(this); if (!getMethodName().equals("<init>") || invokedOn.getRegisterNumber() != 0) { accumulator.accumulateBug( new BugInstance(this, "DM_USELESS_THREAD", LOW_PRIORITY).addClassAndMethod(this), this); } } } if (seen == INVOKESPECIAL && getClassConstantOperand().equals("java/math/BigDecimal") && getNameConstantOperand().equals("<init>") && getSigConstantOperand().equals("(D)V")) { OpcodeStack.Item top = stack.getStackItem(0); Object value = top.getConstant(); if (value instanceof Double) { double arg = ((Double) value).doubleValue(); String dblString = Double.toString(arg); String bigDecimalString = new BigDecimal(arg).toString(); boolean ok = dblString.equals(bigDecimalString) || dblString.equals(bigDecimalString + ".0"); if (!ok) { boolean scary = dblString.length() <= 8 && bigDecimalString.length() > 12 && dblString.toUpperCase().indexOf("E") == -1; bugReporter.reportBug(new BugInstance(this, "DMI_BIGDECIMAL_CONSTRUCTED_FROM_DOUBLE", scary ? NORMAL_PRIORITY : LOW_PRIORITY).addClassAndMethod(this).addCalledMethod(this) .addMethod("java.math.BigDecimal", "valueOf", "(D)Ljava/math/BigDecimal;", true) .describe(MethodAnnotation.METHOD_ALTERNATIVE_TARGET).addString(dblString) .addString(bigDecimalString).addSourceLine(this)); } } } } finally { prevOpcode = seen; } } private void checkForCompatibleLongComparison(OpcodeStack.Item left, OpcodeStack.Item right) { if (left.getSpecialKind() == Item.RESULT_OF_I2L && right.getConstant() != null) { long value = ((Number) right.getConstant()).longValue(); if (SystemProperties.getBoolean("report_TESTING_pattern_in_standard_detectors") && (value > Integer.MAX_VALUE || value < Integer.MIN_VALUE)) { int priority = Priorities.HIGH_PRIORITY; if (value == Integer.MAX_VALUE+1 || value == Integer.MIN_VALUE -1) priority = Priorities.NORMAL_PRIORITY; accumulator.accumulateBug(new BugInstance(this, "TESTING", priority ).addClassAndMethod(this) .addValueSource(left, this) .addString(Long.toString(value)).describe(StringAnnotation.STRING_NONSTRING_CONSTANT_ROLE) .addString("Incompatible comparison of result of I2L and long constant"), this); } } } /** * @param seen * @param item */ private void reportVacuousBitOperation(int seen, OpcodeStack.Item item) { if (item.getConstant() == null) accumulator .accumulateBug( new BugInstance(this, "INT_VACUOUS_BIT_OPERATION", NORMAL_PRIORITY) .addClassAndMethod(this) .addString(OPCODE_NAMES[seen]) .addOptionalAnnotation( LocalVariableAnnotation.getLocalVariableAnnotation(getMethod(), item, getPC())), this); } /** * Return index of stack entry that must be nonnegative. * * Return -1 if no stack entry is required to be nonnegative. * * @param seen * @return */ private int stackEntryThatMustBeNonnegative(int seen) { switch (seen) { case INVOKEINTERFACE: if (getClassConstantOperand().equals("java/util/List")) { return getStackEntryOfListCallThatMustBeNonnegative(); } break; case INVOKEVIRTUAL: if (getClassConstantOperand().equals("java/util/LinkedList") || getClassConstantOperand().equals("java/util/ArrayList")) { return getStackEntryOfListCallThatMustBeNonnegative(); } break; case IALOAD: case AALOAD: case SALOAD: case CALOAD: case BALOAD: case LALOAD: case DALOAD: case FALOAD: return 0; case IASTORE: case AASTORE: case SASTORE: case CASTORE: case BASTORE: case LASTORE: case DASTORE: case FASTORE: return 1; } return -1; } private int getStackEntryOfListCallThatMustBeNonnegative() { String name = getNameConstantOperand(); if ((name.equals("add") || name.equals("set")) && getSigConstantOperand().startsWith("(I")) { return 1; } if ((name.equals("get") || name.equals("remove")) && getSigConstantOperand().startsWith("(I)")) { return 0; } return -1; } private void checkMonitorWait() { try { TypeDataflow typeDataflow = getClassContext().getTypeDataflow(getMethod()); TypeDataflow.LocationAndFactPair pair = typeDataflow.getLocationAndFactForInstruction(getPC()); if (pair == null) { return; } Type receiver = pair.frame.getInstance(pair.location.getHandle().getInstruction(), getClassContext() .getConstantPoolGen()); if (!(receiver instanceof ReferenceType)) { return; } if (Hierarchy.isSubtype((ReferenceType) receiver, CONDITION_TYPE)) { accumulator.accumulateBug( new BugInstance(this, "DM_MONITOR_WAIT_ON_CONDITION", HIGH_PRIORITY).addClassAndMethod(this), this); } } catch (ClassNotFoundException e) { bugReporter.reportMissingClass(e); } catch (DataflowAnalysisException e) { bugReporter.logError("Exception caught by DumbMethods", e); } catch (CFGBuilderException e) { bugReporter.logError("Exception caught by DumbMethods", e); } } private boolean isMonitorWait(String name, String sig) { // System.out.println("Check call " + name + "," + sig); return name.equals("wait") && (sig.equals("()V") || sig.equals("(J)V") || sig.equals("(JI)V")); } @Override public void visit(Code obj) { super.visit(obj); flush(); } /** * A heuristic - how long a catch block for OutOfMemoryError might be. */ private static final int OOM_CATCH_LEN = 20; /** * Flush out cached state at the end of a method. */ private void flush() { if (pendingAbsoluteValueBug != null) { accumulator.accumulateBug(pendingAbsoluteValueBug, pendingAbsoluteValueBugSourceLine); pendingAbsoluteValueBug = null; pendingAbsoluteValueBugSourceLine = null; } if (gcInvocationBugReport != null && !sawCurrentTimeMillis) { // Make sure the GC invocation is not in an exception handler // for OutOfMemoryError. boolean outOfMemoryHandler = false; for (CodeException handler : exceptionTable) { if (gcInvocationPC < handler.getHandlerPC() || gcInvocationPC > handler.getHandlerPC() + OOM_CATCH_LEN) { continue; } int catchTypeIndex = handler.getCatchType(); if (catchTypeIndex > 0) { ConstantPool cp = getThisClass().getConstantPool(); Constant constant = cp.getConstant(catchTypeIndex); if (constant instanceof ConstantClass) { String exClassName = (String) ((ConstantClass) constant).getConstantValue(cp); if (exClassName.equals("java/lang/OutOfMemoryError")) { outOfMemoryHandler = true; break; } } } } if (!outOfMemoryHandler) { bugReporter.reportBug(gcInvocationBugReport); } } sawCurrentTimeMillis = false; gcInvocationBugReport = null; exceptionTable = null; } }