001: /*
002: * FindBugs - Find bugs in Java programs
003: * Copyright (C) 2005, University of Maryland
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation; either
008: * version 2.1 of the License, or (at your option) any later version.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: *
015: * You should have received a copy of the GNU Lesser General Public
016: * License along with this library; if not, write to the Free Software
017: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018: */
019:
020: package edu.umd.cs.findbugs.detect;
021:
022: import java.util.ArrayList;
023: import java.util.Collection;
024:
025: import org.apache.bcel.Repository;
026: import org.apache.bcel.classfile.Code;
027: import org.apache.bcel.classfile.Constant;
028: import org.apache.bcel.classfile.ConstantString;
029: import org.apache.bcel.classfile.JavaClass;
030:
031: import edu.umd.cs.findbugs.BugAccumulator;
032: import edu.umd.cs.findbugs.BugAnnotation;
033: import edu.umd.cs.findbugs.BugInstance;
034: import edu.umd.cs.findbugs.BugReporter;
035: import edu.umd.cs.findbugs.IntAnnotation;
036: import edu.umd.cs.findbugs.LocalVariableAnnotation;
037: import edu.umd.cs.findbugs.OpcodeStack;
038: import edu.umd.cs.findbugs.SystemProperties;
039: import edu.umd.cs.findbugs.OpcodeStack.Item;
040: import edu.umd.cs.findbugs.ba.AnalysisContext;
041: import edu.umd.cs.findbugs.ba.XFactory;
042: import edu.umd.cs.findbugs.ba.XMethod;
043: import edu.umd.cs.findbugs.bcel.OpcodeStackDetector;
044: import edu.umd.cs.findbugs.visitclass.Util;
045:
046: public class FindPuzzlers extends OpcodeStackDetector {
047:
048: private static final boolean VAMISMATCH_DEBUG = SystemProperties
049: .getBoolean("vamismatch.debug");
050:
051: final BugReporter bugReporter;
052: final BugAccumulator bugAccumulator;
053:
054: public FindPuzzlers(BugReporter bugReporter) {
055: this .bugReporter = bugReporter;
056: this .bugAccumulator = new BugAccumulator(bugReporter);
057: }
058:
059: @Override
060: public void visit(Code obj) {
061: prevOpcodeIncrementedRegister = -1;
062: best_priority_for_ICAST_INTEGER_MULTIPLY_CAST_TO_LONG = LOW_PRIORITY + 1;
063: prevOpCode = NOP;
064: previousMethodInvocation = null;
065: badlyComputingOddState = 0;
066: resetIMulCastLong();
067: imul_distance = 10000;
068: ternaryConversionState = 0;
069: super .visit(obj);
070: bugAccumulator.reportAccumulatedBugs();
071: }
072:
073: int imul_constant;
074: int imul_distance;
075: boolean imul_operand_is_parameter;
076: int prevOpcodeIncrementedRegister;
077: int valueOfConstantArgumentToShift;
078: int best_priority_for_ICAST_INTEGER_MULTIPLY_CAST_TO_LONG;
079: boolean constantArgumentToShift;
080: boolean shiftOfNonnegativeValue;
081: int ternaryConversionState = 0;
082:
083: int badlyComputingOddState;
084: int prevOpCode;
085: XMethod previousMethodInvocation;
086: boolean isTigerOrHigher;
087:
088: private static final int FS_STATE_NONE = 0;
089: private static final int FS_STATE_SAW_STR_LOAD = 1;
090: private static final int FS_STATE_SAW_CONST_PUSH = 2;
091: private static final int FS_STATE_SAW_NEWARRAY = 3;
092:
093: private int prevConst = -1;
094: private int fsState = FS_STATE_NONE;
095: private int fsAAStores = 0;
096: private String fsFmtStr = null;
097:
098: @Override
099: public void visit(JavaClass obj) {
100: isTigerOrHigher = obj.getMajor() >= MAJOR_1_5;
101: }
102:
103: private void resetIMulCastLong() {
104: imul_constant = 1;
105: imul_operand_is_parameter = false;
106: }
107:
108: private int adjustPriority(int factor, int priority) {
109: if (factor <= 4)
110: return LOW_PRIORITY + 2;
111: if (factor <= 10000)
112: return priority + 1;
113: if (factor <= 60 * 60 * 1000)
114: return priority;
115: return priority - 1;
116: }
117:
118: private int adjustMultiplier(Object constant, int mul) {
119: if (!(constant instanceof Integer))
120: return mul;
121: return Math.abs(((Integer) constant).intValue()) * mul;
122:
123: }
124:
125: @Override
126: public void sawOpcode(int seen) {
127:
128: // System.out.println(getPC() + " " + OPCODE_NAMES[seen] + " " + ternaryConversionState);
129: if (seen == IMUL) {
130: if (imul_distance != 1)
131: resetIMulCastLong();
132: imul_distance = 0;
133: if (stack.getStackDepth() > 1) {
134: OpcodeStack.Item item0 = stack.getStackItem(0);
135: OpcodeStack.Item item1 = stack.getStackItem(1);
136: imul_constant = adjustMultiplier(item0.getConstant(),
137: imul_constant);
138: imul_constant = adjustMultiplier(item1.getConstant(),
139: imul_constant);
140:
141: if (item0.isInitialParameter()
142: || item1.isInitialParameter())
143: imul_operand_is_parameter = true;
144: }
145: } else {
146: imul_distance++;
147: }
148:
149: if (prevOpCode == IMUL && seen == I2L) {
150: int priority = adjustPriority(imul_constant,
151: NORMAL_PRIORITY);
152: if (priority >= LOW_PRIORITY && imul_operand_is_parameter)
153: priority = NORMAL_PRIORITY;
154: if (priority <= best_priority_for_ICAST_INTEGER_MULTIPLY_CAST_TO_LONG) {
155: best_priority_for_ICAST_INTEGER_MULTIPLY_CAST_TO_LONG = priority;
156: bugAccumulator
157: .accumulateBug(new BugInstance(this ,
158: "ICAST_INTEGER_MULTIPLY_CAST_TO_LONG",
159: priority).addClassAndMethod(this ), this );
160: }
161: }
162:
163: if (getMethodName().equals("<clinit>")
164: && (seen == PUTSTATIC || seen == GETSTATIC || seen == INVOKESTATIC)) {
165: String clazz = getClassConstantOperand();
166: if (!clazz.equals(getClassName())) {
167: try {
168: JavaClass targetClass = Repository
169: .lookupClass(clazz);
170: if (Repository.instanceOf(targetClass,
171: getThisClass())) {
172: int priority = NORMAL_PRIORITY;
173: if (seen == GETSTATIC)
174: priority--;
175: if (!targetClass.isPublic())
176: priority++;
177: bugAccumulator
178: .accumulateBug(
179: new BugInstance(
180: this ,
181: "IC_SUPERCLASS_USES_SUBCLASS_DURING_INITIALIZATION",
182: priority)
183: .addClassAndMethod(this )
184: .addClass(
185: getDottedClassConstantOperand()),
186: this );
187:
188: }
189: } catch (ClassNotFoundException e) {
190: // ignore it
191: }
192:
193: }
194: }
195: if (false
196: && (seen == INVOKEVIRTUAL)
197: && getNameConstantOperand().equals("equals")
198: && getSigConstantOperand().equals(
199: "(Ljava/lang/Object;)Z")
200: && stack.getStackDepth() > 1) {
201: OpcodeStack.Item item0 = stack.getStackItem(0);
202: OpcodeStack.Item item1 = stack.getStackItem(1);
203:
204: if (item0.isArray() || item1.isArray()) {
205: bugAccumulator.accumulateBug(new BugInstance(
206: "EC_BAD_ARRAY_COMPARE", NORMAL_PRIORITY)
207: .addClassAndMethod(this ), this );
208: }
209: }
210:
211: if (seen >= IALOAD && seen <= SALOAD || seen >= IASTORE
212: && seen <= SASTORE) {
213: Item index = stack.getStackItem(0);
214: if (index.getSpecialKind() == Item.AVERAGE_COMPUTED_USING_DIVISION)
215: bugAccumulator.accumulateBug(new BugInstance(this ,
216: "IM_AVERAGE_COMPUTATION_COULD_OVERFLOW",
217: NORMAL_PRIORITY).addClassAndMethod(this ), this );
218:
219: }
220:
221: if ((seen == IFEQ || seen == IFNE)
222: && getPrevOpcode(1) == IMUL
223: && (getPrevOpcode(2) == SIPUSH || getPrevOpcode(2) == BIPUSH)
224: && getPrevOpcode(3) == IREM)
225: bugAccumulator.accumulateBug(new BugInstance(this ,
226: "IM_MULTIPLYING_RESULT_OF_IREM", LOW_PRIORITY)
227: .addClassAndMethod(this ), this );
228:
229: if (seen == I2S
230: && getPrevOpcode(1) == IUSHR
231: && !shiftOfNonnegativeValue
232: && (!constantArgumentToShift || valueOfConstantArgumentToShift % 16 != 0)
233: || seen == I2B
234: && getPrevOpcode(1) == IUSHR
235: && !shiftOfNonnegativeValue
236: && (!constantArgumentToShift || valueOfConstantArgumentToShift % 8 != 0))
237:
238: bugAccumulator.accumulateBug(new BugInstance(this ,
239: "ICAST_QUESTIONABLE_UNSIGNED_RIGHT_SHIFT",
240: NORMAL_PRIORITY).addClassAndMethod(this ), this );
241:
242: constantArgumentToShift = false;
243: shiftOfNonnegativeValue = false;
244: if ((seen == IUSHR || seen == ISHR || seen == ISHL)) {
245: if (stack.getStackDepth() <= 1) {
246: // don't understand; lie so other detectors won't get concerned
247: constantArgumentToShift = true;
248: valueOfConstantArgumentToShift = 8;
249: } else {
250: Object rightHandSide = stack.getStackItem(0)
251: .getConstant();
252:
253: Object leftHandSide = stack.getStackItem(1)
254: .getConstant();
255: shiftOfNonnegativeValue = stack.getStackItem(1)
256: .isNonNegative();
257: if (rightHandSide instanceof Integer) {
258: constantArgumentToShift = true;
259: valueOfConstantArgumentToShift = ((Integer) rightHandSide);
260: if (valueOfConstantArgumentToShift < 0
261: || valueOfConstantArgumentToShift >= 32)
262: bugAccumulator
263: .accumulateBug(
264: new BugInstance(
265: this ,
266: "ICAST_BAD_SHIFT_AMOUNT",
267: valueOfConstantArgumentToShift < 0 ? LOW_PRIORITY
268: : HIGH_PRIORITY)
269: .addClassAndMethod(this )
270: .addInt(
271: valueOfConstantArgumentToShift)
272: .describe(
273: IntAnnotation.INT_SHIFT),
274: this );
275: }
276: if (leftHandSide != null
277: && leftHandSide instanceof Integer
278: && ((Integer) leftHandSide) > 0) {
279: // boring; lie so other detectors won't get concerned
280: constantArgumentToShift = true;
281: valueOfConstantArgumentToShift = 8;
282: }
283: }
284: }
285:
286: if (seen == INVOKEVIRTUAL && stack.getStackDepth() > 0
287: && getClassConstantOperand().equals("java/util/Date")
288: && getNameConstantOperand().equals("setMonth")
289: && getSigConstantOperand().equals("(I)V")) {
290: OpcodeStack.Item item = stack.getStackItem(0);
291: Object o = item.getConstant();
292: if (o != null && o instanceof Integer) {
293: int v = (Integer) o;
294: if (v < 0 || v > 11)
295: bugReporter.reportBug(new BugInstance(this ,
296: "DMI_BAD_MONTH", HIGH_PRIORITY)
297: .addClassAndMethod(this ).addInt(v)
298: .describe(IntAnnotation.INT_VALUE)
299: .addCalledMethod(this ).addSourceLine(this ));
300: }
301: }
302:
303: if (seen == INVOKEVIRTUAL
304: && stack.getStackDepth() > 1
305: && getClassConstantOperand().equals(
306: "java/util/Calendar")
307: && getNameConstantOperand().equals("set")
308:
309: || seen == INVOKESPECIAL
310: && stack.getStackDepth() > 1
311: && getClassConstantOperand().equals(
312: "java/util/GregorianCalendar")
313: && getNameConstantOperand().equals("<init>")
314:
315: ) {
316: String sig = getSigConstantOperand();
317: if (sig.startsWith("(III")) {
318: int pos = sig.length() - 5;
319: OpcodeStack.Item item = stack.getStackItem(pos);
320: Object o = item.getConstant();
321: if (o != null && o instanceof Integer) {
322: int v = (Integer) o;
323: if (v < 0 || v > 11)
324: bugReporter.reportBug(new BugInstance(this ,
325: "DMI_BAD_MONTH", NORMAL_PRIORITY)
326: .addClassAndMethod(this ).addInt(v)
327: .describe(IntAnnotation.INT_VALUE)
328: .addCalledMethod(this ).addSourceLine(
329: this ));
330: }
331: }
332: }
333:
334: if (isRegisterStore()
335: && (seen == ISTORE || seen == ISTORE_0
336: || seen == ISTORE_1 || seen == ISTORE_2 || seen == ISTORE_3)
337: && getRegisterOperand() == prevOpcodeIncrementedRegister) {
338: bugAccumulator.accumulateBug(new BugInstance(this ,
339: "DLS_OVERWRITTEN_INCREMENT", HIGH_PRIORITY)
340: .addClassAndMethod(this ), this );
341:
342: }
343: if (seen == IINC) {
344: prevOpcodeIncrementedRegister = getRegisterOperand();
345: } else
346: prevOpcodeIncrementedRegister = -1;
347:
348: // Java Puzzlers, Chapter 2, puzzle 1
349: // Look for ICONST_2 IREM ICONST_1 IF_ICMPNE L1
350:
351: switch (badlyComputingOddState) {
352: case 0:
353: if (seen == ICONST_2)
354: badlyComputingOddState++;
355: break;
356: case 1:
357: if (seen == IREM) {
358: OpcodeStack.Item item = stack.getStackItem(1);
359: if (!item.isNonNegative()
360: && item.getSpecialKind() != OpcodeStack.Item.MATH_ABS)
361: badlyComputingOddState++;
362: else
363: badlyComputingOddState = 0;
364: } else
365: badlyComputingOddState = 0;
366: break;
367: case 2:
368: if (seen == ICONST_1)
369: badlyComputingOddState++;
370: else
371: badlyComputingOddState = 0;
372: break;
373: case 3:
374: if (seen == IF_ICMPEQ || seen == IF_ICMPNE) {
375: bugAccumulator.accumulateBug(new BugInstance(this ,
376: "IM_BAD_CHECK_FOR_ODD", NORMAL_PRIORITY)
377: .addClassAndMethod(this ), this );
378: }
379: badlyComputingOddState = 0;
380: break;
381: }
382:
383: // Java Puzzlers, chapter 3, puzzle 12
384: if (seen == INVOKEVIRTUAL
385: && stack.getStackDepth() > 0
386: && (getNameConstantOperand().equals("toString")
387: && getSigConstantOperand().equals(
388: "()Ljava/lang/String;")
389: || getNameConstantOperand().equals("append")
390: && getSigConstantOperand()
391: .equals(
392: "(Ljava/lang/Object;)Ljava/lang/StringBuilder;")
393: && getClassConstantOperand().equals(
394: "java/lang/StringBuilder") || getNameConstantOperand()
395: .equals("append")
396: && getSigConstantOperand()
397: .equals(
398: "(Ljava/lang/Object;)Ljava/lang/StringBuffer;")
399: && getClassConstantOperand().equals(
400: "java/lang/StringBuffer"))) {
401: OpcodeStack.Item item = stack.getStackItem(0);
402: String signature = item.getSignature();
403: if (signature != null && signature.startsWith("[")) {
404: String name = null;
405: int reg = item.getRegisterNumber();
406: Collection<BugAnnotation> as = new ArrayList<BugAnnotation>();
407: if (reg != -1) {
408: LocalVariableAnnotation lva = LocalVariableAnnotation
409: .getLocalVariableAnnotation(getMethod(),
410: reg, getPC(), getPC() - 1);
411: name = "array " + lva.getName() + "[]";
412: as.add(lva);
413: }
414: if (name != null) {
415: bugAccumulator.accumulateBug(new BugInstance(this ,
416: "DMI_INVOKING_TOSTRING_ON_ARRAY",
417: NORMAL_PRIORITY).addClassAndMethod(this )
418: .addAnnotations(as), this );
419: } else {
420: bugAccumulator.accumulateBug(new BugInstance(this ,
421: "DMI_INVOKING_TOSTRING_ON_ANONYMOUS_ARRAY",
422: NORMAL_PRIORITY).addClassAndMethod(this ),
423: this );
424: }
425: }
426: }
427:
428: if (isTigerOrHigher) {
429: if (previousMethodInvocation != null
430: && prevOpCode == INVOKESPECIAL
431: && seen == INVOKEVIRTUAL) {
432: String classNameForPreviousMethod = previousMethodInvocation
433: .getClassName();
434: String classNameForThisMethod = getClassConstantOperand();
435: if (classNameForPreviousMethod.startsWith("java.lang.")
436: && classNameForPreviousMethod
437: .equals(classNameForThisMethod.replace(
438: '/', '.'))
439: && getNameConstantOperand().endsWith("Value")
440: && getSigConstantOperand().length() == 3) {
441: if (getSigConstantOperand().charAt(2) == previousMethodInvocation
442: .getSignature().charAt(1))
443: bugAccumulator.accumulateBug(new BugInstance(
444: this , "BX_BOXING_IMMEDIATELY_UNBOXED",
445: NORMAL_PRIORITY)
446: .addClassAndMethod(this ), this );
447:
448: else
449: bugAccumulator
450: .accumulateBug(
451: new BugInstance(
452: this ,
453: "BX_BOXING_IMMEDIATELY_UNBOXED_TO_PERFORM_COERCION",
454: NORMAL_PRIORITY)
455: .addClassAndMethod(this ),
456: this );
457:
458: ternaryConversionState = 1;
459: } else
460: ternaryConversionState = 0;
461:
462: } else if (seen == INVOKEVIRTUAL) {
463: if (getClassConstantOperand().startsWith("java/lang")
464: && getNameConstantOperand().endsWith("Value")
465: && getSigConstantOperand().length() == 3)
466: ternaryConversionState = 1;
467: else
468: ternaryConversionState = 0;
469: } else if (ternaryConversionState == 1) {
470: if (I2L <= seen && seen <= I2S)
471: ternaryConversionState = 2;
472: else
473: ternaryConversionState = 0;
474: } else if (ternaryConversionState == 2) {
475: ternaryConversionState = 0;
476: if (seen == GOTO)
477: bugReporter
478: .reportBug(new BugInstance(
479: this ,
480: "BX_UNBOXED_AND_COERCED_FOR_TERNARY_OPERATOR",
481: NORMAL_PRIORITY).addClassAndMethod(
482: this ).addSourceLine(this ));
483: }
484: }
485:
486: if (seen == INVOKESTATIC)
487: if ((getNameConstantOperand().startsWith("assert") || getNameConstantOperand()
488: .startsWith("fail"))
489: && getMethodName().equals("run")
490: && implements Runnable(getThisClass())) {
491: try {
492: int size1 = Util.getSizeOfSurroundingTryBlock(
493: getConstantPool(), getMethod().getCode(),
494: "java/lang/Throwable", getPC());
495: int size2 = Util.getSizeOfSurroundingTryBlock(
496: getConstantPool(), getMethod().getCode(),
497: "java/lang/Error", getPC());
498: int size3 = Util.getSizeOfSurroundingTryBlock(
499: getConstantPool(), getMethod().getCode(),
500: "java/lang/AssertionFailureError", getPC());
501: int size = Math.min(Math.min(size1, size2), size3);
502: if (size == Integer.MAX_VALUE) {
503: JavaClass targetClass = AnalysisContext
504: .currentAnalysisContext().lookupClass(
505: getClassConstantOperand()
506: .replace('/', '.'));
507: if (targetClass.getSuperclassName().startsWith(
508: "junit")) {
509: bugAccumulator
510: .accumulateBug(
511: new BugInstance(
512: this ,
513: "IJU_ASSERT_METHOD_INVOKED_FROM_RUN_METHOD",
514: NORMAL_PRIORITY)
515: .addClassAndMethod(this ),
516: this );
517:
518: }
519: }
520: } catch (ClassNotFoundException e) {
521: AnalysisContext.reportMissingClass(e);
522: }
523:
524: }
525: if (seen == INVOKESPECIAL
526: && getClassConstantOperand().startsWith("java/lang/")
527: && getNameConstantOperand().equals("<init>")
528: && getSigConstantOperand().length() == 4)
529:
530: previousMethodInvocation = XFactory
531: .createReferencedXMethod(this );
532: else if (seen == INVOKESTATIC
533: && getClassConstantOperand().startsWith("java/lang/")
534: && getNameConstantOperand().equals("valueOf")
535: && getSigConstantOperand().length() == 4)
536: previousMethodInvocation = XFactory
537: .createReferencedXMethod(this );
538: else
539: previousMethodInvocation = null;
540: prevOpCode = seen;
541:
542: // Check for PrintStream.printf, PrintStream.format, String.format,
543: // Formatter.format
544:
545: if (fsState == FS_STATE_NONE) {
546: if (seen == LDC) {
547: Constant c = getConstantRefOperand();
548: if (c instanceof ConstantString) {
549: fsFmtStr = getStringConstantOperand();
550: if (VAMISMATCH_DEBUG) {
551: System.out.println("Format str: " + fsFmtStr);
552: }
553: fsState = FS_STATE_SAW_STR_LOAD;
554: }
555: }
556: } else if (fsState == FS_STATE_SAW_STR_LOAD) {
557: if (seen == ICONST_0) {
558: prevConst = 0;
559: fsState = FS_STATE_SAW_CONST_PUSH;
560: } else if (seen == ICONST_1) {
561: prevConst = 1;
562: fsState = FS_STATE_SAW_CONST_PUSH;
563: } else if (seen == ICONST_2) {
564: prevConst = 2;
565: fsState = FS_STATE_SAW_CONST_PUSH;
566: } else if (seen == ICONST_3) {
567: prevConst = 3;
568: fsState = FS_STATE_SAW_CONST_PUSH;
569: } else if (seen == ICONST_4) {
570: prevConst = 4;
571: fsState = FS_STATE_SAW_CONST_PUSH;
572: } else if (seen == ICONST_5) {
573: prevConst = 5;
574: fsState = FS_STATE_SAW_CONST_PUSH;
575: } else if (seen == BIPUSH) {
576: prevConst = getIntConstant();
577: fsState = FS_STATE_SAW_CONST_PUSH;
578: } else {
579: prevConst = -1;
580: fsState = FS_STATE_NONE;
581: }
582: } else if (fsState == FS_STATE_SAW_CONST_PUSH) {
583: if (seen == ANEWARRAY) {
584: if (VAMISMATCH_DEBUG) {
585: System.out.println("New array with const: "
586: + prevConst);
587: }
588: fsState = FS_STATE_SAW_NEWARRAY;
589: fsAAStores = 0;
590: } else {
591: fsState = FS_STATE_NONE;
592: }
593: } else if (fsState == FS_STATE_SAW_NEWARRAY) {
594: if (seen == AASTORE) {
595: fsAAStores++;
596: if (VAMISMATCH_DEBUG) {
597: System.out.println("Saw an AASTORE; new count: "
598: + fsAAStores);
599: }
600: if (fsAAStores > prevConst) {
601: // Stored too many array elements!
602: if (VAMISMATCH_DEBUG) {
603: System.out.println("Oops, " + fsAAStores
604: + " is too many");
605: }
606: fsState = FS_STATE_NONE;
607: }
608: } else if (fsAAStores == prevConst
609: && (seen == INVOKESPECIAL || seen == INVOKEVIRTUAL || seen == INVOKESTATIC)) {
610: String cl = getClassConstantOperand();
611: String nm = getNameConstantOperand();
612: XMethod xm = XFactory.createReferencedXMethod(this );
613: int flags = xm.getAccessFlags();
614: if ((flags & ACC_TRANSIENT) != 0) {
615: if (VAMISMATCH_DEBUG) {
616: System.out.println("Found VARARGS method: "
617: + cl + "." + nm);
618: }
619: if ("java/util/Formatter".equals(cl)
620: && "format".equals(nm)
621: || "java/lang/String".equals(cl)
622: && "format".equals(nm)
623: || "java/io/PrintStream".equals(cl)
624: && "format".equals(nm)
625: || "java/io/PrintStream".equals(cl)
626: && "printf".equals(nm)) {
627: // Get the format string if possible
628: int pcts = 0; // number of normally consumed args
629: int highAbs = 0; // highest indexed arg
630: for (int i = 0; i < fsFmtStr.length(); i++) {
631: if (fsFmtStr.charAt(i) == '%') {
632: if (i < fsFmtStr.length() - 1 &&
633: // If next char is '%', then it's just
634: // an escaped percent. If next char is
635: // 'n', then it's just the platform-
636: // specific line separator. In neither
637: // case is an argument consumed.
638: (fsFmtStr.charAt(i + 1) == '%' || fsFmtStr
639: .charAt(i + 1) == 'n')) {
640: i++; // skip over
641: } else if (i < fsFmtStr.length() - 1
642: && fsFmtStr.charAt(i + 1) == '<') {
643: // this just refers to whatever the
644: // previous placeholder referred to
645: i++; // skip over
646: } else if (i < fsFmtStr.length() - 1
647: && fsFmtStr.charAt(i + 1) >= '0'
648: && fsFmtStr.charAt(i + 1) <= '9') {
649: int idx = fsFmtStr.charAt(i + 1) - '0';
650: i++;
651: // Parse index into idx
652: while (i + 1 < fsFmtStr.length()) {
653: if (fsFmtStr.charAt(i + 1) >= '0'
654: && fsFmtStr
655: .charAt(i + 1) <= '9') {
656: idx *= 10;
657: idx += fsFmtStr
658: .charAt(i + 1) - '0';
659: } else {
660: if (fsFmtStr.charAt(i + 1) != '$') {
661: // oops, can't parse
662: // this as an absolute
663: idx = 0;
664: pcts++;
665: }
666: break;
667: }
668: i++;
669: }
670: // This is the highest absolute
671: // index used so far
672: if (idx > highAbs) {
673: if (VAMISMATCH_DEBUG) {
674: System.out
675: .println("New highest abs idx: "
676: + idx);
677: }
678: highAbs = idx;
679: } else {
680: if (VAMISMATCH_DEBUG) {
681: System.out
682: .println("Not highest abs idx: "
683: + idx
684: + " (highest: "
685: + highAbs
686: + ")");
687: }
688: }
689: } else {
690: pcts++;
691: }
692: }
693: }
694: int consumedPcts = Math.max(pcts, highAbs);
695: if (VAMISMATCH_DEBUG) {
696: System.out.println("Consumed pcts is: "
697: + consumedPcts + ", normal pcts: "
698: + pcts + ", highest absolute: "
699: + highAbs);
700: }
701: if (consumedPcts != prevConst) {
702: // Bug!
703: bugReporter
704: .reportBug(new BugInstance(
705: this ,
706: "VA_FORMAT_STRING_ARG_MISMATCH",
707: NORMAL_PRIORITY)
708: .addClassAndMethod(this )
709: .addCalledMethod(this )
710: .addString(fsFmtStr)
711: .addInt(consumedPcts)
712: .describe(
713: IntAnnotation.INT_EXPECTED_ARGUMENTS)
714: .addInt(prevConst)
715: .describe(
716: IntAnnotation.INT_ACTUAL_ARGUMENTS)
717: .addSourceLine(this ));
718: if (VAMISMATCH_DEBUG) {
719: System.out
720: .println("WARNING: # consumed percent signs ("
721: + consumedPcts
722: + ") doesn't match vararg array size: "
723: + prevConst);
724: }
725: } else {
726: // OK
727: if (VAMISMATCH_DEBUG) {
728: System.out
729: .println("# consumed percent signs ("
730: + consumedPcts
731: + ") matchs vararg array size: "
732: + prevConst);
733: }
734: }
735: } else {
736: // OK
737: if (VAMISMATCH_DEBUG) {
738: System.out
739: .println("Not a VARARGS I'm familiar with");
740: }
741: }
742: } else {
743: if ("java/util/Formatter".equals(cl)
744: && "format".equals(nm)
745: || "java/lang/String".equals(cl)
746: && "format".equals(nm)
747: || "java/io/PrintStream".equals(cl)
748: && "format".equals(nm)
749: || "java/io/PrintStream".equals(cl)
750: && "printf".equals(nm)) {
751: // OK
752: if (VAMISMATCH_DEBUG) {
753: System.out.println("Oops: " + cl + "." + nm
754: + " not a VARARGS");
755: }
756: }
757: }
758: fsState = FS_STATE_NONE;
759: }
760: }
761: }
762:
763: boolean implements Runnable(JavaClass obj) {
764: if (obj.getSuperclassName().equals("java.lang.Thread"))
765: return true;
766: for (String s : obj.getInterfaceNames())
767: if (s.equals("java.lang.Runnable"))
768: return true;
769: return false;
770: }
771:
772: }
|