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

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

The newest version!
/*
 * 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.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

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

import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.CodeException;
import org.apache.bcel.classfile.LocalVariable;
import org.apache.bcel.classfile.LocalVariableTable;
import org.apache.bcel.classfile.Method;

import com.mebigfatguy.fbcontrib.collect.MethodInfo;
import com.mebigfatguy.fbcontrib.collect.Statistics;
import com.mebigfatguy.fbcontrib.utils.BugType;
import com.mebigfatguy.fbcontrib.utils.FQMethod;
import com.mebigfatguy.fbcontrib.utils.OpcodeUtils;
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.ToString;
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;
import edu.umd.cs.findbugs.ba.XField;

/**
 * looks for variable assignments at a scope larger than its use. In this case,
 * the assignment can be pushed down into the smaller scope to reduce the
 * performance impact of that assignment.
 */
@CustomUserValue
public class BloatedAssignmentScope extends BytecodeScanningDetector {
    private static final Set dangerousAssignmentClassSources = UnmodifiableSet.create(
            // @formatter:off
            "java/io/BufferedInputStream", "java/io/DataInput", "java/io/DataInputStream", "java/io/InputStream",
            "java/io/ObjectInputStream", "java/io/BufferedReader", "java/io/FileReader", "java/io/Reader",
            "javax/nio/channels/Channel", "io/netty/channel/Channel"
    // @formatter:on
    );

    private static final Set dangerousAssignmentMethodSources = UnmodifiableSet.create(
            // @formatter:off
            new FQMethod(Values.SLASHED_JAVA_LANG_SYSTEM, "currentTimeMillis", SignatureBuilder.SIG_VOID_TO_LONG),
            new FQMethod(Values.SLASHED_JAVA_LANG_SYSTEM, "nanoTime", SignatureBuilder.SIG_VOID_TO_LONG),
            new FQMethod("java/util/Calendar", "get", SignatureBuilder.SIG_INT_TO_INT),
            new FQMethod("java/util/GregorianCalendar", "get", SignatureBuilder.SIG_INT_TO_INT),
            new FQMethod("java/util/Iterator", "next", SignatureBuilder.SIG_VOID_TO_OBJECT),
            new FQMethod("java/util/regex/Matcher", "start", SignatureBuilder.SIG_VOID_TO_INT),
            new FQMethod("java/util/concurrent/TimeUnit", "toMillis", SignatureBuilder.SIG_LONG_TO_LONG)
    // @formatter:on
    );

    private static final Set dangerousAssignmentMethodPatterns = UnmodifiableSet.create(
            // @formatter:off
            Pattern.compile(".*serial.*", Pattern.CASE_INSENSITIVE),
            Pattern.compile(".*\\.read[^.]*", Pattern.CASE_INSENSITIVE),
            Pattern.compile(".*\\.create[^.]*", Pattern.CASE_INSENSITIVE)
    // @formatter:on
    );

    private static final Set dangerousStoreClassSigs = UnmodifiableSet.create("Ljava/util/concurrent/Future;");

    BugReporter bugReporter;
    private OpcodeStack stack;
    BitSet ignoreRegs;
    private ScopeBlock rootScopeBlock;
    private BitSet tryBlocks;
    private BitSet catchHandlers;
    private BitSet switchTargets;
    private List monitorSyncPCs;
    private boolean dontReport;
    private boolean sawDup;
    private boolean sawNull;

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

    /**
     * implements the visitor to create and the clear the register to location map
     *
     * @param classContext the context object of the currently parsed class
     */
    @Override
    public void visitClassContext(ClassContext classContext) {
        try {
            ignoreRegs = new BitSet();
            tryBlocks = new BitSet();
            catchHandlers = new BitSet();
            switchTargets = new BitSet();
            monitorSyncPCs = new ArrayList<>(5);
            stack = new OpcodeStack();
            super.visitClassContext(classContext);
        } finally {
            ignoreRegs = null;
            tryBlocks = null;
            catchHandlers = null;
            switchTargets = null;
            monitorSyncPCs = null;
            stack = null;
        }
    }

    /**
     * implements the visitor to reset the register to location map
     *
     * @param obj the context object of the currently parsed code block
     */
    @Override
    public void visitCode(Code obj) {
        try {

            ignoreRegs.clear();
            Method method = getMethod();
            if (!method.isStatic()) {
                ignoreRegs.set(0);
            }

            int[] parmRegs = RegisterUtils.getParameterRegisters(method);
            for (int parm : parmRegs) {
                ignoreRegs.set(parm);
            }

            rootScopeBlock = new ScopeBlock(0, obj.getLength());
            tryBlocks.clear();
            catchHandlers.clear();
            CodeException[] exceptions = obj.getExceptionTable();
            if (exceptions != null) {
                for (CodeException ex : exceptions) {
                    tryBlocks.set(ex.getStartPC());
                    catchHandlers.set(ex.getHandlerPC());
                }
            }

            switchTargets.clear();
            stack.resetForMethodEntry(this);
            dontReport = false;
            sawDup = false;
            sawNull = false;
            super.visitCode(obj);

            if (!dontReport) {
                rootScopeBlock.findBugs(new HashSet());
            }

        } finally {
            rootScopeBlock = null;
        }
    }

    /**
     * implements the visitor to look for variables assigned below the scope in
     * which they are used.
     *
     * @param seen the opcode of the currently parsed instruction
     */
    @Override
    public void sawOpcode(int seen) {
        UserObject uo = null;
        try {
            stack.precomputation(this);

            int pc = getPC();
            if (tryBlocks.get(pc)) {
                ScopeBlock sb = new ScopeBlock(pc, findCatchHandlerFor(pc));
                sb.setTry();
                rootScopeBlock.addChild(sb);
            }

            if (OpcodeUtils.isStore(seen)) {
                sawStore(seen, pc);
            } else if (OpcodeUtils.isLoad(seen)) {
                sawLoad(seen, pc);
            } else if ((seen == Const.INVOKEVIRTUAL) || (seen == Const.INVOKEINTERFACE)) {
                uo = sawInstanceCall(pc);
            } else if ((seen == Const.INVOKESTATIC) || (seen == Const.INVOKESPECIAL)) {
                uo = sawStaticCall();
            } else if (((seen >= Const.IFEQ) && (seen <= Const.GOTO)) || (seen == Const.IFNULL)
                    || (seen == Const.IFNONNULL) || (seen == Const.GOTO_W)) {
                sawBranch(seen, pc);
            } else if (seen == Const.GETFIELD) {
                uo = sawGetField();
            } else if (seen == Const.PUTFIELD) {
                sawPutField(pc);
            } else if (seen == Const.IINC) {
                sawIINC(pc);
            } else if ((seen == Const.TABLESWITCH) || (seen == Const.LOOKUPSWITCH)) {
                sawSwitch(pc);
            } else if (seen == Const.MONITORENTER) {
                sawMonitorEnter(pc);
            } else if (seen == Const.MONITOREXIT) {
                sawMonitorExit(pc);
            }

            sawDup = seen == Const.DUP;
            sawNull = seen == Const.ACONST_NULL;
        } finally {
            TernaryPatcher.pre(stack, seen);
            stack.sawOpcode(this, seen);
            TernaryPatcher.post(stack, seen);
            if ((uo != null) && (stack.getStackDepth() > 0)) {
                OpcodeStack.Item item = stack.getStackItem(0);
                item.setUserValue(uo);
            }
        }
    }

    /**
     * processes a register store by updating the appropriate scope block to mark
     * this register as being stored in the block
     *
     * @param seen the currently parsed opcode
     * @param pc   the current program counter
     */
    private void sawStore(int seen, int pc) {
        int reg = RegisterUtils.getStoreReg(this, seen);

        if (catchHandlers.get(pc)) {
            ignoreRegs.set(reg);
            ScopeBlock catchSB = findScopeBlock(rootScopeBlock, pc + 1);
            if ((catchSB != null) && (catchSB.getStart() < pc)) {
                ScopeBlock sb = new ScopeBlock(pc, catchSB.getFinish());
                catchSB.setFinish(getPC() - 1);
                rootScopeBlock.addChild(sb);
            }
        } else if (!monitorSyncPCs.isEmpty()) {
            ignoreRegs.set(reg);
        } else if (sawNull) {
            ignoreRegs.set(reg);
        } else if (isRiskyStoreClass(reg)) {
            ignoreRegs.set(reg);
        }

        if (!ignoreRegs.get(reg)) {
            ScopeBlock sb = findScopeBlock(rootScopeBlock, pc);
            if (sb != null) {
                UserObject assoc = null;
                if (stack.getStackDepth() > 0) {
                    OpcodeStack.Item srcItm = stack.getStackItem(0);
                    assoc = (UserObject) srcItm.getUserValue();
                    if (assoc == null) {
                        if (srcItm.getRegisterNumber() >= 0) {
                            assoc = new UserObject(srcItm.getRegisterNumber());
                        }
                    }
                }

                if ((assoc != null) && assoc.isRisky) {
                    ignoreRegs.set(reg);
                } else {
                    sb.addStore(reg, pc, assoc);
                    if (sawDup) {
                        sb.addLoad(reg, pc);
                    }
                }
            } else {
                ignoreRegs.set(reg);
            }
        }

        ScopeBlock sb = findScopeBlock(rootScopeBlock, pc);
        if (sb != null) {
            sb.markFieldAssociatedWrites(reg);
        }
    }

    /**
     * processes a register IINC by updating the appropriate scope block to mark
     * this register as being stored in the block
     *
     * @param pc the current program counter
     */
    private void sawIINC(int pc) {
        int reg = getRegisterOperand();
        if (!ignoreRegs.get(reg)) {
            ScopeBlock sb = findScopeBlock(rootScopeBlock, pc);
            if (sb != null) {
                sb.addLoad(reg, pc);
            } else {
                ignoreRegs.set(reg);
            }
        }
        if (catchHandlers.get(pc)) {
            ignoreRegs.set(reg);
        } else if (!monitorSyncPCs.isEmpty()) {
            ignoreRegs.set(reg);
        } else if (sawNull) {
            ignoreRegs.set(reg);
        }

        if (!ignoreRegs.get(reg)) {
            ScopeBlock sb = findScopeBlock(rootScopeBlock, pc);
            if (sb != null) {
                sb.addStore(reg, pc, null);
                if (sawDup) {
                    sb.addLoad(reg, pc);
                }
            } else {
                ignoreRegs.set(reg);
            }
        }
    }

    /**
     * processes a register store by updating the appropriate scope block to mark
     * this register as being read in the block
     *
     * @param seen the currently parsed opcode
     * @param pc   the current program counter
     */
    private void sawLoad(int seen, int pc) {
        int reg = RegisterUtils.getLoadReg(this, seen);
        if (!ignoreRegs.get(reg)) {
            ScopeBlock sb = findScopeBlock(rootScopeBlock, pc);
            if (sb != null) {
                sb.addLoad(reg, pc);
            } else {
                ignoreRegs.set(reg);
            }
        }
    }

    /**
     * creates a scope block to describe this branch location.
     *
     * @param seen the currently parsed opcode
     * @param pc   the current program counter
     */
    private void sawBranch(int seen, int pc) {
        int target = getBranchTarget();
        if (target > pc) {
            if ((seen == Const.GOTO) || (seen == Const.GOTO_W)) {
                int nextPC = getNextPC();
                if (!switchTargets.get(nextPC)) {
                    ScopeBlock sb = findScopeBlockWithTarget(rootScopeBlock, pc, nextPC);
                    if (sb == null) {
                        sb = new ScopeBlock(pc, target);
                        sb.setLoop();
                        sb.setGoto();
                        rootScopeBlock.addChild(sb);
                    } else {
                        sb = new ScopeBlock(nextPC, target);
                        sb.setGoto();
                        rootScopeBlock.addChild(sb);
                    }
                }
            } else {
                ScopeBlock sb = findScopeBlockWithTarget(rootScopeBlock, pc, target);
                if ((sb != null) && !sb.isLoop() && !sb.isCase() && !sb.hasChildren()) {
                    if (sb.isGoto()) {
                        ScopeBlock parent = sb.getParent();
                        sb.pushUpLoadStores();
                        if (parent != null) {
                            parent.removeChild(sb);
                        }
                        sb = new ScopeBlock(pc, target);
                        rootScopeBlock.addChild(sb);
                    } else {
                        sb.pushUpLoadStores();
                        sb.setStart(pc);
                    }
                } else {
                    sb = new ScopeBlock(pc, target);
                    rootScopeBlock.addChild(sb);
                }
            }
        } else {
            ScopeBlock sb = findScopeBlock(rootScopeBlock, pc);
            if (sb != null) {
                ScopeBlock parentSB = sb.getParent();
                while (parentSB != null) {
                    if (parentSB.getStart() >= target) {
                        sb = parentSB;
                        parentSB = parentSB.getParent();
                    } else {
                        break;
                    }
                }

                if (sb.getStart() > target) {
                    ScopeBlock previous = findPreviousSiblingScopeBlock(sb);
                    if ((previous != null) && (previous.getStart() >= target)) {
                        sb = previous;
                    }
                }
                sb.setLoop();
            }
        }
    }

    /**
     * creates a new scope block for each case statement
     *
     * @param pc the current program counter
     */
    private void sawSwitch(int pc) {
        int[] offsets = getSwitchOffsets();
        List targets = new ArrayList<>(offsets.length);
        for (int offset : offsets) {
            targets.add(Integer.valueOf(offset + pc));
        }
        Integer defOffset = Integer.valueOf(getDefaultSwitchOffset() + pc);
        if (!targets.contains(defOffset)) {
            targets.add(defOffset);
        }
        Collections.sort(targets);

        Integer lastTarget = targets.get(0);
        for (int i = 1; i < targets.size(); i++) {
            Integer nextTarget = targets.get(i);
            ScopeBlock sb = new ScopeBlock(lastTarget.intValue(), nextTarget.intValue());
            sb.setCase();
            rootScopeBlock.addChild(sb);
            lastTarget = nextTarget;
        }
        for (Integer target : targets) {
            switchTargets.set(target.intValue());
        }
    }

    /**
     * processes a instance method call to see if that call is modifies state or is
     * otherwise'risky', if so mark the variable(s) associated with the caller as
     * not reportable
     *
     * @param pc the current program counter
     *
     * @return a user object to place on the return value's OpcodeStack item
     */
    @Nullable
    private UserObject sawInstanceCall(int pc) {
        String signature = getSigConstantOperand();
        String name = getNameConstantOperand();

        // this is kind of a wart. there should be a more seamless way to check this
        if ("wasNull".equals(getNameConstantOperand()) && SignatureBuilder.SIG_VOID_TO_BOOLEAN.equals(signature)) {
            dontReport = true;
        }

        if (signature.endsWith(Values.SIG_VOID)) {
            return null;
        }

        MethodInfo mi = Statistics.getStatistics().getMethodStatistics(getClassConstantOperand(), name, signature);

        UserObject uo = new UserObject(getCallingObject(), mi.getModifiesState() || isRiskyMethodCall());

        if (uo.caller != null) {
            ScopeBlock sb = findScopeBlock(rootScopeBlock, pc);
            if (sb != null) {
                sb.removeByAssoc(uo.caller);
            }
        }

        return uo;
    }

    /**
     * processes a static call or initializer by checking to see if the call is
     * risky, and returning a OpcodeStack item user value saying so.
     *
     * @return the user object to place on the OpcodeStack
     */
    @Nullable
    private UserObject sawStaticCall() {

        if (getSigConstantOperand().endsWith(Values.SIG_VOID)) {
            return null;
        }

        return new UserObject(isRiskyMethodCall());
    }

    @Nullable
    private UserObject sawGetField() {
        if (stack.getStackDepth() > 0) {
            OpcodeStack.Item itm = stack.getStackItem(0);
            int reg = itm.getRegisterNumber();

            if (reg >= 0) {
                return new UserObject(reg);
            }
        }

        return null;
    }

    private void sawPutField(int pc) {
        if (stack.getStackDepth() > 1) {
            OpcodeStack.Item itm = stack.getStackItem(1);
            int reg = itm.getRegisterNumber();

            if (reg >= 0) {

                ScopeBlock sb = findScopeBlock(rootScopeBlock, pc);
                if (sb != null) {
                    sb.markFieldAssociatedWrites(reg);
                }
            }
        }
    }

    /**
     * processes a monitor enter call to create a scope block
     *
     * @param pc the current program counter
     */
    private void sawMonitorEnter(int pc) {
        monitorSyncPCs.add(Integer.valueOf(pc));

        ScopeBlock sb = new ScopeBlock(pc, Integer.MAX_VALUE);
        sb.setSync();
        rootScopeBlock.addChild(sb);
    }

    /**
     * processes a monitor exit to set the end of the already created scope block
     *
     * @param pc the current program counter
     */
    private void sawMonitorExit(int pc) {
        if (!monitorSyncPCs.isEmpty()) {
            ScopeBlock sb = findSynchronizedScopeBlock(rootScopeBlock, monitorSyncPCs.get(0).intValue());
            if (sb != null) {
                sb.setFinish(pc);
            }
            monitorSyncPCs.remove(monitorSyncPCs.size() - 1);
        }
    }

    /**
     * returns either a register number of a field reference of the object that a
     * method is being called on, or null, if it can't be determined.
     *
     * @return either an Integer for a register, or a String for the field name, or
     *         null
     */
    @Nullable
    private Comparable getCallingObject() {
        String sig = getSigConstantOperand();
        if (Values.SIG_VOID.equals(SignatureUtils.getReturnSignature(sig))) {
            return null;
        }

        int numParameters = SignatureUtils.getNumParameters(sig);

        if (stack.getStackDepth() <= numParameters) {
            return null;
        }

        OpcodeStack.Item caller = stack.getStackItem(numParameters);
        UserObject uo = (UserObject) caller.getUserValue();
        if ((uo != null) && (uo.caller != null)) {
            return uo.caller;
        }

        int reg = caller.getRegisterNumber();
        if (reg >= 0) {
            return Integer.valueOf(reg);
        }

        /*
         * We ignore the possibility of two fields with the same name in different
         * classes
         */
        XField f = caller.getXField();
        if (f != null) {
            return f.getName();
        }
        return null;
    }

    /**
     * returns the scope block in which this register was assigned, by traversing
     * the scope block tree
     *
     * @param sb the scope block to start searching in
     * @param pc the current program counter
     * @return the scope block or null if not found
     */
    @Nullable
    private ScopeBlock findScopeBlock(ScopeBlock sb, int pc) {

        if ((pc <= sb.getStart()) || (pc >= sb.getFinish())) {
            return null;
        }

        List children = sb.getChildren();
        if (children != null) {
            for (ScopeBlock child : children) {
                ScopeBlock foundSb = findScopeBlock(child, pc);
                if (foundSb != null) {
                    return foundSb;
                }
            }
        }
        return sb;
    }

    /**
     * returns an existing scope block that has the same target as the one looked
     * for
     *
     * @param sb     the scope block to start with
     * @param start  the current pc
     * @param target the target to look for
     *
     * @return the scope block found or null
     */
    private ScopeBlock findScopeBlockWithTarget(ScopeBlock sb, int start, int target) {
        ScopeBlock parentBlock = null;
        int finishLocation = sb.getFinish();
        if ((sb.getStart() < start) && (finishLocation >= start)
                && ((finishLocation <= target) || (sb.isGoto() && !sb.isLoop()))) {
            parentBlock = sb;
        }

        List children = sb.getChildren();
        if (children != null) {
            for (ScopeBlock child : children) {
                ScopeBlock targetBlock = findScopeBlockWithTarget(child, start, target);
                if (targetBlock != null) {
                    return targetBlock;
                }
            }
        }

        return parentBlock;
    }

    /**
     * looks for the ScopeBlock has the same parent as this given one, but precedes
     * it in the list.
     *
     * @param sb the scope block to look for the previous scope block
     * @return the previous sibling scope block, or null if doesn't exist
     */
    @Nullable
    private ScopeBlock findPreviousSiblingScopeBlock(ScopeBlock sb) {
        ScopeBlock parent = sb.getParent();
        if (parent == null) {
            return null;
        }

        List children = parent.getChildren();
        if (children == null) {
            return null;
        }

        ScopeBlock lastSibling = null;
        for (ScopeBlock sibling : children) {
            if (sibling.equals(sb)) {
                return lastSibling;
            }
            lastSibling = sibling;
        }

        return null;
    }

    /**
     * finds the scope block that is the active synchronized block
     *
     * @param sb             the parent scope block to start with
     * @param monitorEnterPC the pc where the current synchronized block starts
     * @return the scope block
     */
    private ScopeBlock findSynchronizedScopeBlock(ScopeBlock sb, int monitorEnterPC) {

        ScopeBlock monitorBlock = sb;

        if (sb.hasChildren()) {
            for (ScopeBlock child : sb.getChildren()) {
                if (child.isSync() && (child.getStart() > monitorBlock.getStart())) {
                    monitorBlock = child;
                    monitorBlock = findSynchronizedScopeBlock(monitorBlock, monitorEnterPC);
                }
            }
        }

        return monitorBlock;
    }

    /**
     * returns the catch handler for a given try block
     *
     * @param pc the current instruction
     * @return the pc of the handler for this pc if it's the start of a try block,
     *         or -1
     *
     */
    private int findCatchHandlerFor(int pc) {
        CodeException[] exceptions = getMethod().getCode().getExceptionTable();
        if (exceptions != null) {
            for (CodeException ex : exceptions) {
                if (ex.getStartPC() == pc) {
                    return ex.getHandlerPC();
                }
            }
        }

        return -1;
    }

    /**
     * holds the description of a scope { } block, be it a for, if, while block
     */
    private class ScopeBlock {
        private ScopeBlock parent;
        private int startLocation;
        private int finishLocation;
        private boolean isLoop;
        private boolean isGoto;
        private boolean isSync;
        private boolean isTry;
        private boolean isCase;
        private Map loads;
        private Map stores;
        private Map assocs;
        private List children;

        /**
         * constructs a new scope block
         *
         * @param start  the beginning of the block
         * @param finish the end of the block
         */
        public ScopeBlock(int start, int finish) {
            parent = null;
            startLocation = start;
            finishLocation = finish;
            isLoop = false;
            isGoto = false;
            isSync = false;
            isTry = false;
            isCase = false;
            loads = null;
            stores = null;
            assocs = null;
            children = null;
        }

        /**
         * returns a string representation of the scope block
         *
         * @return a string representation
         */
        @Override
        public String toString() {
            return ToString.build(this, "parent");
        }

        /**
         * returns the scope blocks parent
         *
         * @return the parent of this scope block
         */
        public ScopeBlock getParent() {
            return parent;
        }

        /**
         * returns the children of this scope block
         *
         * @return the scope blocks children
         */
        public List getChildren() {
            return children;
        }

        /**
         * returns the start of the block
         *
         * @return the start of the block
         */
        public int getStart() {
            return startLocation;
        }

        /**
         * returns the end of the block
         *
         * @return the end of the block
         */
        public int getFinish() {
            return finishLocation;
        }

        /**
         * sets the start pc of the block
         *
         * @param start the start pc
         */
        public void setStart(int start) {
            startLocation = start;
        }

        /**
         * sets the finish pc of the block
         *
         * @param finish the finish pc
         */
        public void setFinish(int finish) {
            finishLocation = finish;
        }

        public boolean hasChildren() {
            return children != null;
        }

        /**
         * sets that this block is a loop
         */
        public void setLoop() {
            isLoop = true;
        }

        /**
         * returns whether this scope block is a loop
         *
         * @return whether this block is a loop
         */
        public boolean isLoop() {
            return isLoop;
        }

        /**
         * sets that this block was caused from a goto, (an if block exit)
         */
        public void setGoto() {
            isGoto = true;
        }

        /**
         * returns whether this block was caused from a goto
         *
         * @return whether this block was caused by a goto
         */
        public boolean isGoto() {
            return isGoto;
        }

        /**
         * sets that this block was caused from a synchronized block
         */
        public void setSync() {
            isSync = true;
        }

        /**
         * returns whether this block was caused from a synchronized block
         *
         * @return whether this block was caused by a synchronized block
         */
        public boolean isSync() {
            return isSync;
        }

        /**
         * sets that this block was caused from a try block
         */
        public void setTry() {
            isTry = true;
        }

        /**
         * returns whether this block was caused from a try block
         *
         * @return whether this block was caused by a try block
         */
        public boolean isTry() {
            return isTry;
        }

        /**
         * sets that this block was caused from a case block
         */
        public void setCase() {
            isCase = true;
        }

        /**
         * returns whether this block was caused from a case block
         *
         * @return whether this block was caused by a case block
         */
        public boolean isCase() {
            return isCase;
        }

        /**
         * adds the register as a store in this scope block
         *
         * @param reg         the register that was stored
         * @param pc          the instruction that did the store
         * @param assocObject the the object that is associated with this store, usually
         *                    the field from which this came
         */
        public void addStore(int reg, int pc, UserObject assocObject) {
            if (stores == null) {
                stores = new HashMap<>(6);
            }

            stores.put(Integer.valueOf(reg), Integer.valueOf(pc));

            if (assocObject != null) {
                if (assocs == null) {
                    assocs = new HashMap<>(6);
                }
                assocs.put(assocObject, Integer.valueOf(reg));
            }
        }

        /**
         * removes stores to registers that where retrieved from method calls on
         * assocObject
         *
         * @param assocObject the object that a method call was just performed on
         */
        public void removeByAssoc(Object assocObject) {
            if (assocs != null) {
                Integer reg = assocs.remove(assocObject);
                if (reg != null) {
                    if (loads != null) {
                        loads.remove(reg);
                    }
                    if (stores != null) {
                        stores.remove(reg);
                    }
                }
            }
        }

        /**
         * adds the register as a load in this scope block
         *
         * @param reg the register that was loaded
         * @param pc  the instruction that did the load
         */
        public void addLoad(int reg, int pc) {
            if (loads == null) {
                loads = new HashMap<>(10);
            }

            loads.put(Integer.valueOf(reg), Integer.valueOf(pc));
        }

        /**
         * adds a scope block to this subtree by finding the correct place in the
         * hierarchy to store it
         *
         * @param newChild the scope block to add to the tree
         */
        public void addChild(ScopeBlock newChild) {
            newChild.parent = this;

            if (children != null) {
                for (ScopeBlock child : children) {
                    if ((newChild.startLocation > child.startLocation)
                            && (newChild.startLocation < child.finishLocation)) {
                        if (newChild.finishLocation > child.finishLocation) {
                            newChild.finishLocation = child.finishLocation;
                        }
                        child.addChild(newChild);
                        return;
                    }
                }
                int pos = 0;
                for (ScopeBlock child : children) {
                    if (newChild.startLocation < child.startLocation) {
                        children.add(pos, newChild);
                        return;
                    }
                    pos++;
                }
                children.add(newChild);
                return;
            }
            children = new ArrayList<>();
            children.add(newChild);
        }

        /**
         * removes a child from this node
         *
         * @param child the child to remove
         */
        public void removeChild(ScopeBlock child) {
            if (children != null) {
                children.remove(child);
            }
        }

        public void markFieldAssociatedWrites(int sourceReg) {
            if (assocs != null) {
                for (Map.Entry entry : assocs.entrySet()) {
                    UserObject uo = entry.getKey();
                    if ((uo.registerSource == sourceReg)
                            || ((uo.caller instanceof Integer) && (((Integer) uo.caller).intValue() == sourceReg))) {
                        Integer preWrittenFromField = entry.getValue();
                        if ((preWrittenFromField != null) && (stores != null)) {
                            stores.remove(preWrittenFromField);
                        }
                    }
                }
            }
        }

        /**
         * report stores that occur at scopes higher than associated loads that are not
         * involved with loops
         *
         * @param parentUsedRegs the set of registers that where used by the parent
         *                       scope block
         */
        public void findBugs(Set parentUsedRegs) {
            if (isLoop) {
                return;
            }

            Set usedRegs = new HashSet<>(parentUsedRegs);
            if (stores != null) {
                usedRegs.addAll(stores.keySet());
            }
            if (loads != null) {
                usedRegs.addAll(loads.keySet());
            }

            if (stores != null) {
                if (loads != null) {
                    stores.keySet().removeAll(loads.keySet());
                }
                stores.keySet().removeAll(parentUsedRegs);
                for (int r = ignoreRegs.nextSetBit(0); r >= 0; r = ignoreRegs.nextSetBit(r + 1)) {
                    stores.remove(Integer.valueOf(r));
                }

                if ((children != null) && !stores.isEmpty()) {
                    for (Map.Entry entry : stores.entrySet()) {
                        int childUseCount = 0;
                        boolean inIgnoreSB = false;
                        Integer reg = entry.getKey();
                        for (ScopeBlock child : children) {
                            if (child.usesReg(reg)) {
                                if (child.isLoop || child.isSync() || child.isTry()) {
                                    inIgnoreSB = true;
                                    break;
                                }
                                childUseCount++;
                            }
                        }
                        if (!inIgnoreSB && (childUseCount == 1)) {
                            if (appearsToBeUserRegister(reg)) {
                                bugReporter.reportBug(new BugInstance(BloatedAssignmentScope.this,
                                        BugType.BAS_BLOATED_ASSIGNMENT_SCOPE.name(), NORMAL_PRIORITY)
                                                .addClass(BloatedAssignmentScope.this)
                                                .addMethod(BloatedAssignmentScope.this).addSourceLine(
                                                        BloatedAssignmentScope.this, entry.getValue().intValue()));
                            }
                        }
                    }
                }
            }

            if (children != null) {
                for (ScopeBlock child : children) {
                    child.findBugs(usedRegs);
                }
            }
        }

        /**
         * in some cases the java compiler synthesizes variable for its own purposes.
         * Hopefully when it does this these, can not be found in the localvariable
         * table. If we find this to be the case, don't report them
         *
         * @param reg the register to check
         *
         * @return if reg variable appears in the local variable table
         */
        private boolean appearsToBeUserRegister(int reg) {
            LocalVariableTable lvt = getMethod().getLocalVariableTable();
            if (lvt == null) {
                return false;
            }

            LocalVariable lv = lvt.getLocalVariable(reg);
            return lv != null;
        }

        /**
         * returns whether this block either loads or stores into the register in
         * question
         *
         * @param reg the register to look for loads or stores
         *
         * @return whether the block uses the register
         */
        public boolean usesReg(Integer reg) {
            if ((loads != null) && loads.containsKey(reg)) {
                return true;
            }
            if ((stores != null) && stores.containsKey(reg)) {
                return true;
            }

            if (children != null) {
                for (ScopeBlock child : children) {
                    if (child.usesReg(reg)) {
                        return true;
                    }
                }
            }

            return false;
        }

        /**
         * push all loads and stores to this block up to the parent
         */
        public void pushUpLoadStores() {
            if (parent != null) {
                if (loads != null) {
                    if (parent.loads != null) {
                        parent.loads.putAll(loads);
                    } else {
                        parent.loads = loads;
                    }
                }
                if (stores != null) {
                    if (parent.stores != null) {
                        parent.stores.putAll(stores);
                    } else {
                        parent.stores = stores;
                    }
                }
                loads = null;
                stores = null;
            }
        }
    }

    public boolean isRiskyMethodCall() {

        String clsName = getClassConstantOperand();

        if (dangerousAssignmentClassSources.contains(clsName)) {
            return true;
        }

        FQMethod key = new FQMethod(clsName, getNameConstantOperand(), getSigConstantOperand());
        if (dangerousAssignmentMethodSources.contains(key)) {
            return true;
        }

        String sig = key.toFQMethodSignature();
        for (Pattern p : dangerousAssignmentMethodPatterns) {
            Matcher m = p.matcher(sig);
            if (m.matches()) {
                return true;
            }
        }

        return false;
    }

    public boolean isRiskyStoreClass(int reg) {
        LocalVariableTable lvt = getMethod().getLocalVariableTable();
        if (lvt != null) {
            LocalVariable lv = lvt.getLocalVariable(reg, getNextPC());
            if ((lv != null) && dangerousStoreClassSigs.contains(lv.getSignature())) {
                return true;
            }
        }

        return false;
    }

    /**
     * represents the source of an assignment to a variable, which could be a method
     * call or a field
     */
    static class UserObject {
        Comparable caller;
        boolean isRisky;
        int registerSource = -1;

        public UserObject(Comparable callerObj, boolean risky) {
            caller = callerObj;
            isRisky = risky;
        }

        public UserObject(boolean risky) {
            isRisky = risky;
        }

        public UserObject(int reg) {
            registerSource = reg;
        }

        @Override
        public int hashCode() {
            return ((caller == null) ? 0 : caller.hashCode()) | (isRisky ? 1 : 0) | registerSource;
        }

        @Override
        public boolean equals(Object o) {
            if (!(o instanceof UserObject)) {
                return false;
            }

            UserObject that = (UserObject) o;

            if (caller == null) {
                if (that.caller != null) {
                    return false;
                }
            } else {
                boolean eq = caller.equals(that.caller);
                if (!eq) {
                    return false;
                }
            }

            return (isRisky == that.isRisky) && (registerSource == that.registerSource);
        }

        @Override
        public String toString() {
            return ToString.build(this);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy