All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.main.java.com.mebigfatguy.fbcontrib.detect.UnrelatedCollectionContents Maven / Gradle / Ivy

Go to download

An auxiliary findbugs.sourceforge.net plugin for java bug detectors that fall outside the narrow scope of detectors to be packaged with the product itself.

There is a newer version: 7.6.8
Show newest version
/*
 * fb-contrib - Auxiliary detectors for Java programs
 * Copyright (C) 2005-2018 Dave Brosius
 *
 * 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 com.mebigfatguy.fbcontrib.detect;

import java.util.BitSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.JavaClass;

import com.mebigfatguy.fbcontrib.utils.BugType;
import com.mebigfatguy.fbcontrib.utils.FQField;
import com.mebigfatguy.fbcontrib.utils.OpcodeUtils;
import com.mebigfatguy.fbcontrib.utils.RegisterUtils;
import com.mebigfatguy.fbcontrib.utils.SignatureBuilder;
import com.mebigfatguy.fbcontrib.utils.UnmodifiableSet;
import com.mebigfatguy.fbcontrib.utils.Values;

import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.BytecodeScanningDetector;
import edu.umd.cs.findbugs.OpcodeStack;
import edu.umd.cs.findbugs.SourceLineAnnotation;
import edu.umd.cs.findbugs.ba.ClassContext;
import edu.umd.cs.findbugs.ba.XField;

/**
 * looks for collections or arrays that hold objects that are unrelated thru class or interface inheritance other than java.lang.Object. Doing so, makes for
 * brittle code, relying either on positional correspondence for type, or a reliance on instanceof to determine type. A better design usually can be had by
 * creating a separate class, which defines the different types required, and add an instance of that class to the collection, or array.
 */
public class UnrelatedCollectionContents extends BytecodeScanningDetector {
    private static final Set COLLECTION_CLASSES = UnmodifiableSet.create(Values.SLASHED_JAVA_UTIL_COLLECTION, Values.SLASHED_JAVA_UTIL_LIST,
            Values.SLASHED_JAVA_UTIL_MAP, Values.SLASHED_JAVA_UTIL_SET, "java/util/SortedMap", "java/util/SortedSet");

    private final BugReporter bugReporter;
    private OpcodeStack stack;
    private Map> memberCollections;
    private Map> localCollections;
    private Map localScopeEnds;
    private Map> memberSourceLineAnnotations;
    private Map> localSourceLineAnnotations;

    /**
     * constructs a UCC detector given the reporter to report bugs on
     *
     * @param bugReporter
     *            the sync of bug reports
     */
    public UnrelatedCollectionContents(final BugReporter bugReporter) {
        this.bugReporter = bugReporter;
    }

    /**
     * implements the visitor to create and destroy the stack and member collections
     *
     * @param classContext
     *            the context object for the currently parsed class
     */
    @Override
    public void visitClassContext(final ClassContext classContext) {
        try {
            stack = new OpcodeStack();
            memberCollections = new HashMap<>();
            memberSourceLineAnnotations = new HashMap<>();
            super.visitClassContext(classContext);
        } finally {
            stack = null;
            memberCollections = null;
            memberSourceLineAnnotations = null;
        }
    }

    /**
     * implements the visitor to reset the opcode stack, and clear the various collections
     *
     * @param obj
     *            the currently parsed code block
     */
    @Override
    public void visitCode(final Code obj) {
        try {
            localCollections = new HashMap<>();
            localScopeEnds = new HashMap<>();
            localSourceLineAnnotations = new HashMap<>();
            stack.resetForMethodEntry(this);
            super.visitCode(obj);
        } finally {
            localCollections = null;
            localScopeEnds = null;
            localSourceLineAnnotations = null;
        }
    }

    /**
     * implements the visitor to look for collection method calls that put objects into the collection that are unrelated by anything besides java.lang.Objecct
     *
     * @param seen
     *            the currently parsed opcode
     */
    @Override
    public void sawOpcode(final int seen) {
        try {
            stack.precomputation(this);

            BitSet regs = localScopeEnds.remove(Integer.valueOf(getPC()));
            if (regs != null) {
                int i = regs.nextSetBit(0);
                while (i >= 0) {
                    localCollections.remove(i);
                    localSourceLineAnnotations.remove(i);
                    i = regs.nextSetBit(i + 1);
                }
            }
            if (seen == INVOKEINTERFACE) {
                String className = getClassConstantOperand();
                if (COLLECTION_CLASSES.contains(className)) {
                    String methodName = getNameConstantOperand();
                    String methodSig = getSigConstantOperand();
                    if ("add".equals(methodName) && SignatureBuilder.SIG_OBJECT_TO_BOOLEAN.equals(methodSig)) {
                        if (stack.getStackDepth() > 1) {
                            OpcodeStack.Item colItm = stack.getStackItem(1);
                            OpcodeStack.Item addItm = stack.getStackItem(0);
                            checkAdd(colItm, addItm);
                        }
                    } else if (("put".equals(methodName) && SignatureBuilder.SIG_TWO_OBJECTS_TO_OBJECT.equals(methodSig)) && (stack.getStackDepth() > 2)) {
                        // For maps, just check the keys
                        OpcodeStack.Item colItm = stack.getStackItem(2);
                        OpcodeStack.Item addItm = stack.getStackItem(1);
                        checkAdd(colItm, addItm);
                    }
                }
            } else if (seen == AASTORE) {
                if (stack.getStackDepth() > 2) {
                    OpcodeStack.Item arrayItm = stack.getStackItem(2);
                    OpcodeStack.Item addItm = stack.getStackItem(0);
                    checkAdd(arrayItm, addItm);
                }
            } else if (OpcodeUtils.isAStore(seen)) {
                Integer reg = Integer.valueOf(RegisterUtils.getAStoreReg(this, seen));
                localCollections.remove(reg);
                localSourceLineAnnotations.remove(reg);
            }
        } catch (ClassNotFoundException cnfe) {
            bugReporter.reportMissingClass(cnfe);
        } finally {
            stack.sawOpcode(this, seen);
        }
    }

    /**
     * processes an add into a collection, by processing all the super classes/interfaces of an object and removing the possible set of parent classes that have
     * been seen so far, by doing what amounts to a intersection of what has been seen before, and this occurance.
     *
     * @param colItm
     *            the collection that is being added to
     * @param addItm
     *            the added item
     * @throws ClassNotFoundException
     *             if a super class is not found
     */
    private void checkAdd(final OpcodeStack.Item colItm, final OpcodeStack.Item addItm) throws ClassNotFoundException {
        int reg = colItm.getRegisterNumber();
        if (reg == -1) {
            XField field = colItm.getXField();
            if (field == null) {
                return;
            }

            Set sla = memberSourceLineAnnotations.get(field.getName());
            if (sla == null) {
                sla = new HashSet<>();
                memberSourceLineAnnotations.put(field.getName(), sla);
            }
            sla.add(SourceLineAnnotation.fromVisitedInstruction(this));
            FQField fqField = new FQField(field.getClassName(), field.getName(), field.getSignature());
            Set commonSupers = memberCollections.get(fqField);
            if (commonSupers == null) {
                commonSupers = new HashSet<>();
                memberCollections.put(fqField, commonSupers);
                addNewItem(commonSupers, addItm);
            } else {
                mergeItem(commonSupers, sla, addItm);
            }
        } else {
            Integer regNumber = Integer.valueOf(reg);
            Set pcs = localSourceLineAnnotations.get(regNumber);
            if (pcs == null) {
                pcs = new HashSet<>();
                localSourceLineAnnotations.put(regNumber, pcs);
            }
            pcs.add(SourceLineAnnotation.fromVisitedInstruction(this, getPC()));
            Set commonSupers = localCollections.get(regNumber);
            if (commonSupers == null) {
                commonSupers = new HashSet<>();
                localCollections.put(Integer.valueOf(reg), commonSupers);
                addNewItem(commonSupers, addItm);
                Integer scopeEnd = Integer.valueOf(RegisterUtils.getLocalVariableEndRange(getMethod().getLocalVariableTable(), reg, getNextPC()));
                BitSet regs = localScopeEnds.get(scopeEnd);
                if (regs == null) {
                    regs = new BitSet();
                    localScopeEnds.put(scopeEnd, regs);
                }
                regs.set(regNumber);
            } else {
                mergeItem(commonSupers, pcs, addItm);
            }
        }
    }

    /**
     * intersects the set of possible superclass that this collection might have seen before with this one. If we find that there is no commonality between
     * superclasses, report it as a bug.
     *
     * @param supers
     *            the collection of possible superclass/interfaces that has been seen for this collection thus far
     * @param sla
     *            the location of this add
     * @param addItm
     *            the currently added item
     * @throws ClassNotFoundException
     *             if a superclass/interface can not be found
     */
    private void mergeItem(final Set supers, final Set sla, final OpcodeStack.Item addItm) throws ClassNotFoundException {

        if (supers.isEmpty()) {
            return;
        }

        Set s = new HashSet<>();
        addNewItem(s, addItm);

        if (s.isEmpty()) {
            return;
        }

        intersection(supers, s);

        if (supers.isEmpty()) {
            BugInstance bug = new BugInstance(this, BugType.UCC_UNRELATED_COLLECTION_CONTENTS.name(), NORMAL_PRIORITY).addClass(this);

            if (addItm.getRegisterNumber() != -1) {
                bug.addMethod(this);
            }

            for (SourceLineAnnotation a : sla) {
                bug.addSourceLine(a);
            }

            bugReporter.reportBug(bug);
        }
    }

    /**
     * adds this item's type and all of it's superclass/interfaces to the set of possible types that could define this added item
     *
     * @param supers
     *            the current set of superclass items
     * @param addItm
     *            the item we are adding
     * @throws ClassNotFoundException
     *             if a superclass/interface is not found
     */
    private static void addNewItem(final Set supers, final OpcodeStack.Item addItm) throws ClassNotFoundException {

        String itemSignature = addItm.getSignature();
        if (itemSignature.length() == 0) {
            return;
        }

        if (itemSignature.startsWith(Values.SIG_ARRAY_PREFIX)) {
            supers.add(itemSignature);
            return;
        }

        JavaClass cls = addItm.getJavaClass();
        if ((cls == null) || Values.DOTTED_JAVA_LANG_OBJECT.equals(cls.getClassName())) {
            return;
        }

        supers.add(cls.getClassName());

        JavaClass[] infs = cls.getAllInterfaces();
        for (JavaClass inf : infs) {
            String infName = inf.getClassName();
            if (!"java.io.Serializable".equals(infName) && !"java.lang.Cloneable".equals(infName)) {
                supers.add(infName);
            }
        }

        JavaClass[] sups = cls.getSuperClasses();
        for (JavaClass sup : sups) {
            String name = sup.getClassName();
            if (!Values.DOTTED_JAVA_LANG_OBJECT.equals(name)) {
                supers.add(name);
            }
        }
    }

    /**
     * performs a typical set intersection between what types of possible superclasses/interfaces has been seen before, for this collection, and what is now
     * seen.
     *
     * @param orig
     *            the existing set of superclasses used for this collection
     * @param add
     *            the currently added item
     */
    private static void intersection(final Set orig, final Set add) {
        Iterator it = orig.iterator();
        while (it.hasNext()) {
            if (!add.contains(it.next())) {
                it.remove();
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy