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 java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.NotThreadSafe;
import org.mutabilitydetector.checkers.settermethod.CandidatesInitialisersMapping.Initialisers;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*;
import com.google.common.base.Objects;
import com.google.common.base.Objects.ToStringHelper;
/**
* @author Juergen Fickel (jufickel@htwg-konstanz.de)
* @version 14.02.2013
*/
@NotThreadSafe
final class InitialValueFinder implements Finder<Set<UnknownTypeValue>> {
@Immutable
private static final class InitialValueFactory {
private final FieldNode variable;
public InitialValueFactory(final FieldNode theVariable) {
variable = theVariable;
}
public UnknownTypeValue getConcreteInitialValueFor(final AbstractInsnNode variableValueSetupInsn) {
final UnknownTypeValue result;
if (isLdcInsn(variableValueSetupInsn)) {
result = getInitialValueOfLdcInsn(variableValueSetupInsn);
} else if (isIntInsn(variableValueSetupInsn)) {
result = getInitialValueOfIntInsn(variableValueSetupInsn);
} else if (isStackConstantPushInsn(variableValueSetupInsn)) {
result = getInitialValueOfStackConstantInsn(variableValueSetupInsn);
} else {
result = getInitialValueOfUnknownTypeOfVariable();
}
return result;
}
private static boolean isLdcInsn(final AbstractInsnNode abstractInsnNode) {
return AbstractInsnNode.LDC_INSN == abstractInsnNode.getType();
}
private static UnknownTypeValue getInitialValueOfLdcInsn(final AbstractInsnNode setupInsn) {
final LdcInsnNode ldcInsn = (LdcInsnNode) setupInsn;
final Object cst = ldcInsn.cst;
return DefaultUnknownTypeValue.getInstance(cst);
}
private static boolean isIntInsn(final AbstractInsnNode abstractInsnNode) {
return AbstractInsnNode.INT_INSN == abstractInsnNode.getType();
}
private static UnknownTypeValue getInitialValueOfIntInsn(final AbstractInsnNode setupInsn) {
final IntInsnNode singleIntOperandInsn = (IntInsnNode) setupInsn;
final int operand = singleIntOperandInsn.operand;
return DefaultUnknownTypeValue.getInstance(Integer.valueOf(operand));
}
private static boolean isStackConstantPushInsn(final AbstractInsnNode setupInsn) {
final SortedSet<Opcode> constantsInstructions = Opcode.constants();
final Opcode opcode = Opcode.forInt(setupInsn.getOpcode());
return constantsInstructions.contains(opcode);
}
private static UnknownTypeValue getInitialValueOfStackConstantInsn(final AbstractInsnNode setupInsn) {
final Opcode opcode = Opcode.forInt(setupInsn.getOpcode());
return opcode.stackValue();
}
private UnknownTypeValue getInitialValueOfUnknownTypeOfVariable() {
final Type variableType = Type.getType(variable.desc);
final int typeSort = variableType.getSort();
final UnknownTypeValue result;
if (Type.OBJECT == typeSort || Type.ARRAY == typeSort || Type.METHOD == typeSort) {
result = DefaultUnknownTypeValue.getInstanceForUnknownReference();
} else {
result = DefaultUnknownTypeValue.getInstanceForUnknownPrimitive();
}
return result;
}
public UnknownTypeValue getJvmDefaultInitialValueFor(final Type type) {
final UnknownTypeValue result;
final int sort = type.getSort();
if (Type.BOOLEAN == sort) {
result = DefaultUnknownTypeValue.getInstance(Boolean.FALSE);
} else if (Type.BYTE == sort) {
result = DefaultUnknownTypeValue.getInstance(Byte.valueOf((byte) 0));
} else if (Type.CHAR == sort) {
result = DefaultUnknownTypeValue.getInstance(Character.valueOf((char) 0));
} else if (Type.SHORT == sort) {
result = DefaultUnknownTypeValue.getInstance(Short.valueOf((short) 0));
} else if (Type.INT == sort) {
result = DefaultUnknownTypeValue.getInstance(Integer.valueOf(0));
} else if (Type.LONG == sort) {
result = DefaultUnknownTypeValue.getInstance(Long.valueOf(0L));
} else if (Type.FLOAT == sort) {
result = DefaultUnknownTypeValue.getInstance(Float.valueOf(0.0F));
} else if (Type.DOUBLE == sort) {
result = DefaultUnknownTypeValue.getInstance(Double.valueOf(0.0D));
} else {
result = DefaultUnknownTypeValue.getInstanceForNull();
}
return result;
}
} // class JvmInitialValueFactory
private final FieldNode variable;
private final Initialisers initialisers;
private final EnhancedClassNode enhancedClassNode;
private final Set<UnknownTypeValue> possibleInitialValues;
private volatile boolean arePossibleInitialValuesAlreadyFound;
private InitialValueFinder(final FieldNode theVariable, final Initialisers theSetters,
final EnhancedClassNode theEnhancedClassNode) {
variable = theVariable;
initialisers = theSetters;
enhancedClassNode = theEnhancedClassNode;
final byte supposedMaximumOfPossibleInitialValues = 5;
possibleInitialValues = new HashSet<UnknownTypeValue>(supposedMaximumOfPossibleInitialValues);
arePossibleInitialValuesAlreadyFound = false;
}
/**
* Factory method for this class. None of the parameters must be
* {@code null}.
*
* @param variable
* the variable to find the initial value for.
* @param initialisers
* the initialisers for {@code variable}.
* @return a new instance of this class.
*/
public static InitialValueFinder newInstance(final FieldNode variable, final Initialisers initialisers,
final EnhancedClassNode enhancedClassNode) {
return new InitialValueFinder(checkNotNull(variable), checkNotNull(initialisers),
checkNotNull(enhancedClassNode));
}
/**
* Gets all possible values the given variable may have after initialisation
* of its class. {@link #run()} has to be invoked beforehand!
*
* @return all possible values the given variable may have after
* initialisation of its class. This is never {@code null} .
* @throws IllegalStateException
* if {@code run} was not invoked before this method.
*/
@Override
public Set<UnknownTypeValue> find() {
if (!arePossibleInitialValuesAlreadyFound) {
findPossibleInitialValues();
arePossibleInitialValuesAlreadyFound = true;
}
return Collections.unmodifiableSet(possibleInitialValues);
}
private void findPossibleInitialValues() {
if (hasNoConstructors()) {
addJvmInitialValueForVariable();
} else {
addConcreteInitialValuesByConstructor();
}
}
private boolean hasNoConstructors() {
final List<MethodNode> constructors = initialisers.getConstructors();
return constructors.isEmpty();
}
private void addJvmInitialValueForVariable() {
final InitialValueFactory factory = new InitialValueFactory(variable);
final Type type = Type.getType(variable.desc);
possibleInitialValues.add(factory.getJvmDefaultInitialValueFor(type));
}
private void addConcreteInitialValuesByConstructor() {
for (final MethodNode constructor : initialisers.getConstructors()) {
final AbstractInsnNode valueSetUpInsn = findValueSetUpInsnIn(constructor);
addSupposedInitialValueFor(valueSetUpInsn);
}
}
private AbstractInsnNode findValueSetUpInsnIn(final MethodNode constructor) {
final List<ControlFlowBlock> blocks = enhancedClassNode.getControlFlowBlocksForMethod(constructor);
final Finder<AssignmentInsn> f = EffectiveAssignmentInsnFinder.newInstance(variable, blocks);
final AssignmentInsn effectiveAssignmentInsn = f.find();
final int indexOfAssignmentInstruction = effectiveAssignmentInsn.getIndexWithinMethod();
final InsnList instructions = constructor.instructions;
return instructions.get(indexOfAssignmentInstruction - 1);
}
private void addSupposedInitialValueFor(final AbstractInsnNode variableValueSetupInsn) {
final InitialValueFactory factory = new InitialValueFactory(variable);
final UnknownTypeValue initialValue = factory.getConcreteInitialValueFor(variableValueSetupInsn);
possibleInitialValues.add(initialValue);
}
@Override
public String toString() {
final ToStringHelper helper = Objects.toStringHelper(this);
helper.add("variable", variable).add("setters", initialisers);
helper.add("possibleInitialValues", possibleInitialValues);
return helper.toString();
}
}