/*
 * FindBugs - Find bugs in Java programs
 * Copyright (C) 2003-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 org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.Method;

import edu.umd.cs.findbugs.BugAccumulator;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.OpcodeStack;
import edu.umd.cs.findbugs.StatelessDetector;
import edu.umd.cs.findbugs.bcel.OpcodeStackDetector;

public class FindNonShortCircuit extends OpcodeStackDetector implements
		StatelessDetector {

	int stage1 = 0;
	int stage2 = 0;
	int distance = 0;
	int operator;

	boolean sawDanger;
	boolean sawNullTestOld;
	boolean sawNullTestVeryOld;
	boolean sawNullTest;
	boolean sawDangerOld;
	boolean sawNumericTest, sawNumericTestOld, sawNumericTestVeryOld;
	boolean sawArrayDanger, sawArrayDangerOld;
	boolean sawMethodCall, sawMethodCallOld;

	private BugAccumulator bugAccumulator;

	public FindNonShortCircuit(BugReporter bugReporter) {
		this.bugAccumulator = new BugAccumulator(bugReporter);
	}

	@Override
	public void visit(Method obj) {
		clearAll();
		prevOpcode = NOP;
	}

	private void clearAll() {
		stage1 = 0;
		stage2 = 0;
		distance = 1000000;
		sawArrayDanger = sawArrayDangerOld = false;
		sawDanger = sawDangerOld = false;
		sawMethodCall = sawMethodCallOld = false;
		sawNullTest = sawNullTestOld = sawNullTestVeryOld = false;
		sawNumericTest = sawNumericTestOld = sawNumericTestVeryOld = false;
	}
	int prevOpcode;
	
	@Override
	public void visit(Code code) {
		super.visit(code);
		bugAccumulator.reportAccumulatedBugs();
	}
	@Override
	public void sawOpcode(int seen) {
		// System.out.println(getPC() + " " + OPCODE_NAMES[seen] + " " + stage1 + " " + stage2);
		// System.out.println(stack);
		// System.out.println(getPC() + " " + OPCODE_NAMES[seen] + " " + sawMethodCall + " " + sawMethodCallOld + " " + stage1 + " " + stage2);
		distance++;
		scanForBooleanValue(seen);
		scanForDanger(seen);
		scanForShortCircuit(seen);
		prevOpcode = seen;
	}

	private void scanForDanger(int seen) {
		switch (seen) {
		case AALOAD:
		case BALOAD:
		case SALOAD:
		case CALOAD:
		case IALOAD:
		case LALOAD:
		case FALOAD:
		case DALOAD:
			sawArrayDanger = true;
			sawDanger = true;
			break;

		case INVOKEVIRTUAL:
			if (getNameConstantOperand().equals("length") && getClassConstantOperand().equals("java/lang/String")) break;
			sawDanger = true;
			sawMethodCall = true;
			break;
		case INVOKEINTERFACE:
		case INVOKESPECIAL:
		case INVOKESTATIC:
			   sawDanger = true;
				sawMethodCall = true;
				break;
		case IDIV:
		case IREM:
		case LDIV:
		case LREM:
			sawDanger = true;
			break;

		case ARRAYLENGTH:
		case GETFIELD:
			// null pointer detector will handle these
			break;
		default:
			break;
		}

	}

	private void scanForShortCircuit(int seen) {
		switch (seen) {
		case IAND:
		case IOR:

			// System.out.println("Saw IOR or IAND at distance " + distance);
			OpcodeStack.Item item0 = stack.getStackItem(0);
			OpcodeStack.Item item1 = stack.getStackItem(1);
			if (item0.getConstant() == null && item1.getConstant() == null && distance < 4) {
				if (item0.getRegisterNumber() >= 0 && item1.getRegisterNumber() >= 0)
					if (false) 
						clearAll();
				operator = seen;
				stage2 = 1;
			} else
				stage2 = 0;
			break;
		case IFEQ:
		case IFNE:
			if (stage2 == 1) {
				// System.out.println("Found nsc");
				reportBug();
			}
			stage2 = 0;
			break;
		case PUTFIELD:
		case PUTSTATIC:
		case IRETURN:
			if (operator == IAND && stage2 == 1) {
				reportBug();
			}
			stage2 = 0;
			break;
		default:
			stage2 = 0;
			break;
		}
	}

	private void reportBug() {
		int priority = LOW_PRIORITY;
		String pattern = "NS_NON_SHORT_CIRCUIT";

		if (sawDangerOld) {
			if (sawNullTestVeryOld) priority = HIGH_PRIORITY;
			if (sawMethodCallOld || sawNumericTestVeryOld && sawArrayDangerOld)  {
				priority = HIGH_PRIORITY;
				pattern = "NS_DANGEROUS_NON_SHORT_CIRCUIT";
			}
			else priority = NORMAL_PRIORITY;
		}

		bugAccumulator.accumulateBug(new  BugInstance(this, pattern,priority).addClassAndMethod(this), this);
	}


	private void scanForBooleanValue(int seen) {
		switch (seen) {

		case IAND:
		case IOR:
			switch(prevOpcode) {
			case ILOAD:
			case ILOAD_0:
			case ILOAD_1:
			case ILOAD_2:
			case ILOAD_3:
				clearAll();
			}
			break;
		case ICONST_1:
			stage1 = 1;
			switch(prevOpcode) {
			case IFNONNULL:
			case IFNULL:
				sawNullTest = true;
				break;
			case IF_ICMPGT:
			case IF_ICMPGE:
			case IF_ICMPLT:
			case IF_ICMPLE:
				sawNumericTest = true;
			break;
			}

			break;
		case GOTO:
			if (stage1 == 1)
				stage1 = 2;
			else {
				stage1 = 0;
				clearAll();
			}
			break;
		case ICONST_0:
			if (stage1 == 2) 
				sawBooleanValue();
			stage1 = 0;
			break;
		case INVOKEINTERFACE:
		case INVOKEVIRTUAL:
		case INVOKESPECIAL:
		case INVOKESTATIC:
			String sig = getSigConstantOperand();
			if (sig.endsWith(")Z"))
				sawBooleanValue();
			stage1 = 0;
			break;
		default:
			stage1 = 0;
		}
	}

	private void sawBooleanValue() {
		sawMethodCallOld = sawMethodCall;
		sawDangerOld = sawDanger;
		sawArrayDangerOld = sawArrayDanger;
		sawNullTestVeryOld = sawNullTestOld;
		sawNullTestOld = sawNullTest;
		sawNumericTestVeryOld = sawNumericTestOld;
		sawNumericTestOld = sawNumericTest;
		sawNumericTest = false;
		sawDanger = false;
		sawArrayDanger = false;
		sawMethodCall = false;
		distance = 0;
		stage1 = 0;

	}
}
