/*******************************************************************************
 * Copyright (c) 2000, 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Stephan Herrmann <stephan@cs.tu-berlin.de> - Contributions for 
 *     				bug 292478 - Report potentially null across variable assignment
 *     				bug 332637 - Dead Code detection removing code that isn't dead
 *******************************************************************************/
package org.eclipse.jdt.internal.compiler.flow;

import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.IfStatement;
import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;

public abstract class FlowInfo {

	public int tagBits; // REACHABLE by default
	public final static int REACHABLE = 0;
	/* unreachable code 
	 * eg. while (true);
	 *     i++;  --> unreachable code 
	 */
	public final static int UNREACHABLE_OR_DEAD = 1;
	/* unreachable code as inferred by null analysis
	 * eg. str = null;
	 *     if (str != null) {
	 *        // dead code
	 *     }
	 */
	public final static int UNREACHABLE_BY_NULLANALYSIS = 2;
	/*
	 * code unreachable in any fashion
	 */
	public final static int UNREACHABLE = UNREACHABLE_OR_DEAD | UNREACHABLE_BY_NULLANALYSIS;
	public final static int NULL_FLAG_MASK = 4;
	
	public final static int UNKNOWN = 1;
	public final static int NULL = 2;
	public final static int NON_NULL = 4;
	public final static int POTENTIALLY_UNKNOWN = 8;
	public final static int POTENTIALLY_NULL = 16;
	public final static int POTENTIALLY_NON_NULL = 32;

	public static final UnconditionalFlowInfo DEAD_END; // Represents a dead branch status of initialization
	static {
		DEAD_END = new UnconditionalFlowInfo();
		DEAD_END.tagBits = UNREACHABLE;
	}

/**
 * Add other inits to this flow info, then return this. The operation semantics
 * are to match as closely as possible the application to this flow info of all
 * the operations that resulted into otherInits.
 * @param otherInits other inits to add to this
 * @return this, modified according to otherInits information
 */
abstract public FlowInfo addInitializationsFrom(FlowInfo otherInits);

/**
 * Add all null information from otherInits to this flow info and return this.
 * The operation models the effect of an unconditional sequence of this flow info
 * and otherInits.
 */
abstract public FlowInfo addNullInfoFrom(FlowInfo otherInits);


/**
 * Compose other inits over this flow info, then return this. The operation
 * semantics are to wave into this flow info the consequences of a possible
 * path into the operations that resulted into otherInits. The fact that this
 * path may be left unexecuted under peculiar conditions results into less
 * specific results than {@link #addInitializationsFrom(FlowInfo)
 * addInitializationsFrom}.
 * @param otherInits other inits to compose over this
 * @return this, modified according to otherInits information
 */
abstract public FlowInfo addPotentialInitializationsFrom(FlowInfo otherInits);

	public FlowInfo asNegatedCondition() {

		return this;
	}

	public static FlowInfo conditional(FlowInfo initsWhenTrue, FlowInfo initsWhenFalse){
		if (initsWhenTrue == initsWhenFalse) return initsWhenTrue;
		// if (initsWhenTrue.equals(initsWhenFalse)) return initsWhenTrue; -- could optimize if #equals is defined
		return new ConditionalFlowInfo(initsWhenTrue, initsWhenFalse);
	}

/**
 * Check whether a given local variable is known to be unable to gain a definite
 * non null or definite null status by the use of an enclosing flow info. The
 * semantics are that if the current flow info marks the variable as potentially
 * unknown or else as being both potentially null and potentially non null,
 * then it won't ever be promoted as definitely null or definitely non null. (It
 * could still get promoted to definite unknown).
 * @param local the variable to check
 * @return true iff this flow info prevents local from being promoted to
 *         definite non null or definite null against an enclosing flow info
 */
public boolean cannotBeDefinitelyNullOrNonNull(LocalVariableBinding local) {
	return isPotentiallyUnknown(local) ||
		isPotentiallyNonNull(local) && isPotentiallyNull(local);
}

/**
 * Check whether a given local variable is known to be non null, either because
 * it is definitely non null, or because is has been tested against non null.
 * @param local the variable to ckeck
 * @return true iff local cannot be null for this flow info
 */
public boolean cannotBeNull(LocalVariableBinding local) {
	return isDefinitelyNonNull(local) || isProtectedNonNull(local);
}

/**
 * Check whether a given local variable is known to be null, either because it
 * is definitely null, or because is has been tested against null.
 * @param local the variable to ckeck
 * @return true iff local can only be null for this flow info
 */
public boolean canOnlyBeNull(LocalVariableBinding local) {
	return isDefinitelyNull(local) || isProtectedNull(local);
}

/**
 * Return a deep copy of the current instance.
 * @return a deep copy of this flow info
 */
	abstract public FlowInfo copy();

	public static UnconditionalFlowInfo initial(int maxFieldCount) {
		UnconditionalFlowInfo info = new UnconditionalFlowInfo();
		info.maxFieldCount = maxFieldCount;
		return info;
	}

/**
 * Return the flow info that would result from the path associated to the
 * value false for the condition expression that generated this flow info.
 * May be this flow info if it is not an instance of {@link
 * ConditionalFlowInfo}. May have a side effect on subparts of this flow
 * info (subtrees get merged).
 * @return the flow info associated to the false branch of the condition
 * 			that generated this flow info
 */
abstract public FlowInfo initsWhenFalse();

/**
 * Return the flow info that would result from the path associated to the
 * value true for the condition expression that generated this flow info.
 * May be this flow info if it is not an instance of {@link
 * ConditionalFlowInfo}. May have a side effect on subparts of this flow
 * info (subtrees get merged).
 * @return the flow info associated to the true branch of the condition
 * 			that generated this flow info
 */
	abstract public FlowInfo initsWhenTrue();

	/**
	 * Check status of definite assignment for a field.
	 */
	 abstract public boolean isDefinitelyAssigned(FieldBinding field);

	/**
	 * Check status of definite assignment for a local.
	 */
	public abstract boolean isDefinitelyAssigned(LocalVariableBinding local);

/**
 * Check status of definite non-null value for a given local variable.
 * @param local the variable to ckeck
 * @return true iff local is definitely non null for this flow info
 */
	public abstract boolean isDefinitelyNonNull(LocalVariableBinding local);

/**
 * Check status of definite null value for a given local variable.
 * @param local the variable to ckeck
 * @return true iff local is definitely null for this flow info
 */
public abstract boolean isDefinitelyNull(LocalVariableBinding local);

/**
 * Check status of definite unknown value for a given local variable.
 * @param local the variable to ckeck
 * @return true iff local is definitely unknown for this flow info
 */
public abstract boolean isDefinitelyUnknown(LocalVariableBinding local);

	/**
	 * Check status of potential assignment for a field.
	 */
	 abstract public boolean isPotentiallyAssigned(FieldBinding field);

	/**
	 * Check status of potential assignment for a local variable.
	 */

	 abstract public boolean isPotentiallyAssigned(LocalVariableBinding field);

/**
 * Check status of potential null assignment for a local. Return true if there
 * is a reasonable expectation that the variable be non null at this point.
 * @param local LocalVariableBinding - the binding for the checked local
 * @return true if there is a reasonable expectation that local be non null at
 * this point
 */
public abstract boolean isPotentiallyNonNull(LocalVariableBinding local);

/**
 * Check status of potential null assignment for a local. Return true if there
 * is a reasonable expectation that the variable be null at this point. This
 * includes the protected null case, so as to augment diagnostics, but does not
 * really check that someone deliberately assigned to null on any specific
 * path
 * @param local LocalVariableBinding - the binding for the checked local
 * @return true if there is a reasonable expectation that local be null at
 * this point
 */
public abstract boolean isPotentiallyNull(LocalVariableBinding local);

/**
 * Return true if the given local may have been assigned to an unknown value.
 * @param local the local to check
 * @return true if the given local may have been assigned to an unknown value
 */
public abstract boolean isPotentiallyUnknown(LocalVariableBinding local);

/**
 * Return true if the given local is protected by a test against a non null
 * value.
 * @param local the local to check
 * @return true if the given local is protected by a test against a non null
 */
public abstract boolean isProtectedNonNull(LocalVariableBinding local);

/**
 * Return true if the given local is protected by a test against null.
 * @param local the local to check
 * @return true if the given local is protected by a test against null
 */
public abstract boolean isProtectedNull(LocalVariableBinding local);

/**
 * Record that a local variable got checked to be non null.
 * @param local the checked local variable
 */
abstract public void markAsComparedEqualToNonNull(LocalVariableBinding local);

/**
 * Record that a local variable got checked to be null.
 * @param local the checked local variable
 */
abstract public void markAsComparedEqualToNull(LocalVariableBinding local);

	/**
	 * Record a field got definitely assigned.
	 */
	abstract public void markAsDefinitelyAssigned(FieldBinding field);

	/**
	 * Record a local got definitely assigned to a non-null value.
	 */
	abstract public void markAsDefinitelyNonNull(LocalVariableBinding local);

	/**
	 * Record a local got definitely assigned to null.
	 */
	abstract public void markAsDefinitelyNull(LocalVariableBinding local);

	/**
	 * Reset all null-information about a given local.
	 */
	abstract public void resetNullInfo(LocalVariableBinding local);

	/**
	 * Record a local may have got assigned to unknown (set the bit on existing info).
	 */
	abstract public void markPotentiallyUnknownBit(LocalVariableBinding local);

	/**
	 * Record a local may have got assigned to null (set the bit on existing info).
	 */
	abstract public void markPotentiallyNullBit(LocalVariableBinding local);

	/**
	 * Record a local may have got assigned to non-null (set the bit on existing info).
	 */
	abstract public void markPotentiallyNonNullBit(LocalVariableBinding local);

	/**
	 * Record a local got definitely assigned.
	 */
	abstract public void markAsDefinitelyAssigned(LocalVariableBinding local);

/**
 * Record a local got definitely assigned to an unknown value.
 */
abstract public void markAsDefinitelyUnknown(LocalVariableBinding local);

/**
 * Mark the null status of the given local according to the given status
 * @param local
 * @param nullStatus bitset of FLowInfo.UNKNOWN ... FlowInfo.POTENTIALLY_NON_NULL
 */
public void markNullStatus(LocalVariableBinding local, int nullStatus) {
	switch(nullStatus) {
		// definite status?
		case FlowInfo.UNKNOWN :
			markAsDefinitelyUnknown(local);
			break;
		case FlowInfo.NULL :
			markAsDefinitelyNull(local);
			break;
		case FlowInfo.NON_NULL :
			markAsDefinitelyNonNull(local);
			break;
		default:
			// collect potential status:
			resetNullInfo(local);
			if ((nullStatus & FlowInfo.POTENTIALLY_UNKNOWN) != 0)
				markPotentiallyUnknownBit(local);
			if ((nullStatus & FlowInfo.POTENTIALLY_NULL) != 0)
				markPotentiallyNullBit(local);
			if ((nullStatus & FlowInfo.POTENTIALLY_NON_NULL) != 0)
				markPotentiallyNonNullBit(local);
			if ((nullStatus & (FlowInfo.POTENTIALLY_NULL|FlowInfo.POTENTIALLY_NON_NULL|FlowInfo.POTENTIALLY_UNKNOWN)) == 0)
				markAsDefinitelyUnknown(local);
	}
}

/**
 * Answer the null status of the given local
 * @param local
 * @return bitset of FlowInfo.UNKNOWN ... FlowInfo.POTENTIALLY_NON_NULL
 */
public int nullStatus(LocalVariableBinding local) {
	if (isDefinitelyUnknown(local))
		return FlowInfo.UNKNOWN;
	if (isDefinitelyNull(local))
		return FlowInfo.NULL;
	if (isDefinitelyNonNull(local))
		return FlowInfo.NON_NULL;
	int status = 0;
	if (isPotentiallyUnknown(local))
		status |= FlowInfo.POTENTIALLY_UNKNOWN;
	if (isPotentiallyNull(local))
		status |= FlowInfo.POTENTIALLY_NULL;
	if (isPotentiallyNonNull(local))
		status |= FlowInfo.POTENTIALLY_NON_NULL;
	if (status > 0)
		return status;
	return FlowInfo.UNKNOWN;
}

/**
 * Merge branches using optimized boolean conditions
 */
public static UnconditionalFlowInfo mergedOptimizedBranches(
		FlowInfo initsWhenTrue, boolean isOptimizedTrue,
		FlowInfo initsWhenFalse, boolean isOptimizedFalse,
		boolean allowFakeDeadBranch) {
	UnconditionalFlowInfo mergedInfo;
	if (isOptimizedTrue){
		if (initsWhenTrue == FlowInfo.DEAD_END && allowFakeDeadBranch) {
			mergedInfo = initsWhenFalse.setReachMode(FlowInfo.UNREACHABLE_OR_DEAD).
				unconditionalInits();
		}
		else {
			mergedInfo =
				initsWhenTrue.addPotentialInitializationsFrom(initsWhenFalse.
					nullInfoLessUnconditionalCopy()).
				unconditionalInits();
		}
	}
	else if (isOptimizedFalse) {
		if (initsWhenFalse == FlowInfo.DEAD_END && allowFakeDeadBranch) {
			mergedInfo = initsWhenTrue.setReachMode(FlowInfo.UNREACHABLE_OR_DEAD).
				unconditionalInits();
		}
		else {
			mergedInfo =
				initsWhenFalse.addPotentialInitializationsFrom(initsWhenTrue.
					nullInfoLessUnconditionalCopy()).
				unconditionalInits();
		}
	}
	else {
		mergedInfo = initsWhenTrue.
			mergedWith(initsWhenFalse.unconditionalInits());
	}
	return mergedInfo;
}

/**
 * Merge if-else branches using optimized boolean conditions
 */
public static UnconditionalFlowInfo mergedOptimizedBranchesIfElse(
		FlowInfo initsWhenTrue, boolean isOptimizedTrue,
		FlowInfo initsWhenFalse, boolean isOptimizedFalse,
		boolean allowFakeDeadBranch, FlowInfo flowInfo, IfStatement ifStatement,
		boolean reportDeadCodeInKnownPattern) {
	UnconditionalFlowInfo mergedInfo;
	if (isOptimizedTrue){
		if (initsWhenTrue == FlowInfo.DEAD_END && allowFakeDeadBranch) {
			if (!reportDeadCodeInKnownPattern) {
				// https://bugs.eclipse.org/bugs/show_bug.cgi?id=256796
				// do not report code even after if-else as dead as a consequence of analysis done in known dead code pattern
				// when the CompilerOptions$reportDeadCodeInTrivialIfStatement option is disabled
				if (ifStatement.elseStatement == null) {
					mergedInfo = flowInfo.unconditionalInits();
				} else {
					mergedInfo = initsWhenFalse.unconditionalInits();
					if (initsWhenFalse != FlowInfo.DEAD_END) {
						// let the definitely true status of known dead code pattern not affect the reachability
						mergedInfo.setReachMode(flowInfo.reachMode());
					}
				}
			} else {
				mergedInfo = initsWhenFalse.setReachMode(FlowInfo.UNREACHABLE_OR_DEAD).
					unconditionalInits();
			}
		}
		else {
			mergedInfo =
				initsWhenTrue.addPotentialInitializationsFrom(initsWhenFalse.
					nullInfoLessUnconditionalCopy()).
				unconditionalInits();
		}
	}
	else if (isOptimizedFalse) {
		if (initsWhenFalse == FlowInfo.DEAD_END && allowFakeDeadBranch) {
			if (!reportDeadCodeInKnownPattern) {
				// https://bugs.eclipse.org/bugs/show_bug.cgi?id=256796
				// do not report code even after if-else as dead as a consequence of analysis done in known dead code pattern
				// when the CompilerOptions$reportDeadCodeInTrivialIfStatement option is disabled
				if (ifStatement.thenStatement == null) {
					mergedInfo = flowInfo.unconditionalInits();
				} else {
					mergedInfo = initsWhenTrue.unconditionalInits();
					if (initsWhenTrue != FlowInfo.DEAD_END) {
						// let the definitely false status of known dead code pattern not affect the reachability
						mergedInfo.setReachMode(flowInfo.reachMode());
					}
				}
			} else {
				mergedInfo = initsWhenTrue.setReachMode(FlowInfo.UNREACHABLE_OR_DEAD).
					unconditionalInits();
			}
		}
		else {
			mergedInfo =
				initsWhenFalse.addPotentialInitializationsFrom(initsWhenTrue.
					nullInfoLessUnconditionalCopy()).
				unconditionalInits();
		}
	}
	else if ((flowInfo.tagBits & FlowInfo.UNREACHABLE) == 0 &&
				(ifStatement.bits & ASTNode.IsElseStatementUnreachable) != 0 &&
				initsWhenTrue != FlowInfo.DEAD_END &&
				initsWhenFalse != FlowInfo.DEAD_END) {
		// Done when the then branch will always be executed but the condition does not have a boolean
		// true or false (i.e if(true), etc) for sure
		// We don't do this if both if and else branches themselves are in an unreachable code
		// or if any of them is a DEAD_END (e.g. contains 'return' or 'throws')
		mergedInfo =
			initsWhenTrue.addPotentialInitializationsFrom(initsWhenFalse.
				nullInfoLessUnconditionalCopy()).
			unconditionalInits();
		// if a variable is only initialized in one branch and not initialized in the other,
		// then we need to cast a doubt on its initialization in the merged info
		mergedInfo.definiteInits &= initsWhenFalse.unconditionalCopy().definiteInits;
		
	}
	else if ((flowInfo.tagBits & FlowInfo.UNREACHABLE) == 0 &&
			(ifStatement.bits & ASTNode.IsThenStatementUnreachable) != 0 && initsWhenTrue != FlowInfo.DEAD_END
			&& initsWhenFalse != FlowInfo.DEAD_END) {
		// Done when the else branch will always be executed but the condition does not have a boolean
		// true or false (i.e if(true), etc) for sure
		// We don't do this if both if and else branches themselves are in an unreachable code
		// or if any of them is a DEAD_END (e.g. contains 'return' or 'throws')
		mergedInfo = 
			initsWhenFalse.addPotentialInitializationsFrom(initsWhenTrue.
				nullInfoLessUnconditionalCopy()).
			unconditionalInits();
		// if a variable is only initialized in one branch and not initialized in the other,
		// then we need to cast a doubt on its initialization in the merged info
		mergedInfo.definiteInits &= initsWhenTrue.unconditionalCopy().definiteInits;
	}
	else {
		mergedInfo = initsWhenTrue.
			mergedWith(initsWhenFalse.unconditionalInits());
	}
	return mergedInfo;
}

/**
 * Find out the reachability mode of this flowInfo.
 * @return REACHABLE if this flow info is reachable, otherwise
 *         either UNREACHABLE_OR_DEAD or UNREACHABLE_BY_NULLANALYSIS.
 */
public int reachMode() {
	return this.tagBits & UNREACHABLE;
}

/**
 * Return a flow info that carries the same information as the result of
 * {@link #initsWhenTrue() initsWhenTrue}, but warrantied to be different
 * from this.<br>
 * Caveat: side effects on the result may affect components of this.
 * @return the result of initsWhenTrue or a copy of it
 */
abstract public FlowInfo safeInitsWhenTrue();

/**
 * Set this flow info reach mode and return this.
 * @param reachMode one of {@link #REACHABLE REACHABLE}, {@link #UNREACHABLE_OR_DEAD UNREACHABLE_OR_DEAD},
 * {@link #UNREACHABLE_BY_NULLANALYSIS UNREACHABLE_BY_NULLANALYSIS} or {@link #UNREACHABLE UNREACHABLE}
 * @return this, with the reach mode set to reachMode
 */
abstract public FlowInfo setReachMode(int reachMode);

/**
 * Return the intersection of this and otherInits, that is
 * one of:<ul>
 *   <li>the receiver updated in the following way:<ul>
 *     <li>intersection of definitely assigned variables,
 *     <li>union of potentially assigned variables,
 *     <li>similar operations for null,</ul>
 *   <li>or the receiver or otherInits if the other one is non
 *       reachable.</ul>
 * otherInits is not affected, and is not returned either (no
 * need to protect the result).
 * @param otherInits the flow info to merge with this
 * @return the intersection of this and otherInits.
 */
abstract public UnconditionalFlowInfo mergedWith(
		UnconditionalFlowInfo otherInits);

/**
 * Return a copy of this unconditional flow info, deprived from its null
 * info. {@link #DEAD_END DEAD_END} is returned unmodified.
 * @return a copy of this unconditional flow info deprived from its null info
 */
abstract public UnconditionalFlowInfo nullInfoLessUnconditionalCopy();

	public String toString(){

		if (this == DEAD_END){
			return "FlowInfo.DEAD_END"; //$NON-NLS-1$
		}
		return super.toString();
	}

/**
 * Return a new flow info that holds the same information as this would after
 * a call to unconditionalInits, but leaving this info unaffected. Moreover,
 * the result can be modified without affecting this.
 * @return a new flow info carrying this unconditional flow info
 */
abstract public UnconditionalFlowInfo unconditionalCopy();

/**
 * Return a new flow info that holds the same information as this would after
 * a call to {@link #unconditionalInits() unconditionalInits} followed by the
 * erasure of fields specific information, but leaving this flow info unaffected.
 * @return a new flow info carrying the unconditional flow info for local variables
 */
abstract public UnconditionalFlowInfo unconditionalFieldLessCopy();

/**
 * Return a flow info that merges the possible paths of execution described by
 * this flow info. In case of an unconditional flow info, return this. In case
 * of a conditional flow info, merge branches recursively. Caveat: this may
 * be affected, and modifying the result may affect this.
 * @return a flow info that merges the possible paths of execution described by
 * 			this
 */
abstract public UnconditionalFlowInfo unconditionalInits();

/**
 * Return a new flow info that holds the same information as this would after
 * a call to {@link #unconditionalInits() unconditionalInits}, but leaving
 * this info unaffected. Side effects on the result might affect this though
 * (consider it as read only).
 * @return a flow info carrying this unconditional flow info
 */
abstract public UnconditionalFlowInfo unconditionalInitsWithoutSideEffect();

/**
 * Resets the definite and potential initialization info for the given local variable
 * @param local
 */
abstract public void resetAssignmentInfo(LocalVariableBinding local);
}
