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

src.main.java.com.mebigfatguy.fbcontrib.detect.SluggishGui 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.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

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

import com.mebigfatguy.fbcontrib.utils.BugType;
import com.mebigfatguy.fbcontrib.utils.StopOpcodeParsingException;
import com.mebigfatguy.fbcontrib.utils.UnmodifiableSet;

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

/**
 * looks for methods that implement awt or swing listeners and perform time consuming operations. Doing these operations in the gui thread will cause the
 * interface to appear sluggish and non-responsive to the user. It is better to use a separate thread to do the time consuming work so that the user has a
 * better experience.
 */
public class SluggishGui extends BytecodeScanningDetector {

    private static final Set expensiveCalls = UnmodifiableSet.create("java/io/BufferedOutputStream:", "java/io/DataOutputStream:",
            "java/io/FileOutputStream:", "java/io/ObjectOutputStream:", "java/io/PipedOutputStream:", "java/io/BufferedInputStream:",
            "java/io/DataInputStream:", "java/io/FileInputStream:", "java/io/ObjectInputStream:", "java/io/PipedInputStream:",
            "java/io/BufferedWriter:", "java/io/FileWriter:", "java/io/OutpuStreamWriter:", "java/io/BufferedReader:",
            "java/io/FileReader:", "java/io/InputStreamReader:", "java/io/RandomAccessFile:", "java/lang/Class:getResourceAsStream",
            "java/lang/ClassLoader:getResourceAsStream", "java/lang/ClassLoader:loadClass", "java/sql/DriverManager:getConnection",
            "java/sql/Connection:createStatement", "java/sql/Connection:prepareStatement", "java/sql/Connection:prepareCall",
            "javax/sql/DataSource:getConnection", "javax/xml/parsers/DocumentBuilder:parse", "javax/xml/parsers/DocumentBuilder:parse",
            "javax/xml/parsers/SAXParser:parse", "javax/xml/transform/Transformer:transform");

    private BugReporter bugReporter;
    private Set expensiveThisCalls;
    private Set guiInterfaces;
    private Map listenerCode;
    private String methodName;
    private String methodSig;
    private boolean isListenerMethod = false;

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

    /**
     * overrides the visitor to reset look for gui interfaces
     *
     * @param classContext
     *            the context object for the currently parsed class
     */
    @Override
    public void visitClassContext(ClassContext classContext) {
        try {
            guiInterfaces = new HashSet<>();
            JavaClass cls = classContext.getJavaClass();
            JavaClass[] infs = cls.getAllInterfaces();
            for (JavaClass inf : infs) {
                String name = inf.getClassName();
                if ((name.startsWith("java.awt.") || name.startsWith("javax.swing.")) && name.endsWith("Listener")) {
                    guiInterfaces.add(inf);
                }
            }

            if (!guiInterfaces.isEmpty()) {
                listenerCode = new LinkedHashMap<>();
                expensiveThisCalls = new HashSet<>();
                super.visitClassContext(classContext);
            }
        } catch (ClassNotFoundException cnfe) {
            bugReporter.reportMissingClass(cnfe);
        } finally {
            guiInterfaces = null;
            listenerCode = null;
            expensiveThisCalls = null;
        }
    }

    /**
     * overrides the visitor to visit all of the collected listener methods
     *
     * @param obj
     *            the context object of the currently parsed class
     */
    @Override
    public void visitAfter(JavaClass obj) {
        isListenerMethod = true;
        for (Code l : listenerCode.keySet()) {
            try {
                super.visitCode(l);
            } catch (StopOpcodeParsingException e) {
                // method already reported
            }
        }
        super.visitAfter(obj);
    }

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

    /**
     * overrides the visitor to segregate method into two, those that implement listeners, and those that don't. The ones that don't are processed first.
     *
     * @param obj
     *            the context object of the currently parsed code block
     */
    @Override
    public void visitCode(Code obj) {
        try {
            for (JavaClass inf : guiInterfaces) {
                Method[] methods = inf.getMethods();
                for (Method m : methods) {
                    if (m.getName().equals(methodName) && m.getSignature().equals(methodSig)) {
                        listenerCode.put(obj, this.getMethod());
                        return;
                    }
                }
            }
            isListenerMethod = false;
            super.visitCode(obj);
        } catch (StopOpcodeParsingException e) {
            // method already reported
        }
    }

    /**
     * overrides the visitor to look for the execution of expensive calls
     *
     * @param seen
     *            the currently parsed opcode
     */
    @Override
    public void sawOpcode(int seen) {

        if ((seen == INVOKEINTERFACE) || (seen == INVOKEVIRTUAL) || (seen == INVOKESPECIAL) || (seen == INVOKESTATIC)) {
            String clsName = getClassConstantOperand();
            String mName = getNameConstantOperand();
            String methodInfo = clsName + ':' + mName;
            String thisMethodInfo = (clsName.equals(getClassName())) ? (mName + ':' + methodSig) : "0";

            if (expensiveCalls.contains(methodInfo) || expensiveThisCalls.contains(thisMethodInfo)) {
                if (isListenerMethod) {
                    bugReporter.reportBug(new BugInstance(this, BugType.SG_SLUGGISH_GUI.name(), NORMAL_PRIORITY).addClass(this)
                            .addMethod(this.getClassContext().getJavaClass(), listenerCode.get(this.getCode())));
                } else {
                    expensiveThisCalls.add(getMethodName() + ':' + getMethodSig());
                }
                throw new StopOpcodeParsingException();
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy