/*
 * fb-contrib - Auxiliary detectors for Java programs
 * Copyright (C) 2005-2014 Dave Brosius
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package com.mebigfatguy.fbcontrib.detect;

import java.util.Map;

import org.apache.bcel.Constants;
import org.apache.bcel.classfile.Code;
import org.apache.bcel.generic.Type;

import com.mebigfatguy.fbcontrib.collect.MethodInfo;
import com.mebigfatguy.fbcontrib.collect.Statistics;
import com.mebigfatguy.fbcontrib.collect.StatisticsKey;

import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.BytecodeScanningDetector;
import edu.umd.cs.findbugs.OpcodeStack;
import edu.umd.cs.findbugs.ba.ClassContext;
import edu.umd.cs.findbugs.classfile.ClassDescriptor;

public class OverlyPermissiveMethod extends BytecodeScanningDetector {

	private BugReporter bugReporter;
	private OpcodeStack stack;
	private String callingPackage;
	private String callingClass;
	
	public OverlyPermissiveMethod(BugReporter bugReporter) {
		this.bugReporter = bugReporter;
	}
	
	@Override
	public void visitClassContext(ClassContext classContext) {
		try {
			ClassDescriptor cd = classContext.getClassDescriptor();
			callingClass = cd.getClassName();
			callingPackage = cd.getPackageName();
			stack = new OpcodeStack();
			super.visitClassContext(classContext);
		} finally {
			callingPackage = null;
			stack = null;
		}
	}
	
	@Override
	public void visitCode(Code obj) {
		stack.resetForMethodEntry(this);
		super.visitCode(obj);
	}
	
	@Override
	public void sawOpcode(int seen) {
		try {
			stack.precomputation(this);

			switch (seen) {
				case INVOKEVIRTUAL:
				case INVOKEINTERFACE:
				case INVOKESTATIC:
				case INVOKESPECIAL:
					String calledClass = getClassConstantOperand();
					String sig = getSigConstantOperand();
					MethodInfo mi = Statistics.getStatistics().getMethodStatistics(calledClass, getNameConstantOperand(), sig);
					if (mi != null) {
						if (seen == INVOKEINTERFACE) {
							mi.addCallingAccess(Constants.ACC_PUBLIC);
						} else {
							String calledPackage;
							int slashPos = calledClass.lastIndexOf('/');
							if (slashPos >= 0) {
								calledPackage = calledClass.substring(0, slashPos);
							} else {
								calledPackage = "";
							}
							boolean sameClass = calledClass.equals(callingClass);
							boolean samePackage = calledPackage.equals(callingPackage);
							
							if (sameClass) {
								mi.addCallingAccess(Constants.ACC_PRIVATE);
							} else if (samePackage) {
								mi.addCallingAccess(0);
							} else {
								if (seen == INVOKESTATIC) {
									mi.addCallingAccess(Constants.ACC_PUBLIC);
								} else if (isCallingOnThis(sig)) {
									mi.addCallingAccess(Constants.ACC_PROTECTED);
								} else {
									mi.addCallingAccess(Constants.ACC_PUBLIC);
								}
							}
						}
					}
					break;
					
				default:
					break;
			}
		} finally {
			stack.sawOpcode(this, seen);
		}
	}
	
	public boolean isCallingOnThis(String sig) {
		Type[] argTypes = Type.getArgumentTypes(sig);
		if (stack.getStackDepth() < argTypes.length) {
			return false;
		}
		
		OpcodeStack.Item item = stack.getStackItem(argTypes.length);
		return item.getRegisterNumber() == 0;
	}
	
	@Override
	public void report() {		
		for (Map.Entry<StatisticsKey, MethodInfo> entry : Statistics.getStatistics()) {
			MethodInfo mi = entry.getValue();
			
			int declaredAccess = mi.getDeclaredAccess();
			if ((declaredAccess & Constants.ACC_PRIVATE) != 0) {
				continue;
			}

			if (mi.wasCalledPublicly() || !mi.wasCalled()) {
				continue;
			}
			
			StatisticsKey key = entry.getKey();
			
			if (isOverlyPermissive(declaredAccess, mi)) {
				bugReporter.reportBug(new BugInstance(this, "OPM_OVERLY_PERMISSIVE_METHOD", NORMAL_PRIORITY)
								.addClass(key.getClassName())
								.addMethod(key.getClassName(), key.getMethodName(), key.getSignature(), (declaredAccess & Constants.ACC_STATIC) != 0));
			}
		}
	}
		
	private boolean isOverlyPermissive(int declaredAccess, MethodInfo mi) {
		if ((declaredAccess & Constants.ACC_PUBLIC) != 0) {
			return true;
		}
		
		//TODO: add more permission checks
		
		//TODO Need to see if this method is defined in a superclass or interface and ignore here
		//TODO really need to see if someone derives from this as well.
		return false;
	}
}

@@ -4372,24 +4372,100 @@
 ess, mi)) %7B%0A
+%09%09%09%09//TODO: check to see that this method isn't derived from something%0A%09%09%09%09%0A
 %09%09%09%09bugRepor
@@ -4921,169 +4921,8 @@
 cks%0A
-%09%09%0A%09%09//TODO Need to see if this method is defined in a superclass or interface and ignore here%0A%09%09//TODO really need to see if someone derives from this as well.%0A
 %09%09re

move todo comment