All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mutabilitydetector.checkers.SetterMethodChecker Maven / Gradle / Ivy

There is a newer version: 0.10.6
Show newest version
/* 
 * Mutability Detector
 *
 * Copyright 2009 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.
 */
package org.mutabilitydetector.checkers;

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.ClassLocation.fromInternalName;
import static org.mutabilitydetector.locations.Slashed.slashed;

import org.mutabilitydetector.MutabilityReason;
import org.mutabilitydetector.checkers.VarStack.VarStackSnapshot;
import org.mutabilitydetector.checkers.info.MethodIdentifier;
import org.mutabilitydetector.checkers.info.PrivateMethodInvocationInformation;
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
 * 
 */
public class SetterMethodChecker extends AbstractMutabilityChecker {
	
	
	private PrivateMethodInvocationInformation privateMethodInvocationInfo;
	
	/**
	 * @see #newSetterMethodChecker(PrivateMethodInvocationInformation)
	 */
	private SetterMethodChecker(PrivateMethodInvocationInformation privateMethodInvocationInfo) {
		this.privateMethodInvocationInfo = privateMethodInvocationInfo;
	}
	
	public static SetterMethodChecker newSetterMethodChecker(PrivateMethodInvocationInformation privateMethodInvocationInfo) {
		return new SetterMethodChecker(privateMethodInvocationInfo);
	}

	@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, privateMethodInvocationInfo);
	}

	class SetterAssignmentVisitor extends FieldAssignmentVisitor {

		private VarStack varStack = new VarStack();
		private final PrivateMethodInvocationInformation privateMethodInvocationInfo;

		public SetterAssignmentVisitor(String ownerName, int access, String name, String desc, String signature, String[] exceptions, 
				PrivateMethodInvocationInformation privateMethodInvocationInfo) 
		{
			super(ownerName, access, name, desc, signature, exceptions);
			this.privateMethodInvocationInfo = privateMethodInvocationInfo;
		}
		
		protected void visitFieldAssignmentFrame(Frame assignmentFrame, FieldInsnNode fieldInsnNode, BasicValue stackValue) {
			if (isConstructor() || isInvalidStackValue(stackValue)) {
				return;
			}
			
			if(method(access).isStatic()) {
				detectInStaticMethod(fieldInsnNode);
			} else {
				detectInInstanceMethod(fieldInsnNode, stackValue);
			}
			
		}

		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).
			 */
			boolean reassignmentIsOnATypeIncludedInParameters = this.desc.contains(fieldInsnNode.owner);
			
			return reassignmentIsOnATypeIncludedInParameters;
		}

		private boolean reassignedIsThisType(String ownerOfReassignedField) {
			return this.owner.compareTo(ownerOfReassignedField) == 0;
		}

		private void detectInInstanceMethod(FieldInsnNode fieldInsnNode, BasicValue stackValue) {
			if(isOnlyCalledFromConstructor()) {
				return;
			}
			
			VarStackSnapshot varStackSnapshot = varStack.next();
			if(varStackSnapshot.thisObjectWasAddedToStack()) {
				
				int indexOfOwningObject = varStackSnapshot.indexOfOwningObject();
				if(isThisObject(indexOfOwningObject)) { 
					setIsImmutableResult(fieldInsnNode.name);
				} else {
					// Setting field on other instance of 'this' type
				}
				
			}
		}

		@Override
		public void visitFieldInsn(int opcode, String owner, String name, String desc) {
			super.visitFieldInsn(opcode, owner, name, desc);
			if(opcode == Opcodes.PUTFIELD) {
				varStack.takeSnapshotOfVarsAtPutfield();
			}
		}
		
		private boolean isThisObject(int indexOfOwningObject) {
			return indexOfOwningObject == 0;
		}
		
		
		@Override
		public void visitVarInsn(int opcode, int var) {
			super.visitVarInsn(opcode, var);
			varStack.visitVarInsn(opcode, var);
		}

		private boolean isConstructor() {
			return "".equals(name);
		}


		private void setIsImmutableResult(String fieldName) {
			String message = format("Field [%s] can be reassigned within method [%s]", fieldName, this.name);
			addResult(message, fromInternalName(owner), MutabilityReason.FIELD_CAN_BE_REASSIGNED);
		}
		
	}
	
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy