/** * */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 java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.NotThreadSafe; import javax.annotation.concurrent.ThreadSafe; import org.objectweb.asm.Label; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.LabelNode; import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.analysis.Analyzer; import org.objectweb.asm.tree.analysis.AnalyzerException; import org.objectweb.asm.tree.analysis.BasicInterpreter; import org.objectweb.asm.tree.analysis.BasicValue; /** * Range of a method's instructions which is delimited by label nodes. * * @author Juergen Fickel (jufickel@htwg-konstanz.de) * @version 18.02.2013 */ @ThreadSafe final class ControlFlowBlock implements Comparable<ControlFlowBlock> { @Immutable static final class Range { /** Value of this range's lower boundary. */ public final int lowerBoundary; /** Value of this range's upper boundary. */ public final int upperBoundary; /** All items of this range. */ public final List<Integer> allItems; private Range(final int theLowerBoundary, final int theUpperBoundary, final List<Integer> allItems) { lowerBoundary = theLowerBoundary; upperBoundary = theUpperBoundary; this.allItems = Collections.unmodifiableList(allItems); } public static Range newInstance(final SortedSet<Integer> allItems) { final List<Integer> allItemsList = new ArrayList<Integer>(allItems.size()); for (final Integer item : new TreeSet<Integer>(allItems)) { allItemsList.add(item); } final int lowerBoundary = allItemsList.isEmpty() ? -1 : allItemsList.get(0); final int upperBoundary = allItemsList.isEmpty() ? -1 : allItemsList.get(allItemsList.size() - 1); return new Range(lowerBoundary, upperBoundary, allItemsList); } public boolean covers(final int index) { return allItems.contains(Integer.valueOf(index)); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + allItems.hashCode(); result = prime * result + lowerBoundary; result = prime * result + upperBoundary; return result; } @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof Range)) { return false; } final Range other = (Range) obj; if (!allItems.equals(other.allItems)) { return false; } if (lowerBoundary != other.lowerBoundary) { return false; } if (upperBoundary != other.upperBoundary) { return false; } return true; } @Override public String toString() { final StringBuilder builder = new StringBuilder(); builder.append(getClass().getSimpleName()); builder.append(" [lowerBoundary=").append(lowerBoundary).append(", upperBoundary=").append(upperBoundary); builder.append(", allItems=").append(allItems).append(']'); return builder.toString(); } } // class Range @NotThreadSafe private static final class Builder { private final int blockNumber; private final String identifier; private final InsnList allInstructions; private final SortedSet<Integer> rangeItems; public Builder(final int theBlockNumber, final String theIdentifier, final InsnList allInstructions) { checkArgument(!theIdentifier.isEmpty()); blockNumber = theBlockNumber; identifier = theIdentifier; this.allInstructions = checkNotNull(allInstructions); rangeItems = new TreeSet<Integer>(); } public void addInstruction(final int instructionIndex) { rangeItems.add(Integer.valueOf(instructionIndex)); } public ControlFlowBlock build() { final Range range = Range.newInstance(rangeItems); return ControlFlowBlock.newInstance(blockNumber, identifier, allInstructions, range); } } // class Builder @ThreadSafe public static final class ControlFlowBlockFactory { private final String owner; private final MethodNode method; private final InsnList allInstructions; private final List<ControlFlowBlock> controlFlowBlocks; private final AtomicInteger currentBlockNumber; private final Analyzer<BasicValue> analyser = new Analyzer<BasicValue>(new BasicInterpreter()) { @Override protected void newControlFlowEdge(final int src, final int dest) { interlinkControlFlowBlocks(src, dest); } private void interlinkControlFlowBlocks(final int src, final int dest) { ControlFlowBlock srcBlock = null; ControlFlowBlock destBlock = null; for (final ControlFlowBlock b : controlFlowBlocks) { if (b.covers(src)) { srcBlock = b; } else if (b.covers(dest)) { destBlock = b; } } if (null != srcBlock && null != destBlock) { srcBlock.successors.add(destBlock); destBlock.predecessors.add(srcBlock); } } }; private ControlFlowBlockFactory(final String theOwner, final MethodNode theMethod) { method = theMethod; owner = theOwner; allInstructions = theMethod.instructions; controlFlowBlocks = new ArrayList<ControlFlowBlock>(); currentBlockNumber = new AtomicInteger(0); } public static ControlFlowBlockFactory newInstance(final String owner, final MethodNode method) { checkArgument(!owner.isEmpty()); final ControlFlowBlockFactory result = new ControlFlowBlockFactory(owner, checkNotNull(method)); result.createAllControlFlowBlockBuilders(); result.analyseMethod(); return result; } private void createAllControlFlowBlockBuilders() { final Builder builder = handleFirstInstruction(); handleRemainingInstructions(builder); } private Builder handleFirstInstruction() { final Builder result; final AbstractInsnNode firstInsn = allInstructions.get(currentBlockNumber.get()); if (isLabel(firstInsn)) { result = createNewControlFlowBlockBuilderForLabel(firstInsn); } else { result = new Builder(currentBlockNumber.getAndIncrement(), "L<Pseudo>", allInstructions); } result.addInstruction(0); return result; } private static boolean isLabel(final AbstractInsnNode insn) { return AbstractInsnNode.LABEL == insn.getType(); } private Builder createNewControlFlowBlockBuilderForLabel(final AbstractInsnNode insn) { final LabelNode labelNode = (LabelNode) insn; final Label label = labelNode.getLabel(); return new Builder(currentBlockNumber.getAndIncrement(), label.toString(), allInstructions); } private void handleRemainingInstructions(final Builder controlFlowBlockBuilder) { Builder builder = controlFlowBlockBuilder; for (int i = 1; i < allInstructions.size(); i++) { final AbstractInsnNode insn = allInstructions.get(i); if (isLabel(insn)) { controlFlowBlocks.add(builder.build()); builder = createNewControlFlowBlockBuilderForLabel(insn); } builder.addInstruction(i); } controlFlowBlocks.add(builder.build()); } private void analyseMethod() { tryToAnalyseMethod(); } private void tryToAnalyseMethod() { try { analyser.analyze(owner, method); } catch (final AnalyzerException e) { e.printStackTrace(); } } public List<ControlFlowBlock> getAllControlFlowBlocksForMethod() { final ArrayList<ControlFlowBlock> result = new ArrayList<ControlFlowBlock>(controlFlowBlocks.size()); for (final ControlFlowBlock b : controlFlowBlocks) { if (b.isNotEmpty()) { result.add(b); } } result.trimToSize(); return result; } } // class ControlFlowFactory private final int blockNumber; private final String identifier; private final InsnList methodInstructions; private final Range rangeOfBlockInstructions; private final Set<ControlFlowBlock> predecessors; private final Set<ControlFlowBlock> successors; private int hashCode; private String stringRepresentation; private ControlFlowBlock(final int theBlockNumber, final String theIdentifier, final InsnList theMethodInstructions, final Range theRangeOfBlockInstructionIndices) { blockNumber = theBlockNumber; identifier = theIdentifier; methodInstructions = theMethodInstructions; rangeOfBlockInstructions = theRangeOfBlockInstructionIndices; predecessors = new HashSet<ControlFlowBlock>(); successors = new HashSet<ControlFlowBlock>(); hashCode = 0; stringRepresentation = null; } public static ControlFlowBlock newInstance(final int blockNumber, final String identifier, final InsnList methodInstructions, final Range rangeOfInstructionIndices) { checkArgument(!identifier.isEmpty()); return new ControlFlowBlock(blockNumber, identifier, checkNotNull(methodInstructions), checkNotNull(rangeOfInstructionIndices)); } public boolean isEmpty() { final boolean result; final List<AbstractInsnNode> blockInstructions = getBlockInstructions(); if (blockInstructions.isEmpty()) { result = true; } else { if (1 == blockInstructions.size()) { final AbstractInsnNode soleBlockInstruction = blockInstructions.get(0); result = AbstractInsnNode.LABEL == soleBlockInstruction.getType(); } else { result = false; } } return result; } public boolean isNotEmpty() { return !isEmpty(); } public List<AbstractInsnNode> getBlockInstructions() { final ArrayList<AbstractInsnNode> result = new ArrayList<AbstractInsnNode>(); for (int i = rangeOfBlockInstructions.lowerBoundary; i <= rangeOfBlockInstructions.upperBoundary; i++) { result.add(methodInstructions.get(i)); } result.trimToSize(); return result; } public String getIdentifier() { return identifier; } public int getBlockNumber() { return blockNumber; } public boolean covers(final int someInstructionIndex) { return rangeOfBlockInstructions.covers(someInstructionIndex); } /* * For test purposes only. */ boolean isDirectPredecessorOf(final ControlFlowBlock successor) { return successors.contains(successor); } /* * For test purposes only. */ boolean isPredecessorOf(final ControlFlowBlock possibleSuccessor) { boolean result = false; for (final ControlFlowBlock directSuccessor : successors) { if (directSuccessor.equals(possibleSuccessor) || directSuccessor.isPredecessorOf(possibleSuccessor)) { result = true; break; } } return result; } /* * For test purposes only. */ boolean isSuccessorOf(final ControlFlowBlock possiblePredecessor) { return possiblePredecessor.isPredecessorOf(this); } public AbstractInsnNode getBlockInstructionForIndex(final int index) { final int resultIndex = rangeOfBlockInstructions.lowerBoundary + index; AbstractInsnNode result; try { result = methodInstructions.get(resultIndex); } catch (final IndexOutOfBoundsException e) { result = null; } return result; } public int getIndexWithinMethod(final int indexWithinBlock) { final int result = indexWithinBlock + rangeOfBlockInstructions.lowerBoundary; if (rangeOfBlockInstructions.upperBoundary < result) { final String msgTemplate = "Index would be %d which is bigger than the maximum index of method (%d)."; final String msg = String.format(msgTemplate, result, rangeOfBlockInstructions.upperBoundary); throw new IndexOutOfBoundsException(msg); } return result; } public int getIndexWithinBlock(final int indexWithinMethod) { return indexWithinMethod - rangeOfBlockInstructions.lowerBoundary; } public Set<ControlFlowBlock> getPredecessors() { return Collections.unmodifiableSet(predecessors); } public Set<ControlFlowBlock> getSuccessors() { return Collections.unmodifiableSet(successors); } @Override public int compareTo(final ControlFlowBlock o) { final Integer thisBlockNumber = Integer.valueOf(blockNumber); final Integer otherBlockNumber = Integer.valueOf(o.blockNumber); return thisBlockNumber.compareTo(otherBlockNumber); } @Override public synchronized int hashCode() { if (0 == hashCode) { final int prime = 31; int result = 1; result = prime * result + blockNumber; result = prime * result + identifier.hashCode(); result = prime * result + methodInstructions.hashCode(); result = prime * result + rangeOfBlockInstructions.hashCode(); result = prime * result + predecessors.hashCode(); result = prime * result + successors.hashCode(); hashCode = result; } return hashCode; } @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof ControlFlowBlock)) { return false; } final ControlFlowBlock other = (ControlFlowBlock) obj; if (blockNumber != other.blockNumber) { return false; } if (!identifier.equals(other.identifier)) { return false; } if (!methodInstructions.equals(other.methodInstructions)) { return false; } if (!rangeOfBlockInstructions.equals(other.rangeOfBlockInstructions)) { return false; } if (!predecessors.equals(other.predecessors)) { return false; } if (!successors.equals(other.successors)) { return false; } return true; } @Override public synchronized String toString() { if (null == stringRepresentation) { final StringBuilder builder = new StringBuilder(getClass().getSimpleName()); builder.append("[blockNumber=").append(blockNumber); builder.append(", identifier=").append(identifier); builder.append(", rangeOfBlockInstructionIndices=").append(rangeOfBlockInstructions); builder.append(", predecessors=").append(setToString(predecessors)); builder.append(", successors=").append(setToString(successors)); builder.append(']'); stringRepresentation = builder.toString(); } return stringRepresentation; } private static String setToString(final Set<ControlFlowBlock> cfbSet) { final StringBuilder result = new StringBuilder(); result.append('{'); final String separator = ", "; String sep = ""; for (final ControlFlowBlock controlFlowBlock : cfbSet) { result.append(sep).append(controlFlowBlock.getBlockNumber()); sep = separator; } result.append('}'); return result.toString(); } }