<commit_before>/*
 * fb-contrib - Auxiliary detectors for Java programs
 * Copyright (C) 2005-2016 <NAME>
 *
 * 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.SignatureUtils;
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 JavaClass runtimeExceptionClass;
    private static JavaClass exceptionClass;
    private static final Set<String> safeClasses = UnmodifiableSet.create(
            //@formatter:off
            "java/lang/Object",
            "java/lang/String",
            "java/lang/Integer",
            "java/lang/Long",
            "java/lang/Float",
            "java/lang/Double",
            "java/lang/Short",
            "java/lang/Boolean"
            //@formatter:on
    );

    static {

        try {
            runtimeExceptionClass = Repository.lookupClass("java/lang/RuntimeException");
            exceptionClass = Repository.lookupClass("java/lang/Exception");

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

    private final BugReporter bugReporter;
    private OpcodeStack stack;
    private Set<String> declaredCheckedExceptions;
    private boolean classIsFinal;
    private boolean classIsAnonymous;

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

    /**
     * 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<String>(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()) {
                    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);
                    }
                }
            }

            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 constructor of an instance based inner class, as jdk1.5 compiler has a bug where it attaches bogus exception declarations
     * to this constructors in some 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) {
        if (!Values.CONSTRUCTOR.equals(m.getName())) {
            return false;
        }

        String clsName = cls.getClassName();
        int dollarPos = clsName.lastIndexOf('$');
        if (dollarPos < 0) {
            return false;
        }

        String signature = m.getSignature();
        return ("(L" + clsName.substring(0, dollarPos).replace('.', '/') + ";)V").equals(signature);
    }

    /**
     * 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) {
        if (declaredCheckedExceptions.isEmpty()) {
            return;
        }

        try {

            stack.precomputation(this);

            if ((seen == INVOKEVIRTUAL) || (seen == INVOKEINTERFACE) || (seen == INVOKESPECIAL) || (seen == INVOKESTATIC)) {
                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;<commit_msg>add INVOKEDYNAMIC to BED<commit_after>/*
 * fb-contrib - Auxiliary detectors for Java programs
 * Copyright (C) 2005-2016 <NAME>
 *
 * 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.SignatureUtils;
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 JavaClass runtimeExceptionClass;
    private static JavaClass exceptionClass;
    private static final Set<String> safeClasses = UnmodifiableSet.create(
            //@formatter:off
            "java/lang/Object",
            "java/lang/String",
            "java/lang/Integer",
            "java/lang/Long",
            "java/lang/Float",
            "java/lang/Double",
            "java/lang/Short",
            "java/lang/Boolean"
            //@formatter:on
    );

    static {

        try {
            runtimeExceptionClass = Repository.lookupClass("java/lang/RuntimeException");
            exceptionClass = Repository.lookupClass("java/lang/Exception");

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

    private final BugReporter bugReporter;
    private OpcodeStack stack;
    private Set<String> declaredCheckedExceptions;
    private boolean classIsFinal;
    private boolean classIsAnonymous;

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

    /**
     * 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<String>(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()) {
                    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);
                    }
                }
            }

            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 constructor of an instance based inner class, as jdk1.5 compiler has a bug where it attaches bogus exception declarations
     * to this constructors in some 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) {
        if (!Values.CONSTRUCTOR.equals(m.getName())) {
            return false;
        }

        String clsName = cls.getClassName();
        int dollarPos = clsName.lastIndexOf('$');
        if (dollarPos < 0) {
            return false;
        }

        String signature = m.getSignature();
        return ("(L" + clsName.substring(0, dollarPos).replace('.', '/') + ";)V").equals(signature);
    }

    /**
     * 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) {
        if (declaredCheckedExceptions.isEmpty()) {
            return;
        }

        try {

            stack.precomputation(this);

            if ((seen == INVOKEVIRTUAL) || (seen == INVOKEINTERFACE) || (seen == INVOKESPECIAL) || (seen == INVOKESTATIC) || (seen == INVOKEDYNAMIC)) {
                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;
{'fraction_non_alphanumeric': 0.08708383038715975, 'fraction_numerical': 0.004663268625962477, 'mean_word_length': 1.8682737169517885, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3403225', 'n_tokens_mistral': 4819, 'n_tokens_neox': 4587, 'n_words': 1260}