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.base.Joiner; import com.google.common.base.Optional; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.mutabilitydetector.asmoverride.AsmVerifierFactory; import org.mutabilitydetector.checkers.CollectionTypeWrappedInUnmodifiableIdiomChecker.UnmodifiableWrapResult; import org.mutabilitydetector.checkers.info.AnalysisInProgress; import org.mutabilitydetector.checkers.info.CyclicReferences; import org.mutabilitydetector.checkers.info.MutableTypeInformation; import org.mutabilitydetector.checkers.info.MutableTypeInformation.MutabilityLookup; import org.mutabilitydetector.checkers.info.TypeStructureInformation; import org.mutabilitydetector.locations.CodeLocation.ClassLocation; import org.mutabilitydetector.locations.CodeLocation.FieldLocation; import org.mutabilitydetector.locations.Dotted; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.signature.SignatureReader; import org.objectweb.asm.signature.SignatureVisitor; import org.objectweb.asm.tree.FieldInsnNode; import org.objectweb.asm.tree.analysis.BasicValue; import org.objectweb.asm.tree.analysis.Frame; import java.util.List; import java.util.Map; import java.util.Set; import static java.lang.String.format; import static org.mutabilitydetector.IsImmutable.IMMUTABLE; import static org.mutabilitydetector.MutabilityReason.*; import static org.mutabilitydetector.locations.CodeLocation.FieldLocation.fieldLocation; import static org.mutabilitydetector.locations.Dotted.dotted; public final class MutableTypeToFieldChecker extends AsmMutabilityChecker { private final TypeStructureInformation typeStructureInformation; private final MutableTypeInformation mutableTypeInfo; private final AsmVerifierFactory verifierFactory; private final Set<Dotted> immutableContainerClasses; private final List<String> genericTypesOfClass = Lists.newLinkedList(); private final Map<String, String> genericFields = Maps.newHashMap(); private final AnalysisInProgress analysisInProgress; private final Map<String, String> typeSignatureByFieldName = Maps.newHashMap(); public MutableTypeToFieldChecker(TypeStructureInformation info, MutableTypeInformation mutableTypeInfo, AsmVerifierFactory verifierFactory, Set<Dotted> immutableContainerClasses, AnalysisInProgress analysisInProgress) { this.typeStructureInformation = info; this.mutableTypeInfo = mutableTypeInfo; this.verifierFactory = verifierFactory; this.immutableContainerClasses = immutableContainerClasses; this.analysisInProgress = analysisInProgress; } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { super.visit(version, access, name, signature, superName, interfaces); if (signature == null) { return; } new SignatureReader(signature).accept(new SignatureVisitor(Opcodes.ASM5) { @Override public void visitFormalTypeParameter(String name) { genericTypesOfClass.add(name); } }); } @Override public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { if (signature == null) { return null ; } typeSignatureByFieldName.put(name, signature); GenericFieldVisitor visitor = new GenericFieldVisitor(); new SignatureReader(signature).acceptType(visitor); Optional<String> declaredType = visitor.declaredType(); if (declaredType.isPresent() && genericTypesOfClass.contains(declaredType.get())) { genericFields.put(name, declaredType.get()); } return super.visitField(access, name, desc, signature, value); } static final class GenericFieldVisitor extends SignatureVisitor { private String declaredType; private boolean fieldIsOfGenericType = true; public GenericFieldVisitor() { super(Opcodes.ASM5); } @Override public void visitTypeVariable(String name) { declaredType = name; } @Override public void visitClassType(String name) { fieldIsOfGenericType = false; } public Optional<String> declaredType() { return fieldIsOfGenericType ? Optional.of(declaredType) : Optional.<String>absent(); } } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { return new AssignMutableTypeToFieldChecker(ownerClass, access, name, desc, signature, exceptions, verifierFactory); } class AssignMutableTypeToFieldChecker extends FieldAssignmentVisitor { public AssignMutableTypeToFieldChecker(String owner, int access, String name, String desc, String signature, String[] exceptions, AsmVerifierFactory verifierFactory) { super(owner, access, name, desc, signature, exceptions, verifierFactory); } @Override protected void visitFieldAssignmentFrame(Frame<BasicValue> assignmentFrame, FieldInsnNode fieldInsnNode, BasicValue stackValue) { if (isInvalidStackValue(stackValue)) { return; } checkIfClassIsMutable(fieldInsnNode, stackValue.getType()); } private void checkIfClassIsMutable(FieldInsnNode fieldInsnNode, Type typeAssignedToField) { int sort = typeAssignedToField.getSort(); String fieldName = fieldInsnNode.name; FieldLocation fieldLocation = fieldLocation(fieldName, ClassLocation.fromInternalName(ownerClass)); switch (sort) { case Type.OBJECT: Dotted assignedToField = dotted(typeAssignedToField.getInternalName()); if (isAssigningToGenericField(fieldName)) { setAssigningToGenericFieldResult(fieldName, fieldLocation); break; } MutabilityLookup mutabilityLookup = mutableTypeInfo.resultOf(dotted(ownerClass), assignedToField, analysisInProgress); if (mutabilityLookup.foundCyclicReference) { setCyclicReferenceResult(fieldLocation, mutabilityLookup.cyclicReference); break; } else if (isImmutableContainerType(assignedToField)) { /** * Allow {@link CollectionWithMutableElementTypeToFieldChecker} to decide if this concrete class * is immutable as long as it has an immutable element type. */ break; } else if (!isConcreteType(assignedToField)) { String fieldSignature = typeSignatureByFieldName.get(fieldName); UnmodifiableWrapResult unmodifiableWrapResult = new CollectionTypeWrappedInUnmodifiableIdiomChecker( fieldInsnNode, typeAssignedToField, mutableTypeInfo.hardcodedCopyMethods(), fieldSignature) .checkWrappedInUnmodifiable(); if (!unmodifiableWrapResult.canBeWrapped()) { setAbstractFieldAssignmentResult(fieldLocation, assignedToField); break; } else if (unmodifiableWrapResult.invokesWhitelistedWrapperMethod()) { if (unmodifiableWrapResult.safelyCopiesBeforeWrapping()) { break; } else { setWrappingWithoutFirstCopyingResult(fieldLocation, unmodifiableWrapResult.getWrappingHint(fieldLocation.fieldName())); break; } } else { setUnsafeWrappingResult(fieldLocation, unmodifiableWrapResult.getWrappingHint(fieldLocation.fieldName())); break; } } else { if (!mutabilityLookup.result.isImmutable.equals(IMMUTABLE)) { setMutableFieldAssignmentResult(fieldLocation, assignedToField); } else { break; } } break; case Type.ARRAY: setResult("Field can have a mutable type (an array) assigned to it.", fieldLocation, MUTABLE_TYPE_TO_FIELD); break; default: } } private boolean isImmutableContainerType(Dotted assignedToField) { return immutableContainerClasses.contains(assignedToField); } private void setAssigningToGenericFieldResult(String fieldName, FieldLocation fieldLocation) { setResult(String.format("Field can have a generic type (%s) assigned to it.", genericTypeOf(fieldName)), fieldLocation, MUTABLE_TYPE_TO_FIELD); } private String genericTypeOf(String fieldName) { return genericFields.get(fieldName); } private boolean isAssigningToGenericField(String fieldName) { return genericFields.containsKey(fieldName); } private boolean isConcreteType(Dotted className) { return !(typeStructureInformation.isTypeAbstract(className) || typeStructureInformation.isTypeInterface(className)); } private void setUnsafeWrappingResult(FieldLocation fieldLocation, String wrappingHintMessage) { String message = String.format("Field is not a wrapped collection type.%s", wrappingHintMessage); setResult(message, fieldLocation, ABSTRACT_COLLECTION_TYPE_TO_FIELD); } private void setWrappingWithoutFirstCopyingResult(FieldLocation fieldLocation, String wrappingHintMessage) { String message = String.format("Attempts to wrap mutable collection type without safely performing a copy first.%s", wrappingHintMessage); setResult(message, fieldLocation, ABSTRACT_COLLECTION_TYPE_TO_FIELD); } private void setAbstractFieldAssignmentResult(FieldLocation fieldLocation, Dotted assignedToField) { setResult(format("Field can have an abstract type (%s) assigned to it.", assignedToField), fieldLocation, ABSTRACT_TYPE_TO_FIELD); } private void setMutableFieldAssignmentResult(FieldLocation fieldLocation, Dotted assignedToField) { setResult("Field can have a mutable type (" + assignedToField + ") " + "assigned to it.", fieldLocation, MUTABLE_TYPE_TO_FIELD); } private void setCyclicReferenceResult(FieldLocation fieldLocation, CyclicReferences.CyclicReference cyclicReference) { setResult("There is a field assigned which creates a cyclic reference. " + "(" + Joiner.on(" -> ").join(cyclicReference.references) + ")", fieldLocation, MUTABLE_TYPE_TO_FIELD); } } }