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

edu.umd.cs.findbugs.detect.BuildStringPassthruGraph Maven / Gradle / Ivy

The newest version!
/*
 * FindBugs - Find Bugs in Java programs
 * Copyright (C) 2003-2008 University of Maryland
 *
 * 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 edu.umd.cs.findbugs.detect;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.Set;

import org.apache.bcel.Const;
import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.Type;

import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.NonReportingDetector;
import edu.umd.cs.findbugs.OpcodeStack.Item;
import edu.umd.cs.findbugs.bcel.OpcodeStackDetector;
import edu.umd.cs.findbugs.classfile.Global;
import edu.umd.cs.findbugs.classfile.MethodDescriptor;

/**
 * Builds the database of string parameters passed from method to method unchanged.
 * @author Tagir Valeev
 */
public class BuildStringPassthruGraph extends OpcodeStackDetector implements NonReportingDetector {
    private static final int PRIME = 31;

    public static class MethodParameter {
        final MethodDescriptor md;

        final int parameterNumber;

        public MethodParameter(MethodDescriptor md, int parameterNumber) {
            super();
            this.md = md;
            this.parameterNumber = parameterNumber;
        }

        public MethodDescriptor getMethodDescriptor() {
            return md;
        }

        public int getParameterNumber() {
            return parameterNumber;
        }

        @Override
        public String toString() {
            return this.md + "[" + this.parameterNumber + "]";
        }

        @Override
        public int hashCode() {
            int result = 1;
            result = PRIME * result + ((md == null) ? 0 : md.hashCode());
            result = PRIME * result + parameterNumber;
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            MethodParameter other = (MethodParameter) obj;
            return Objects.equals(md, other.md)
                    && parameterNumber == other.parameterNumber;
        }
    }

    public static class StringPassthruDatabase {
        private static final List FILENAME_STRING_METHODS = Arrays.asList(
                new MethodDescriptor("java/io/File", Const.CONSTRUCTOR_NAME, "(Ljava/lang/String;)V"),
                new MethodDescriptor("java/io/File", Const.CONSTRUCTOR_NAME, "(Ljava/lang/String;Ljava/lang/String;)V"),
                new MethodDescriptor("java/io/RandomAccessFile", Const.CONSTRUCTOR_NAME, "(Ljava/lang/String;Ljava/lang/String;)V"),
                new MethodDescriptor("java/nio/file/Paths", "get", "(Ljava/lang/String;[Ljava/lang/String;)Ljava/nio/file/Path;", true),
                new MethodDescriptor("java/io/FileReader", Const.CONSTRUCTOR_NAME, "(Ljava/lang/String;)V"),
                new MethodDescriptor("java/io/FileWriter", Const.CONSTRUCTOR_NAME, "(Ljava/lang/String;)V"),
                new MethodDescriptor("java/io/FileWriter", Const.CONSTRUCTOR_NAME, "(Ljava/lang/String;Z)V"),
                new MethodDescriptor("java/io/FileInputStream", Const.CONSTRUCTOR_NAME, "(Ljava/lang/String;)V"),
                new MethodDescriptor("java/io/FileOutputStream", Const.CONSTRUCTOR_NAME, "(Ljava/lang/String;)V"),
                new MethodDescriptor("java/io/FileOutputStream", Const.CONSTRUCTOR_NAME, "(Ljava/lang/String;Z)V"),
                new MethodDescriptor("java/util/Formatter", Const.CONSTRUCTOR_NAME, "(Ljava/lang/String;)V"),
                new MethodDescriptor("java/util/Formatter", Const.CONSTRUCTOR_NAME, "(Ljava/lang/String;Ljava/lang/String;)V"),
                new MethodDescriptor("java/util/Formatter", Const.CONSTRUCTOR_NAME, "(Ljava/lang/String;Ljava/lang/String;Ljava/util/Locale;)V"),
                new MethodDescriptor("java/util/jar/JarFile", Const.CONSTRUCTOR_NAME, "(Ljava/lang/String;)V"),
                new MethodDescriptor("java/util/jar/JarFile", Const.CONSTRUCTOR_NAME, "(Ljava/lang/String;Z)V"),
                new MethodDescriptor("java/util/zip/ZipFile", Const.CONSTRUCTOR_NAME, "(Ljava/lang/String;)V"),
                new MethodDescriptor("java/util/zip/ZipFile", Const.CONSTRUCTOR_NAME, "(Ljava/lang/String;Ljava/nio/charset/Charset;)V"),
                new MethodDescriptor("java/io/PrintStream", Const.CONSTRUCTOR_NAME, "(Ljava/lang/String;)V"),
                new MethodDescriptor("java/io/PrintStream", Const.CONSTRUCTOR_NAME, "(Ljava/lang/String;Ljava/lang/String;)V"),
                new MethodDescriptor("java/io/PrintWriter", Const.CONSTRUCTOR_NAME, "(Ljava/lang/String;)V"),
                new MethodDescriptor("java/io/PrintWriter", Const.CONSTRUCTOR_NAME, "(Ljava/lang/String;Ljava/lang/String;)V"));

        private final Map> graph = new HashMap<>();

        /**
         * Adds edge to the string passthru graph
         * @param in callee
         * @param out caller
         */
        void addEdge(MethodParameter in, MethodParameter out) {
            Set outs = graph.computeIfAbsent(in, k -> new HashSet<>());
            outs.add(out);
        }

        Set findLinked(Set inputs) {
            Set result = new HashSet<>(inputs);
            Queue toCheck = new ArrayDeque<>(inputs);
            while (!toCheck.isEmpty()) {
                MethodParameter in = toCheck.poll();
                Set outs = graph.get(in);
                if (outs != null) {
                    for (MethodParameter out : outs) {
                        if (!result.contains(out)) {
                            result.add(out);
                            toCheck.add(out);
                        }
                    }
                }
            }
            return result;
        }

        /**
         * Returns methods which call directly or indirectly methods from inputs
         * passing the parameter unchanged
         *
         * @param inputs
         *            input methods with parameter
         * @return Map where keys are methods and values are parameter indexes which can be passed to requested methods unchanged
         */
        public Map findLinkedMethods(Set inputs) {
            Map result = new HashMap<>();
            for (MethodParameter found : findLinked(inputs)) {
                int[] params = result.get(found.getMethodDescriptor());
                if (params == null) {
                    params = new int[] { found.getParameterNumber() };
                    result.put(found.getMethodDescriptor(), params);
                } else {
                    int[] newParams = new int[params.length + 1];
                    System.arraycopy(params, 0, newParams, 0, params.length);
                    newParams[params.length] = found.getParameterNumber();
                    result.put(found.getMethodDescriptor(), newParams);
                }
            }
            return result;
        }

        /**
         * Returns methods which parameter is the file name
         * @return Map where keys are methods and values are parameter indexes which are used as file names
         */
        public Map getFileNameStringMethods() {
            Set fileNameStringMethods = new HashSet<>();
            for (MethodDescriptor md : FILENAME_STRING_METHODS) {
                fileNameStringMethods.add(new MethodParameter(md, 0));
            }
            return findLinkedMethods(fileNameStringMethods);
        }
    }

    private final StringPassthruDatabase cache = new StringPassthruDatabase();

    private int nArgs;

    private int[] argNums;

    private List[] passedParameters;

    public BuildStringPassthruGraph(BugReporter bugReporter) {
        Global.getAnalysisCache().eagerlyPutDatabase(StringPassthruDatabase.class, cache);
    }

    @SuppressWarnings("unchecked")
    @Override
    public void visitMethod(Method obj) {
        argNums = null;
        Type[] argumentTypes = obj.getArgumentTypes();
        if (argumentTypes.length == 0) {
            return;
        }
        int lvNum = obj.isStatic() ? 0 : 1;
        nArgs = argumentTypes.length;
        int argCount = lvNum;
        for (Type type : argumentTypes) {
            argCount += type.getSize();
        }
        for (int i = 0; i < nArgs; i++) {
            if (argumentTypes[i].getSignature().equals("Ljava/lang/String;")) {
                if (argNums == null) {
                    argNums = new int[argCount];
                    Arrays.fill(argNums, -1);
                }
                argNums[lvNum] = i;
            }
            lvNum += argumentTypes[i].getSize();
        }
        if (argNums != null) {
            passedParameters = new List[nArgs];
        }
        super.visitMethod(obj);
    }

    @Override
    public boolean shouldVisitCode(Code obj) {
        return argNums != null;
    }

    @Override
    public void visitAfter(Code obj) {
        super.visitAfter(obj);
        for (int i = 0; i < nArgs; i++) {
            List list = passedParameters[i];
            if (list != null) {
                MethodParameter cur = new MethodParameter(getMethodDescriptor(), i);
                for (MethodParameter mp : list) {
                    cache.addEdge(mp, cur);
                }
            }
        }
    }

    @Override
    public void sawOpcode(int seen) {
        if (isRegisterStore()) {
            int param = getRegisterOperand();
            if (param < argNums.length) {
                int argNum = argNums[param];
                argNums[param] = -1;
                if (argNum >= 0) {
                    passedParameters[argNum] = null;
                }
            }
        }
        switch (seen) {
        case Const.INVOKESPECIAL:
        case Const.INVOKESTATIC:
        case Const.INVOKEINTERFACE:
        case Const.INVOKEVIRTUAL:
            MethodDescriptor md = getMethodDescriptorOperand();
            int callArgs = getNumberArguments(md.getSignature());
            for (int i = 0; i < callArgs; i++) {
                Item item = getStack().getStackItem(callArgs - 1 - i);
                int param = item.getRegisterNumber();
                if (param >= 0 && param < argNums.length && argNums[param] != -1) {
                    List list = passedParameters[argNums[param]];
                    if (list == null) {
                        passedParameters[argNums[param]] = list = new ArrayList<>();
                    }
                    list.add(new MethodParameter(md, i));
                }
            }
            break;
        default:
            break;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy