Java tutorial
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 static com.google.common.base.Preconditions.checkNotNull; import static java.lang.String.format; import static org.objectweb.asm.Opcodes.*; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.annotation.concurrent.NotThreadSafe; import org.mutabilitydetector.MutabilityReason; import org.mutabilitydetector.checkers.settermethod.CandidatesInitialisersMapping.Entry; import org.mutabilitydetector.checkers.settermethod.CandidatesInitialisersMapping.Initialisers; import org.objectweb.asm.tree.*; /** * @author Juergen Fickel (jufickel@htwg-konstanz.de) * @version 13.03.2013 */ @NotThreadSafe final class AssignmentGuardVerifier { @NotThreadSafe private final class ZeroCheckAssignmentGuardVerifier { private final FieldNode candidate; private final JumpInsn assignmentGuard; private final ControlFlowBlock controlFlowBlock; public ZeroCheckAssignmentGuardVerifier(final FieldNode theCandidate, final JumpInsn theAssignmentGuard, final ControlFlowBlock theControlFlowBlock) { candidate = theCandidate; assignmentGuard = theAssignmentGuard; controlFlowBlock = theControlFlowBlock; } public void verify() { final String candidateName = candidate.name; final int indexOfPredecessor = assignmentGuard.getIndexWithinBlock() - 1; final AbstractInsnNode predecessor = controlFlowBlock.getBlockInstructionForIndex(indexOfPredecessor); if (isGetInstructionForVariable(predecessor, candidate)) { final String msgTemplate = "The assignment guard for lazy field [%s] is not correct."; final String msg = format(msgTemplate, candidateName); if (isZeroOnlyPossibleInitialValueForVariable() && assignmentGuard.getOpcode() == Opcode.IFEQ) { setterMethodChecker.setFieldCanBeReassignedResult(msg); } else if (!isZeroOnlyPossibleInitialValueForVariable() && assignmentGuard.getOpcode() == Opcode.IFNE) { setterMethodChecker.setFieldCanBeReassignedResult(msg); } } else if (isComparisonInsn(predecessor)) { verifyPredecessorOfComparisonInstruction(indexOfPredecessor - 1); } else if (checksAgainstOtherObject(assignmentGuard, controlFlowBlock, candidate)) { if (isOtherObjectNotAnInitialValue(assignmentGuard, controlFlowBlock)) { final String msgTemplate = "The compared object is not a possible initial value of lazy field " + "[%s]."; final String msg = format(msgTemplate, candidateName); setterMethodChecker.setFieldCanBeReassignedResult(msg); } } } private boolean isZeroOnlyPossibleInitialValueForVariable() { boolean result = true; final Collection<UnknownTypeValue> possibleInitialValuesForVariable = initialValues.get(candidate); final Iterator<UnknownTypeValue> i = possibleInitialValuesForVariable.iterator(); while (result && i.hasNext()) { final UnknownTypeValue u = i.next(); result = u.isZero(); } return result; } private void verifyPredecessorOfComparisonInstruction(final int indexOfInstructionToVerify) { final AbstractInsnNode predecessorOfComparisonInsn = controlFlowBlock .getBlockInstructionForIndex(indexOfInstructionToVerify); if (isGetInstructionForVariable(predecessorOfComparisonInsn, candidate)) { verifyGetInstructionForVariable(indexOfInstructionToVerify, controlFlowBlock, candidate); } else if (isLoadInstructionForAlias(candidate, controlFlowBlock, predecessorOfComparisonInsn)) { verifyLoadInstructionForAlias(indexOfInstructionToVerify, controlFlowBlock, candidate); } } } // class ZeroCheckAssignmentGuardVerifier private final class NonNullCheckAssignmentGuardVerifier { private final FieldNode candidate; private final ControlFlowBlock controlFlowBlock; private final JumpInsn assignmentGuard; public NonNullCheckAssignmentGuardVerifier(final FieldNode theCandidate, final ControlFlowBlock theControlFlowBlock, final JumpInsn theAssignmentGuard) { candidate = theCandidate; controlFlowBlock = theControlFlowBlock; assignmentGuard = theAssignmentGuard; } public void verify() { final String candidateName = candidate.name; if (isNotPossibleInitialValueOfCandidate(DefaultUnknownTypeValue.getInstanceForNull(), candidate)) { final String msgTemplate = "The assignment guard for lazy field [%s] should check against null. " + "Otherwise the field gets never initialised."; setterMethodChecker.setNonFinalFieldResult(format(msgTemplate, candidateName), candidateName); } else if (checksAgainstOtherObject(assignmentGuard, controlFlowBlock, candidate)) { } } } // class NonNullCheckAssignmentGuardVerifier private final Map<FieldNode, Collection<UnknownTypeValue>> initialValues; private final Map<FieldNode, Collection<JumpInsn>> assignmentGuards; private final CandidatesInitialisersMapping candidatesInitialisersMapping; private final AbstractSetterMethodChecker setterMethodChecker; private AssignmentGuardVerifier(final Map<FieldNode, Collection<UnknownTypeValue>> theInitialValues, final Map<FieldNode, Collection<JumpInsn>> theAssignmentGuards, final CandidatesInitialisersMapping theVariableInitialisersAssociation, final AbstractSetterMethodChecker theSetterMethodChecker) { initialValues = new HashMap<FieldNode, Collection<UnknownTypeValue>>(theInitialValues); assignmentGuards = new HashMap<FieldNode, Collection<JumpInsn>>(theAssignmentGuards); candidatesInitialisersMapping = theVariableInitialisersAssociation; setterMethodChecker = theSetterMethodChecker; } public static AssignmentGuardVerifier newInstance( final Map<FieldNode, Collection<UnknownTypeValue>> initialValues, final Map<FieldNode, Collection<JumpInsn>> assignmentGuards, final CandidatesInitialisersMapping variableInitialisersAssociation, final AbstractSetterMethodChecker setterMethodChecker) { return new AssignmentGuardVerifier(checkNotNull(initialValues), checkNotNull(assignmentGuards), checkNotNull(variableInitialisersAssociation), checkNotNull(setterMethodChecker)); } public void verify() { for (final Entry e : candidatesInitialisersMapping) { verifyEachCandidateInitialisersPair(e); } } private void verifyEachCandidateInitialisersPair(final Entry e) { if (hasAssignmentGuardFor(e.getCandidate())) { verifyEachInitialisingMethodForCandidate(e.getCandidate(), e.getInitialisers()); } else { setFieldCanBeReassignedResultBecauseOfMissingAssignmentGuards(e.getCandidate()); } } private boolean hasAssignmentGuardFor(final FieldNode candidate) { return assignmentGuards.containsKey(candidate); } private void verifyEachInitialisingMethodForCandidate(final FieldNode candidate, final Initialisers initialisers) { for (final MethodNode initialisingMethod : initialisers.getMethods()) { verifyEachAssignmentGuardWithinInitialisingMethod(candidate, initialisingMethod); } } private void verifyEachAssignmentGuardWithinInitialisingMethod(final FieldNode candidate, final MethodNode method) { final EnhancedClassNode enhancedClassNode = setterMethodChecker.getEnhancedClassNode(); final List<ControlFlowBlock> controlFlowBlocks = enhancedClassNode.getControlFlowBlocksForMethod(method); for (final JumpInsn assignmentGuard : assignmentGuards.get(candidate)) { final ControlFlowBlock block = getControlFlowBlockWhichCovers(controlFlowBlocks, assignmentGuard); if (isOneValueJumpInstruction(assignmentGuard)) { verifyOneValueAssignmentGuard(candidate, assignmentGuard, block); } else if (isTwoValuesJumpInstruction(assignmentGuard)) { verifyTwoValuesAssignmentGuard(candidate, assignmentGuard, block); } } } private ControlFlowBlock getControlFlowBlockWhichCovers(final Collection<ControlFlowBlock> controlFlowBlocks, final JumpInsn assignmentGuard) { for (final ControlFlowBlock controlFlowBlock : controlFlowBlocks) { if (controlFlowBlock.covers(assignmentGuard.getIndexWithinMethod())) { return controlFlowBlock; } } return null; } private static boolean isOneValueJumpInstruction(final JumpInsn jumpInstruction) { switch (getOpcode(jumpInstruction)) { case IFEQ: case IFNE: case IFLT: case IFGE: case IFGT: case IFLE: case IFNULL: case IFNONNULL: return true; default: return false; } } private void setFieldCanBeReassignedResultBecauseOfMissingAssignmentGuards(final FieldNode candidate) { final String msgTemplate = "Lazy initialisation requires at least one assignment guard for field [%s]"; final String msg = format(msgTemplate, candidate.name); setterMethodChecker.setResultForClass(msg, MutabilityReason.FIELD_CAN_BE_REASSIGNED); } private void verifyOneValueAssignmentGuard(final FieldNode candidate, final JumpInsn assignmentGuard, final ControlFlowBlock block) { if (checksAgainstZero(assignmentGuard)) { verifyZeroCheckAssignmentGuard(candidate, assignmentGuard, block); } else if (checksAgainstNonNull(assignmentGuard)) { verifyNonNullCheckAssignmentGuard(candidate, assignmentGuard, block); } } private static boolean checksAgainstZero(final JumpInsn jumpInstruction) { switch (getOpcode(jumpInstruction)) { case IFEQ: case IFNE: case IFLT: case IFGE: case IFGT: case IFLE: return true; default: return false; } } private void verifyZeroCheckAssignmentGuard(final FieldNode candidate, final JumpInsn assignmentGuard, final ControlFlowBlock controlFlowBlock) { final ZeroCheckAssignmentGuardVerifier v = new ZeroCheckAssignmentGuardVerifier(candidate, assignmentGuard, controlFlowBlock); v.verify(); } private static boolean checksAgainstNonNull(final JumpInsn jumpInstruction) { return IFNONNULL == getOpcode(jumpInstruction); } private static boolean isGetInstructionForVariable(final AbstractInsnNode insn, final FieldNode candidate) { boolean result = false; if (GETFIELD == insn.getOpcode() || GETSTATIC == insn.getOpcode()) { final FieldInsnNode getfield = (FieldInsnNode) insn; result = candidate.name.equals(getfield.name); } return result; } private static boolean isLoadInstructionForAlias(final FieldNode candidate, final ControlFlowBlock blockWithAssignmentGuard, final AbstractInsnNode insn) { final Finder<Alias> f = AliasFinder.newInstance(candidate.name, blockWithAssignmentGuard); final Alias alias = f.find(); return alias.doesExist && isLoadInstructionForAlias(insn, alias); } private static boolean isLoadInstructionForAlias(final AbstractInsnNode insn, final Alias alias) { boolean result = false; if (AbstractInsnNode.VAR_INSN == insn.getType()) { final VarInsnNode loadInstruction = (VarInsnNode) insn; result = loadInstruction.var == alias.localVariable; } return result; } private static boolean isComparisonInsn(final AbstractInsnNode abstractInsnNode) { switch (abstractInsnNode.getOpcode()) { case LCMP: case FCMPL: case FCMPG: case DCMPL: case DCMPG: case IF_ICMPEQ: case IF_ICMPNE: case IF_ICMPLT: case IF_ICMPGE: case IF_ICMPGT: case IF_ICMPLE: case IF_ACMPEQ: case IF_ACMPNE: return true; default: return false; } } private void verifyGetInstructionForVariable(final int indexOfPreComparisonInsn, final ControlFlowBlock block, final FieldNode candidate) { final int indexOfGetInstruction = indexOfPreComparisonInsn - 2; verifyComparativeValueOf(block.getBlockInstructionForIndex(indexOfGetInstruction), candidate); } private void verifyComparativeValueOf(final AbstractInsnNode insn, final FieldNode candidate) { final UnknownTypeValue comparativeValue = getComparativeValue(insn); if (isNotPossibleInitialValueOfCandidate(comparativeValue, candidate)) { final String msgTemplate = "Assignment for field [%s] guard does not check against a possible " + "initial value"; final String msg = String.format(msgTemplate, candidate.name); setterMethodChecker.setResultForClass(msg, MutabilityReason.FIELD_CAN_BE_REASSIGNED); } } private static UnknownTypeValue getComparativeValue(final AbstractInsnNode insn) { UnknownTypeValue result = null; if (AbstractInsnNode.INSN == insn.getType()) { final Opcode opcode = Opcode.forInt(insn.getOpcode()); result = opcode.stackValue(); } else if (AbstractInsnNode.LDC_INSN == insn.getType()) { final LdcInsnNode ldcInsn = (LdcInsnNode) insn; result = DefaultUnknownTypeValue.getInstance(ldcInsn.cst); } else if (AbstractInsnNode.INT_INSN == insn.getType()) { final IntInsnNode intInsnNode = (IntInsnNode) insn; result = DefaultUnknownTypeValue.getInstance(intInsnNode.operand); } return result; } private boolean isNotPossibleInitialValueOfCandidate(final UnknownTypeValue comparativeValue, final FieldNode candidate) { final boolean result; final Collection<UnknownTypeValue> possibleInitialValuesOfCandidate = initialValues.get(candidate); if (null != possibleInitialValuesOfCandidate) { result = !possibleInitialValuesOfCandidate.contains(comparativeValue); } else { result = true; } return result; } private void verifyLoadInstructionForAlias(final int indexOfLoadInstruction, final ControlFlowBlock block, final FieldNode candidate) { final int indexOfLoadInsnPredecessor = indexOfLoadInstruction - 1; verifyComparativeValueOf(block.getBlockInstructionForIndex(indexOfLoadInsnPredecessor), candidate); } private static boolean checksAgainstOtherObject(final JumpInsn assignmentGuard, final ControlFlowBlock block, final FieldNode candidate) { final int indexWithinBlock = assignmentGuard.getIndexWithinBlock(); final AbstractInsnNode possibleEqualsInsn = block.getBlockInstructionForIndex(indexWithinBlock - 1); final boolean result; if (isEqualsInstruction(possibleEqualsInsn)) { final AbstractInsnNode possibleGetInstructionForVariable = block .getBlockInstructionForIndex(indexWithinBlock - 3); result = isGetInstructionForVariable(possibleGetInstructionForVariable, candidate); } else { result = false; } return result; } private static boolean isEqualsInstruction(final AbstractInsnNode insn) { final boolean result; if (AbstractInsnNode.METHOD_INSN == insn.getType()) { final MethodInsnNode methodInsnNode = (MethodInsnNode) insn; result = methodInsnNode.name.equals("equals"); } else { result = false; } return result; } private static boolean isOtherObjectNotAnInitialValue(final JumpInsn assignmentGuard, final ControlFlowBlock block) { final int indexWithinBlock = assignmentGuard.getIndexWithinBlock(); final AbstractInsnNode predecessorInsn = block.getBlockInstructionForIndex(indexWithinBlock - 2); final boolean result; if (ACONST_NULL != predecessorInsn.getOpcode()) { result = false; } else { result = true; } return result; } private void verifyNonNullCheckAssignmentGuard(final FieldNode candidate, final JumpInsn assignmentGuard, final ControlFlowBlock controlFlowBlock) { final NonNullCheckAssignmentGuardVerifier v = new NonNullCheckAssignmentGuardVerifier(candidate, controlFlowBlock, assignmentGuard); v.verify(); } private static boolean isTwoValuesJumpInstruction(final JumpInsn assignmentGuard) { switch (getOpcode(assignmentGuard)) { case IF_ICMPEQ: case IF_ICMPNE: case IF_ICMPLT: case IF_ICMPGE: case IF_ICMPGT: case IF_ICMPLE: case IF_ACMPEQ: case IF_ACMPNE: return true; default: return false; } } private static int getOpcode(final JumpInsn jumpInstruction) { final JumpInsnNode jumpInsnNode = jumpInstruction.getJumpInsnNode(); return jumpInsnNode.getOpcode(); } private void verifyTwoValuesAssignmentGuard(final FieldNode candidate, final JumpInsn assignmentGuard, final ControlFlowBlock block) { final int indexOfPredecessor = assignmentGuard.getIndexWithinBlock() - 1; final AbstractInsnNode predecessor = block.getBlockInstructionForIndex(indexOfPredecessor); if (isGetInstructionForVariable(predecessor, candidate)) { verifyGetInstructionForVariable(indexOfPredecessor, block, candidate); } else if (isLoadInstructionForAlias(candidate, block, predecessor)) { verifyLoadInstructionForAlias(indexOfPredecessor, block, candidate); } } }