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

src.main.java.com.mebigfatguy.fbcontrib.detect.ReflectionOnObjectMethods Maven / Gradle / Ivy

/*
 * fb-contrib - Auxiliary detectors for Java programs
 * Copyright (C) 2005-2019 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.HashMap;
import java.util.Map;
import java.util.Set;

import javax.annotation.Nullable;
import org.apache.bcel.Const;

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.RegisterUtils;
import com.mebigfatguy.fbcontrib.utils.SignatureBuilder;
import com.mebigfatguy.fbcontrib.utils.SignatureUtils;
import com.mebigfatguy.fbcontrib.utils.TernaryPatcher;
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.OpcodeStack.CustomUserValue;
import edu.umd.cs.findbugs.ba.ClassContext;

/**
 * looks for method calls through reflection on methods found in
 * java.lang.Object. As these methods are always available, there's no reason to
 * do this.
 */
@CustomUserValue
public class ReflectionOnObjectMethods extends BytecodeScanningDetector {

    public static final String SIG_STRING_AND_CLASS_ARRAY_TO_METHOD = new SignatureBuilder()
            .withParamTypes(Values.SLASHED_JAVA_LANG_STRING,
                    SignatureUtils.toArraySignature(Values.SLASHED_JAVA_LANG_CLASS))
            .withReturnType(Method.class).toString();

    private static final Set objectSigs = UnmodifiableSet.create(
            // "clone()", // clone is declared protected
            "equals(Ljava/lang/Object;)", "finalize()", "getClass()", "hashCode()", "notify()", "notifyAll()",
            "toString()", "wait", "wait(J)", "wait(JI)");

    private final BugReporter bugReporter;
    private OpcodeStack stack;
    private Map localClassTypes;
    private Map fieldClassTypes;
    /**
     * This object is not thread-safe, but can be reused, provided that all
     * necessary fields are overwritten.
     */
    private final SignatureBuilder sigWithoutReturn = new SignatureBuilder().withoutReturnType();

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

    /**
     * implements the visitor to create the stack and local and field maps for Class
     * arrays to be used for getting the reflection method
     *
     * @param classContext the context object of the currently parse class
     */
    @Override
    public void visitClassContext(ClassContext classContext) {
        try {
            stack = new OpcodeStack();
            localClassTypes = new HashMap<>();
            fieldClassTypes = new HashMap<>();
            JavaClass cls = classContext.getJavaClass();
            Method staticInit = findStaticInitializer(cls);
            if (staticInit != null) {
                setupVisitorForClass(cls);
                doVisitMethod(staticInit);
            }

            super.visitClassContext(classContext);
        } finally {
            stack = null;
            localClassTypes = null;
            fieldClassTypes = null;
        }
    }

    /**
     * implements the visitor to reset the opcode stack and clear the local variable
     * map@
     *
     * @param obj the context object of the currently parsed code block
     */
    @Override
    public void visitCode(Code obj) {
        stack.resetForMethodEntry(this);
        localClassTypes.clear();
        super.visitCode(obj);
    }

    /**
     * implements the visitor to look for calls that invoke a method through
     * reflection where the method is defined in java.lang.Object
     *
     * @param seen the currently parsed opcode
     */
    @Override
    public void sawOpcode(int seen) {
        Integer arraySize = null;
        String[] loadedTypes = null;

        try {
            stack.precomputation(this);

            switch (seen) {
            case Const.ANEWARRAY: {
                if (Values.SLASHED_JAVA_LANG_CLASS.equals(getClassConstantOperand()) && (stack.getStackDepth() >= 1)) {
                    OpcodeStack.Item item = stack.getStackItem(0);
                    arraySize = (Integer) item.getConstant();
                }
            }
                break;

            case Const.AASTORE: {
                if (stack.getStackDepth() >= 3) {
                    OpcodeStack.Item arrayItem = stack.getStackItem(2);
                    String[] arrayTypes = (String[]) arrayItem.getUserValue();
                    if (arrayTypes != null) {
                        OpcodeStack.Item valueItem = stack.getStackItem(0);
                        String type = (String) valueItem.getConstant();
                        if (type != null) {
                            OpcodeStack.Item indexItem = stack.getStackItem(1);
                            Integer index = (Integer) indexItem.getConstant();
                            if (index != null) {
                                arrayTypes[index.intValue()] = type;
                            }
                        }
                    }
                }
            }
                break;

            case Const.PUTFIELD:
            case Const.PUTSTATIC: {
                String name = getNameConstantOperand();
                if (stack.getStackDepth() >= 1) {
                    OpcodeStack.Item item = stack.getStackItem(0);
                    String[] arrayTypes = (String[]) item.getUserValue();
                    if (arrayTypes != null) {
                        fieldClassTypes.put(name, arrayTypes);
                        return;
                    }
                }
                fieldClassTypes.remove(name);
            }
                break;

            case Const.GETFIELD:
            case Const.GETSTATIC: {
                String name = getNameConstantOperand();
                loadedTypes = fieldClassTypes.get(name);
            }
                break;

            case Const.ASTORE_0:
            case Const.ASTORE_1:
            case Const.ASTORE_2:
            case Const.ASTORE_3:
            case Const.ASTORE: {
                Integer reg = Integer.valueOf(RegisterUtils.getAStoreReg(this, seen));
                if (stack.getStackDepth() >= 1) {
                    OpcodeStack.Item item = stack.getStackItem(0);
                    String[] arrayTypes = (String[]) item.getUserValue();
                    if (arrayTypes != null) {
                        localClassTypes.put(reg, arrayTypes);
                        return;
                    }
                }
                localClassTypes.remove(reg);
            }
                break;

            case Const.ALOAD_0:
            case Const.ALOAD_1:
            case Const.ALOAD_2:
            case Const.ALOAD_3:
            case Const.ALOAD: {
                int reg = RegisterUtils.getAStoreReg(this, seen);
                loadedTypes = localClassTypes.get(Integer.valueOf(reg));
            }
                break;

            case Const.INVOKEVIRTUAL: {
                String cls = getClassConstantOperand();
                if (Values.SLASHED_JAVA_LANG_CLASS.equals(cls)) {
                    String method = getNameConstantOperand();
                    if ("getMethod".equals(method)) {
                        String sig = getSigConstantOperand();
                        if (SIG_STRING_AND_CLASS_ARRAY_TO_METHOD.equals(sig) && (stack.getStackDepth() >= 2)) {
                            OpcodeStack.Item clsArgs = stack.getStackItem(0);
                            String[] arrayTypes = (String[]) clsArgs.getUserValue();
                            if ((arrayTypes != null) || clsArgs.isNull()) {
                                OpcodeStack.Item methodItem = stack.getStackItem(1);
                                String methodName = (String) methodItem.getConstant();
                                if (methodName != null) {
                                    String reflectionSig = sigWithoutReturn.withMethodName(methodName)
                                            .withParamTypes(arrayTypes).build();
                                    if (objectSigs.contains(reflectionSig)) {
                                        loadedTypes = (arrayTypes == null) ? new String[0] : arrayTypes;
                                    }
                                }
                            }
                        }
                    }
                } else if ("java/lang/reflect/Method".equals(cls)) {
                    String method = getNameConstantOperand();
                    if ("invoke".equals(method) && (stack.getStackDepth() >= 3)) {
                        OpcodeStack.Item methodItem = stack.getStackItem(2);
                        String[] arrayTypes = (String[]) methodItem.getUserValue();
                        if (arrayTypes != null) {
                            bugReporter
                                    .reportBug(new BugInstance(this, BugType.ROOM_REFLECTION_ON_OBJECT_METHODS.name(),
                                            NORMAL_PRIORITY).addClass(this).addMethod(this).addSourceLine(this));
                        }
                    }
                }
            }
                break;

            default:
                break;
            }
        } finally {
            TernaryPatcher.pre(stack, seen);
            stack.sawOpcode(this, seen);
            TernaryPatcher.post(stack, seen);

            if (stack.getStackDepth() >= 1) {
                if (arraySize != null) {
                    OpcodeStack.Item item = stack.getStackItem(0);
                    item.setUserValue(new String[arraySize.intValue()]);
                } else if (loadedTypes != null) {
                    OpcodeStack.Item item = stack.getStackItem(0);
                    item.setUserValue(loadedTypes);
                }
            }
        }
    }

    /**
     * finds the method that is the static initializer for the class
     *
     * @param cls the class to find the initializer for
     *
     * @return the Method of the static initializer or null if this class has none
     */
    @Nullable
    private static Method findStaticInitializer(JavaClass cls) {
        Method[] methods = cls.getMethods();
        for (Method m : methods) {
            if (Values.STATIC_INITIALIZER.equals(m.getName())) {
                return m;
            }
        }

        return null;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy