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

src.main.java.com.mebigfatguy.fbcontrib.detect.SuspiciousJDKVersionUse 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.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import javax.annotation.Nullable;

import org.apache.bcel.Constants;
import org.apache.bcel.Repository;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;

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

/**
 * looks for calls to classes and methods that do not exist in the JDK for which this class is compiled. This can happen if you specify the -source and -target
 * options of the javac compiler, and specify a target that is less than the jdk version of the javac compiler.
 */
public class SuspiciousJDKVersionUse extends BytecodeScanningDetector {
    private static final Map VER_REG_EX = new HashMap<>();

    static {
        VER_REG_EX.put(Integer.valueOf(Constants.MAJOR_1_1), "(jdk|j2?re)1.1");
        VER_REG_EX.put(Integer.valueOf(Constants.MAJOR_1_2), "(jdk|j2?re)1.2");
        VER_REG_EX.put(Integer.valueOf(Constants.MAJOR_1_3), "(jdk|j2?re)1.3");
        VER_REG_EX.put(Integer.valueOf(Constants.MAJOR_1_4), "(jdk|j2?re)1.4");
        VER_REG_EX.put(Values.JAVA_5, "((jdk|j2?re)1.5)|(java-5)");
        VER_REG_EX.put(Integer.valueOf(Constants.MAJOR_1_6), "((jdk|j2?re)1.6)|(java-6)");
        VER_REG_EX.put(Integer.valueOf(Constants.MAJOR_1_7), "((jdk|j2?re)1.7)|(java-7)");
        VER_REG_EX.put(Integer.valueOf(Constants.MAJOR_1_8), "((jdk|j2?re)1.8)|(java-8)");
    }

    private static final Map HUMAN_VERSIONS = new HashMap<>();

    static {
        HUMAN_VERSIONS.put(Integer.valueOf(Constants.MAJOR_1_1), Values.ONE);
        HUMAN_VERSIONS.put(Integer.valueOf(Constants.MAJOR_1_2), Values.TWO);
        HUMAN_VERSIONS.put(Integer.valueOf(Constants.MAJOR_1_3), Values.THREE);
        HUMAN_VERSIONS.put(Integer.valueOf(Constants.MAJOR_1_4), Values.FOUR);
        HUMAN_VERSIONS.put(Values.JAVA_5, Values.FIVE);
        HUMAN_VERSIONS.put(Integer.valueOf(Constants.MAJOR_1_6), Values.SIX);
        HUMAN_VERSIONS.put(Integer.valueOf(Constants.MAJOR_1_7), Values.SEVEN);
        HUMAN_VERSIONS.put(Integer.valueOf(Constants.MAJOR_1_8), Values.EIGHT);
    }

    private static Set knownJDKJavaxPackageRoots = UnmodifiableSet.create(
    // @formatter:off
        "javax/accessibility/",
        "javax/activation/",
        "javax/activity/",
        "javax/annotation/",
        "javax/imageio/",
        "javax/jws/",
        "javax/lang/",
        "javax/management/",
        "javax/naming/",
        "javax/net/",
        "javax/print/",
        "javax/rmi/",
        "javax/script/",
        "javax/security/",
        "javax/smartcardio/",
        "javax/sound/",
        "javax/sql/",
        "javax/swing/",
        "javax/tools/",
        "javax/transaction/"
        // "javax/xml/" // javax.xml might be internal or external, hard to tell, so just don't report it
    // @formatter:on
    );

    private static final Pattern jarPattern;
    private static final String SJVU_JDKHOME = "fb-contrib.sjvu.jdkhome";

    static {
        String os = System.getProperty("os.name");
        if (os.toLowerCase(Locale.getDefault()).startsWith("windows")) {
            jarPattern = Pattern.compile("jar:file:/*([^!]*)");
        } else {
            jarPattern = Pattern.compile("jar:file:([^!]*)");
        }

    }

    private final Map versionPaths;
    private final Map>> validMethodsByVersion;
    private final Map superNames;
    private final Map jdkZips;
    private File jdksRoot = null;
    private Integer clsMajorVersion;
    private ZipFile jdkZip;
    private final BugReporter bugReporter;

    public SuspiciousJDKVersionUse(BugReporter bugReporter) {
        this.bugReporter = bugReporter;
        versionPaths = new HashMap<>();
        jdkZips = new HashMap<>();
        validMethodsByVersion = new HashMap<>();
        superNames = new HashMap<>();
    }

    @Override
    public void visitClassContext(ClassContext classContext) {
        try {
            clsMajorVersion = Integer.valueOf(classContext.getJavaClass().getMajor());
            jdkZip = jdkZips.get(clsMajorVersion);
            if (jdkZip == null) {
                File rtJar = getRTJarFile();
                if (rtJar == null) {
                    rtJar = getRTJarFromProperty(clsMajorVersion);
                }
                if (rtJar != null) {
                    jdkZip = new ZipFile(rtJar);
                    jdkZips.put(clsMajorVersion, jdkZip);
                }
            }

            if (jdkZip == null) {
                return;
            }

            super.visitClassContext(classContext);
        } catch (IOException ioe) {
            // Hmm What to do
        } finally {
            clsMajorVersion = null;
            jdkZip = null;
        }
    }

    @Override
    public void sawOpcode(int seen) {

        String clsName;
        try {
            if ((seen == INVOKEVIRTUAL) // Interfaces are more difficult, ignore
                                        // for now
                    || (seen == INVOKESTATIC) || (seen == INVOKESPECIAL)) {
                clsName = getClassConstantOperand();
                if ((clsName.startsWith("java/")) || (clsName.startsWith("javax/"))) {
                    Method m = findCalledMethod();
                    if (m == null) {
                        return;
                    }

                    Map> validMethods = validMethodsByVersion.get(clsMajorVersion);
                    if (validMethods == null) {
                        validMethods = new HashMap<>();
                        validMethodsByVersion.put(clsMajorVersion, validMethods);
                    }

                    if (!isValid(validMethods, clsName)) {
                        bugReporter.reportBug(new BugInstance(this, BugType.SJVU_SUSPICIOUS_JDK_VERSION_USE.name(), HIGH_PRIORITY).addClass(this)
                                .addMethod(this).addSourceLine(this).addCalledMethod(this));
                    }
                }
            }
        } catch (ClassNotFoundException cnfe) {
            bugReporter.reportMissingClass(cnfe);
        } catch (IOException ioe) {
            // Hmm what do do.
        }
    }

    @Nullable
    private Method findCalledMethod() {
        try {
            JavaClass clss = Repository.lookupClass(getClassConstantOperand());
            Method[] methods = clss.getMethods();
            String calledMethod = getNameConstantOperand();
            String calledSignature = getSigConstantOperand();
            for (Method m : methods) {
                if (m.getName().equals(calledMethod) && m.getSignature().equals(calledSignature)) {
                    return m;
                }
            }

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

    private boolean isValid(Map> validMethods, String clsName) throws IOException, ClassNotFoundException {

        Set methodInfos = validMethods.get(clsName);
        if (methodInfos == null) {

            ZipEntry ze = jdkZip.getEntry(clsName + ".class");
            if (ze == null) {
                if (isJavaXExternal(clsName)) {
                    return true;
                }

                bugReporter.reportBug(new BugInstance(this, BugType.SJVU_SUSPICIOUS_JDK_VERSION_USE.name(), HIGH_PRIORITY).addClass(this).addMethod(this)
                        .addSourceLine(this).addClass(clsName));
            } else if (clsName.startsWith("java/")) {
                JavaClass calledClass = null;
                try (InputStream is = new BufferedInputStream(jdkZip.getInputStream(ze))) {
                    ClassParser parser = new ClassParser(is, clsName);
                    calledClass = parser.parse();
                }

                superNames.put(clsName, calledClass.getSuperclassName().replace('.', '/'));
                Method[] methods = calledClass.getMethods();

                methodInfos = new HashSet<>(methods.length);
                validMethods.put(clsName, methodInfos);

                for (Method m : methods) {
                    methodInfos.add(m.getName() + m.getSignature());
                }
            }
        }

        if (methodInfos == null) {
            return true;
        }
        String wantedMethod = getNameConstantOperand() + getSigConstantOperand();
        if (methodInfos.contains(wantedMethod)) {
            return true;
        } else if (Values.SLASHED_JAVA_LANG_OBJECT.equals(clsName)) {
            return false;
        } else {
            return isValid(validMethods, superNames.get(clsName));
        }
    }

    /**
     * checks to see if this class is a javax.xxxx.Foo class if so, looks to see if the package is at least in the jdk if a whole new package comes into javax
     * in rt.jar, this will be missed. Need a better solution here.
     *
     * @param className
     *            the slashed class in question
     * @return whether it is in the default jdk rt.jar
     */
    private boolean isJavaXExternal(String className) {
        if (!className.startsWith("javax/")) {
            return false;
        }

        if (className.startsWith("javax/xml/")) {
            return true;
        }

        int lastSlashPos = className.lastIndexOf('/');
        String packageName = className.substring(0, lastSlashPos);
        ZipEntry ze = jdkZip.getEntry(packageName);
        if (ze != null) {
            return false;
        }

        for (String root : knownJDKJavaxPackageRoots) {
            if (className.startsWith(root)) {
                return false;
            }
        }

        return true;
    }

    @Nullable
    private File getRTJarFile() {
        String versionStr = VER_REG_EX.get(clsMajorVersion);
        if (versionStr == null) {
            return null;
        }

        File rtPath = versionPaths.get(versionStr);
        if (rtPath != null) {
            return rtPath;
        }
        if (versionPaths.containsKey(versionStr)) {
            return null;
        }

        if (jdksRoot == null) {
            URL jdkUrl = SuspiciousJDKVersionUse.class.getResource("/java/lang/Object.class");
            if (jdkUrl != null) {
                Matcher m = jarPattern.matcher(jdkUrl.toExternalForm());

                if (m.find()) {
                    String path = m.group(1);
                    jdksRoot = new File(path);
                    Pattern verPat = Pattern.compile(versionStr);
                    m = verPat.matcher(jdksRoot.getName());
                    while ((jdksRoot.getParentFile() != null) && !m.find()) {
                        jdksRoot = jdksRoot.getParentFile();
                        m = verPat.matcher(jdksRoot.getName());
                    }

                    if (jdksRoot.getParentFile() == null) {
                        versionPaths.put(versionStr, null);
                        return null;
                    }

                    try {
                        String encoding = System.getProperty("file.encoding");
                        jdksRoot = new File(URLDecoder.decode(jdksRoot.getParentFile().getPath(), encoding));
                    } catch (UnsupportedEncodingException uee) {
                        versionPaths.put(versionStr, null);
                        return null;
                    }
                }
            }
        }

        if (jdksRoot != null) {
            File[] possibleJdks = jdksRoot.listFiles();
            if (possibleJdks != null) {
                for (File possibleJdk : possibleJdks) {
                    Pattern verPat = Pattern.compile(versionStr);
                    Matcher m = verPat.matcher(possibleJdk.getName());
                    if (m.find()) {
                        File wantedRtJar = new File(possibleJdk, "lib/rt.jar");
                        if (!wantedRtJar.exists()) {
                            wantedRtJar = new File(possibleJdk, "jre/lib/rt.jar");
                            if (!wantedRtJar.exists()) {
                                versionPaths.put(versionStr, null);
                                return null;
                            }
                        }
                        versionPaths.put(versionStr, wantedRtJar);
                        return wantedRtJar;
                    }
                }
            }
        }

        versionPaths.put(versionStr, null);

        return null;
    }

    @Nullable
    private static File getRTJarFromProperty(Integer requestedVersion) {
        String jdkHome = System.getProperty(SJVU_JDKHOME + '.' + HUMAN_VERSIONS.get(requestedVersion));
        if (jdkHome == null) {
            return null;
        }

        File rtJar = new File(jdkHome, "lib/rt.jar");
        if (rtJar.exists()) {
            return rtJar;
        }
        rtJar = new File(jdkHome, "jre/lib/rt.jar");
        if (rtJar.exists()) {
            return rtJar;
        }

        return null;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy