/*
 * FindBugs - Find Bugs in Java programs
 * Copyright (C) 2005, University of Maryland
 * 
 * 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 edu.umd.cs.findbugs.detect;

import java.util.BitSet;
import java.util.Iterator;

import org.apache.bcel.Constants;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InvokeInstruction;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.Type;

import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.Detector;
import edu.umd.cs.findbugs.SystemProperties;
import edu.umd.cs.findbugs.ba.BasicBlock;
import edu.umd.cs.findbugs.ba.CFG;
import edu.umd.cs.findbugs.ba.CFGBuilderException;
import edu.umd.cs.findbugs.ba.ClassContext;
import edu.umd.cs.findbugs.ba.DataflowAnalysisException;
import edu.umd.cs.findbugs.ba.PostDominatorsAnalysis;
import edu.umd.cs.findbugs.ba.SignatureConverter;
import edu.umd.cs.findbugs.ba.SignatureParser;
import edu.umd.cs.findbugs.ba.heap.FieldSet;
import edu.umd.cs.findbugs.ba.heap.LoadDataflow;
import edu.umd.cs.findbugs.ba.heap.StoreDataflow;
import edu.umd.cs.findbugs.ba.type.TypeFrame;
import edu.umd.cs.findbugs.ba.vna.ValueNumber;
import edu.umd.cs.findbugs.ba.vna.ValueNumberDataflow;
import edu.umd.cs.findbugs.ba.vna.ValueNumberFrame;

/**
 * Signal an infinite loop if either:
 * we see a call to the same method with the same parameters, or
 * we see a call to the same (dynamically dispatched method), and there
 * has been no transfer of control.
 * 
 * <p>This does the same thing as InfiniteRecursiveLoop, but uses CFG-based
 * analysis for greater precision.</p>
 * 
 * @author Bill Pugh
 * @author David Hovemeyer	
 */
public @Deprecated class InfiniteRecursiveLoop2 implements Detector {
	private static final boolean DEBUG = SystemProperties.getBoolean("irl.debug");
	private static final String IRL_METHOD = SystemProperties.getProperty("irl.method");

	private BugReporter bugReporter;

	public InfiniteRecursiveLoop2(BugReporter bugReporter) {
		this.bugReporter = bugReporter;
	}

	/* (non-Javadoc)
	 * @see edu.umd.cs.findbugs.Detector#visitClassContext(edu.umd.cs.findbugs.ba.ClassContext)
	 */
	public void visitClassContext(ClassContext classContext) {
		Method[] methodList = classContext.getJavaClass().getMethods();
		for (Method method : methodList) {
			if (method.getCode() == null)
				continue;

			if (IRL_METHOD != null && !method.getName().equals(IRL_METHOD))
				continue;

			try {
				if (DEBUG) {
					System.out.println("Checking method " +
							SignatureConverter.convertMethodSignature(classContext.getJavaClass(), method));
				}
				analyzeMethod(classContext, method);
			} catch (CFGBuilderException e) {
				bugReporter.logError("Error checking for infinite recursive loop in " +
						SignatureConverter.convertMethodSignature(classContext.getJavaClass(), method), e);
			} catch (DataflowAnalysisException e) {
				bugReporter.logError("Error checking for infinite recursive loop in " +
						SignatureConverter.convertMethodSignature(classContext.getJavaClass(), method), e);
			}
		}
	}

	private void analyzeMethod(ClassContext classContext, Method method) throws CFGBuilderException, DataflowAnalysisException {
		CFG cfg = classContext.getCFG(method);

		// Look for recursive calls which either
		//   - postdominate the CFG entry, or
		//   - pass all of the parameters as arguments
		for (Iterator<BasicBlock> i = cfg.blockIterator(); i.hasNext(); ) {
			BasicBlock basicBlock = i.next();

			// Check if it's a method invocation.
			if (!basicBlock.isExceptionThrower())
				continue;
			InstructionHandle thrower = basicBlock.getExceptionThrower();
			Instruction ins = thrower.getInstruction();
			if (!(ins instanceof InvokeInstruction))
				continue;

			// Recursive call?
			if (isRecursiveCall((InvokeInstruction) ins, classContext, method)) {
				checkRecursiveCall(classContext, method, cfg, basicBlock, thrower, (InvokeInstruction) ins);
			}

			// Call to add(Object)?
			if (isCallToAdd((InvokeInstruction) ins, classContext.getConstantPoolGen())) {
				if (DEBUG) { System.out.println("Checking call to add..."); }
				checkCallToAdd(classContext, method, basicBlock, thrower);
			}
		}
	}

	private boolean isRecursiveCall(InvokeInstruction instruction, ClassContext classContext, Method method) {
		if ((instruction.getOpcode() == Constants.INVOKESTATIC) != method.isStatic())
			return false;

		ConstantPoolGen cpg = classContext.getConstantPoolGen();
		if (!instruction.getClassName(cpg).equals(classContext.getJavaClass().getClassName())
				|| !instruction.getName(cpg).equals(method.getName())
				|| !instruction.getSignature(cpg).equals(method.getSignature()))
			return false;

		return true;
	}

	private void checkRecursiveCall(
			ClassContext classContext,
			Method method,
			CFG cfg,
			BasicBlock basicBlock,
			InstructionHandle thrower,
			InvokeInstruction ins) throws DataflowAnalysisException, CFGBuilderException {

		if (DEBUG) {
			System.out.println("Checking recursive call in " +
					SignatureConverter.convertMethodSignature(classContext.getJavaClass(), method));
		}

		PostDominatorsAnalysis postDominators =
			classContext.getNonImplicitExceptionDominatorsAnalysis(method);

		ValueNumberDataflow vnaDataflow = classContext.getValueNumberDataflow(method);
		ValueNumberFrame vnaFrameAtEntry = vnaDataflow.getStartFact(cfg.getEntry());

		// Get blocks which postdominate the method entry
		BitSet entryPostDominators = postDominators.getAllDominatorsOf(cfg.getEntry());

		// How many arguments need to be checked to find out whether
		// the parameters are passed to recursive calls verbatim?
		int numArgsToCheck = new SignatureParser(method.getSignature()).getNumParameters();
		if (!method.isStatic())
			++numArgsToCheck;

		boolean report = false;

		// Check to see if this block postdominates the method entry,
		// and the called method is known exactly.
		report = entryPostDominators.get(basicBlock.getLabel())
				&& targetMethodKnownExactly(classContext, method, basicBlock, ins);

		if (!report) {
			// See if 
			// (1) all parameters are passed unconditionally as arguments
			// (2) no fields which might have been read have been written to
			//     (meaning a different path could be taken in the called method)
			report = allParamsPassedAsArgs(classContext, vnaDataflow, vnaFrameAtEntry, numArgsToCheck, basicBlock, ins)
					&& !checkedStateHasBeenModified(classContext, method, basicBlock);
		}

		if (report) {
			JavaClass javaClass = classContext.getJavaClass();
			BugInstance warning = new BugInstance(this, "IL_INFINITE_RECURSIVE_LOOP", HIGH_PRIORITY)
					.addClassAndMethod(javaClass, method)
					.addSourceLine(classContext, method, thrower);
			bugReporter.reportBug(warning);
		}
	}

	private boolean targetMethodKnownExactly(ClassContext classContext, Method method, BasicBlock basicBlock, InvokeInstruction ins)
			throws DataflowAnalysisException, CFGBuilderException {

		// Ways in which we can be confident that the called method
		// is the same as the calling method:
		// 1. invocation is nonvirtual: invokestatic or invokespecial
		// 2. method is private
		// 3. method or class is final
		// 4. receiver instance is the same as "this"
		// 5. receiver type is known exactly, and is the same as the class
		//    containing the calling method

		if (ins.getOpcode() == Constants.INVOKESTATIC || ins.getOpcode() == Constants.INVOKESPECIAL)
			return true;

		if (method.isPrivate())
			return true;

		if (method.isFinal() || classContext.getJavaClass().isFinal())
			return true;

		// See if caller and callee are the same.
		// Technically, the call could still dispatch to a subclass,
		// but that's pretty unlikely.
		ValueNumberDataflow vnaDataflow = classContext.getValueNumberDataflow(method);
		ValueNumber caller = vnaDataflow.getAnalysis().getThisValue();
		ValueNumberFrame frameAtCall = vnaDataflow.getStartFact(basicBlock);
		ValueNumber callee = frameAtCall.getInstance(ins, classContext.getConstantPoolGen());
		if (caller.equals(callee)) {
			return true;
		}

		TypeFrame typeFrame = classContext.getTypeDataflow(method).getStartFact(basicBlock); 
		int receiverStackSlot = typeFrame.getInstanceSlot(ins, classContext.getConstantPoolGen());

		if (!typeFrame.isExact(receiverStackSlot))
			return false;

		Type receiverType = typeFrame.getValue(receiverStackSlot);
		if (!(receiverType instanceof ObjectType))
			return false;

		return (((ObjectType) receiverType).getClassName().equals(
				classContext.getJavaClass().getClassName()));
	}

	private boolean allParamsPassedAsArgs(
			ClassContext classContext,
			ValueNumberDataflow vnaDataflow,
			ValueNumberFrame vnaFrameAtEntry,
			int numArgsToCheck,
			BasicBlock basicBlock,
			InvokeInstruction ins) throws DataflowAnalysisException {

		boolean allParamsPassedAsArgs = false;

		ValueNumberFrame vnaFrame = vnaDataflow.getStartFact(basicBlock);
		if (vnaFrame.isValid() && vnaFrame.getStackDepth() >= numArgsToCheck) {
			allParamsPassedAsArgs = true;

		checkArgsLoop:
			for (int arg = 0; arg < numArgsToCheck; ++arg) {
				ValueNumber paramVal = vnaFrameAtEntry.getValue(arg);
				ValueNumber argVal = vnaFrame.getOperand(ins, classContext.getConstantPoolGen(), arg); 

				if (DEBUG) {
					System.out.println("param="+paramVal.getNumber()+", arg=" + argVal.getNumber());
				}

				if (!paramVal.equals(argVal)) {
					allParamsPassedAsArgs = false;
					break checkArgsLoop;
				}
			}
		}

		return allParamsPassedAsArgs;
	}

	private boolean checkedStateHasBeenModified(ClassContext classContext, Method method, BasicBlock basicBlock)
			throws CFGBuilderException, DataflowAnalysisException {

		LoadDataflow loadDataflow = classContext.getLoadDataflow(method);
		FieldSet loadSet = loadDataflow.getStartFact(basicBlock);

		StoreDataflow storeDataflow = classContext.getStoreDataflow(method);
		FieldSet storeSet = storeDataflow.getStartFact(basicBlock);

		if (DEBUG) {
			System.out.println("Checking state: loads=" + loadSet + ", stores=" + storeSet);
		}

		if (loadSet.isEmpty() || storeSet.isEmpty())
			return false;

		// Bottom means "any field is potentially loaded or stored"
		if (loadSet.isBottom() || storeSet.isBottom())
			return true;

		// Top generally means dead code, so we should repress the warning
		if (loadSet.isTop() || storeSet.isTop())
			return true;

		return loadSet.isIntersectionNonEmpty(storeSet);
	}

	private boolean isCallToAdd(InvokeInstruction ins, ConstantPoolGen cpg) {
		return ins.getOpcode() != Constants.INVOKESTATIC 
			&& ins.getName(cpg).equals("add")
			&& ins.getSignature(cpg).equals("(Ljava/lang/Object;)Z");
	}

	private void checkCallToAdd(
			ClassContext classContext,
			Method method,
			BasicBlock basicBlock,
			InstructionHandle thrower) throws DataflowAnalysisException, CFGBuilderException {
		ValueNumberDataflow vnaDataflow = classContext.getValueNumberDataflow(method);
		ValueNumberFrame vnaFrame = vnaDataflow.getStartFact(basicBlock);

		if (vnaFrame.isValid() && vnaFrame.getStackDepth() >= 2) {
			ValueNumber top = vnaFrame.getStackValue(0);
			ValueNumber next = vnaFrame.getStackValue(1);
			if (DEBUG) {
				System.out.println("top=" + top.getNumber() + ", next=" + next.getNumber());
			}
			if (top.equals(next)) {
				JavaClass javaClass = classContext.getJavaClass();
				BugInstance warning = new BugInstance(this, "IL_CONTAINER_ADDED_TO_ITSELF", NORMAL_PRIORITY)
					.addClassAndMethod(javaClass, method)
					.addSourceLine(classContext, method, thrower);
				bugReporter.reportBug(warning);
			}
		}
	}

	/* (non-Javadoc)
	 * @see edu.umd.cs.findbugs.Detector#report()
	 */
	public void report() {
	}

}
