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.ImmutableSet;
import org.mutabilitydetector.MutabilityReason;
import org.mutabilitydetector.asmoverride.AsmVerifierFactory;
import org.mutabilitydetector.checkers.CollectionField.GenericType;
import org.mutabilitydetector.checkers.info.AnalysisInProgress;
import org.mutabilitydetector.checkers.info.MutableTypeInformation;
import org.mutabilitydetector.checkers.info.MutableTypeInformation.MutabilityLookup;
import org.mutabilitydetector.locations.CodeLocation.ClassLocation;
import org.mutabilitydetector.locations.Dotted;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.analysis.BasicValue;
import org.objectweb.asm.tree.analysis.Frame;
import java.util.Map;
import static com.google.common.collect.Maps.newHashMap;
import static java.lang.String.format;
import static org.mutabilitydetector.IsImmutable.IMMUTABLE;
import static org.mutabilitydetector.locations.CodeLocation.FieldLocation.fieldLocation;
import static org.mutabilitydetector.locations.Dotted.dotted;
public final class CollectionWithMutableElementTypeToFieldChecker extends AsmMutabilityChecker {
private final MutableTypeInformation mutableTypeInfo;
private final AsmVerifierFactory verifierFactory;
private final ImmutableSet<Dotted> immutableContainerTypes;
private final JdkCollectionTypes jdkCollectionTypes = new JdkCollectionTypes();
private final Map<String, String> fieldSignatures = newHashMap();
private final AnalysisInProgress analysisInProgress;
public CollectionWithMutableElementTypeToFieldChecker(
MutableTypeInformation mutableTypeInfo,
AsmVerifierFactory verifierFactory,
ImmutableSet<Dotted> immutableContainerTypes,
AnalysisInProgress analysisInProgress) {
this.mutableTypeInfo = mutableTypeInfo;
this.verifierFactory = verifierFactory;
this.immutableContainerTypes = immutableContainerTypes;
this.analysisInProgress = analysisInProgress;
}
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
fieldSignatures.put(name, signature);
return super.visitField(access, name, desc, signature, value);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
return new AssignCollectionToFieldVisitor(ownerClass, access, name, desc, signature, exceptions, verifierFactory);
}
class AssignCollectionToFieldVisitor extends FieldAssignmentVisitor {
public AssignCollectionToFieldVisitor(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; }
Type typeAssignedToField = stackValue.getType();
if (typeAssignedToField.getSort() == Type.OBJECT) {
checkIfClassIsMutable(fieldInsnNode, typeAssignedToField);
}
}
private void checkIfClassIsMutable(FieldInsnNode fieldInsnNode, Type typeAssignedToField) {
Dotted fieldClass = dotted(typeAssignedToField.getInternalName());
if (jdkCollectionTypes.isCollectionType(fieldClass) || immutableContainerTypes.contains(fieldClass)) {
String fieldName = fieldInsnNode.name;
String fieldSignature = fieldSignatures.get(fieldName);
CollectionField collectionField = CollectionField.from(fieldInsnNode.desc, fieldSignature);
Iterable<GenericType> genericParameters = collectionField.getGenericParameterTypes();
if (!collectionField.isGeneric() || anyGenericParameterTypesAreMutable(genericParameters)) {
setResult(format("Field can have collection with mutable element type (%s) assigned to it.", collectionField.asString()),
fieldLocation(fieldName, ClassLocation.fromInternalName(ownerClass)),
MutabilityReason.COLLECTION_FIELD_WITH_MUTABLE_ELEMENT_TYPE);
}
}
}
private boolean anyGenericParameterTypesAreMutable(Iterable<GenericType> genericParameters) {
for (GenericType genericType : genericParameters) {
if (genericType.equals(GenericType.wildcard())) {
return true;
} else if (genericType.isVariable) {
return true;
} else if (genericType.isArray) {
return true;
}
MutabilityLookup mutabilityLookup = mutableTypeInfo.resultOf(dotted(ownerClass), genericType.type, analysisInProgress);
if (mutabilityLookup.foundCyclicReference || !mutabilityLookup.result.isImmutable.equals(IMMUTABLE)) {
return true;
}
}
return false;
}
}
}