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

org.mutabilitydetector.checkers.ImmutableCollectionChecker Maven / Gradle / Ivy

There is a newer version: 0.10.6
Show newest version
package org.mutabilitydetector.checkers;

/*
 * #%L
 * MutabilityDetector
 * %%
 * Copyright (C) 2008 - 2014 Graham Allan
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */


import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import org.mutabilitydetector.checkers.hint.WrappingHint;
import org.mutabilitydetector.checkers.hint.WrappingHintGenerator;
import org.mutabilitydetector.checkers.info.CopyMethod;
import org.mutabilitydetector.locations.ClassNameConverter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*;

import java.util.Optional;

import static com.google.common.base.Preconditions.checkArgument;
import static org.mutabilitydetector.checkers.ImmutableCollectionChecker.UnmodifiableWrapResult.UnmodifiableWrapStatus.*;
import static org.mutabilitydetector.checkers.hint.WrappingHint.NO_HINT;
import static org.mutabilitydetector.locations.Dotted.dotted;

public class ImmutableCollectionChecker {

    public enum Configuration {
        INSTANCE;

        public final ImmutableSet CAN_BE_WRAPPED = ImmutableSet.of("java.util.Collection",
                "java.util.Set",
                "java.util.SortedSet",
                "java.util.List",
                "java.util.Map",
                "java.util.SortedMap",
                "com.google.common.collect.ImmutableList");

        public final String UNMODIFIABLE_METHOD_OWNER = "java.util.Collections";

        public final ImmutableMap FIELD_TYPE_TO_IMMUTABLE_METHOD = ImmutableMap.builder()
                .put("java.util.Set", "of")
                .put("java.util.List", "of")
                .put("java.util.Map", "of")
                .build();

        public final ImmutableMap FIELD_TYPE_TO_UNMODIFIABLE_METHOD = ImmutableMap.builder()
                .put("java.util.Collection", "unmodifiableCollection")
                .put("java.util.Set", "unmodifiableSet")
                .put("java.util.SortedSet", "unmodifiableSortedSet")
                .put("java.util.List", "unmodifiableList")
                .put("java.util.Map", "unmodifiableMap")
                .put("java.util.SortedMap", "unmodifiableSortedMap")
                .build();

        public final ImmutableMultimap FIELD_TYPE_TO_COPY_METHODS = ImmutableMultimap.builder()
                .putAll("java.util.List",
                        new CopyMethod(dotted("java.util.ArrayList"), "", "(Ljava/util/Collection;)V", true),
                        new CopyMethod(dotted("java.util.LinkedList"), "", "(Ljava/util/Collection;)V", true),
                        new CopyMethod(dotted("java.util.Vector"), "", "(Ljava/util/Collection;)V", true),
                        new CopyMethod(dotted("java.util.concurrent.CopyOnWriteArrayList"), "", "(Ljava/util/Collection;)V", true))
                .putAll("java.util.Set",
                        new CopyMethod(dotted("java.util.HashSet"), "", "(Ljava/util/Collection;)V", true),
                        new CopyMethod(dotted("java.util.LinkedHashSet"), "", "(Ljava/util/Collection;)V", true),
                        new CopyMethod(dotted("java.util.TreeSet"), "", "(Ljava/util/Collection;)V", true),
                        new CopyMethod(dotted("java.util.TreeSet"), "", "(Ljava/util/SortedSet;)V", true),
                        new CopyMethod(dotted("java.util.concurrent.ConcurrentSkipListSet"), "", "(Ljava/util/Collection;)V", true),
                        new CopyMethod(dotted("java.util.concurrent.ConcurrentSkipListSet"), "", "(Ljava/util/SortedSet;)V", true),
                        new CopyMethod(dotted("java.util.concurrent.CopyOnWriteArraySet"), "", "(Ljava/util/Collection;)V", true))
                .putAll("java.util.Map",
                        new CopyMethod(dotted("java.util.HashMap"), "", "(Ljava/util/Map;)V", true),
                        new CopyMethod(dotted("java.util.IdentityHashMap"), "", "(Ljava/util/Map;)V", true),
                        new CopyMethod(dotted("java.util.TreeMap"), "", "(Ljava/util/Map;)V", true),
                        new CopyMethod(dotted("java.util.TreeMap"), "", "(Ljava/util/SortedMap;)V", true),
                        new CopyMethod(dotted("java.util.WeakHashMap"), "", "(Ljava/util/Map;)V", true),
                        new CopyMethod(dotted("java.util.Hashtable"), "", "(Ljava/util/Map;)V", true),
                        new CopyMethod(dotted("java.util.IdentityHashMap"), "", "(Ljava/util/Map;)V", true),
                        new CopyMethod(dotted("java.util.LinkedHashMap"), "", "(Ljava/util/Map;)V", true),
                        new CopyMethod(dotted("java.util.concurrent.ConcurrentHashMap"), "", "(Ljava/util/Map;)V", true),
                        new CopyMethod(dotted("java.util.concurrent.ConcurrentSkipListMap"), "", "(Ljava/util/Map;)V", true),
                        new CopyMethod(dotted("java.util.concurrent.ConcurrentSkipListMap"), "", "(Ljava/util/SortedMap;)V", true))
                .putAll("java.util.SortedMap",
                        new CopyMethod(dotted("java.util.TreeMap"), "", "(Ljava/util/Map;)V", true),
                        new CopyMethod(dotted("java.util.TreeMap"), "", "(Ljava/util/SortedMap;)V", true),
                        new CopyMethod(dotted("java.util.concurrent.ConcurrentSkipListMap"), "", "(Ljava/util/Map;)V", true),
                        new CopyMethod(dotted("java.util.concurrent.ConcurrentSkipListMap"), "", "(Ljava/util/SortedMap;)V", true))
                .putAll("java.util.SortedSet",
                        new CopyMethod(dotted("java.util.TreeSet"), "", "(Ljava/util/Collection;)V", true),
                        new CopyMethod(dotted("java.util.TreeSet"), "", "(Ljava/util/SortedSet;)V", true),
                        new CopyMethod(dotted("java.util.concurrent.ConcurrentSkipListSet"), "", "(Ljava/util/Collection;)V", true),
                        new CopyMethod(dotted("java.util.concurrent.ConcurrentSkipListSet"), "", "(Ljava/util/SortedSet;)V", true))
                .putAll("java.util.Collection",
                        new CopyMethod(dotted("java/util/ArrayList"), "", "(Ljava/util/Collection;)V", true),
                        new CopyMethod(dotted("java/util/concurrent/CopyOnWriteArrayList"), "", "(Ljava/util/Collection;)V", true),
                        new CopyMethod(dotted("java/util/LinkedList"), "", "(Ljava/util/Collection;)V", true),
                        new CopyMethod(dotted("java/util/Vector"), "", "(Ljava/util/Collection;)V", true),
                        new CopyMethod(dotted("java/util/HashSet"), "", "(Ljava/util/Collection;)V", true),
                        new CopyMethod(dotted("java/util/LinkedHashSet"), "", "(Ljava/util/Collection;)V", true),
                        new CopyMethod(dotted("java/util/TreeSet"), "", "(Ljava/util/Collection;)V", true),
                        new CopyMethod(dotted("java/util/TreeSet"), "", "(Ljava/util/SortedSet;)V", true),
                        new CopyMethod(dotted("java/util/concurrent/ConcurrentSkipListSet"), "", "(Ljava/util/Collection;)V", true),
                        new CopyMethod(dotted("java/util/concurrent/ConcurrentSkipListSet"), "", "(Ljava/util/SortedSet;)V", true),
                        new CopyMethod(dotted("java/util/concurrent/CopyOnWriteArraySet"), "", "(Ljava/util/Collection;)V", true),
                        new CopyMethod(dotted("java/util/concurrent/ConcurrentLinkedQueue"), "", "(Ljava/util/Collection;)V", true),
                        new CopyMethod(dotted("java/util/concurrent/DelayQueue"), "", "(Ljava/util/Collection;)V", true),
                        new CopyMethod(dotted("java/util/concurrent/LinkedBlockingDeque"), "", "(Ljava/util/Collection;)V", true),
                        new CopyMethod(dotted("java/util/concurrent/LinkedBlockingQueue"), "", "(Ljava/util/Collection;)V", true),
                        new CopyMethod(dotted("java/util/concurrent/LinkedTransferQueue"), "", "(Ljava/util/Collection;)V", true),
                        new CopyMethod(dotted("java/util/concurrent/PriorityBlockingQueue"), "", "(Ljava/util/Collection;)V", true),
                        new CopyMethod(dotted("java/util/concurrent/PriorityQueue"), "", "(Ljava/util/Collection;)V", true),
                        new CopyMethod(dotted("java/util/concurrent/PriorityQueue"), "", "(Ljava/util/concurrent/PriorityQueue;)V", true),
                        new CopyMethod(dotted("java/util/concurrent/PriorityQueue"), "", "(Ljava/util/SortedSet;)V", true),
                        new CopyMethod(dotted("java/util/concurrent/ConcurrentLinkedDeque"), "", "(Ljava/util/Collection;)V", true),
                        new CopyMethod(dotted("java/util/ArrayDeque"), "", "(Ljava/util/Collection;)V", true),
                        new CopyMethod(dotted("java/util/concurrent/ArrayBlockingQueue"), "", "(IZLjava/util/Collection;)V", true))
                .build();
    }

    private static final ClassNameConverter CLASS_NAME_CONVERTER = new ClassNameConverter();
    private final FieldInsnNode fieldInsnNode;
    private final Type typeAssignedToField;
    private final ImmutableMultimap userDefinedCopyMethods;
    private final String typeSignature;

    public ImmutableCollectionChecker(FieldInsnNode fieldInsnNode, Type typeAssignedToField,
                                      ImmutableMultimap userDefinedCopyMethods) {
        this(fieldInsnNode, typeAssignedToField, userDefinedCopyMethods, null);
    }

    public ImmutableCollectionChecker(FieldInsnNode fieldInsnNode, Type typeAssignedToField,
                                      ImmutableMultimap userDefinedCopyMethods, String typeSignature) {
        checkArgument(fieldInsnNode.getOpcode() == Opcodes.PUTFIELD, "Checking for unmodifiable wrap idiom requires PUTFIELD instruction");
        this.fieldInsnNode = fieldInsnNode;
        this.typeAssignedToField = typeAssignedToField;
        this.userDefinedCopyMethods = userDefinedCopyMethods;
        this.typeSignature = typeSignature;
    }

    public UnmodifiableWrapResult checkWrappedInUnmodifiable() {
        if (!Configuration.INSTANCE.CAN_BE_WRAPPED.contains(typeAssignedToField())) {
            return new UnmodifiableWrapResult(FIELD_TYPE_CANNOT_BE_WRAPPED, NO_HINT);
        }

        if (wrapsInUnmodifiable()) {
            MethodInsnNode wrappingMethodCall = (MethodInsnNode) lastMeaningfulNode(fieldInsnNode);
            if (safelyCopiesBeforeWrapping(wrappingMethodCall)) {
                return new UnmodifiableWrapResult(WRAPS_AND_COPIES_SAFELY, NO_HINT);
            } else {
                return new UnmodifiableWrapResult(WRAPS_BUT_DOES_NOT_COPY, generateHint());
            }
        } else {
            return new UnmodifiableWrapResult(DOES_NOT_WRAP_USING_WHITELISTED_METHOD, generateHint());
        }
    }

    private WrappingHint generateHint() {
        WrappingHintGenerator hintGenerator = new WrappingHintGenerator(Configuration.INSTANCE,
                typeSignature, typeAssignedToField, userDefinedCopyMethods);
        return hintGenerator.generate();
    }

    private String typeAssignedToField() {
        return CLASS_NAME_CONVERTER.dotted(typeAssignedToField.getInternalName());
    }

    private Optional getLastMethodInsnNode() {
        AbstractInsnNode lastMeaningfulNode = lastMeaningfulNode(fieldInsnNode);
        if (lastMeaningfulNode instanceof MethodInsnNode) {
            return Optional.of((MethodInsnNode) lastMeaningfulNode);
        } else {
            return Optional.empty();
        }
    }

    public boolean checkInvokesImmutableInterfaceMethod() {
        return getLastMethodInsnNode().map(previousInvocation ->
                        Configuration.INSTANCE.FIELD_TYPE_TO_IMMUTABLE_METHOD.containsKey(CLASS_NAME_CONVERTER.dotted(previousInvocation.owner)) &&
                        Configuration.INSTANCE.FIELD_TYPE_TO_IMMUTABLE_METHOD.get(typeAssignedToField()).equals(previousInvocation.name))
                    .orElse(false);

    }

    private boolean wrapsInUnmodifiable() {
        return getLastMethodInsnNode().map(previousInvocation ->
                        Configuration.INSTANCE.UNMODIFIABLE_METHOD_OWNER.equals(CLASS_NAME_CONVERTER.dotted(previousInvocation.owner)) &&
                        Configuration.INSTANCE.FIELD_TYPE_TO_UNMODIFIABLE_METHOD.get(typeAssignedToField()).equals(previousInvocation.name))
                    .orElse(false);
    }

    private boolean safelyCopiesBeforeWrapping(MethodInsnNode unmodifiableMethodCall) {
        AbstractInsnNode lastMeaningfulNode = lastMeaningfulNode(unmodifiableMethodCall);
        if (!(lastMeaningfulNode instanceof MethodInsnNode)) {
            return false;
        }

        MethodInsnNode shouldBeCopyConstructor = (MethodInsnNode) lastMeaningfulNode;
        return configuratedAsImmutableCopyMethod(shouldBeCopyConstructor);
    }

    private boolean configuratedAsImmutableCopyMethod(MethodInsnNode shouldBeCopyConstructor) {
        return (Configuration.INSTANCE.FIELD_TYPE_TO_COPY_METHODS.containsEntry(typeAssignedToField(), CopyMethod.from(shouldBeCopyConstructor))
                || userDefinedCopyMethods.containsEntry(typeAssignedToField(), CopyMethod.from(shouldBeCopyConstructor)));
    }

    private AbstractInsnNode lastMeaningfulNode(AbstractInsnNode node) {
        AbstractInsnNode previous = node.getPrevious();
        if (previous instanceof JumpInsnNode) {
           previous = previous.getPrevious();
        }
        if(previous instanceof FrameNode) {
            previous = lastMeaningfulNode(previous,0);
        }
        if(previous instanceof InsnNode || previous instanceof VarInsnNode) {
            while(previous!=null && !(previous instanceof JumpInsnNode))
                previous = previous.getNext()!=null ? previous.getNext()
                        : null;
            if(previous!=null)    
                previous = ((JumpInsnNode)previous).label;
        }
        return (previous instanceof LabelNode) || 
                (previous instanceof LineNumberNode)
                ? lastMeaningfulNode(previous)
                : previous;
    }
    
    private AbstractInsnNode lastMeaningfulNode(AbstractInsnNode node, int jumpCount) {
        if(jumpCount==2) {
            if (node instanceof JumpInsnNode) {
                JumpInsnNode jumpInsn = (JumpInsnNode)node;
                node = jumpInsn.label ;
            }
            return node;
        }
        AbstractInsnNode previous = node.getPrevious();
        if(!(previous instanceof JumpInsnNode)) {
            return lastMeaningfulNode(previous, jumpCount);
        }else {
            return lastMeaningfulNode(previous, jumpCount+1);
        }
    }

    public static class UnmodifiableWrapResult {
        public final UnmodifiableWrapStatus status;
        private final WrappingHint wrappingHint;

        public UnmodifiableWrapResult(UnmodifiableWrapStatus status, WrappingHint wrappingHint) {
            this.status = status;
            this.wrappingHint = wrappingHint;
        }

        public boolean canBeWrapped() {
            return status.canBeWrapped;
        }

        public boolean invokesWhitelistedWrapperMethod() {
            return status.invokesWhitelistedWrapperMethod;
        }

        public boolean safelyCopiesBeforeWrapping() {
            return status.safelyCopiesBeforeWrapping;
        }

        public String getWrappingHint(String fieldName) {
            return wrappingHint.getWrappingHint(fieldName);
        }

        public enum UnmodifiableWrapStatus {
            FIELD_TYPE_CANNOT_BE_WRAPPED(false, false, false),
            DOES_NOT_WRAP_USING_WHITELISTED_METHOD(true, false, false),
            WRAPS_BUT_DOES_NOT_COPY(true, true, false),
            WRAPS_AND_COPIES_SAFELY(true, true, true);

            public final boolean canBeWrapped;
            public final boolean invokesWhitelistedWrapperMethod;
            public final boolean safelyCopiesBeforeWrapping;

            UnmodifiableWrapStatus(boolean canBeWrapped, boolean isWrapped, boolean safelyCopiesBeforeWrapping) {
                this.canBeWrapped = canBeWrapped;
                this.invokesWhitelistedWrapperMethod = isWrapped;
                this.safelyCopiesBeforeWrapping = safelyCopiesBeforeWrapping;
            }
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy