/* * Copyright (c) 2008-2013 Graham Allan, Juergen Fickel * * 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. * */package org.mutabilitydetector.checkers.settermethod; /* * #%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 org.mutabilitydetector.MutabilityReason; import org.mutabilitydetector.checkers.AsmMutabilityChecker; import org.mutabilitydetector.checkers.info.MethodIdentifier; import org.mutabilitydetector.checkers.info.PrivateMethodInvocationInformation; import org.mutabilitydetector.checkers.settermethod.CandidatesInitialisersMapping.Entry; import org.mutabilitydetector.checkers.settermethod.CandidatesInitialisersMapping.Initialisers; import org.mutabilitydetector.locations.Slashed; import org.objectweb.asm.tree.FieldNode; import org.objectweb.asm.tree.MethodNode; import javax.annotation.concurrent.NotThreadSafe; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import static com.google.common.base.Preconditions.checkNotNull; import static java.lang.String.format; import static org.mutabilitydetector.checkers.info.MethodIdentifier.forMethod; import static org.mutabilitydetector.locations.Slashed.slashed; /** * @author Juergen Fickel (jufickel@htwg-konstanz.de) * @version 13.03.2013 */ @NotThreadSafe public final class SetterMethodChecker extends AbstractSetterMethodChecker { private final class MethodIdentifierFactory { private final MethodNode method; public MethodIdentifierFactory(final MethodNode theMethod) { method = checkNotNull(theMethod); } public MethodIdentifier getMethodIdentifier() { final Slashed className = getSlashedClassName(); final String methodDescriptor = getMethodDescriptor(); return forMethod(className, methodDescriptor); } private Slashed getSlashedClassName() { final String owner = getEnhancedClassNode().getName(); return slashed(owner); } private String getMethodDescriptor() { final String methodName = method.name; final String methodDesc = method.desc; return String.format("%s:%s", methodName, methodDesc); } @Override public String toString() { return getClass().getSimpleName() + " [method=" + method + "]"; } } private final PrivateMethodInvocationInformation privateMethodInvocationInfo; private final Map<FieldNode, Collection<UnknownTypeValue>> initialValues; private final Map<FieldNode, Collection<JumpInsn>> assignmentGuards; private final Map<FieldNode, AssignmentInsn> effectiveAssignmentInstructions; private SetterMethodChecker(final PrivateMethodInvocationInformation thePrivateMethodInvocationInfo) { super(); privateMethodInvocationInfo = thePrivateMethodInvocationInfo; initialValues = new HashMap<FieldNode, Collection<UnknownTypeValue>>(); assignmentGuards = new HashMap<FieldNode, Collection<JumpInsn>>(); effectiveAssignmentInstructions = new HashMap<FieldNode, AssignmentInsn>(); } public static AsmMutabilityChecker newInstance() { return new SetterMethodChecker(null); } public static AsmMutabilityChecker newInstance(final PrivateMethodInvocationInformation privateMethodInvocationInfo) { return new SetterMethodChecker(checkNotNull(privateMethodInvocationInfo)); } @Override protected void collectInitialisers() { final Collection<MethodNode> methodsOfAnalysedClass = getEnhancedClassNode().getMethods(); final Finder<CandidatesInitialisersMapping> f = InitialisersFinder.newInstance(methodsOfAnalysedClass, candidatesInitialisersMapping); candidatesInitialisersMapping = f.find(); } @Override protected void verifyCandidates() { final Collection<FieldNode> unassociatedVariables = candidatesInitialisersMapping .removeAndGetCandidatesWithoutInitialisingMethod(); for (final FieldNode unassociatedVariable : unassociatedVariables) { setNonFinalFieldResult(unassociatedVariable.name); } } @Override protected void verifyInitialisers() { for (final Entry entry : candidatesInitialisersMapping) { verifyInitialisersFor(entry.getCandidate(), entry.getInitialisers()); } verifyVisibleSetterMethods(); } private void verifyInitialisersFor(final FieldNode candidate, final Initialisers allInitialisersForCandidate) { final Collection<MethodNode> initialisingMethods = allInitialisersForCandidate.getMethods(); if (containsMoreThanOne(initialisingMethods)) { setFieldCanBeReassignedResultForEachMethodInitialiser(candidate.name, initialisingMethods); } else if (hasPrivateMethodInvocationInfo()) { for (final MethodNode initialisingMethod : initialisingMethods) { removeCandidateIfInitialisingMethodIsOnlyCalledFromContructor(initialisingMethod); } } } private void verifyVisibleSetterMethods() { final Map<String, Set<MethodNode>> allVisibleSetterMethods = candidatesInitialisersMapping .getAllVisibleSetterMethods(); for (final Map.Entry<String, Set<MethodNode>> e : allVisibleSetterMethods.entrySet()) { final String variableName = e.getKey(); for (final MethodNode visibleSetterMethod : e.getValue()) { setFieldCanBeReassignedResult(variableName, visibleSetterMethod.name); } } } private static boolean containsMoreThanOne(final Collection<?> aCollection) { return 1 < aCollection.size(); } private void setFieldCanBeReassignedResultForEachMethodInitialiser(final String candidateName, final Collection<MethodNode> methodInitialisers) { for (final MethodNode methodInitialiser : methodInitialisers) { setFieldCanBeReassignedResult(candidateName, methodInitialiser.name); } } private boolean hasPrivateMethodInvocationInfo() { return null != privateMethodInvocationInfo; } private void removeCandidateIfInitialisingMethodIsOnlyCalledFromContructor(final MethodNode initialisingMethod) { if (isOnlyCalledFromConstructor(initialisingMethod)) { candidatesInitialisersMapping.removeAndGetCandidateForInitialisingMethod(initialisingMethod); } } private boolean isOnlyCalledFromConstructor(final MethodNode initialisingMethod) { final MethodIdentifierFactory factory = new MethodIdentifierFactory(initialisingMethod); final MethodIdentifier methodId = factory.getMethodIdentifier(); return privateMethodInvocationInfo.isOnlyCalledFromConstructor(methodId); } @Override protected void collectPossibleInitialValues() { for (final Entry entry : candidatesInitialisersMapping) { final FieldNode candidate = entry.getCandidate(); final Initialisers initialisers = entry.getInitialisers(); final Finder<Set<UnknownTypeValue>> f = InitialValueFinder.newInstance(candidate, initialisers, getEnhancedClassNode()); final Set<UnknownTypeValue> possibleInitialValues = f.find(); initialValues.put(candidate, possibleInitialValues); } } @Override protected void verifyPossibleInitialValues() { if (hasAnyVariableMoreThanOneInitialValue()) { setFieldCanBeReassignedResultForEachInitialValue(); } } private boolean hasAnyVariableMoreThanOneInitialValue() { for (final Map.Entry<FieldNode, Collection<UnknownTypeValue>> e : initialValues.entrySet()) { final Collection<UnknownTypeValue> initialValuesForVariable = e.getValue(); if (1 < initialValuesForVariable.size()) { return true; } } return false; } private void setFieldCanBeReassignedResultForEachInitialValue() { for (final Map.Entry<FieldNode, Collection<UnknownTypeValue>> e : initialValues.entrySet()) { final Collection<UnknownTypeValue> initialValuesForCandidate = e.getValue(); final String msgTmpl = "Field [%s] has too many possible initial values for lazy initialisation: [%s]"; final String candidateName = e.getKey().name; final String initialValues = initialValuesToString(initialValuesForCandidate); final String msg = format(msgTmpl, candidateName, initialValues); setResultForClass(msg, MutabilityReason.FIELD_CAN_BE_REASSIGNED); } } private static String initialValuesToString(final Collection<UnknownTypeValue> initialValuesForCandidate) { final StringBuilder result = new StringBuilder(); final String separatorValue = ", "; String separator = ""; for (final UnknownTypeValue initialValue : initialValuesForCandidate) { result.append(separator).append(initialValue); separator = separatorValue; } return result.toString(); } @Override protected void collectEffectiveAssignmentInstructions() { for (final Entry e : candidatesInitialisersMapping) { final FieldNode candidate = e.getCandidate(); final MethodNode initialisingMethod = getSoleInitialisingMethod(e.getInitialisers()); addEffectiveAssignmentInstructionForCandidateIfPossible(candidate, initialisingMethod); } } /* * There must be at most one initialising method. * This is verified by `verifyInitialisers()`. */ private MethodNode getSoleInitialisingMethod(final Initialisers initialisers) { final List<MethodNode> initialisingMethods = initialisers.getMethods(); final MethodNode result; if (!initialisingMethods.isEmpty()) { result = initialisingMethods.get(0); } else { result = null; } return result; } private void addEffectiveAssignmentInstructionForCandidateIfPossible(final FieldNode candidate, final MethodNode initialisingMethod) { if (null != initialisingMethod) { final EnhancedClassNode cn = getEnhancedClassNode(); final Collection<ControlFlowBlock> blocks = cn.getControlFlowBlocksForMethod(initialisingMethod); final Finder<AssignmentInsn> f = EffectiveAssignmentInsnFinder.newInstance(candidate, blocks); effectiveAssignmentInstructions.put(candidate, f.find()); } } @Override protected void verifyEffectiveAssignmentInstructions() { for (final Map.Entry<FieldNode, AssignmentInsn> e : effectiveAssignmentInstructions.entrySet()) { final EffectiveAssignmentInsnVerifier v = EffectiveAssignmentInsnVerifier.newInstance(e.getValue(), e.getKey(), this); v.verify(); } } @Override protected void collectAssignmentGuards() { for (final Entry e : candidatesInitialisersMapping) { final Initialisers initialisers = e.getInitialisers(); collectAssignmentGuardsForEachInitialisingMethod(e.getCandidate(), initialisers.getMethods()); } } private void collectAssignmentGuardsForEachInitialisingMethod(final FieldNode candidate, final Collection<MethodNode> initialisingMethods) { for (final MethodNode initialisingMethod : initialisingMethods) { final EnhancedClassNode cn = getEnhancedClassNode(); final Collection<ControlFlowBlock> blocks = cn.getControlFlowBlocksForMethod(initialisingMethod); collectAssignmentGuardsForEachControlFlowBlock(candidate, blocks); } } private void collectAssignmentGuardsForEachControlFlowBlock(final FieldNode candidate, final Collection<ControlFlowBlock> controlFlowBlocks) { for (final ControlFlowBlock controlFlowBlock : controlFlowBlocks) { final Finder<JumpInsn> f = AssignmentGuardFinder.newInstance(candidate.name, controlFlowBlock); final JumpInsn supposedAssignmentGuard = f.find(); addToAssignmentGuards(candidate, supposedAssignmentGuard); } } private void addToAssignmentGuards(final FieldNode candidate, final JumpInsn supposedAssignmentGuard) { if (supposedAssignmentGuard.isAssignmentGuard()) { final Collection<JumpInsn> assignmentGuardsForCandidate; if (assignmentGuards.containsKey(candidate)) { assignmentGuardsForCandidate = assignmentGuards.get(candidate); } else { final byte expectedMaximum = 3; assignmentGuardsForCandidate = new ArrayList<JumpInsn>(expectedMaximum); assignmentGuards.put(candidate, assignmentGuardsForCandidate); } assignmentGuardsForCandidate.add(supposedAssignmentGuard); } } @Override protected void verifyAssignmentGuards() { final AssignmentGuardVerifier v = AssignmentGuardVerifier.newInstance(initialValues, assignmentGuards, candidatesInitialisersMapping, this); v.verify(); } @Override public String toString() { return getClass().getSimpleName() + " [initialValues=" + initialValues + ", assignmentGuards=" + assignmentGuards + ", effectiveAssignmentInstructions=" + effectiveAssignmentInstructions + ", candidatesInitialisersMapping=" + candidatesInitialisersMapping + "]"; } }