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 java.lang.String.format;
import static org.mutabilitydetector.checkers.AccessModifierQuery.field;
import static org.mutabilitydetector.locations.CodeLocation.ClassLocation.fromInternalName;
import static org.mutabilitydetector.locations.CodeLocation.FieldLocation.fieldLocation;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.NotThreadSafe;
import org.mutabilitydetector.MutabilityReason;
import org.mutabilitydetector.Reason;
import org.mutabilitydetector.checkers.AsmMutabilityChecker;
import org.mutabilitydetector.locations.CodeLocation.ClassLocation;
import org.mutabilitydetector.locations.CodeLocation.FieldLocation;
import org.objectweb.asm.*;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
/**
* @author Juergen Fickel (jufickel@htwg-konstanz.de)
* @version 13.03.2013
*/
@NotThreadSafe
abstract class AbstractSetterMethodChecker extends AsmMutabilityChecker {
protected CandidatesInitialisersMapping candidatesInitialisersMapping;
private final ClassNode classNode;
@GuardedBy("this")
private volatile EnhancedClassNode enhancedClassNode;
public AbstractSetterMethodChecker() {
candidatesInitialisersMapping = CandidatesInitialisersMapping.newInstance();
classNode = new ClassNode();
enhancedClassNode = null;
}
public final void accept(final ClassVisitor cv) {
classNode.accept(cv);
}
@Override
public final void visit(final int version, final int access, final String name, final String signature,
final String superName, final String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
classNode.visit(version, access, name, signature, superName, interfaces);
}
@Override
public final void visitSource(final String file, final String debug) {
classNode.visitSource(file, debug);
}
@Override
public final void visitOuterClass(final String owner, final String name, final String desc) {
classNode.visitOuterClass(owner, name, desc);
}
@Override
public final AnnotationVisitor visitAnnotation(final String desc, final boolean visible) {
return classNode.visitAnnotation(desc, visible);
}
@Override
public final void visitAttribute(final Attribute attr) {
classNode.visitAttribute(attr);
}
@Override
public final void visitInnerClass(final String name, final String outerName, final String innerName,
final int access) {
classNode.visitInnerClass(name, outerName, innerName, access);
}
@Override
public final FieldVisitor visitField(final int access, final String name, final String desc,
final String signature, final Object value) {
FieldVisitor result = super.visitField(0, null, null, null, null);
if (isCandidate(access)) {
result = new FieldNode(access, name, desc, signature, value);
candidatesInitialisersMapping.addCandidate((FieldNode) result);
} else if (field(access).isNotFinal() && field(access).isNotStatic()) {
setNonFinalFieldResult(name);
}
if (super.visitField(0, null, null, null, null) == result) {
result = classNode.visitField(access, name, desc, signature, value);
}
return result;
}
private static boolean isCandidate(final int access) {
return field(access).isPrivate() && field(access).isNotFinal();
}
@Override
public final MethodVisitor visitMethod(final int access, final String name, final String desc,
final String signature, final String[] exceptions) {
return classNode.visitMethod(access, name, desc, signature, exceptions);
}
@Override
public final void visitEnd() {
classNode.visitEnd();
verify();
}
/**
* Template method for verification of lazy initialisation.
*/
protected final void verify() {
collectInitialisers();
verifyCandidates();
verifyInitialisers();
collectPossibleInitialValues();
verifyPossibleInitialValues();
collectEffectiveAssignmentInstructions();
verifyEffectiveAssignmentInstructions();
collectAssignmentGuards();
verifyAssignmentGuards();
end();
}
protected abstract void collectInitialisers();
protected abstract void verifyCandidates();
protected abstract void verifyInitialisers();
protected abstract void collectPossibleInitialValues();
protected abstract void verifyPossibleInitialValues();
protected abstract void collectEffectiveAssignmentInstructions();
protected abstract void verifyEffectiveAssignmentInstructions();
protected abstract void collectAssignmentGuards();
protected abstract void verifyAssignmentGuards();
protected void end() {}
protected final EnhancedClassNode getEnhancedClassNode() {
EnhancedClassNode result = enhancedClassNode;
if (null == result) {
synchronized (this) {
result = enhancedClassNode;
if (null == result) {
result = EnhancedClassNode.newInstance(classNode);
enhancedClassNode = result;
}
}
}
return result;
}
protected void setResultForClass(final String message, final Reason reason) {
super.setResult(message, fromInternalName(classNode.name), reason);
}
final void setNonFinalFieldResult(final String variableName) {
final String msg = "Field is not final, if shared across threads the Java Memory Model will not"
+ " guarantee it is initialised before it is read.";
setNonFinalFieldResult(msg, variableName);
}
final void setNonFinalFieldResult(final String message, final String variableName) {
final FieldLocation location = fieldLocation(variableName, ClassLocation.fromInternalName(ownerClass));
setResult(message, location, MutabilityReason.NON_FINAL_FIELD);
}
final void setFieldCanBeReassignedResult(final String variableName, final String methodName) {
final String msgTemplate = "Field [%s] can be reassigned within method [%s]";
final String msg = format(msgTemplate, variableName, methodName);
setFieldCanBeReassignedResult(msg);
}
final void setFieldCanBeReassignedResult(final String message) {
setResultForClass(message, MutabilityReason.FIELD_CAN_BE_REASSIGNED);
}
final void setMutableTypeToFieldResult(final String message, final String variableName) {
final FieldLocation location = fieldLocation(variableName, ClassLocation.fromInternalName(ownerClass));
setResult(message, location, MutabilityReason.MUTABLE_TYPE_TO_FIELD);
}
@Override
public String toString() {
return getClass().getSimpleName()
+ ", [candidatesInitialisersMapping=" + candidatesInitialisersMapping
+ ", classNode=" + classNode
+ ", enhancedClassNode=" + enhancedClassNode + "]";
}
}