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

src.main.java.com.mebigfatguy.fbcontrib.detect.BogusExceptionDeclaration 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.Set;

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

import com.mebigfatguy.fbcontrib.utils.BugType;
import com.mebigfatguy.fbcontrib.utils.OpcodeUtils;
import com.mebigfatguy.fbcontrib.utils.SignatureUtils;
import com.mebigfatguy.fbcontrib.utils.StopOpcodeParsingException;
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.ba.ClassContext;

/**
 * looks for constructors, private methods or static methods that declare that they throw specific checked exceptions, but that do not. This just causes callers
 * of these methods to do extra work to handle an exception that will never be thrown. also looks for throws clauses where two exceptions declared to be thrown
 * are related through inheritance.
 */
public class BogusExceptionDeclaration extends BytecodeScanningDetector {

    private static final Set safeClasses = UnmodifiableSet.create(
    //@formatter:off
            Values.SLASHED_JAVA_LANG_OBJECT,
            Values.SLASHED_JAVA_LANG_STRING,
            Values.SLASHED_JAVA_LANG_INTEGER,
            Values.SLASHED_JAVA_LANG_LONG,
            Values.SLASHED_JAVA_LANG_FLOAT,
            Values.SLASHED_JAVA_LANG_DOUBLE,
            Values.SLASHED_JAVA_LANG_SHORT,
            Values.SLASHED_JAVA_LANG_BYTE,
            Values.SLASHED_JAVA_LANG_BOOLEAN
            //@formatter:on
    );

    private final BugReporter bugReporter;
    private JavaClass runtimeExceptionClass;
    private JavaClass exceptionClass;

    private OpcodeStack stack;
    private Set declaredCheckedExceptions;
    private boolean classIsFinal;
    private boolean classIsAnonymous;

    public BogusExceptionDeclaration(BugReporter bugReporter) {
        this.bugReporter = bugReporter;

        try {
            runtimeExceptionClass = Repository.lookupClass(Values.SLASHED_JAVA_LANG_RUNTIMEEXCEPTION);
            exceptionClass = Repository.lookupClass(Values.SLASHED_JAVA_LANG_EXCEPTION);

        } catch (ClassNotFoundException cnfe) {
            bugReporter.reportMissingClass(cnfe);
            runtimeExceptionClass = null;
            exceptionClass = null;
        }
    }

    /**
     * overrides the visitor to create the opcode stack
     *
     * @param classContext
     *            the context object of the currently parsed class
     *
     */
    @Override
    public void visitClassContext(ClassContext classContext) {
        try {
            if ((runtimeExceptionClass != null) && (exceptionClass != null)) {
                stack = new OpcodeStack();
                declaredCheckedExceptions = new HashSet<>(6);
                JavaClass cls = classContext.getJavaClass();
                classIsFinal = cls.isFinal();
                classIsAnonymous = cls.isAnonymous();
                super.visitClassContext(classContext);
            }
        } finally {
            declaredCheckedExceptions = null;
            stack = null;
        }
    }

    /**
     * implements the visitor to see if the method declares that it throws any checked exceptions.
     *
     * @param obj
     *            the context object of the currently parsed code block
     */
    @Override
    public void visitCode(Code obj) {
        Method method = getMethod();

        if (method.isSynthetic()) {
            return;
        }

        declaredCheckedExceptions.clear();
        stack.resetForMethodEntry(this);

        ExceptionTable et = method.getExceptionTable();
        if (et != null) {
            if (classIsFinal || classIsAnonymous || method.isStatic() || method.isPrivate() || method.isFinal()
                    || ((Values.CONSTRUCTOR.equals(method.getName()) && !isAnonymousInnerCtor(method, getThisClass())))) {
                String[] exNames = et.getExceptionNames();
                for (String exName : exNames) {
                    try {
                        JavaClass exCls = Repository.lookupClass(exName);
                        if (!exCls.instanceOf(runtimeExceptionClass)) {
                            declaredCheckedExceptions.add(exName);
                        }
                    } catch (ClassNotFoundException cnfe) {
                        bugReporter.reportMissingClass(cnfe);
                    }
                }
                if (!declaredCheckedExceptions.isEmpty()) {
                    try {
                        super.visitCode(obj);
                        if (!declaredCheckedExceptions.isEmpty()) {
                            BugInstance bi = new BugInstance(this, BugType.BED_BOGUS_EXCEPTION_DECLARATION.name(), NORMAL_PRIORITY).addClass(this)
                                    .addMethod(this).addSourceLine(this, 0);
                            for (String ex : declaredCheckedExceptions) {
                                bi.addString(ex.replaceAll("/", "."));
                            }
                            bugReporter.reportBug(bi);
                        }
                    } catch (StopOpcodeParsingException e) {
                        // no exceptions left
                    }
                }
            }

            String[] exNames = et.getExceptionNames();
            for (int i = 0; i < (exNames.length - 1); i++) {
                try {
                    JavaClass exCls1 = Repository.lookupClass(exNames[i]);
                    for (int j = i + 1; j < exNames.length; j++) {
                        JavaClass exCls2 = Repository.lookupClass(exNames[j]);
                        JavaClass childEx;
                        JavaClass parentEx;
                        if (exCls1.instanceOf(exCls2)) {
                            childEx = exCls1;
                            parentEx = exCls2;
                        } else if (exCls2.instanceOf(exCls1)) {
                            childEx = exCls2;
                            parentEx = exCls1;
                        } else {
                            continue;
                        }

                        if (!parentEx.equals(exceptionClass)) {
                            bugReporter.reportBug(new BugInstance(this, BugType.BED_HIERARCHICAL_EXCEPTION_DECLARATION.name(), NORMAL_PRIORITY).addClass(this)
                                    .addMethod(this).addString(childEx.getClassName() + " derives from " + parentEx.getClassName()));
                            return;
                        }

                    }
                } catch (ClassNotFoundException cnfe) {
                    bugReporter.reportMissingClass(cnfe);
                }
            }
        }
    }

    /**
     * checks to see if this method is a constructor of an instance based inner class, the handling of the Exception table for this method is odd, -- doesn't
     * seem correct, in some cases. So just ignore these cases
     *
     * @param m
     *            the method to check
     * @param cls
     *            the cls that owns the method
     * @return whether this method is a ctor of an instance based anonymous inner class
     */
    private static boolean isAnonymousInnerCtor(Method m, JavaClass cls) {
        return Values.CONSTRUCTOR.equals(m.getName()) && (cls.getClassName().lastIndexOf(Values.INNER_CLASS_SEPARATOR) >= 0);
    }

    /**
     * implements the visitor to look for method calls that could throw the exceptions that are listed in the declaration.
     */
    @Override
    public void sawOpcode(int seen) {
        try {

            stack.precomputation(this);

            if (OpcodeUtils.isStandardInvoke(seen)) {
                String clsName = getClassConstantOperand();
                if (!safeClasses.contains(clsName)) {
                    try {
                        JavaClass cls = Repository.lookupClass(clsName);
                        Method[] methods = cls.getMethods();
                        String methodName = getNameConstantOperand();
                        String signature = getSigConstantOperand();
                        boolean found = false;
                        for (Method m : methods) {
                            if (m.getName().equals(methodName) && m.getSignature().equals(signature)) {

                                if (isAnonymousInnerCtor(m, cls)) {
                                    // The java compiler doesn't properly attached an Exception Table to anonymous constructors, so just clear if so
                                    break;
                                }

                                ExceptionTable et = m.getExceptionTable();
                                if (et != null) {
                                    String[] thrownExceptions = et.getExceptionNames();
                                    for (String thrownException : thrownExceptions) {
                                        removeThrownExceptionHierarchy(thrownException);
                                    }
                                }
                                found = true;
                                break;
                            }
                        }

                        if (!found) {
                            clearExceptions();
                        }
                    } catch (ClassNotFoundException cnfe) {
                        bugReporter.reportMissingClass(cnfe);
                        clearExceptions();
                    }
                } else if ("wait".equals(getNameConstantOperand())) {
                    removeException("java.lang.InterruptedException");
                }
            } else if (seen == ATHROW) {
                if (stack.getStackDepth() > 0) {
                    OpcodeStack.Item item = stack.getStackItem(0);
                    String exSig = item.getSignature();
                    String thrownException = SignatureUtils.stripSignature(exSig);
                    removeThrownExceptionHierarchy(thrownException);
                } else {
                    clearExceptions();
                }
            }
        } finally {
            stack.sawOpcode(this, seen);
        }
    }

    /**
     * removes this thrown exception the list of declared thrown exceptions, including all exceptions in this exception's hierarchy. If an exception class is
     * found that can't be loaded, then just clear the list of declared checked exceptions and get out.
     *
     * @param thrownException
     *            the exception and it's hierarchy to remove
     */
    private void removeThrownExceptionHierarchy(String thrownException) {
        try {
            if (Values.DOTTED_JAVA_LANG_EXCEPTION.equals(thrownException) || Values.DOTTED_JAVA_LANG_THROWABLE.equals(thrownException)) {
                // Exception/Throwable can be thrown even tho the method isn't declared to throw Exception/Throwable in the case of templated Exceptions
                clearExceptions();
            } else {
                removeException(thrownException);
                JavaClass exCls = Repository.lookupClass(thrownException);
                String clsName;

                do {
                    exCls = exCls.getSuperClass();
                    if (exCls == null) {
                        break;
                    }
                    clsName = exCls.getClassName();
                    removeException(clsName);
                } while (!declaredCheckedExceptions.isEmpty() && !Values.DOTTED_JAVA_LANG_EXCEPTION.equals(clsName)
                        && !Values.DOTTED_JAVA_LANG_ERROR.equals(clsName));
            }

        } catch (ClassNotFoundException cnfe) {
            bugReporter.reportMissingClass(cnfe);
            clearExceptions();
        }
    }

    /**
     * removes the declared checked exception, and if that was the last declared exception, stops opcode parsing by throwing exception
     *
     * @param clsName
     *            the name of the exception to remove
     */
    private void removeException(String clsName) {
        declaredCheckedExceptions.remove(clsName);
        if (declaredCheckedExceptions.isEmpty()) {
            throw new StopOpcodeParsingException();
        }
    }

    /**
     * clears all declared checked exceptions and throws an exception to stop opcode parsing
     */
    private void clearExceptions() {
        declaredCheckedExceptions.clear();
        throw new StopOpcodeParsingException();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy