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.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static org.objectweb.asm.Opcodes.*; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.annotation.concurrent.Immutable; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.FieldInsnNode; import org.objectweb.asm.tree.JumpInsnNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.VarInsnNode; /** * * * @author Juergen Fickel * @version 02.03.2013 */ @Immutable final class AssignmentGuardFinder implements Finder<JumpInsn> { private final String candidateName; private final ControlFlowBlock controlFlowBlock; private AssignmentGuardFinder(final String theCandidateName, final ControlFlowBlock theControlFlowBlock) { candidateName = theCandidateName; controlFlowBlock = theControlFlowBlock; } /** * Creates a new instance of this class. None of the arguments must be * {@code null}. * * @param candidateName * name of the lazy variable. Must not be empty! * @param controlFlowBlock * the control flow block which is supposed to contain an * {@link AssignmentGuard}. * @return a new instance of this class. */ public static AssignmentGuardFinder newInstance(final String candidateName, final ControlFlowBlock controlFlowBlock) { checkArgument(!candidateName.isEmpty()); return new AssignmentGuardFinder(candidateName, checkNotNull(controlFlowBlock)); } @Override public JumpInsn find() { final Collection<JumpInsn> supposedAssignmentGuards = collectSupposedAssignmentGuards(); if (1 < supposedAssignmentGuards.size()) { throw new IllegalStateException("There exists more than one assignment guard in this block."); } for (final JumpInsn jumpInsn : supposedAssignmentGuards) { return jumpInsn; } return NullJumpInsn.getInstance(); } private Collection<JumpInsn> collectSupposedAssignmentGuards() { final Set<JumpInsn> result = new HashSet<JumpInsn>(); for (final JumpInsn jumpInsn : collectAllConditionCheckInstructionsOfBlock()) { final AssignmentGuard.Builder builder = new AssignmentGuard.Builder(jumpInsn); final JumpInsn possibleAssignmentGuard = getAssignmentGuard(jumpInsn.getIndexWithinBlock(), builder); if (possibleAssignmentGuard.isAssignmentGuard()) { result.add(possibleAssignmentGuard); } } return result; } private Collection<JumpInsn> collectAllConditionCheckInstructionsOfBlock() { final ArrayList<JumpInsn> result = new ArrayList<JumpInsn>(); int indexWithinBlock = 0; for (final AbstractInsnNode insn : controlFlowBlock.getBlockInstructions()) { if (isConditionCheckInstruction(insn)) { final JumpInsnNode jumpInsnNode = (JumpInsnNode) insn; final int indexWithinMethod = controlFlowBlock.getIndexWithinMethod(indexWithinBlock); result.add(DefaultJumpInsn.newInstance(jumpInsnNode, indexWithinBlock, indexWithinMethod)); } indexWithinBlock++; } result.trimToSize(); return result; } private static boolean isConditionCheckInstruction(final AbstractInsnNode insn) { final int opcode = insn.getOpcode(); return AbstractInsnNode.JUMP_INSN == insn.getType() && opcode != Opcodes.GOTO && opcode != Opcodes.JSR && opcode != Opcodes.RET; } private JumpInsn getAssignmentGuard(final int indexOfInstructionToAnalyse, final AssignmentGuard.Builder builder) { final JumpInsn result; final List<AbstractInsnNode> blockInstructions = controlFlowBlock.getBlockInstructions(); final int indexOfPredecessorInstruction = indexOfInstructionToAnalyse - 1; final AbstractInsnNode predecessorInstruction = blockInstructions.get(indexOfPredecessorInstruction); builder.addPredecessorInstruction(predecessorInstruction); if (isGetfieldOrGetstaticForCandidate(predecessorInstruction)) { result = builder.build(); } else if (isLoadInstructionForAlias(predecessorInstruction)) { result = builder.build(); } else if (isEqualsInstruction(predecessorInstruction)) { result = NullJumpInsn.getInstance(); } else if (isPushNullOntoStackInstruction(predecessorInstruction)) { result = getAssignmentGuard(indexOfPredecessorInstruction, builder); } else if (isComparisonInstruction(predecessorInstruction)) { result = getAssignmentGuard(indexOfPredecessorInstruction, builder); } else { result = NullJumpInsn.getInstance(); } return result; } private boolean isGetfieldOrGetstaticForCandidate(final AbstractInsnNode insn) { boolean result = false; if (GETFIELD == insn.getOpcode() || GETSTATIC == insn.getOpcode()) { final FieldInsnNode getInstruction = (FieldInsnNode) insn; result = candidateName.equals(getInstruction.name); } return result; } private boolean isLoadInstructionForAlias(final AbstractInsnNode insn) { final Finder<Alias> f = AliasFinder.newInstance(candidateName, controlFlowBlock); 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 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 isPushNullOntoStackInstruction(final AbstractInsnNode insn) { return ACONST_NULL == insn.getOpcode(); } private static boolean isComparisonInstruction(final AbstractInsnNode insn) { switch (insn.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; } } @Override public String toString() { final StringBuilder b = new StringBuilder(); b.append(getClass().getSimpleName()).append(" [candidateName=").append(candidateName); b.append(", controlFlowBlock=").append(controlFlowBlock).append(']'); return b.toString(); } }