/*******************************************************************************
 * Copyright (c) 2000, 2014 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Stephan Herrmann - Contributions for
 *								bug 186342 - [compiler][null] Using annotations for null checking
 *								bug 365519 - editorial cleanup after bug 186342 and bug 365387
 *								bug 368546 - [compiler][resource] Avoid remaining false positives found when compiling the Eclipse SDK
 *								bug 365859 - [compiler][null] distinguish warnings based on flow analysis vs. null annotations
 *								bug 385626 - @NonNull fails across loop boundaries
 *								bug 388996 - [compiler][resource] Incorrect 'potential resource leak'
 *								bug 403147 - [compiler][null] FUP of bug 400761: consolidate interaction between unboxing, NPE, and deferred checking
 *								Bug 453483 - [compiler][null][loop] Improve null analysis for loops
 *								Bug 455723 - Nonnull argument not correctly inferred in loop
 *     Jesper S Moller - Contributions for
 *								bug 404657 - [1.8][compiler] Analysis for effectively final variables fails to consider loops
 *******************************************************************************/
package org.aspectj.org.eclipse.jdt.internal.compiler.flow;

import org.aspectj.org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.Expression;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.Reference;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.Scope;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.TagBits;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.TypeIds;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.VariableBinding;

/**
 * Reflects the context of code analysis, keeping track of enclosing
 *	try statements, exception handlers, etc...
 */
public class FinallyFlowContext extends TryFlowContext {

	Reference[] finalAssignments;
	VariableBinding[] finalVariables;
	int assignCount;

	// the following three arrays are in sync regarding their indices:
	LocalVariableBinding[] nullLocals; // slots can be null for checkType == IN_UNBOXING
	ASTNode[] nullReferences;	// Expressions for null checking, Statements for resource analysis
								// cast to Expression is safe if corresponding nullCheckType != EXIT_RESOURCE
	int[] nullCheckTypes;
	int nullCount;
	// see also the related field FlowContext#expectedTypes

	// back reference to the flow context of the corresponding try statement
	public FlowContext tryContext;

	public FinallyFlowContext(FlowContext parent, ASTNode associatedNode, ExceptionHandlingFlowContext tryContext) {
		super(parent, associatedNode);
		this.tryContext = tryContext;
	}

/**
 * Given some contextual initialization info (derived from a try block or a catch block), this
 * code will check that the subroutine context does not also initialize a final variable potentially set
 * redundantly.
 */
public void complainOnDeferredChecks(FlowInfo flowInfo, BlockScope scope) {

	// check redundant final assignments
	for (int i = 0; i < this.assignCount; i++) {
		VariableBinding variable = this.finalVariables[i];
		if (variable == null) continue;

		boolean complained = false; // remember if have complained on this final assignment
		if (variable instanceof FieldBinding) {
			// final field
			if (flowInfo.isPotentiallyAssigned((FieldBinding)variable)) {
				complained = true;
				scope.problemReporter().duplicateInitializationOfBlankFinalField((FieldBinding)variable, this.finalAssignments[i]);
			}
		} else {
			// final local variable
			if (flowInfo.isPotentiallyAssigned((LocalVariableBinding)variable)) {
				variable.tagBits &= ~TagBits.IsEffectivelyFinal;
				if (variable.isFinal()) {
					complained = true;
					scope.problemReporter().duplicateInitializationOfFinalLocal(
						(LocalVariableBinding) variable,
						this.finalAssignments[i]);
				}
			}
		}
		// any reference reported at this level is removed from the parent context
		// where it could also be reported again
		if (complained) {
			FlowContext currentContext = this.getLocalParent();
			while (currentContext != null) {
				//if (currentContext.isSubRoutine()) {
				currentContext.removeFinalAssignmentIfAny(this.finalAssignments[i]);
				//}
				currentContext = currentContext.getLocalParent();
			}
		}
	}

	// check inconsistent null checks
	if ((this.tagBits & FlowContext.DEFER_NULL_DIAGNOSTIC) != 0) { // within an enclosing loop, be conservative
		for (int i = 0; i < this.nullCount; i++) {
			ASTNode location = this.nullReferences[i];
			switch (this.nullCheckTypes[i] & ~HIDE_NULL_COMPARISON_WARNING_MASK) {
				case ASSIGN_TO_NONNULL:
					int nullStatus = flowInfo.nullStatus(this.nullLocals[i]);
					if (nullStatus != FlowInfo.NON_NULL) {
						this.parent.recordNullityMismatch(scope, (Expression) location,
								this.providedExpectedTypes[i][0], this.providedExpectedTypes[i][1], flowInfo, nullStatus, null);
					}
					break;
				case IN_UNBOXING:
					checkUnboxing(scope, (Expression) location, flowInfo);
					break;
				default:
					this.parent.recordUsingNullReference(scope, this.nullLocals[i],
							this.nullReferences[i],	this.nullCheckTypes[i], flowInfo);
			}

		}
	}
	else { // no enclosing loop, be as precise as possible right now
		for (int i = 0; i < this.nullCount; i++) {
			ASTNode location = this.nullReferences[i];
			// final local variable
			LocalVariableBinding local = this.nullLocals[i];
			switch (this.nullCheckTypes[i] & ~HIDE_NULL_COMPARISON_WARNING_MASK) {
				case CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NULL:
				case CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NON_NULL:
					if (flowInfo.isDefinitelyNonNull(local)) {
						if ((this.nullCheckTypes[i] & ~HIDE_NULL_COMPARISON_WARNING_MASK) == (CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NON_NULL)) {
							if ((this.nullCheckTypes[i] & HIDE_NULL_COMPARISON_WARNING) == 0) {
								scope.problemReporter().localVariableRedundantCheckOnNonNull(local, location);
							}
						} else {
							scope.problemReporter().localVariableNonNullComparedToNull(local, location);
						}
						continue;
					}
					//$FALL-THROUGH$
				case CAN_ONLY_NULL | IN_COMPARISON_NULL:
				case CAN_ONLY_NULL | IN_COMPARISON_NON_NULL:
				case CAN_ONLY_NULL | IN_ASSIGNMENT:
				case CAN_ONLY_NULL | IN_INSTANCEOF:
					Expression expression = (Expression) location;
					if (flowInfo.isDefinitelyNull(local)) {
						switch(this.nullCheckTypes[i] & CONTEXT_MASK) {
							case FlowContext.IN_COMPARISON_NULL:
								if (((this.nullCheckTypes[i] & CHECK_MASK & ~HIDE_NULL_COMPARISON_WARNING_MASK) == CAN_ONLY_NULL) && (expression.implicitConversion & TypeIds.UNBOXING) != 0) { // check for auto-unboxing first and report appropriate warning
									scope.problemReporter().localVariableNullReference(local, expression);
									continue;
								}
								if ((this.nullCheckTypes[i] & HIDE_NULL_COMPARISON_WARNING) == 0) {
									scope.problemReporter().localVariableRedundantCheckOnNull(local, expression);
								}
								continue;
							case FlowContext.IN_COMPARISON_NON_NULL:
								if (((this.nullCheckTypes[i] & CHECK_MASK & ~HIDE_NULL_COMPARISON_WARNING_MASK) == CAN_ONLY_NULL) && (expression.implicitConversion & TypeIds.UNBOXING) != 0) { // check for auto-unboxing first and report appropriate warning
									scope.problemReporter().localVariableNullReference(local, expression);
									continue;
								}
								scope.problemReporter().localVariableNullComparedToNonNull(local, expression);
								continue;
							case FlowContext.IN_ASSIGNMENT:
								scope.problemReporter().localVariableRedundantNullAssignment(local, expression);
								continue;
							case FlowContext.IN_INSTANCEOF:
								scope.problemReporter().localVariableNullInstanceof(local, expression);
								continue;
						}
					} else if (flowInfo.isPotentiallyNull(local)) {
						switch(this.nullCheckTypes[i] & CONTEXT_MASK) {
							case FlowContext.IN_COMPARISON_NULL:
								this.nullReferences[i] = null;
								if (((this.nullCheckTypes[i] & CHECK_MASK & ~HIDE_NULL_COMPARISON_WARNING_MASK) == CAN_ONLY_NULL) && (expression.implicitConversion & TypeIds.UNBOXING) != 0) { // check for auto-unboxing first and report appropriate warning
									scope.problemReporter().localVariablePotentialNullReference(local, expression);
									continue;
								}
								break;
							case FlowContext.IN_COMPARISON_NON_NULL:
								this.nullReferences[i] = null;
								if (((this.nullCheckTypes[i] & CHECK_MASK & ~HIDE_NULL_COMPARISON_WARNING_MASK) == CAN_ONLY_NULL) && (expression.implicitConversion & TypeIds.UNBOXING) != 0) { // check for auto-unboxing first and report appropriate warning
									scope.problemReporter().localVariablePotentialNullReference(local, expression);
									continue;
								}
								break;
						}
					}
					break;
				case MAY_NULL:
					if (flowInfo.isDefinitelyNull(local)) {
						scope.problemReporter().localVariableNullReference(local, location);
						continue;
					}
					if (flowInfo.isPotentiallyNull(local)) {
						scope.problemReporter().localVariablePotentialNullReference(local, location);
					}
					break;
				case ASSIGN_TO_NONNULL:
					int nullStatus = flowInfo.nullStatus(local);
					if (nullStatus != FlowInfo.NON_NULL) {
						char[][] annotationName = scope.environment().getNonNullAnnotationName();
						scope.problemReporter().nullityMismatch((Expression) location, this.providedExpectedTypes[i][0], this.providedExpectedTypes[i][1], nullStatus, annotationName);
					}
					break;
				case IN_UNBOXING:
					checkUnboxing(scope, (Expression) location, flowInfo);	
					break;
				default:
					// should not happen
			}
		}
	}
}

	@Override
	public String individualToString() {

		StringBuffer buffer = new StringBuffer("Finally flow context"); //$NON-NLS-1$
		buffer.append("[finalAssignments count - ").append(this.assignCount).append(']'); //$NON-NLS-1$
		buffer.append("[nullReferences count - ").append(this.nullCount).append(']'); //$NON-NLS-1$
		return buffer.toString();
	}

	@Override
	public boolean isSubRoutine() {
		return true;
	}

	@Override
	protected boolean recordFinalAssignment(
		VariableBinding binding,
		Reference finalAssignment) {
		if (this.assignCount == 0) {
			this.finalAssignments = new Reference[5];
			this.finalVariables = new VariableBinding[5];
		} else {
			if (this.assignCount == this.finalAssignments.length)
				System.arraycopy(
					this.finalAssignments,
					0,
					(this.finalAssignments = new Reference[this.assignCount * 2]),
					0,
					this.assignCount);
			System.arraycopy(
				this.finalVariables,
				0,
				(this.finalVariables = new VariableBinding[this.assignCount * 2]),
				0,
				this.assignCount);
		}
		this.finalAssignments[this.assignCount] = finalAssignment;
		this.finalVariables[this.assignCount++] = binding;
		return true;
	}

	@Override
	public void recordUsingNullReference(Scope scope, LocalVariableBinding local,
			ASTNode location, int checkType, FlowInfo flowInfo) {
		if ((flowInfo.tagBits & FlowInfo.UNREACHABLE) == 0 && !flowInfo.isDefinitelyUnknown(local))	{
			// if reference is being recorded inside an assert, we will not raise redundant null check warnings
			checkType |= (this.tagBits & FlowContext.HIDE_NULL_COMPARISON_WARNING);
			int checkTypeWithoutHideNullWarning = checkType & ~FlowContext.HIDE_NULL_COMPARISON_WARNING_MASK;
			if ((this.tagBits & FlowContext.DEFER_NULL_DIAGNOSTIC) != 0) { // within an enclosing loop, be conservative
				switch (checkTypeWithoutHideNullWarning) {
					case CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NULL:
					case CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NON_NULL:
					case CAN_ONLY_NULL | IN_COMPARISON_NULL:
					case CAN_ONLY_NULL | IN_COMPARISON_NON_NULL:
					case CAN_ONLY_NULL | IN_ASSIGNMENT:
					case CAN_ONLY_NULL | IN_INSTANCEOF:
						Expression reference = (Expression) location;
						if (flowInfo.cannotBeNull(local)) {
							if (checkTypeWithoutHideNullWarning == (CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NON_NULL)) {
								if ((checkType & HIDE_NULL_COMPARISON_WARNING) == 0) {
									scope.problemReporter().localVariableRedundantCheckOnNonNull(local, reference);
								}
								flowInfo.initsWhenFalse().setReachMode(FlowInfo.UNREACHABLE_BY_NULLANALYSIS);
							} else if (checkTypeWithoutHideNullWarning == (CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NULL)) {
								scope.problemReporter().localVariableNonNullComparedToNull(local, reference);
								flowInfo.initsWhenTrue().setReachMode(FlowInfo.UNREACHABLE_BY_NULLANALYSIS);
							}
							return;
						}
						if (flowInfo.canOnlyBeNull(local)) {
							switch(checkTypeWithoutHideNullWarning & CONTEXT_MASK) {
								case FlowContext.IN_COMPARISON_NULL:
									if (((checkTypeWithoutHideNullWarning & CHECK_MASK) == CAN_ONLY_NULL) && (reference.implicitConversion & TypeIds.UNBOXING) != 0) { // check for auto-unboxing first and report appropriate warning
										scope.problemReporter().localVariableNullReference(local, reference);
										return;
									}
									if ((checkType & HIDE_NULL_COMPARISON_WARNING) == 0) {
										scope.problemReporter().localVariableRedundantCheckOnNull(local, reference);
									}
									flowInfo.initsWhenFalse().setReachMode(FlowInfo.UNREACHABLE_BY_NULLANALYSIS);
									return;
								case FlowContext.IN_COMPARISON_NON_NULL:
									if (((checkTypeWithoutHideNullWarning & CHECK_MASK) == CAN_ONLY_NULL) && (reference.implicitConversion & TypeIds.UNBOXING) != 0) { // check for auto-unboxing first and report appropriate warning
										scope.problemReporter().localVariableNullReference(local, reference);
										return;
									}
									scope.problemReporter().localVariableNullComparedToNonNull(local, reference);
									flowInfo.initsWhenTrue().setReachMode(FlowInfo.UNREACHABLE_BY_NULLANALYSIS);
									return;
								case FlowContext.IN_ASSIGNMENT:
									scope.problemReporter().localVariableRedundantNullAssignment(local, reference);
									return;
								case FlowContext.IN_INSTANCEOF:
									scope.problemReporter().localVariableNullInstanceof(local, reference);
									return;
							}
						} else if (flowInfo.isPotentiallyNull(local)) {
							switch(checkTypeWithoutHideNullWarning & CONTEXT_MASK) {
								case FlowContext.IN_COMPARISON_NULL:
									if (((checkTypeWithoutHideNullWarning & CHECK_MASK) == CAN_ONLY_NULL) && (reference.implicitConversion & TypeIds.UNBOXING) != 0) { // check for auto-unboxing first and report appropriate warning
										scope.problemReporter().localVariablePotentialNullReference(local, reference);
										return;
									}
									break;
								case FlowContext.IN_COMPARISON_NON_NULL:
									if (((checkTypeWithoutHideNullWarning & CHECK_MASK) == CAN_ONLY_NULL) && (reference.implicitConversion & TypeIds.UNBOXING) != 0) { // check for auto-unboxing first and report appropriate warning
										scope.problemReporter().localVariablePotentialNullReference(local, reference);
										return;
									}
									break;
							}
						}
						break;
					case MAY_NULL :
						if (flowInfo.cannotBeNull(local)) {
							return;
						}
						if (flowInfo.canOnlyBeNull(local)) {
							scope.problemReporter().localVariableNullReference(local, location);
							return;
						}
						break;
					default:
						// never happens
				}
			}
			else { // no enclosing loop, be as precise as possible right now
				switch (checkTypeWithoutHideNullWarning) {
					case CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NULL:
					case CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NON_NULL:
						if (flowInfo.isDefinitelyNonNull(local)) {
							if (checkTypeWithoutHideNullWarning == (CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NON_NULL)) {
								if ((checkType & HIDE_NULL_COMPARISON_WARNING) == 0) {
									scope.problemReporter().localVariableRedundantCheckOnNonNull(local, location);
								}
								flowInfo.initsWhenFalse().setReachMode(FlowInfo.UNREACHABLE_BY_NULLANALYSIS);
							} else {
								scope.problemReporter().localVariableNonNullComparedToNull(local, location);
								flowInfo.initsWhenTrue().setReachMode(FlowInfo.UNREACHABLE_BY_NULLANALYSIS);
							}
							return;
						}
						//$FALL-THROUGH$
					case CAN_ONLY_NULL | IN_COMPARISON_NULL:
					case CAN_ONLY_NULL | IN_COMPARISON_NON_NULL:
					case CAN_ONLY_NULL | IN_ASSIGNMENT:
					case CAN_ONLY_NULL | IN_INSTANCEOF:
						Expression reference = (Expression) location;
						if (flowInfo.isDefinitelyNull(local)) {
							switch(checkTypeWithoutHideNullWarning & CONTEXT_MASK) {
								case FlowContext.IN_COMPARISON_NULL:
									if (((checkTypeWithoutHideNullWarning & CHECK_MASK) == CAN_ONLY_NULL) && (reference.implicitConversion & TypeIds.UNBOXING) != 0) { // check for auto-unboxing first and report appropriate warning
										scope.problemReporter().localVariableNullReference(local, reference);
										return;
									}
									if ((checkType & HIDE_NULL_COMPARISON_WARNING) == 0) {
										scope.problemReporter().localVariableRedundantCheckOnNull(local, reference);
									}
									flowInfo.initsWhenFalse().setReachMode(FlowInfo.UNREACHABLE_BY_NULLANALYSIS);
									return;
								case FlowContext.IN_COMPARISON_NON_NULL:
									if (((checkTypeWithoutHideNullWarning & CHECK_MASK) == CAN_ONLY_NULL) && (reference.implicitConversion & TypeIds.UNBOXING) != 0) { // check for auto-unboxing first and report appropriate warning
										scope.problemReporter().localVariableNullReference(local, reference);
										return;
									}
									scope.problemReporter().localVariableNullComparedToNonNull(local, reference);
									flowInfo.initsWhenTrue().setReachMode(FlowInfo.UNREACHABLE_BY_NULLANALYSIS);
									return;
								case FlowContext.IN_ASSIGNMENT:
									scope.problemReporter().localVariableRedundantNullAssignment(local, reference);
									return;
								case FlowContext.IN_INSTANCEOF:
									scope.problemReporter().localVariableNullInstanceof(local, reference);
									return;
							}
						} else if (flowInfo.isPotentiallyNull(local)) {
							switch(checkTypeWithoutHideNullWarning & CONTEXT_MASK) {
								case FlowContext.IN_COMPARISON_NULL:
									if (((checkTypeWithoutHideNullWarning & CHECK_MASK) == CAN_ONLY_NULL) && (reference.implicitConversion & TypeIds.UNBOXING) != 0) { // check for auto-unboxing first and report appropriate warning
										scope.problemReporter().localVariablePotentialNullReference(local, reference);
										return;
									}
									break;
								case FlowContext.IN_COMPARISON_NON_NULL:
									if (((checkTypeWithoutHideNullWarning & CHECK_MASK) == CAN_ONLY_NULL) && (reference.implicitConversion & TypeIds.UNBOXING) != 0) { // check for auto-unboxing first and report appropriate warning
										scope.problemReporter().localVariablePotentialNullReference(local, reference);
										return;
									}
									break;
							}
						}
						break;
					case MAY_NULL :
						if (flowInfo.isDefinitelyNull(local)) {
							scope.problemReporter().localVariableNullReference(local, location);
							return;
						}
						if (flowInfo.isPotentiallyNull(local)) {
							scope.problemReporter().localVariablePotentialNullReference(local, location);
							return;
						}
						if (flowInfo.isDefinitelyNonNull(local)) {
							return; // shortcut: cannot be null
						}
						break;
					default:
						// never happens
				}
			}
			recordNullReference(local, location, checkType, flowInfo);
			// prepare to re-check with try/catch flow info
		}
	}

	@Override
	void removeFinalAssignmentIfAny(Reference reference) {
		for (int i = 0; i < this.assignCount; i++) {
			if (this.finalAssignments[i] == reference) {
				this.finalAssignments[i] = null;
				this.finalVariables[i] = null;
				return;
			}
		}
	}

@Override
protected void recordNullReference(LocalVariableBinding local,
	ASTNode expression, int checkType, FlowInfo nullInfo) {
	if (this.nullCount == 0) {
		this.nullLocals = new LocalVariableBinding[5];
		this.nullReferences = new ASTNode[5];
		this.nullCheckTypes = new int[5];
	}
	else if (this.nullCount == this.nullLocals.length) {
		int newLength = this.nullCount * 2;
		System.arraycopy(this.nullLocals, 0,
			this.nullLocals = new LocalVariableBinding[newLength], 0,
			this.nullCount);
		System.arraycopy(this.nullReferences, 0,
			this.nullReferences = new ASTNode[newLength], 0,
			this.nullCount);
		System.arraycopy(this.nullCheckTypes, 0,
			this.nullCheckTypes = new int[newLength], 0,
			this.nullCount);
	}
	this.nullLocals[this.nullCount] = local;
	this.nullReferences[this.nullCount] = expression;
	this.nullCheckTypes[this.nullCount++] = checkType;
}
@Override
public void recordUnboxing(Scope scope, Expression expression, int nullStatus, FlowInfo flowInfo) {
	if (nullStatus == FlowInfo.NULL)
		super.recordUnboxing(scope, expression, nullStatus, flowInfo);
	else // defer checking:
		recordNullReference(null, expression, IN_UNBOXING, flowInfo);
}
@Override
protected boolean internalRecordNullityMismatch(Expression expression, TypeBinding providedType, FlowInfo flowInfo, int nullStatus, TypeBinding expectedType, int checkType) {
	// cf. decision structure inside FinallyFlowContext.recordUsingNullReference(..)
	if (nullStatus == FlowInfo.UNKNOWN ||
			((this.tagBits & FlowContext.DEFER_NULL_DIAGNOSTIC) != 0 && nullStatus != FlowInfo.NULL)) {
		recordProvidedExpectedTypes(providedType, expectedType, this.nullCount);
		recordNullReference(expression.localVariableBinding(), expression, checkType, flowInfo);
		return true;
	}
	return false;
}
}
