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

src.main.java.com.mebigfatguy.fbcontrib.detect.HangingExecutors 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 Kevin Lubick
 * 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.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

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

import com.mebigfatguy.fbcontrib.utils.BugType;
import com.mebigfatguy.fbcontrib.utils.SignatureUtils;
import com.mebigfatguy.fbcontrib.utils.ToString;
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.Detector;
import edu.umd.cs.findbugs.FieldAnnotation;
import edu.umd.cs.findbugs.OpcodeStack;
import edu.umd.cs.findbugs.Priorities;
import edu.umd.cs.findbugs.ba.ClassContext;
import edu.umd.cs.findbugs.ba.XFactory;
import edu.umd.cs.findbugs.ba.XField;
import edu.umd.cs.findbugs.ba.XMethod;

/**
 * looks for executors that are never shutdown, which will not allow the application to terminate
 */
public class HangingExecutors extends BytecodeScanningDetector {

    private static final Set hangableSig = UnmodifiableSet.create("Ljava/util/concurrent/ExecutorService;",
            "Ljava/util/concurrent/AbstractExecutorService;", "Ljava/util/concurrent/ForkJoinPool;", "Ljava/util/concurrent/ScheduledThreadPoolExecutor;",
            "Ljava/util/concurrent/ThreadPoolExecutor;");

    private static final Set shutdownMethods = UnmodifiableSet.create("shutdown", "shutdownNow");

    private final BugReporter bugReporter;
    private Map hangingFieldCandidates;
    private Map exemptExecutors;
    private OpcodeStack stack;
    private String methodName;
    private boolean isInitializer;

    private final LocalHangingExecutor localHEDetector;

    public HangingExecutors(BugReporter reporter) {
        this.bugReporter = reporter;
        this.localHEDetector = new LocalHangingExecutor(this, reporter);
    }

    /**
     * finds ExecutorService objects that don't get a call to the terminating methods, and thus, never appear to be shutdown properly (the threads exist until
     * shutdown is called)
     *
     * @param classContext
     *            the class context object of the currently parsed java class
     */
    @Override
    public void visitClassContext(ClassContext classContext) {
        localHEDetector.visitClassContext(classContext);
        try {
            hangingFieldCandidates = new HashMap<>();
            exemptExecutors = new HashMap<>();
            parseFieldsForHangingCandidates(classContext);

            if (!hangingFieldCandidates.isEmpty()) {
                stack = new OpcodeStack();
                super.visitClassContext(classContext);

                reportHangingExecutorFieldBugs();
            }
        } finally {
            stack = null;
            hangingFieldCandidates = null;
            exemptExecutors = null;
        }

    }

    private void parseFieldsForHangingCandidates(ClassContext classContext) {
        JavaClass cls = classContext.getJavaClass();
        Field[] fields = cls.getFields();
        for (Field f : fields) {
            String sig = f.getSignature();
            if (hangableSig.contains(sig)) {
                hangingFieldCandidates.put(XFactory.createXField(cls.getClassName(), f.getName(), f.getSignature(), f.isStatic()),
                        new AnnotationPriority(FieldAnnotation.fromBCELField(cls, f), NORMAL_PRIORITY));
            }
        }
    }

    private void reportHangingExecutorFieldBugs() {
        for (Entry entry : hangingFieldCandidates.entrySet()) {
            AnnotationPriority fieldAn = entry.getValue();
            if (fieldAn != null) {
                bugReporter.reportBug(new BugInstance(this, BugType.HES_EXECUTOR_NEVER_SHUTDOWN.name(), fieldAn.priority).addClass(this)
                        .addField(fieldAn.annotation).addField(entry.getKey()));
            }
        }
    }

    /**
     * implements the visitor to reset the opcode stack
     *
     * @param obj
     *            the context object of the currently parsed code block
     */
    @Override
    public void visitCode(Code obj) {
        stack.resetForMethodEntry(this);
        exemptExecutors.clear();

        if (!hangingFieldCandidates.isEmpty()) {
            isInitializer = (Values.STATIC_INITIALIZER.equals(methodName) || Values.CONSTRUCTOR.equals(methodName));
            super.visitCode(obj);
        }
    }

    /**
     * implements the visitor to collect the method name
     *
     * @param obj
     *            the context object of the currently parsed method
     */
    @Override
    public void visitMethod(Method obj) {
        methodName = obj.getName();
    }

    /**
     * Browses for calls to shutdown() and shutdownNow(), and if they happen, remove the hanging candidate, as there is a chance it will be called.
     *
     * @param seen
     *            the opcode of the currently parsed instruction
     */
    @Override
    public void sawOpcode(int seen) {
        if (isInitializer) {
            lookForCustomThreadFactoriesInConstructors(seen);
            return;
        }

        try {
            stack.precomputation(this);

            if ((seen == INVOKEVIRTUAL) || (seen == INVOKEINTERFACE)) {
                processInvoke();
            }
            // TODO Should not include private methods
            else if (seen == ARETURN) {
                removeFieldsThatGetReturned();
            } else if (seen == PUTFIELD) {
                XField f = getXFieldOperand();
                if (f != null) {
                    reportOverwrittenField(f);
                }
            } else if (seen == IFNONNULL) {
                // indicates a null check, which means that we get an exemption
                // until the end of the branch
                OpcodeStack.Item nullCheckItem = stack.getStackItem(0);
                XField fieldWhichWasNullChecked = nullCheckItem.getXField();
                if (fieldWhichWasNullChecked != null) {
                    exemptExecutors.put(fieldWhichWasNullChecked, Integer.valueOf(getPC() + getBranchOffset()));
                }
            }
        } finally {
            stack.sawOpcode(this, seen);
        }
    }

    private void processInvoke() {
        String sig = getSigConstantOperand();
        int argCount = SignatureUtils.getNumParameters(sig);
        if (stack.getStackDepth() > argCount) {
            OpcodeStack.Item invokeeItem = stack.getStackItem(argCount);
            XField fieldOnWhichMethodIsInvoked = invokeeItem.getXField();
            if (fieldOnWhichMethodIsInvoked != null) {
                removeCandidateIfShutdownCalled(fieldOnWhichMethodIsInvoked);
                addExemptionIfShutdownCalled(fieldOnWhichMethodIsInvoked);
            }
        }
    }

    private void lookForCustomThreadFactoriesInConstructors(int seen) {
        try {
            stack.precomputation(this);
            if (seen == PUTFIELD) {
                XField f = getXFieldOperand();
                if ((f != null) && hangableSig.contains(f.getSignature())) {
                    // look at the top of the stack, get the arguments passed
                    // into the function that was called
                    // and then pull out the types.
                    // if the last type is a ThreadFactory, set the priority to
                    // low
                    XMethod method = stack.getStackItem(0).getReturnValueOf();
                    if (method != null) {
                        List argumentTypes = SignatureUtils.getParameterSignatures(method.getSignature());
                        if ((!argumentTypes.isEmpty()) && "Ljava/util/concurrent/ThreadFactory;".equals(argumentTypes.get(argumentTypes.size() - 1))) {
                            AnnotationPriority ap = this.hangingFieldCandidates.get(f);
                            if (ap != null) {
                                ap.priority = LOW_PRIORITY;
                                this.hangingFieldCandidates.put(f, ap);
                            }
                        }
                    } else {
                        // if the object is initialized from parameter, it's not this class's job to close it
                        int reg = stack.getStackItem(0).getRegisterNumber();
                        if (reg >= 0) {
                            Map ctorParmInfo = SignatureUtils.getParameterSlotAndSignatures(false, getMethod().getSignature());
                            if (ctorParmInfo.containsKey(Integer.valueOf(reg))) {
                                hangingFieldCandidates.remove(f);
                            }
                        }
                    }
                }
            }
        } finally {
            stack.sawOpcode(this, seen);
        }
    }

    private void reportOverwrittenField(XField f) {
        if ("Ljava/util/concurrent/ExecutorService;".equals(f.getSignature()) && !checkException(f)) {
            bugReporter.reportBug(new BugInstance(this, BugType.HES_EXECUTOR_OVERWRITTEN_WITHOUT_SHUTDOWN.name(), Priorities.NORMAL_PRIORITY).addClass(this)
                    .addMethod(this).addField(f).addSourceLine(this));
        }
        // after it's been replaced, it no longer uses its exemption.
        exemptExecutors.remove(f);
    }

    private boolean checkException(XField f) {
        if (!exemptExecutors.containsKey(f)) {
            return false;
        }
        int i = exemptExecutors.get(f).intValue();

        return (i == -1) || (getPC() < i);
    }

    private void removeFieldsThatGetReturned() {
        if (stack.getStackDepth() > 0) {
            OpcodeStack.Item returnItem = stack.getStackItem(0); // top thing on
                                                                 // the stack
                                                                 // was the
                                                                 // variable
                                                                 // being
                                                                 // returned
            XField field = returnItem.getXField();
            if (field != null) {
                hangingFieldCandidates.remove(field);
            }
        }
    }

    private void addExemptionIfShutdownCalled(XField fieldOnWhichMethodIsInvoked) {
        String methodBeingInvoked = getNameConstantOperand();
        if (shutdownMethods.contains(methodBeingInvoked)) {
            exemptExecutors.put(fieldOnWhichMethodIsInvoked, Values.NEGATIVE_ONE);
        }
    }

    private void removeCandidateIfShutdownCalled(XField fieldOnWhichMethodIsInvoked) {
        if (hangingFieldCandidates.containsKey(fieldOnWhichMethodIsInvoked)) {
            String methodBeingInvoked = getNameConstantOperand();
            if (shutdownMethods.contains(methodBeingInvoked)) {
                hangingFieldCandidates.remove(fieldOnWhichMethodIsInvoked);
            }
        }
    }

    /**
     * represents a field that is a executor
     */
    private static class AnnotationPriority {

        int priority;
        FieldAnnotation annotation;

        AnnotationPriority(FieldAnnotation annotation, int priority) {
            this.annotation = annotation;
            this.priority = priority;
        }

        @Override
        public String toString() {
            return ToString.build(this);
        }
    }
}

class LocalHangingExecutor extends LocalTypeDetector {

    private static final Map> watchedClassMethods;
    private static final Map syncCtors;

    static {
        Set forExecutors = new HashSet<>();
        forExecutors.add("newCachedThreadPool");
        forExecutors.add("newFixedThreadPool");
        forExecutors.add("newScheduledThreadPool");
        forExecutors.add("newSingleThreadExecutor");

        Map> wcm = new HashMap<>();
        wcm.put("java/util/concurrent/Executors", forExecutors);
        watchedClassMethods = Collections.unmodifiableMap(wcm);

        Map sc = new HashMap<>();
        sc.put("java/util/concurrent/ThreadPoolExecutor", Values.JAVA_5);
        sc.put("java/util/concurrent/ScheduledThreadPoolExecutor", Values.JAVA_5);
        syncCtors = Collections.unmodifiableMap(sc);
    }

    private final BugReporter bugReporter;
    private final Detector delegatingDetector;

    public LocalHangingExecutor(Detector delegatingDetector, BugReporter reporter) {
        this.bugReporter = reporter;
        this.delegatingDetector = delegatingDetector;
    }

    @Override
    protected Map getWatchedConstructors() {
        return syncCtors;
    }

    @Override
    protected Map> getWatchedClassMethods() {
        return watchedClassMethods;
    }

    @Override
    protected Set getSelfReturningMethods() {
        return Collections.emptySet();
    }

    @Override
    protected void reportBug(RegisterInfo cri) {
        // very important to report the bug under the top, parent detector,
        // otherwise it gets filtered out
        bugReporter.reportBug(new BugInstance(delegatingDetector, "HES_LOCAL_EXECUTOR_SERVICE", LOW_PRIORITY).addClass(this).addMethod(this)
                .addSourceLine(cri.getSourceLineAnnotation()));

    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy