/* * FindBugs - Find bugs in Java programs * Copyright (C) 2003,2004 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.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import org.apache.bcel.classfile.Code; import org.apache.bcel.classfile.JavaClass; import org.apache.bcel.classfile.Method; import edu.umd.cs.findbugs.BugInstance; import edu.umd.cs.findbugs.BugReporter; import edu.umd.cs.findbugs.BytecodeScanningDetector; import edu.umd.cs.findbugs.FieldAnnotation; import edu.umd.cs.findbugs.MethodAnnotation; import edu.umd.cs.findbugs.SystemProperties; import edu.umd.cs.findbugs.ba.XField; import edu.umd.cs.findbugs.ba.XMethod; public class InitializationChain extends BytecodeScanningDetector { Set<String> requires = new TreeSet<String>(); Map<String, Set<String>> classRequires = new TreeMap<String, Set<String>>(); private BugReporter bugReporter; private Map<XMethod, Set<XField>> staticFieldsRead = new HashMap<XMethod, Set<XField>>(); private Set<XField> staticFieldsReadInAnyConstructor = new HashSet<XField>(); private Set<XField> fieldsReadInThisConstructor = new HashSet<XField>(); private Set<XMethod> constructorsInvokedInStaticInitializer = new HashSet<XMethod>(); private List<InvocationInfo> invocationInfo = new ArrayList<InvocationInfo>(); private Set<XField> warningGiven = new HashSet<XField>(); private InvocationInfo lastInvocation; static class InvocationInfo { public InvocationInfo(XMethod constructor, int pc) { this.constructor = constructor; this.pc = pc; } XMethod constructor; int pc; XField field; } private static final boolean DEBUG = SystemProperties.getBoolean("ic.debug"); public InitializationChain(BugReporter bugReporter) { this.bugReporter = bugReporter; } @Override protected Iterable<Method> getMethodVisitOrder(JavaClass obj) { ArrayList<Method> visitOrder = new ArrayList<Method>(); Method staticInitializer = null; for(Method m : obj.getMethods()) { String name = m.getName(); if (name.equals("<clinit>")) staticInitializer = m; else if (name.equals("<init>")) visitOrder.add(m); } if (staticInitializer != null) visitOrder.add(staticInitializer); return visitOrder; } @Override public void visit(Code obj) { fieldsReadInThisConstructor = new HashSet<XField>(); super.visit(obj); staticFieldsRead.put(getXMethod(), fieldsReadInThisConstructor); requires.remove(getDottedClassName()); if (getDottedClassName().equals("java.lang.System")) { requires.add("java.io.FileInputStream"); requires.add("java.io.FileOutputStream"); requires.add("java.io.BufferedInputStream"); requires.add("java.io.BufferedOutputStream"); requires.add("java.io.PrintStream"); } if (!requires.isEmpty()) { classRequires.put(getDottedClassName(), requires); requires = new TreeSet<String>(); } } @Override public void visitAfter(JavaClass obj) { staticFieldsRead.clear(); staticFieldsReadInAnyConstructor.clear(); fieldsReadInThisConstructor.clear(); constructorsInvokedInStaticInitializer.clear(); invocationInfo.clear(); lastInvocation = null; } @Override public void sawOpcode(int seen) { InvocationInfo prev = lastInvocation; lastInvocation = null; if (getMethodName().equals("<init>")) { if (seen == GETSTATIC && getClassConstantOperand().equals(getClassName())) { staticFieldsReadInAnyConstructor.add(getXFieldOperand()); fieldsReadInThisConstructor.add(getXFieldOperand()); } return; } if (seen == INVOKESPECIAL && getNameConstantOperand().equals("<init>") && getClassConstantOperand().equals(getClassName())) { XMethod m = getXMethodOperand(); Set<XField> read = staticFieldsRead.get(m); if (constructorsInvokedInStaticInitializer.add(m) && read != null && !read.isEmpty()) { lastInvocation = new InvocationInfo(m, getPC()); invocationInfo.add(lastInvocation); } } if (seen == PUTSTATIC && getClassConstantOperand().equals(getClassName())) { XField f = getXFieldOperand(); if (prev != null) prev.field = f; if (staticFieldsReadInAnyConstructor.contains(f) && !warningGiven.contains(f)) { for(InvocationInfo i : invocationInfo) { Set<XField> fields = staticFieldsRead.get(i.constructor); if (fields != null && fields.contains(f)) { warningGiven.add(f); BugInstance bug = new BugInstance(this, "SI_INSTANCE_BEFORE_FINALS_ASSIGNED", NORMAL_PRIORITY).addClassAndMethod(this); if (i.field != null) { bug.addField(i.field).describe(FieldAnnotation.STORED_ROLE); } bug.addMethod(i.constructor).describe(MethodAnnotation.METHOD_CONSTRUCTOR); bug.addReferencedField(this).describe(FieldAnnotation.VALUE_OF_ROLE).addSourceLine(this, i.pc); bugReporter.reportBug(bug); break; } } } } else if (seen == PUTSTATIC || seen == GETSTATIC || seen == INVOKESTATIC || seen == NEW) if (getPC() + 6 < codeBytes.length) requires.add(getDottedClassConstantOperand()); } public void compute() { Set<String> allClasses = classRequires.keySet(); Set<String> emptyClasses = new TreeSet<String>(); for (String c : allClasses) { Set<String> needs = classRequires.get(c); needs.retainAll(allClasses); Set<String> extra = new TreeSet<String>(); for (String need : needs) extra.addAll(classRequires.get(need)); needs.addAll(extra); needs.retainAll(allClasses); classRequires.put(c, needs); if (needs.isEmpty()) emptyClasses.add(c); } for (String c : emptyClasses) { classRequires.remove(c); } } @Override public void report() { if (DEBUG) System.out.println("Finishing computation"); compute(); compute(); compute(); compute(); compute(); compute(); compute(); compute(); Set<String> allClasses = classRequires.keySet(); for (String c : allClasses) { if (DEBUG) System.out.println("Class " + c + " requires:"); for (String needs : (classRequires.get(c))) { if (DEBUG) System.out.println(" " + needs); Set<String> s = classRequires.get(needs); if (s != null && s.contains(c) && c.compareTo(needs) < 0) bugReporter.reportBug(new BugInstance(this, "IC_INIT_CIRCULARITY", NORMAL_PRIORITY).addClass(c).addClass( needs)); } } } }