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

edu.umd.cs.findbugs.ba.vna.ValueNumberFrame Maven / Gradle / Ivy

There is a newer version: 4.8.6
Show newest version
/*
 * Bytecode Analysis Framework
 * Copyright (C) 2003,2004 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.ba.vna;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;

import edu.umd.cs.findbugs.ba.AnalysisContext;
import edu.umd.cs.findbugs.ba.FieldSummary;
import edu.umd.cs.findbugs.ba.Frame;
import edu.umd.cs.findbugs.ba.XField;
import edu.umd.cs.findbugs.util.Util;

/**
 * A dataflow value representing a Java stack frame with value number
 * information.
 *
 * @author David Hovemeyer
 * @see ValueNumber
 * @see ValueNumberAnalysis
 */
public class ValueNumberFrame extends Frame implements ValueNumberAnalysisFeatures {

    private ArrayList mergedValueList;

    private AvailableLoadBiMap availableLoadMap;

    private Map mergedLoads;

    private Map previouslyKnownAs;

    public boolean phiNodeForLoads;

    private static final boolean USE_WRITTEN_OUTSIDE_OF_CONSTRUCTOR = true;

    static int constructedUnmodifiableMap;

    static int reusedMap;

    static int createdEmptyMap;

    static int madeImmutableMutable;

    static int reusedMutableMap;

    static {
        Util.runLogAtShutdown(() -> {
            System.err.println("Getting updatable previously known as:");
            System.err.println("  " + createdEmptyMap + " created empty map");
            System.err.println("  " + madeImmutableMutable + " made immutable map mutable");
            System.err.println("  " + reusedMutableMap + " reused mutable map");
            System.err.println("Copying map:");
            System.err.println("  " + constructedUnmodifiableMap + " made mutable map unmodifiable");
            System.err.println("  " + reusedMap + " reused immutable map");
            System.err.println();

        });
    }

    public ValueNumberFrame(int numLocals) {
        super(numLocals);
        if (REDUNDANT_LOAD_ELIMINATION) {
            setAvailableLoadMap(AvailableLoadBiMap.emptyMap());
            setMergedLoads(Collections.emptyMap());
            setPreviouslyKnownAs(Collections.emptyMap());
        }
    }

    public String availableLoadMapAsString() {
        StringBuilder buf = new StringBuilder("{ ");
        for (Map.Entry e : getAvailableLoadMap().entrySet()) {
            buf.append(e.getKey());
            buf.append("=");
            for (ValueNumber v : e.getValue()) {
                buf.append(v).append(",");
            }
            buf.append(";  ");
        }

        buf.append(" }");
        return buf.toString();
    }

    public @CheckForNull AvailableLoad getLoad(ValueNumber v) {
        if (!REDUNDANT_LOAD_ELIMINATION) {
            return null;
        }

        return getAvailableLoadMap().getLoad(v);
    }

    /**
     * Look for an available load.
     *
     * @param availableLoad
     *            the AvailableLoad (reference and field)
     * @return the value(s) available, or null if no matching entry is found
     */
    public ValueNumber[] getAvailableLoad(AvailableLoad availableLoad) {
        return getAvailableLoadMap().get(availableLoad);
    }

    /**
     * Add an available load.
     *
     * @param availableLoad
     *            the AvailableLoad (reference and field)
     * @param value
     *            the value(s) loaded
     */
    public void addAvailableLoad(AvailableLoad availableLoad, @Nonnull ValueNumber[] value) {
        Objects.requireNonNull(value);
        getUpdateableAvailableLoadMap().put(availableLoad, value);

        for (ValueNumber v : value) {
            getUpdateablePreviouslyKnownAs().put(v, availableLoad);
            if (RLE_DEBUG) {
                System.out.println("Adding available load of " + availableLoad + " for " + v + " to "
                        + System.identityHashCode(this));
            }
        }
    }

    private static void removeAllKeys(AvailableLoadBiMap map, Iterable removeMe) {
        for (AvailableLoad k : removeMe) {
            map.remove(k);
        }
    }

    /**
     * Kill all loads of given field.
     *
     * @param field
     *            the field
     */
    public void killLoadsOfField(XField field) {
        if (!REDUNDANT_LOAD_ELIMINATION) {
            return;
        }
        HashSet killMe = new HashSet<>();
        for (AvailableLoad availableLoad : getAvailableLoadMap().keySet()) {
            if (availableLoad.getField().equals(field)) {
                if (RLE_DEBUG) {
                    System.out.println("KILLING Load of " + availableLoad + " in " + this);
                }
                killMe.add(availableLoad);
            }
        }
        killAvailableLoads(killMe);
    }

    /**
     * Kill all loads. This conservatively handles method calls where we don't
     * really know what fields might be assigned.
     */
    public void killAllLoads() {
        killAllLoads(false);
    }

    public void killAllLoads(boolean primitiveOnly) {
        if (!REDUNDANT_LOAD_ELIMINATION) {
            return;
        }
        FieldSummary fieldSummary = AnalysisContext.currentAnalysisContext().getFieldSummary();
        HashSet killMe = new HashSet<>();
        for (AvailableLoad availableLoad : getAvailableLoadMap().keySet()) {
            XField field = availableLoad.getField();
            if ((!primitiveOnly || !field.isReferenceType()) && (field.isVolatile() || !field.isFinal()
                    && (!USE_WRITTEN_OUTSIDE_OF_CONSTRUCTOR || fieldSummary.isWrittenOutsideOfConstructor(field)))) {
                if (RLE_DEBUG) {
                    System.out.println("KILLING load of " + availableLoad + " in " + this);
                }
                killMe.add(availableLoad);
            }
        }
        killAvailableLoads(killMe);

    }

    public void killAllLoadsExceptFor(@CheckForNull ValueNumber v) {
        if (!REDUNDANT_LOAD_ELIMINATION) {
            return;
        }
        AvailableLoad myLoad = getLoad(v);
        HashSet killMe = new HashSet<>();
        for (AvailableLoad availableLoad : getAvailableLoadMap().keySet()) {
            if (!availableLoad.getField().isFinal() && !availableLoad.equals(myLoad)) {
                if (RLE_DEBUG) {
                    System.out.println("KILLING load of " + availableLoad + " in " + this);
                }
                killMe.add(availableLoad);
            }
        }
        killAvailableLoads(killMe);
    }

    /**
     * Kill all loads. This conservatively handles method calls where we don't
     * really know what fields might be assigned.
     */
    public void killAllLoadsOf(@CheckForNull ValueNumber v) {
        if (!REDUNDANT_LOAD_ELIMINATION) {
            return;
        }
        FieldSummary fieldSummary = AnalysisContext.currentAnalysisContext().getFieldSummary();

        HashSet killMe = new HashSet<>();
        for (AvailableLoad availableLoad : getAvailableLoadMap().keySet()) {
            if (availableLoad.getReference() != v) {
                continue;
            }
            XField field = availableLoad.getField();
            if (!field.isFinal() && (!USE_WRITTEN_OUTSIDE_OF_CONSTRUCTOR || fieldSummary.isWrittenOutsideOfConstructor(field))) {
                if (RLE_DEBUG) {
                    System.out.println("Killing load of " + availableLoad + " in " + this);
                }
                killMe.add(availableLoad);
            }
        }
        killAvailableLoads(killMe);
    }

    public void killLoadsOf(Set fieldsToKill) {
        if (!REDUNDANT_LOAD_ELIMINATION) {
            return;
        }
        HashSet killMe = new HashSet<>();
        for (AvailableLoad availableLoad : getAvailableLoadMap().keySet()) {

            if (fieldsToKill.contains(availableLoad.getField())) {
                killMe.add(availableLoad);
            }

        }
        killAvailableLoads(killMe);
    }

    public void killLoadsWithSimilarName(String className, String methodName) {
        if (!REDUNDANT_LOAD_ELIMINATION) {
            return;
        }
        String packageName = extractPackageName(className);

        HashSet killMe = new HashSet<>();
        for (AvailableLoad availableLoad : getAvailableLoadMap().keySet()) {

            XField field = availableLoad.getField();
            String fieldPackageName = extractPackageName(field.getClassName());
            if (packageName.equals(fieldPackageName) && field.isStatic()
                    && methodName.toLowerCase().indexOf(field.getName().toLowerCase()) >= 0) {
                killMe.add(availableLoad);
            }

        }
        killAvailableLoads(killMe);
    }

    private void killAvailableLoads(HashSet killMe) {
        if (killMe.size() > 0) {
            removeAllKeys(getUpdateableAvailableLoadMap(), killMe);
        }
    }

    private String extractPackageName(String className) {
        return className.substring(className.lastIndexOf('.') + 1);
    }

    void mergeAvailableLoadSets(ValueNumberFrame other, ValueNumberFactory factory, MergeTree mergeTree) {
        if (REDUNDANT_LOAD_ELIMINATION) {
            // Merge available load sets.
            // Only loads that are available in both frames
            // remain available. All others are discarded.
            String s = "";
            if (RLE_DEBUG) {
                s = "Merging " + this.availableLoadMapAsString() + " and " + other.availableLoadMapAsString();
            }
            boolean changed = false;
            if (other.isBottom()) {
                changed = !this.getAvailableLoadMap().isEmpty();
                setAvailableLoadMap(AvailableLoadBiMap.emptyMap());
            } else if (!other.isTop()) {
                AvailableLoadBiMap updateableAvailableLoadMap = getUpdateableAvailableLoadMap();

                for (Map.Entry e : updateableAvailableLoadMap.entrySet()) {
                    AvailableLoad load = e.getKey();
                    ValueNumber[] myVN = e.getValue();
                    ValueNumber[] otherVN = other.getAvailableLoadMap().get(load);
                    /*
                    if (false && this.phiNodeForLoads && myVN != null && myVN.length == 1
                            && myVN[0].hasFlag(ValueNumber.PHI_NODE)) {
                        continue;
                    }
                     */
                    if (!Arrays.equals(myVN, otherVN)) {

                        ValueNumber phi = getMergedLoads().get(load);
                        if (phi == null) {
                            int flags = -1;
                            for (ValueNumber vn : myVN) {
                                flags = ValueNumber.mergeFlags(flags, vn.getFlags());
                            }
                            if (otherVN != null) {
                                for (ValueNumber vn : otherVN) {
                                    flags = ValueNumber.mergeFlags(flags, vn.getFlags());
                                }
                            }
                            if (flags == -1) {
                                flags = ValueNumber.PHI_NODE;
                            } else {
                                flags |= ValueNumber.PHI_NODE;
                            }

                            phi = factory.createFreshValue(flags);

                            getUpdateableMergedLoads().put(load, phi);
                            for (ValueNumber vn : myVN) {
                                mergeTree.mapInputToOutput(vn, phi);
                            }
                            if (otherVN != null) {
                                for (ValueNumber vn : otherVN) {
                                    mergeTree.mapInputToOutput(vn, phi);
                                }
                            }

                            if (RLE_DEBUG) {
                                System.out.println("Creating phi node " + phi + " for " + load + " from " + Arrays.toString(myVN)
                                        + " x " + Arrays.toString(otherVN) + " in " + System.identityHashCode(this));
                            }
                            changed = true;
                            updateableAvailableLoadMap.updateEntryValue(e, phi);
                        } else {
                            if (RLE_DEBUG) {
                                System.out.println("Reusing phi node : " + phi + " for " + load + " from "
                                        + Arrays.toString(myVN) + " x " + Arrays.toString(otherVN) + " in "
                                        + System.identityHashCode(this));
                            }
                            if (myVN.length != 1 || !myVN[0].equals(phi)) {
                                updateableAvailableLoadMap.updateEntryValue(e, phi);
                            }
                        }

                    }

                }
            }
            Map previouslyKnownAsOther = other.getPreviouslyKnownAs();
            if (getPreviouslyKnownAs() != previouslyKnownAsOther && previouslyKnownAsOther.size() != 0) {
                if (getPreviouslyKnownAs().size() == 0) {
                    assignPreviouslyKnownAs(other);
                } else {
                    getUpdateablePreviouslyKnownAs().putAll(previouslyKnownAsOther);
                }
            }
            if (changed) {
                this.phiNodeForLoads = true;
            }
            if (changed && RLE_DEBUG) {
                System.out.println(s);
                System.out.println("  Result is " + this.availableLoadMapAsString());
                System.out.println(" Set phi for " + System.identityHashCode(this));
            }
        }
    }

    ValueNumber getMergedValue(int slot) {
        return mergedValueList.get(slot);
    }

    void setMergedValue(int slot, ValueNumber value) {
        mergedValueList.set(slot, value);
    }

    @Override
    public void copyFrom(Frame other) {
        if (!(other instanceof ValueNumberFrame)) {
            throw new IllegalArgumentException();
        }
        // If merged value list hasn't been created yet, create it.
        if (mergedValueList == null && other.isValid()) {
            // This is where this frame gets its size.
            // It will have the same size as long as it remains valid.
            mergedValueList = new ArrayList<>(other.getNumSlots());
            int numSlots = other.getNumSlots();
            for (int i = 0; i < numSlots; ++i) {
                mergedValueList.add(null);
            }
        }

        if (REDUNDANT_LOAD_ELIMINATION) {
            assignAvailableLoadMap((ValueNumberFrame) other);
            assignPreviouslyKnownAs((ValueNumberFrame) other);
        }

        super.copyFrom(other);
    }

    private void assignAvailableLoadMap(ValueNumberFrame other) {
        AvailableLoadBiMap availableLoadMapOther = other.getAvailableLoadMap();
        if (availableLoadMapOther.isModifiable()) {
            availableLoadMapOther = AvailableLoadBiMap.unmodifiableMap(availableLoadMapOther);
            other.setAvailableLoadMap(availableLoadMapOther);
            setAvailableLoadMap(availableLoadMapOther);
            constructedUnmodifiableMap++;
        } else {
            setAvailableLoadMap(availableLoadMapOther);
            reusedMap++;
        }
    }

    private void assignPreviouslyKnownAs(ValueNumberFrame other) {
        Map previouslyKnownAsOther = other.getPreviouslyKnownAs();
        if (previouslyKnownAsOther instanceof HashMap) {
            previouslyKnownAsOther = Collections.unmodifiableMap(previouslyKnownAsOther);
            other.setPreviouslyKnownAs(previouslyKnownAsOther);
            setPreviouslyKnownAs(previouslyKnownAsOther);
            constructedUnmodifiableMap++;
        } else {
            setPreviouslyKnownAs(previouslyKnownAsOther);
            reusedMap++;
        }
    }

    @Override
    public String toString() {
        String frameValues = super.toString();
        if (RLE_DEBUG) {
            StringBuilder buf = new StringBuilder();
            buf.append(frameValues);

            Iterator i = getAvailableLoadMap().keySet().iterator();
            boolean first = true;
            while (i.hasNext()) {
                AvailableLoad key = i.next();
                ValueNumber[] value = getAvailableLoadMap().get(key);
                if (first) {
                    first = false;
                } else {
                    buf.append(',');
                }
                buf.append(key + "=" + valueToString(value));
            }

            buf.append(" #");
            buf.append(System.identityHashCode(this));
            if (phiNodeForLoads) {
                buf.append(" phi");
            }
            return buf.toString();
        } else {
            return frameValues;
        }
    }

    private static String valueToString(ValueNumber[] valueNumberList) {
        StringBuilder buf = new StringBuilder();
        buf.append('[');
        boolean first = true;
        for (ValueNumber aValueNumberList : valueNumberList) {
            if (first) {
                first = false;
            } else {
                buf.append(',');
            }
            buf.append(aValueNumberList.getNumber());
        }
        buf.append(']');
        return buf.toString();
    }

    public boolean fuzzyMatch(ValueNumber v1, ValueNumber v2) {
        if (REDUNDANT_LOAD_ELIMINATION) {
            return v1.equals(v2) || fromMatchingLoads(v1, v2) || haveMatchingFlags(v1, v2);
        } else {
            return v1.equals(v2);
        }
    }

    public boolean veryFuzzyMatch(ValueNumber v1, ValueNumber v2) {
        if (REDUNDANT_LOAD_ELIMINATION) {
            return v1.equals(v2) || fromMatchingFields(v1, v2) || haveMatchingFlags(v1, v2);
        } else {
            return v1.equals(v2);
        }
    }

    public boolean fromMatchingLoads(ValueNumber v1, ValueNumber v2) {
        AvailableLoad load1 = getLoad(v1);
        if (load1 == null) {
            load1 = getPreviouslyKnownAs().get(v1);
        }
        if (load1 == null) {
            return false;
        }
        AvailableLoad load2 = getLoad(v2);
        if (load2 == null) {
            load2 = getPreviouslyKnownAs().get(v2);
        }
        if (load2 == null) {
            return false;
        }
        return load1.equals(load2);
    }

    public boolean fromMatchingFields(ValueNumber v1, ValueNumber v2) {
        AvailableLoad load1 = getLoad(v1);
        if (load1 == null) {
            load1 = getPreviouslyKnownAs().get(v1);
        }
        if (load1 == null) {
            return false;
        }
        AvailableLoad load2 = getLoad(v2);
        if (load2 == null) {
            load2 = getPreviouslyKnownAs().get(v2);
        }
        if (load2 == null) {
            return false;
        }
        if (load1.equals(load2)) {
            return true;
        }
        if (load1.getField().equals(load2.getField())) {
            ValueNumber source1 = load1.getReference();
            ValueNumber source2 = load2.getReference();
            if (!this.contains(source1)) {
                return true;
            }
            if (!this.contains(source2)) {
                return true;
            }
        }
        return false;
    }

    /**
     * @return true if v1 and v2 have a flag in common
     */
    public boolean haveMatchingFlags(ValueNumber v1, ValueNumber v2) {
        int flag1 = v1.getFlags();
        int flag2 = v2.getFlags();
        return (flag1 & flag2) != 0;
    }

    public Collection valueNumbersForLoads() {
        HashSet result = new HashSet<>();
        if (REDUNDANT_LOAD_ELIMINATION) {
            for (Map.Entry e : getAvailableLoadMap().entrySet()) {
                if (e.getValue() != null) {
                    Collections.addAll(result, e.getValue());
                }
            }
        }

        return result;
    }

    private void setAvailableLoadMap(AvailableLoadBiMap availableLoadMap) {
        this.availableLoadMap = availableLoadMap;
    }

    private AvailableLoadBiMap getAvailableLoadMap() {
        return availableLoadMap;
    }

    private AvailableLoadBiMap getUpdateableAvailableLoadMap() {
        if (!availableLoadMap.isModifiable()) {
            HashMap tmp = new HashMap<>(availableLoadMap.size() + 4);
            tmp.putAll(availableLoadMap.map);
            availableLoadMap = new AvailableLoadBiMap(tmp);
        }
        return availableLoadMap;
    }

    private void setMergedLoads(Map mergedLoads) {
        this.mergedLoads = mergedLoads;
    }

    private Map getMergedLoads() {
        return mergedLoads;
    }

    private Map getUpdateableMergedLoads() {
        if (!(mergedLoads instanceof HashMap)) {
            mergedLoads = new HashMap<>();
        }

        return mergedLoads;
    }

    private void setPreviouslyKnownAs(Map previouslyKnownAs) {
        this.previouslyKnownAs = previouslyKnownAs;
    }

    private Map getPreviouslyKnownAs() {
        return previouslyKnownAs;
    }

    private Map getUpdateablePreviouslyKnownAs() {
        if (previouslyKnownAs.size() == 0) {
            previouslyKnownAs = new HashMap<>(4);
            createdEmptyMap++;
        } else if (!(previouslyKnownAs instanceof HashMap)) {
            HashMap tmp = new HashMap<>(previouslyKnownAs.size() + 4);
            tmp.putAll(previouslyKnownAs);
            previouslyKnownAs = tmp;
            madeImmutableMutable++;
        } else {
            reusedMutableMap++;
        }

        return previouslyKnownAs;
    }

    @Override
    public boolean sameAs(Frame other) {
        if (!super.sameAs(other)) {
            return false;
        }
        if (isTop() && other.isTop() || isBottom() && other.isBottom()) {
            return true;
        }
        ValueNumberFrame o = (ValueNumberFrame) other;
        if (availableLoadMap.size() != o.availableLoadMap.size()) {
            return false;
        }
        for (Entry entry : availableLoadMap.entrySet()) {
            ValueNumber[] oValue = o.availableLoadMap.get(entry.getKey());
            if (!Arrays.equals(entry.getValue(), oValue)) {
                return false;
            }
        }
        return true;
    }

    public boolean hasAvailableLoads() {
        return !getAvailableLoadMap().isEmpty();
    }

    /**
     * A wrapper for the AvailableLoad to ValueNumber[] map also keeping track of a reverse map. There are a lot of
     * calls to {@link ValueNumberFrame#getLoad(ValueNumber)} so it is faster using a reverse map compared to doing a
     * linear search
     */
    private static class AvailableLoadBiMap {
        private final Map map;
        private final Map reverseMap;

        public AvailableLoadBiMap(Map map) {
            this.map = map;
            this.reverseMap = new HashMap<>();

            for (Map.Entry entry : map.entrySet()) {
                ValueNumber[] value = entry.getValue();

                for (ValueNumber element : value) {
                    reverseMap.put(element, entry.getKey());
                }
            }
        }

        public AvailableLoadBiMap(Map map, Map reverseMap) {
            this.map = map;
            this.reverseMap = reverseMap;
        }

        /**
         * @return an empty (unmodifiable) {@link AvailableLoadBiMap}
         */
        public static AvailableLoadBiMap emptyMap() {
            return new AvailableLoadBiMap(Collections.emptyMap(), Collections.emptyMap());
        }

        /**
         * @param other
         *            The map we want to copy
         * @return an unmodifiable copy backed by the other {@link AvailableLoadBiMap}
         */
        public static AvailableLoadBiMap unmodifiableMap(AvailableLoadBiMap other) {
            return new AvailableLoadBiMap(Collections.unmodifiableMap(other.map),
                    Collections.unmodifiableMap(other.reverseMap));
        }

        /**
         * @return The number of distinct {@link AvailableLoad} in this map
         */
        public int size() {
            return map.size();
        }

        public boolean isEmpty() {
            return map.isEmpty();
        }

        public Set keySet() {
            return map.keySet();
        }

        public Set> entrySet() {
            return map.entrySet();
        }

        public ValueNumber[] get(AvailableLoad key) {
            return map.get(key);
        }

        /**
         * Put an array of {@link ValueNumber} for an {@link AvailableLoad} and update the reverse map
         */
        public ValueNumber[] put(AvailableLoad key, ValueNumber[] value) {
            ValueNumber[] previous = map.put(key, value);

            for (ValueNumber v : value) {
                reverseMap.put(v, key);
            }

            return previous;
        }

        public void updateEntryValue(Entry e, ValueNumber value) {
            for (ValueNumber v : e.getValue()) {
                reverseMap.remove(v);
            }

            e.setValue(new ValueNumber[] { value });

            reverseMap.put(value, e.getKey());
        }

        /**
         * Remove an {@link AvailableLoad} and update the reverse map
         */
        public ValueNumber[] remove(AvailableLoad key) {
            ValueNumber[] value = map.remove(key);

            for (ValueNumber v : value) {
                reverseMap.remove(v);
            }

            return value;
        }

        public AvailableLoad getLoad(ValueNumber v) {
            if (!REDUNDANT_LOAD_ELIMINATION) {
                return null;
            }

            return reverseMap.get(v);
        }

        public boolean isModifiable() {
            return map instanceof HashMap;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy