package org.mutabilitydetector.checkers;
/*
* #%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 java.lang.String.format;
import static org.mutabilitydetector.checkers.AccessModifierQuery.method;
import static org.mutabilitydetector.checkers.info.MethodIdentifier.forMethod;
import static org.mutabilitydetector.locations.CodeLocation.ClassLocation.fromInternalName;
import static org.mutabilitydetector.locations.Slashed.slashed;
import org.mutabilitydetector.MutabilityReason;
import org.mutabilitydetector.asmoverride.AsmVerifierFactory;
import org.mutabilitydetector.checkers.VarStack.VarStackSnapshot;
import org.mutabilitydetector.checkers.info.MethodIdentifier;
import org.mutabilitydetector.checkers.info.PrivateMethodInvocationInformation;
import org.mutabilitydetector.locations.CodeLocation.FieldLocation;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.analysis.BasicValue;
import org.objectweb.asm.tree.analysis.Frame;
/**
* This class checks, for each field, that there is no method available which can change the reference of the field.
*
* The check will pass iff there is no method available to change a reference for ANY field.
*
* @author Graham Allan / Grundlefleck at gmail dot com
*
*/
public final class OldSetterMethodChecker extends AsmMutabilityChecker {
private final PrivateMethodInvocationInformation privateMethodInvocationInfo;
private final AsmVerifierFactory verifierFactory;
private OldSetterMethodChecker(PrivateMethodInvocationInformation privateMethodInvocationInfo,
AsmVerifierFactory verifierFactory) {
this.privateMethodInvocationInfo = privateMethodInvocationInfo;
this.verifierFactory = verifierFactory;
}
public static OldSetterMethodChecker newSetterMethodChecker(PrivateMethodInvocationInformation privateMethodInvocationInfo, AsmVerifierFactory verifierFactory) {
return new OldSetterMethodChecker(privateMethodInvocationInfo, verifierFactory);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
return new SetterAssignmentVisitor(ownerClass,
access,
name,
desc,
signature,
exceptions,
verifierFactory);
}
class SetterAssignmentVisitor extends FieldAssignmentVisitor {
private final VarStack varStack = new VarStack();
public SetterAssignmentVisitor(String ownerName,
int access,
String name,
String desc,
String signature,
String[] exceptions,
AsmVerifierFactory verifierFactory) {
super(ownerName, access, name, desc, signature, exceptions, verifierFactory);
}
@Override
protected void visitFieldAssignmentFrame(Frame<BasicValue> assignmentFrame, FieldInsnNode fieldInsnNode, BasicValue stackValue) {
if (MethodIs.aConstructor(name) || isInvalidStackValue(stackValue)) { return; }
if (method(access).isStatic()) {
detectInStaticMethod(fieldInsnNode);
} else {
detectInInstanceMethod(fieldInsnNode);
}
}
private boolean isOnlyCalledFromConstructor() {
MethodIdentifier methodId = forMethod(slashed(this.owner), name + ":" + desc);
return privateMethodInvocationInfo.isOnlyCalledFromConstructor(methodId);
}
private void detectInStaticMethod(FieldInsnNode fieldInsnNode) {
String ownerOfReassignedField = fieldInsnNode.owner;
if (reassignedIsThisType(ownerOfReassignedField) && assignmentIsNotOnAParameter(fieldInsnNode)) {
setIsImmutableResult(fieldInsnNode.name);
}
}
private boolean assignmentIsNotOnAParameter(FieldInsnNode fieldInsnNode) {
/*
* This is a temporary hack/workaround. It's quite difficult to tell for sure if the owner of the reassigned
* field is a parameter. But if the type is not included in the parameter list, we can guess it's not
* (though it still may be).
*/
return this.desc.contains(fieldInsnNode.owner);
}
private boolean reassignedIsThisType(String ownerOfReassignedField) {
return this.owner.compareTo(ownerOfReassignedField) == 0;
}
private void detectInInstanceMethod(FieldInsnNode fieldInsnNode) {
if (isOnlyCalledFromConstructor()) { return; }
VarStackSnapshot varStackSnapshot = varStack.next();
if (varStackSnapshot.thisObjectWasAddedToStack()) {
// Throwing an NPE, assuming it's mutable for now.
setIsImmutableResult(fieldInsnNode.name);
}
}
@Override
public void visitFieldInsn(int opcode, String fieldsOwner, String fieldName, String fieldDesc) {
super.visitFieldInsn(opcode, fieldsOwner, fieldName, fieldDesc);
if (opcode == Opcodes.PUTFIELD) {
varStack.takeSnapshotOfVarsAtPutfield();
}
}
@Override
public void visitVarInsn(int opcode, int var) {
super.visitVarInsn(opcode, var);
varStack.visitVarInsn(var);
}
private void setIsImmutableResult(String fieldName) {
setResult(format("Field [%s] can be reassigned within method [%s]", fieldName, this.name),
FieldLocation.fieldLocation(fieldName, fromInternalName(owner)),
MutabilityReason.FIELD_CAN_BE_REASSIGNED);
}
}
}