/*******************************************************************************
 * 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 325755 - [compiler] wrong initialization state after conditional expression
 *     						bug 320170 - [compiler] [null] Whitebox issues in null analysis
 *     						bug 292478 - Report potentially null across variable assignment
 *     						bug 332637 - Dead Code detection removing code that isn't dead
 *     						bug 341499 - [compiler][null] allocate extra bits in all methods of UnconditionalFlowInfo
 *     						bug 349326 - [1.7] new warning for missing try-with-resources
 *							bug 345305 - [compiler][null] Compiler misidentifies a case of "variable can only be null"
 *							bug 386181 - [compiler][null] wrong transition in UnconditionalFlowInfo.mergedWith()
 *							bug 394768 - [compiler][resource] Incorrect resource leak warning when creating stream in conditional
 *******************************************************************************/
package org.eclipse.jdt.internal.compiler.flow;

import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.impl.Constant;
import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.TagBits;

/**
 * Record initialization status during definite assignment analysis
 *
 * No caching of pre-allocated instances.
 */
public class UnconditionalFlowInfo extends FlowInfo {
	/**
	 * Exception raised when unexpected behavior is detected.
	 */
	public static class AssertionFailedException extends RuntimeException {
		private static final long serialVersionUID = 1827352841030089703L;

	public AssertionFailedException(String message) {
		super(message);
	}
	}

	// Coverage tests need that the code be instrumented. The following flag
	// controls whether the instrumented code is compiled in or not, and whether
	// the coverage tests methods run or not.
	public final static boolean COVERAGE_TEST_FLAG = false;
	// never release with the coverageTestFlag set to true
	public static int CoverageTestId;

	// assignment bits - first segment
	public long definiteInits;
	public long potentialInits;

	// null bits - first segment
	public long
		nullBit1,
		nullBit2,
		nullBit3,
		nullBit4;
/*
		nullBit1
		 nullBit2...
		0000	start
		0001	pot. unknown
		0010	pot. non null
		0011	pot. nn & pot. un
		0100	pot. null
		0101	pot. n & pot. un
		0110	pot. n & pot. nn
		0111    pot. n & pot. nn & pot. un
		1001	def. unknown
		1010	def. non null
		1011	pot. nn & prot. nn
		1100	def. null
		1101	pot. n & prot. n
		1110	prot. null
		1111	prot. non null
 */

	// extra segments
	public static final int extraLength = 6;
	public long extra[][];
		// extra bit fields for larger numbers of fields/variables
		// extra[0] holds definiteInits values, extra[1] potentialInits, etc.
		// lifecycle is extra == null or else all extra[]'s are allocated
		// arrays which have the same size

	public int maxFieldCount; // limit between fields and locals

	// Constants
	public static final int BitCacheSize = 64; // 64 bits in a long.

/* fakeInitializedFlowInfo: For Lambda expressions tentative analysis during overload resolution. 
   We presume that any and all outer locals touched by the lambda are definitely assigned and 
   effectively final. Whether they are or not is immaterial for overload analysis (errors encountered
   in the body are not supposed to influence the resolution. It is pertinent only for the eventual 
   resolution/analysis post overload resolution. For lambda's the problem is that we start the control/data
   flow analysis abruptly at the start of the lambda, so we need to present a cogent world view and hence 
   all this charade.
*/
public static UnconditionalFlowInfo fakeInitializedFlowInfo(int localsCount, int maxFieldCount) {
		UnconditionalFlowInfo flowInfo = new UnconditionalFlowInfo();
		flowInfo.maxFieldCount = maxFieldCount;
		for (int i = 0; i < localsCount; i++)
			flowInfo.markAsDefinitelyAssigned(i + maxFieldCount);
		return flowInfo;
}

public FlowInfo addInitializationsFrom(FlowInfo inits) {
	return addInfoFrom(inits, true);
}
public FlowInfo addNullInfoFrom(FlowInfo inits) {
	return addInfoFrom(inits, false);
}
private FlowInfo addInfoFrom(FlowInfo inits, boolean handleInits) {
	if (this == DEAD_END)
		return this;
	if (inits == DEAD_END)
		return this;
	UnconditionalFlowInfo otherInits = inits.unconditionalInits();

	if (handleInits) {
		// union of definitely assigned variables,
		this.definiteInits |= otherInits.definiteInits;
		// union of potentially set ones
		this.potentialInits |= otherInits.potentialInits;
	}
	
	// combine null information
	boolean thisHadNulls = (this.tagBits & NULL_FLAG_MASK) != 0,
		otherHasNulls = (otherInits.tagBits & NULL_FLAG_MASK) != 0;
	long
		a1, a2, a3, a4,
		na1, na2, na3, na4,
		b1, b2, b3, b4,
		nb1, nb2, nb3, nb4;
	if (otherHasNulls) {
		if (!thisHadNulls) {
			this.nullBit1 = otherInits.nullBit1;
			this.nullBit2 = otherInits.nullBit2;
			this.nullBit3 = otherInits.nullBit3;
			this.nullBit4 = otherInits.nullBit4;
			if (COVERAGE_TEST_FLAG) {
				if (CoverageTestId == 1) {
				  this.nullBit4 = ~0;
				}
			}
		}
		else {
			this.nullBit1 = (b1 = otherInits.nullBit1)
                				| (a1 = this.nullBit1) & ((a3 = this.nullBit3)
                					& (a4 = this.nullBit4) & (nb2 = ~(b2 = otherInits.nullBit2))
                					& (nb4 = ~(b4 = otherInits.nullBit4))
                        		| ((na4 = ~a4) | (na3 = ~a3))
                        			& ((na2 = ~(a2 = this.nullBit2)) & nb2
                        				| a2 & (nb3 = ~(b3 = otherInits.nullBit3)) & nb4));
			this.nullBit2  = b2 & (nb4 | nb3)
                    			| na3 & na4 & b2
                    			| a2 & (nb3 & nb4
                                			| (nb1 = ~b1) & (na3 | (na1 = ~a1))
                                			| a1 & b2);
			this.nullBit3 = b3 & (nb1 & (b2 | a2 | na1)
                        			| b1 & (b4 | nb2 | a1 & a3)
                         			| na1 & na2 & na4)
                    			| a3 & nb2 & nb4
                    			| nb1 & ((na2 & a4 | na1) & a3
                                			| a1 & na2 & na4 & b2);
			this.nullBit4 = nb1 & (a4 & (na3 & nb3	| (a3 | na2) & nb2)
                      			| a1 & (a3 & nb2 & b4
                              			| a2 & b2 & (b4	| a3 & na4 & nb3)))
                      			| b1 & (a3 & a4 & b4
                          			| na2 & na4 & nb3 & b4
                          			| a2 & ((b3 | a4) & b4
                                  				| na3 & a4 & b2 & b3)
                          			| na1 & (b4	| (a4 | a2) & b2 & b3))
                      			| (na1 & (na3 & nb3 | na2 & nb2)
                      				| a1 & (nb2 & nb3 | a2 & a3)) & b4;
			if (COVERAGE_TEST_FLAG) {
				if (CoverageTestId == 2) {
				  this.nullBit4 = ~0;
				}
			}
		}
		this.tagBits |= NULL_FLAG_MASK; // in all cases - avoid forgetting extras
	}
	// treating extra storage
	if (this.extra != null || otherInits.extra != null) {
		int mergeLimit = 0, copyLimit = 0;
		if (this.extra != null) {
			if (otherInits.extra != null) {
				// both sides have extra storage
				int length, otherLength;
				if ((length = this.extra[0].length) <
						(otherLength = otherInits.extra[0].length)) {
					// current storage is shorter -> grow current
					for (int j = 0; j < extraLength; j++) {
						System.arraycopy(this.extra[j], 0,
							(this.extra[j] = new long[otherLength]), 0, length);
					}
					mergeLimit = length;
					copyLimit = otherLength;
					if (COVERAGE_TEST_FLAG) {
						if (CoverageTestId == 3) {
							throw new AssertionFailedException("COVERAGE 3"); //$NON-NLS-1$
						}
					}
				} else {
					// current storage is longer
					mergeLimit = otherLength;
					if (COVERAGE_TEST_FLAG) {
						if (CoverageTestId == 4) {
							throw new AssertionFailedException("COVERAGE 4"); //$NON-NLS-1$
						}
					}
				}
			}
		} else if (otherInits.extra != null) {
			// no storage here, but other has extra storage.
			// shortcut regular copy because array copy is better
			int otherLength;
			this.extra = new long[extraLength][];
			System.arraycopy(otherInits.extra[0], 0,
				(this.extra[0] = new long[otherLength =
					otherInits.extra[0].length]), 0, otherLength);
			System.arraycopy(otherInits.extra[1], 0,
				(this.extra[1] = new long[otherLength]), 0, otherLength);
			if (otherHasNulls) {
				for (int j = 2; j < extraLength; j++) {
					System.arraycopy(otherInits.extra[j], 0,
						(this.extra[j] = new long[otherLength]), 0, otherLength);
				}
				if (COVERAGE_TEST_FLAG) {
					if (CoverageTestId == 5) {
						this.extra[5][otherLength - 1] = ~0;
					}
				}
			}
			else {
				for (int j = 2; j < extraLength; j++) {
					this.extra[j] = new long[otherLength];
				}
				if (COVERAGE_TEST_FLAG) {
					if (CoverageTestId == 6) {
						throw new AssertionFailedException("COVERAGE 6"); //$NON-NLS-1$
					}
				}
			}
		}
		int i;
		if (handleInits) {
			// manage definite assignment info
			for (i = 0; i < mergeLimit; i++) {
				this.extra[0][i] |= otherInits.extra[0][i];
				this.extra[1][i] |= otherInits.extra[1][i];
			}
			for (; i < copyLimit; i++) {
				this.extra[0][i] = otherInits.extra[0][i];
				this.extra[1][i] = otherInits.extra[1][i];
			
			}
		}
		// tweak limits for nulls
		if (!thisHadNulls) {
		    if (copyLimit < mergeLimit) {
		      	copyLimit = mergeLimit;
		    }
		  	mergeLimit = 0;
		}
		if (!otherHasNulls) {
		  	copyLimit = 0;
		  	mergeLimit = 0;
		}
		for (i = 0; i < mergeLimit; i++) {
			this.extra[1 + 1][i] = (b1 = otherInits.extra[1 + 1][i])
                				| (a1 = this.extra[1 + 1][i]) & ((a3 = this.extra[3 + 1][i])
                					& (a4 = this.extra[4 + 1][i]) & (nb2 = ~(b2 = otherInits.extra[2 + 1][i]))
                					& (nb4 = ~(b4 = otherInits.extra[4 + 1][i]))
                        		| ((na4 = ~a4) | (na3 = ~a3))
                        			& ((na2 = ~(a2 = this.extra[2 + 1][i])) & nb2
                        				| a2 & (nb3 = ~(b3 = otherInits.extra[3 + 1][i])) & nb4));
			this.extra[2 + 1][i]  = b2 & (nb4 | nb3)
                    			| na3 & na4 & b2
                    			| a2 & (nb3 & nb4
                                			| (nb1 = ~b1) & (na3 | (na1 = ~a1))
                                			| a1 & b2);
			this.extra[3 + 1][i] = b3 & (nb1 & (b2 | a2 | na1)
                        			| b1 & (b4 | nb2 | a1 & a3)
                         			| na1 & na2 & na4)
                    			| a3 & nb2 & nb4
                    			| nb1 & ((na2 & a4 | na1) & a3
                                			| a1 & na2 & na4 & b2);
			this.extra[4 + 1][i] = nb1 & (a4 & (na3 & nb3	| (a3 | na2) & nb2)
                      			| a1 & (a3 & nb2 & b4
                              			| a2 & b2 & (b4	| a3 & na4 & nb3)))
                      			| b1 & (a3 & a4 & b4
                          			| na2 & na4 & nb3 & b4
                          			| a2 & ((b3 | a4) & b4
                                  				| na3 & a4 & b2 & b3)
                          			| na1 & (b4	| (a4 | a2) & b2 & b3))
                      			| (na1 & (na3 & nb3 | na2 & nb2)
                      				| a1 & (nb2 & nb3 | a2 & a3)) & b4;
			if (COVERAGE_TEST_FLAG) {
				if (CoverageTestId == 7) {
				  this.extra[5][i] = ~0;
				}
			}
		}
		for (; i < copyLimit; i++) {
			for (int j = 2; j < extraLength; j++) {
				this.extra[j][i] = otherInits.extra[j][i];
			}
			if (COVERAGE_TEST_FLAG) {
				if (CoverageTestId == 8) {
				  this.extra[5][i] = ~0;
				}
			}
		}
	}
	return this;
}

public FlowInfo addPotentialInitializationsFrom(FlowInfo inits) {
	if (this == DEAD_END){
		return this;
	}
	if (inits == DEAD_END){
		return this;
	}
	UnconditionalFlowInfo otherInits = inits.unconditionalInits();
	// union of potentially set ones
	this.potentialInits |= otherInits.potentialInits;
	// treating extra storage
	if (this.extra != null) {
		if (otherInits.extra != null) {
			// both sides have extra storage
			int i = 0, length, otherLength;
			if ((length = this.extra[0].length) < (otherLength = otherInits.extra[0].length)) {
				// current storage is shorter -> grow current
				for (int j = 0; j < extraLength; j++) {
					System.arraycopy(this.extra[j], 0,
						(this.extra[j] = new long[otherLength]), 0, length);
				}
				for (; i < length; i++) {
					this.extra[1][i] |= otherInits.extra[1][i];
				}
				for (; i < otherLength; i++) {
					this.extra[1][i] = otherInits.extra[1][i];
				}
			}
			else {
				// current storage is longer
				for (; i < otherLength; i++) {
					this.extra[1][i] |= otherInits.extra[1][i];
				}
			}
		}
	}
	else if (otherInits.extra != null) {
		// no storage here, but other has extra storage.
		int otherLength = otherInits.extra[0].length;
		this.extra = new long[extraLength][];
		for (int j = 0; j < extraLength; j++) {
			this.extra[j] = new long[otherLength];
		}
		System.arraycopy(otherInits.extra[1], 0, this.extra[1], 0,
			otherLength);
	}
	addPotentialNullInfoFrom(otherInits);
	return this;
}

/**
 * Compose other inits over this flow info, then return this. The operation
 * semantics are to wave into this flow info the consequences upon null
 * information 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}; moreover,
 * only the null information is affected.
 * @param otherInits other null inits to compose over this
 * @return this, modified according to otherInits information
 */
public UnconditionalFlowInfo addPotentialNullInfoFrom(
		UnconditionalFlowInfo otherInits) {
	if ((this.tagBits & UNREACHABLE) != 0 ||
			(otherInits.tagBits & UNREACHABLE) != 0 ||
			(otherInits.tagBits & NULL_FLAG_MASK) == 0) {
		return this;
	}
	// if we get here, otherInits has some null info
	boolean thisHadNulls = (this.tagBits & NULL_FLAG_MASK) != 0,
		thisHasNulls = false;
	long a1, a2, a3, a4,
		na1, na2, na3, na4,
		b1, b2, b3, b4,
		nb1, nb2, nb3, nb4;
	if (thisHadNulls) {
		this.nullBit1  = (a1 = this.nullBit1)
								& ((a3 = this.nullBit3) & (a4 = this.nullBit4)
									& ((nb2 = ~(b2 = otherInits.nullBit2))
										& (nb4 = ~(b4 = otherInits.nullBit4))
											| (b1 = otherInits.nullBit1) & (b3 = otherInits.nullBit3))
                			| (na2 = ~(a2 = this.nullBit2))
                				& (b1 & b3 | ((na4 = ~a4) | (na3 = ~a3)) & nb2)
                			| a2 & ((na4 | na3) & ((nb3 = ~b3) & nb4 | b1 & b2)));
		this.nullBit2 = b2 & (nb3 | (nb1 = ~b1))
    			| a2 & (nb3 & nb4 | b2 | na3 | (na1 = ~a1));
		this.nullBit3 = b3 & (nb1 & b2
            		| a2 & (nb2	| a3)
            		| na1 & nb2
            		| a1 & na2 & na4 & b1)
    			| a3 & (nb2 & nb4 | na2 & a4 | na1)
    			| a1 & na2 & na4 & b2;
		this.nullBit4 = na3 & (nb1 & nb3 & b4
    				| a4 & (nb3 | b1 & b2))
    			| nb2 & (na3 & b1 & nb3	| na2 & (nb1 & b4 | b1 & nb3 | a4))
    			| a3 & (a4 & (nb2 | b1 & b3)
            			| a1 & a2 & (nb1 & b4 | na4 & (b2 | b1) & nb3));
		if (COVERAGE_TEST_FLAG) {
			if (CoverageTestId == 9) {
			  this.nullBit4 = ~0;
			}
		}
		if ((this.nullBit2 | this.nullBit3 | this.nullBit4) != 0) { //  bit1 is redundant
		  	thisHasNulls = true;
		}
	} else {
  		this.nullBit1 = 0;
  		this.nullBit2 = (b2 = otherInits.nullBit2)
  							& ((nb3 = ~(b3 = otherInits.nullBit3)) |
  								(nb1 = ~(b1 = otherInits.nullBit1)));
  		this.nullBit3 = b3 & (nb1 | (nb2 = ~b2));
  		this.nullBit4 = ~b1 & ~b3 & (b4 = otherInits.nullBit4) | ~b2 & (b1 & ~b3 | ~b1 & b4);
		if (COVERAGE_TEST_FLAG) {
			if (CoverageTestId == 10) {
			  this.nullBit4 = ~0;
			}
		}
		if ((this.nullBit2 | this.nullBit3 | this.nullBit4) != 0) { //  bit1 is redundant
		  	thisHasNulls = true;
		}
	}
	// extra storage management
	if (otherInits.extra != null) {
		int mergeLimit = 0, copyLimit = otherInits.extra[0].length;
		if (this.extra == null) {
			this.extra = new long[extraLength][];
			for (int j = 0; j < extraLength; j++) {
				this.extra[j] = new long[copyLimit];
			}
			if (COVERAGE_TEST_FLAG) {
				if (CoverageTestId == 11) {
					throw new AssertionFailedException("COVERAGE 11"); //$NON-NLS-1$
				}
			}
		} else {
			mergeLimit = copyLimit;
			if (mergeLimit > this.extra[0].length) {
				mergeLimit = this.extra[0].length;
				for (int j = 0; j < extraLength; j++) {
					System.arraycopy(this.extra[j], 0,
							this.extra[j] = new long[copyLimit], 0,
							mergeLimit);
				}
				if (! thisHadNulls) {
    				mergeLimit = 0;
    				// will do with a copy -- caveat: only valid because definite assignment bits copied above
        			if (COVERAGE_TEST_FLAG) {
        				if (CoverageTestId == 12) {
							throw new AssertionFailedException("COVERAGE 12"); //$NON-NLS-1$
        				}
        			}
				}
			}
		}
		// PREMATURE skip operations for fields
		int i;
		for (i = 0 ; i < mergeLimit ; i++) {
    		this.extra[1 + 1][i]  = (a1 = this.extra[1 + 1][i])
    								& ((a3 = this.extra[3 + 1][i]) & (a4 = this.extra[4 + 1][i])
    									& ((nb2 = ~(b2 = otherInits.extra[2 + 1][i]))
    										& (nb4 = ~(b4 = otherInits.extra[4 + 1][i]))
    											| (b1 = otherInits.extra[1 + 1][i]) & (b3 = otherInits.extra[3 + 1][i]))
                    			| (na2 = ~(a2 = this.extra[2 + 1][i]))
                    				& (b1 & b3 | ((na4 = ~a4) | (na3 = ~a3)) & nb2)
                    			| a2 & ((na4 | na3) & ((nb3 = ~b3) & nb4 | b1 & b2)));
    		this.extra[2 + 1][i] = b2 & (nb3 | (nb1 = ~b1))
        			| a2 & (nb3 & nb4 | b2 | na3 | (na1 = ~a1));
    		this.extra[3 + 1][i] = b3 & (nb1 & b2
                		| a2 & (nb2	| a3)
                		| na1 & nb2
                		| a1 & na2 & na4 & b1)
        			| a3 & (nb2 & nb4 | na2 & a4 | na1)
        			| a1 & na2 & na4 & b2;
    		this.extra[4 + 1][i] = na3 & (nb1 & nb3 & b4
        				| a4 & (nb3 | b1 & b2))
        			| nb2 & (na3 & b1 & nb3	| na2 & (nb1 & b4 | b1 & nb3 | a4))
        			| a3 & (a4 & (nb2 | b1 & b3)
                			| a1 & a2 & (nb1 & b4 | na4 & (b2 | b1) & nb3));
    		if ((this.extra[2 + 1][i] | this.extra[3 + 1][i] | this.extra[4 + 1][i]) != 0) { //  bit1 is redundant
    		  	thisHasNulls = true;
    		}
			if (COVERAGE_TEST_FLAG) {
				if (CoverageTestId == 13) {
				  this.nullBit4 = ~0;
				}
			}
		}
		for (; i < copyLimit; i++) {
    		this.extra[1 + 1][i] = 0;
    		this.extra[2 + 1][i] = (b2 = otherInits.extra[2 + 1][i])
    							& ((nb3 = ~(b3 = otherInits.extra[3 + 1][i])) |
    								(nb1 = ~(b1 = otherInits.extra[1 + 1][i])));
    		this.extra[3 + 1][i] = b3 & (nb1 | (nb2 = ~b2));
    		this.extra[4 + 1][i] = ~b1 & ~b3 & (b4 = otherInits.extra[4 + 1][i]) | ~b2 & (b1 & ~b3 | ~b1 & b4);
    		if ((this.extra[2 + 1][i] | this.extra[3 + 1][i] | this.extra[4 + 1][i]) != 0) { //  bit1 is redundant
    		  	thisHasNulls = true;
    		}
			if (COVERAGE_TEST_FLAG) {
				if (CoverageTestId == 14) {
				  this.extra[5][i] = ~0;
				}
			}
		}
	}
	if (thisHasNulls) {
		this.tagBits |= NULL_FLAG_MASK;
	}
	else {
		this.tagBits &= NULL_FLAG_MASK;
	}
	return this;
}

final public boolean cannotBeDefinitelyNullOrNonNull(LocalVariableBinding local) {
	if ((this.tagBits & NULL_FLAG_MASK) == 0 ||
			(local.type.tagBits & TagBits.IsBaseType) != 0) {
		return false;
	}
	int position;
	if ((position = local.id + this.maxFieldCount) < BitCacheSize) {
		// use bits
		return (
			(~this.nullBit1
					& (this.nullBit2 & this.nullBit3 | this.nullBit4)
				| ~this.nullBit2 & ~this.nullBit3 & this.nullBit4)
			& (1L << position)) != 0;
	}
	// use extra vector
	if (this.extra == null) {
		return false; // if vector not yet allocated, then not initialized
	}
	int vectorIndex;
	if ((vectorIndex = (position / BitCacheSize) - 1) >=
			this.extra[0].length) {
		return false; // if not enough room in vector, then not initialized
	}
	long a2, a3, a4;
	return (
			(~this.extra[2][vectorIndex]
					& ((a2 = this.extra[3][vectorIndex]) & (a3 = this.extra[4][vectorIndex]) | (a4 = this.extra[5][vectorIndex]))
				| ~a2 & ~a3 & a4)
		    & (1L << (position % BitCacheSize))) != 0;
}

final public boolean cannotBeNull(LocalVariableBinding local) {
	if ((this.tagBits & NULL_FLAG_MASK) == 0 ||
			(local.type.tagBits & TagBits.IsBaseType) != 0) {
		return false;
	}
	int position;
	if ((position = local.id + this.maxFieldCount) < BitCacheSize) {
		// use bits
		return (this.nullBit1 & this.nullBit3
			& ((this.nullBit2 & this.nullBit4) | ~this.nullBit2)
			& (1L << position)) != 0;
	}
	// use extra vector
	if (this.extra == null) {
		return false; // if vector not yet allocated, then not initialized
	}
	int vectorIndex;
	if ((vectorIndex = (position / BitCacheSize) - 1) >=
			this.extra[0].length) {
		return false; // if not enough room in vector, then not initialized
	}
	return (this.extra[2][vectorIndex] & this.extra[4][vectorIndex]
	        & ((this.extra[3][vectorIndex] & this.extra[5][vectorIndex]) |
	        		~this.extra[3][vectorIndex])
		    & (1L << (position % BitCacheSize))) != 0;
}

final public boolean canOnlyBeNull(LocalVariableBinding local) {
	if ((this.tagBits & NULL_FLAG_MASK) == 0 ||
			(local.type.tagBits & TagBits.IsBaseType) != 0) {
		return false;
	}
	int position;
	if ((position = local.id + this.maxFieldCount) < BitCacheSize) {
		// use bits
		return (this.nullBit1 & this.nullBit2
			& (~this.nullBit3 | ~this.nullBit4)
			& (1L << position)) != 0;
	}
	// use extra vector
	if (this.extra == null) {
		return false; // if vector not yet allocated, then not initialized
	}
	int vectorIndex;
	if ((vectorIndex = (position / BitCacheSize) - 1) >=
			this.extra[0].length) {
		return false; // if not enough room in vector, then not initialized
	}
	return (this.extra[2][vectorIndex] & this.extra[3][vectorIndex]
	        & (~this.extra[4][vectorIndex] | ~this.extra[5][vectorIndex])
		    & (1L << (position % BitCacheSize))) != 0;
}

public FlowInfo copy() {
	// do not clone the DeadEnd
	if (this == DEAD_END) {
		return this;
	}
	UnconditionalFlowInfo copy = new UnconditionalFlowInfo();
	// copy slots
	copy.definiteInits = this.definiteInits;
	copy.potentialInits = this.potentialInits;
	boolean hasNullInfo = (this.tagBits & NULL_FLAG_MASK) != 0;
	if (hasNullInfo) {
		copy.nullBit1 = this.nullBit1;
		copy.nullBit2 = this.nullBit2;
		copy.nullBit3 = this.nullBit3;
		copy.nullBit4 = this.nullBit4;
	}
	copy.tagBits = this.tagBits;
	copy.maxFieldCount = this.maxFieldCount;
	if (this.extra != null) {
		int length;
		copy.extra = new long[extraLength][];
		System.arraycopy(this.extra[0], 0,
			(copy.extra[0] = new long[length = this.extra[0].length]), 0,
			length);
		System.arraycopy(this.extra[1], 0,
			(copy.extra[1] = new long[length]), 0, length);
		if (hasNullInfo) {
			for (int j = 2; j < extraLength; j++) {
				System.arraycopy(this.extra[j], 0,
					(copy.extra[j] = new long[length]), 0, length);
			}
		}
		else {
			for (int j = 2; j < extraLength; j++) {
				copy.extra[j] = new long[length];
			}
		}
	}
	return copy;
}

/**
 * Discard definite inits and potential inits from this, then return this.
 * The returned flow info only holds null related information.
 * @return this flow info, minus definite inits and potential inits
 */
public UnconditionalFlowInfo discardInitializationInfo() {
	if (this == DEAD_END) {
		return this;
	}
	this.definiteInits =
		this.potentialInits = 0;
	if (this.extra != null) {
		for (int i = 0, length = this.extra[0].length; i < length; i++) {
			this.extra[0][i] = this.extra[1][i] = 0;
		}
	}
	return this;
}

/**
 * Remove local variables information from this flow info and return this.
 * @return this, deprived from any local variable information
 */
public UnconditionalFlowInfo discardNonFieldInitializations() {
	int limit = this.maxFieldCount;
	if (limit < BitCacheSize) {
		long mask = (1L << limit)-1;
		this.definiteInits &= mask;
		this.potentialInits &= mask;
		this.nullBit1 &= mask;
		this.nullBit2 &= mask;
		this.nullBit3 &= mask;
		this.nullBit4 &= mask;
	}
	// use extra vector
	if (this.extra == null) {
		return this; // if vector not yet allocated, then not initialized
	}
	int vectorIndex, length = this.extra[0].length;
	if ((vectorIndex = (limit / BitCacheSize) - 1) >= length) {
		return this; // not enough room yet
	}
	if (vectorIndex >= 0) {
		// else we only have complete non field array items left
		long mask = (1L << (limit % BitCacheSize))-1;
		for (int j = 0; j < extraLength; j++) {
			this.extra[j][vectorIndex] &= mask;
		}
	}
	for (int i = vectorIndex + 1; i < length; i++) {
		for (int j = 0; j < extraLength; j++) {
			this.extra[j][i] = 0;
		}
	}
	return this;
}

public FlowInfo initsWhenFalse() {
	return this;
}

public FlowInfo initsWhenTrue() {
	return this;
}

/**
 * Check status of definite assignment at a given position.
 * It deals with the dual representation of the InitializationInfo2:
 * bits for the first 64 entries, then an array of booleans.
 */
final private boolean isDefinitelyAssigned(int position) {
	if (position < BitCacheSize) {
		// use bits
		return (this.definiteInits & (1L << position)) != 0;
	}
	// use extra vector
	if (this.extra == null)
		return false; // if vector not yet allocated, then not initialized
	int vectorIndex;
	if ((vectorIndex = (position / BitCacheSize) - 1)
			>= this.extra[0].length) {
		return false; // if not enough room in vector, then not initialized
	}
	return ((this.extra[0][vectorIndex]) &
				(1L << (position % BitCacheSize))) != 0;
}

final public boolean isDefinitelyAssigned(FieldBinding field) {
	// Mirrored in CodeStream.isDefinitelyAssigned(..)
	// do not want to complain in unreachable code
	if ((this.tagBits & UNREACHABLE_OR_DEAD) != 0) {
		return true;
	}
	return isDefinitelyAssigned(field.id);
}

final public boolean isDefinitelyAssigned(LocalVariableBinding local) {
	// do not want to complain in unreachable code if local declared in reachable code
	if ((this.tagBits & UNREACHABLE_OR_DEAD) != 0 && (local.declaration.bits & ASTNode.IsLocalDeclarationReachable) != 0) {
		return true;
	}
	return isDefinitelyAssigned(local.id + this.maxFieldCount);
}

final public boolean isDefinitelyNonNull(LocalVariableBinding local) {
	// do not want to complain in unreachable code
	if ((this.tagBits & UNREACHABLE) != 0 ||
			(this.tagBits & NULL_FLAG_MASK) == 0) {
		return false;
	}
	if ((local.type.tagBits & TagBits.IsBaseType) != 0 ||
			local.constant() != Constant.NotAConstant) { // String instances
		return true;
	}
	int position = local.id + this.maxFieldCount;
	if (position < BitCacheSize) { // use bits
		return ((this.nullBit1 & this.nullBit3 & (~this.nullBit2 | this.nullBit4))
			    & (1L << position)) != 0;
	}
	// use extra vector
	if (this.extra == null) {
		return false; // if vector not yet allocated, then not initialized
	}
	int vectorIndex;
	if ((vectorIndex = (position / BitCacheSize) - 1)
			>= this.extra[2].length) {
		return false; // if not enough room in vector, then not initialized
	}
	return ((this.extra[2][vectorIndex] & this.extra[4][vectorIndex]
		        & (~this.extra[3][vectorIndex] | this.extra[5][vectorIndex]))
		    & (1L << (position % BitCacheSize))) != 0;
}

final public boolean isDefinitelyNull(LocalVariableBinding local) {
	// do not want to complain in unreachable code
	if ((this.tagBits & UNREACHABLE) != 0 ||
			(this.tagBits & NULL_FLAG_MASK) == 0 ||
			(local.type.tagBits & TagBits.IsBaseType) != 0) {
		return false;
	}
	int position = local.id + this.maxFieldCount;
	if (position < BitCacheSize) { // use bits
		return ((this.nullBit1 & this.nullBit2
			        & (~this.nullBit3 | ~this.nullBit4))
			    & (1L << position)) != 0;
	}
	// use extra vector
	if (this.extra == null) {
		return false; // if vector not yet allocated, then not initialized
	}
	int vectorIndex;
	if ((vectorIndex = (position / BitCacheSize) - 1) >=
			this.extra[2].length) {
		return false; // if not enough room in vector, then not initialized
	}
	return ((this.extra[2][vectorIndex] & this.extra[3][vectorIndex]
		        & (~this.extra[4][vectorIndex] | ~this.extra[5][vectorIndex]))
		    & (1L << (position % BitCacheSize))) != 0;
}

final public boolean isDefinitelyUnknown(LocalVariableBinding local) {
	// do not want to complain in unreachable code
	if ((this.tagBits & UNREACHABLE) != 0 ||
			(this.tagBits & NULL_FLAG_MASK) == 0) {
		return false;
	}
	int position = local.id + this.maxFieldCount;
	if (position < BitCacheSize) { // use bits
		return ((this.nullBit1 & this.nullBit4
				& ~this.nullBit2 & ~this.nullBit3) & (1L << position)) != 0;
	}
	// use extra vector
	if (this.extra == null) {
		return false; // if vector not yet allocated, then not initialized
	}
	int vectorIndex;
	if ((vectorIndex = (position / BitCacheSize) - 1) >=
			this.extra[2].length) {
		return false; // if not enough room in vector, then not initialized
	}
	return ((this.extra[2][vectorIndex] & this.extra[5][vectorIndex]
	    & ~this.extra[3][vectorIndex] & ~this.extra[4][vectorIndex])
		    & (1L << (position % BitCacheSize))) != 0;
}

final public boolean hasNullInfoFor(LocalVariableBinding local) {
	// do not want to complain in unreachable code
	if ((this.tagBits & UNREACHABLE) != 0 ||
			(this.tagBits & NULL_FLAG_MASK) == 0) {
		return false;
	}
	int position = local.id + this.maxFieldCount;
	if (position < BitCacheSize) { // use bits
		return ((this.nullBit1 | this.nullBit2
				| this.nullBit3 | this.nullBit4) & (1L << position)) != 0;
	}
	// use extra vector
	if (this.extra == null) {
		return false; // if vector not yet allocated, then not initialized
	}
	int vectorIndex;
	if ((vectorIndex = (position / BitCacheSize) - 1) >=
			this.extra[2].length) {
		return false; // if not enough room in vector, then not initialized
	}
	return ((this.extra[2][vectorIndex] | this.extra[3][vectorIndex]
	    | this.extra[4][vectorIndex] | this.extra[5][vectorIndex])
		    & (1L << (position % BitCacheSize))) != 0;
}

/**
 * Check status of potential assignment at a given position.
 */
final private boolean isPotentiallyAssigned(int position) {
	// id is zero-based
	if (position < BitCacheSize) {
		// use bits
		return (this.potentialInits & (1L << position)) != 0;
	}
	// use extra vector
	if (this.extra == null) {
		return false; // if vector not yet allocated, then not initialized
	}
	int vectorIndex;
	if ((vectorIndex = (position / BitCacheSize) - 1)
			>= this.extra[0].length) {
		return false; // if not enough room in vector, then not initialized
	}
	return ((this.extra[1][vectorIndex]) &
			(1L << (position % BitCacheSize))) != 0;
}

final public boolean isPotentiallyAssigned(FieldBinding field) {
	return isPotentiallyAssigned(field.id);
}

final public boolean isPotentiallyAssigned(LocalVariableBinding local) {
	// final constants are inlined, and thus considered as always initialized
	if (local.constant() != Constant.NotAConstant) {
		return true;
	}
	return isPotentiallyAssigned(local.id + this.maxFieldCount);
}

// TODO (Ayush) Check why this method does not return true for protected non null (1111)
final public boolean isPotentiallyNonNull(LocalVariableBinding local) {
	if ((this.tagBits & NULL_FLAG_MASK) == 0 ||
			(local.type.tagBits & TagBits.IsBaseType) != 0) {
		return false;
	}
	int position;
	if ((position = local.id + this.maxFieldCount) < BitCacheSize) {
		// use bits
		return ((this.nullBit3 & (~this.nullBit1 | ~this.nullBit2))
			    & (1L << position)) != 0;
	}
	// use extra vector
	if (this.extra == null) {
		return false; // if vector not yet allocated, then not initialized
	}
	int vectorIndex;
	if ((vectorIndex = (position / BitCacheSize) - 1) >=
			this.extra[2].length) {
		return false; // if not enough room in vector, then not initialized
	}
	return ((this.extra[4][vectorIndex]
		        & (~this.extra[2][vectorIndex] | ~this.extra[3][vectorIndex]))
		    & (1L << (position % BitCacheSize))) != 0;
}

// TODO (Ayush) Check why this method does not return true for protected null
final public boolean isPotentiallyNull(LocalVariableBinding local) {
	if ((this.tagBits & NULL_FLAG_MASK) == 0 ||
			(local.type.tagBits & TagBits.IsBaseType) != 0) {
		return false;
	}
	int position;
	if ((position = local.id + this.maxFieldCount) < BitCacheSize) {
		// use bits
		return ((this.nullBit2 & (~this.nullBit1 | ~this.nullBit3))
			    & (1L << position)) != 0;
	}
	// use extra vector
	if (this.extra == null) {
		return false; // if vector not yet allocated, then not initialized
	}
	int vectorIndex;
	if ((vectorIndex = (position / BitCacheSize) - 1) >=
			this.extra[2].length) {
		return false; // if not enough room in vector, then not initialized
	}
	return ((this.extra[3][vectorIndex]
		        & (~this.extra[2][vectorIndex] | ~this.extra[4][vectorIndex]))
		    & (1L << (position % BitCacheSize))) != 0;
}

final public boolean isPotentiallyUnknown(LocalVariableBinding local) {
	// do not want to complain in unreachable code
	if ((this.tagBits & UNREACHABLE) != 0 ||
			(this.tagBits & NULL_FLAG_MASK) == 0) {
		return false;
	}
	int position = local.id + this.maxFieldCount;
	if (position < BitCacheSize) { // use bits
		return (this.nullBit4
			& (~this.nullBit1 | ~this.nullBit2 & ~this.nullBit3)
			& (1L << position)) != 0;
	}
	// use extra vector
	if (this.extra == null) {
		return false; // if vector not yet allocated, then not initialized
	}
	int vectorIndex;
	if ((vectorIndex = (position / BitCacheSize) - 1) >=
			this.extra[2].length) {
		return false; // if not enough room in vector, then not initialized
	}
	return (this.extra[5][vectorIndex]
	        & (~this.extra[2][vectorIndex]
	            | ~this.extra[3][vectorIndex] & ~this.extra[4][vectorIndex])
		    & (1L << (position % BitCacheSize))) != 0;
}

final public boolean isProtectedNonNull(LocalVariableBinding local) {
	if ((this.tagBits & NULL_FLAG_MASK) == 0 ||
			(local.type.tagBits & TagBits.IsBaseType) != 0) {
		return false;
	}
	int position;
	if ((position = local.id + this.maxFieldCount) < BitCacheSize) {
		// use bits
		return (this.nullBit1 & this.nullBit3 & this.nullBit4 & (1L << position)) != 0;
	}
	// use extra vector
	if (this.extra == null) {
		return false; // if vector not yet allocated, then not initialized
	}
	int vectorIndex;
	if ((vectorIndex = (position / BitCacheSize) - 1) >=
		this.extra[0].length) {
		return false; // if not enough room in vector, then not initialized
	}
	return (this.extra[2][vectorIndex]
	            & this.extra[4][vectorIndex]
	            & this.extra[5][vectorIndex]
		    & (1L << (position % BitCacheSize))) != 0;
}

final public boolean isProtectedNull(LocalVariableBinding local) {
	if ((this.tagBits & NULL_FLAG_MASK) == 0 ||
			(local.type.tagBits & TagBits.IsBaseType) != 0) {
		return false;
	}
	int position;
	if ((position = local.id + this.maxFieldCount) < BitCacheSize) {
		// use bits
		return (this.nullBit1 & this.nullBit2
			& (this.nullBit3 ^ this.nullBit4)
			& (1L << position)) != 0;
	}
	// use extra vector
	if (this.extra == null) {
		return false; // if vector not yet allocated, then not initialized
	}
	int vectorIndex;
	if ((vectorIndex = (position / BitCacheSize) - 1) >=
			this.extra[0].length) {
		return false; // if not enough room in vector, then not initialized
	}
	return (this.extra[2][vectorIndex] & this.extra[3][vectorIndex]
	        & (this.extra[4][vectorIndex] ^ this.extra[5][vectorIndex])
		    & (1L << (position % BitCacheSize))) != 0;
}
/** Asserts that the given boolean is <code>true</code>. If this
 * is not the case, some kind of unchecked exception is thrown.
 * The given message is included in that exception, to aid debugging.
 *
 * @param expression the outcome of the check
 * @param message the message to include in the exception
 * @return <code>true</code> if the check passes (does not return
 *    if the check fails)
 */
protected static boolean isTrue(boolean expression, String message) {
	if (!expression)
		throw new AssertionFailedException("assertion failed: " + message); //$NON-NLS-1$
	return expression;
}
public void markAsComparedEqualToNonNull(LocalVariableBinding local) {
	// protected from non-object locals in calling methods
	if (this != DEAD_END) {
		this.tagBits |= NULL_FLAG_MASK;
		int position;
		long mask;
		long a1, a2, a3, a4, na2;
		// position is zero-based
		if ((position = local.id + this.maxFieldCount) < BitCacheSize) {
			// use bits
			if (((mask = 1L << position)
				& (a1 = this.nullBit1)
				& (na2 = ~(a2 = this.nullBit2))
				& ~(a3 = this.nullBit3)
				& (a4 = this.nullBit4))
					!= 0) {
			  	this.nullBit4 &= ~mask;
			} else if ((mask & a1 & na2 & a3) == 0) {
			  	this.nullBit4 |= mask;
			  	if ((mask & a1) == 0) {
			  	  	if ((mask & a2 & (a3 ^ a4)) != 0) {
			  	  	  	this.nullBit2 &= ~mask;
			  	  	}
			  	  	else if ((mask & (a2 | a3 | a4)) == 0) {
			  	  	  	this.nullBit2 |= mask;
			  	  	}
			  	}
			}
			this.nullBit1 |= mask;
			this.nullBit3 |= mask;
			if (COVERAGE_TEST_FLAG) {
				if (CoverageTestId == 15) {
				  	this.nullBit4 = ~0;
				}
			}
		}
		else {
			// use extra vector
			int vectorIndex = (position / BitCacheSize) - 1;
			if (this.extra == null) {
				int length = vectorIndex + 1;
				this.extra = new long[extraLength][];
				for (int j = 0; j < extraLength; j++) {
					this.extra[j] = new long[length];
				}
				if (COVERAGE_TEST_FLAG) {
					if (CoverageTestId == 16) {
						throw new AssertionFailedException("COVERAGE 16"); //$NON-NLS-1$
					}
				}
			}
			else {
				int oldLength;
				if (vectorIndex >= (oldLength = this.extra[0].length)) {
					int newLength = vectorIndex + 1;
					for (int j = 0; j < extraLength; j++) {
						System.arraycopy(this.extra[j], 0,
							(this.extra[j] = new long[newLength]), 0,
							oldLength);
					}
					if (COVERAGE_TEST_FLAG) {
						if (CoverageTestId == 17) {
							throw new AssertionFailedException("COVERAGE 17"); //$NON-NLS-1$
						}
					}
				}
			}
			// MACRO :'b,'es/nullBit\(.\)/extra[\1 + 1][vectorIndex]/gc
			if (((mask = 1L << (position % BitCacheSize))
  				& (a1 = this.extra[1 + 1][vectorIndex])
  				& (na2 = ~(a2 = this.extra[2 + 1][vectorIndex]))
  				& ~(a3 = this.extra[3 + 1][vectorIndex])
  				& (a4 = this.extra[4 + 1][vectorIndex]))
  					!= 0) {
  			  	this.extra[4 + 1][vectorIndex] &= ~mask;
  			} else if ((mask & a1 & na2 & a3) == 0) {
  			  	this.extra[4 + 1][vectorIndex] |= mask;
  			  	if ((mask & a1) == 0) {
  			  	  	if ((mask & a2 & (a3 ^ a4)) != 0) {
  			  	  	  	this.extra[2 + 1][vectorIndex] &= ~mask;
  			  	  	}
  			  	  	else if ((mask & (a2 | a3 | a4)) == 0) {
  			  	  	  	this.extra[2 + 1][vectorIndex] |= mask;
  			  	  	}
  			  	}
  			}
  			this.extra[1 + 1][vectorIndex] |= mask;
  			this.extra[3 + 1][vectorIndex] |= mask;
			if (COVERAGE_TEST_FLAG) {
				if (CoverageTestId == 18) {
				  	this.extra[5][vectorIndex] = ~0;
				}
			}
		}
	}
}

public void markAsComparedEqualToNull(LocalVariableBinding local) {
	// protected from non-object locals in calling methods
	if (this != DEAD_END) {
		this.tagBits |= NULL_FLAG_MASK;
		int position;
		long mask;
		// position is zero-based
		if ((position = local.id + this.maxFieldCount) < BitCacheSize) {
			// use bits
			if (((mask = 1L << position) & this.nullBit1) != 0) {
  			  	if ((mask
  			  		& (~this.nullBit2 | this.nullBit3
  			  			| ~this.nullBit4)) != 0) {
  			  	  	this.nullBit4 &= ~mask;
  			  	}
			} else if ((mask & this.nullBit4) != 0) {
			  	  this.nullBit3 &= ~mask;
			} else {
    			if ((mask & this.nullBit2) != 0) {
    			  	this.nullBit3 &= ~mask;
      			  	this.nullBit4 |= mask;
    			} else {
    			  	this.nullBit3 |= mask;
    			}
			}
			this.nullBit1 |= mask;
			this.nullBit2 |= mask;
			if (COVERAGE_TEST_FLAG) {
				if (CoverageTestId == 19) {
				  	this.nullBit4 = ~0;
				}
			}
		}
		else {
			// use extra vector
			int vectorIndex = (position / BitCacheSize) - 1;
			mask = 1L << (position % BitCacheSize);
			if (this.extra == null) {
				int length = vectorIndex + 1;
				this.extra = new long[extraLength][];
				for (int j = 0; j < extraLength; j++) {
					this.extra[j] = new long[length ];
				}
				if (COVERAGE_TEST_FLAG) {
					if(CoverageTestId == 20) {
						throw new AssertionFailedException("COVERAGE 20"); //$NON-NLS-1$
					}
				}
			}
			else {
				int oldLength;
				if (vectorIndex >= (oldLength = this.extra[0].length)) {
					int newLength = vectorIndex + 1;
					for (int j = 0; j < extraLength; j++) {
						System.arraycopy(this.extra[j], 0,
							(this.extra[j] = new long[newLength]), 0,
							oldLength);
					}
					if (COVERAGE_TEST_FLAG) {
						if(CoverageTestId == 21) {
							throw new AssertionFailedException("COVERAGE 21"); //$NON-NLS-1$
						}
					}
				}
			}
			if ((mask & this.extra[1 + 1][vectorIndex]) != 0) {
  			  	if ((mask
  			  		& (~this.extra[2 + 1][vectorIndex] | this.extra[3 + 1][vectorIndex]
  			  			| ~this.extra[4 + 1][vectorIndex])) != 0) {
  			  	  	this.extra[4 + 1][vectorIndex] &= ~mask;
  			  	}
			} else if ((mask & this.extra[4 + 1][vectorIndex]) != 0) {
			  	  this.extra[3 + 1][vectorIndex] &= ~mask;
			} else {
    			if ((mask & this.extra[2 + 1][vectorIndex]) != 0) {
    			  	this.extra[3 + 1][vectorIndex] &= ~mask;
      			  	this.extra[4 + 1][vectorIndex] |= mask;
    			} else {
    			  	this.extra[3 + 1][vectorIndex] |= mask;
    			}
			}
			this.extra[1 + 1][vectorIndex] |= mask;
			this.extra[2 + 1][vectorIndex] |= mask;
		}
	}
}

/**
 * Record a definite assignment at a given position.
 */
final private void markAsDefinitelyAssigned(int position) {

	if (this != DEAD_END) {
		// position is zero-based
		if (position < BitCacheSize) {
			// use bits
			long mask;
			this.definiteInits |= (mask = 1L << position);
			this.potentialInits |= mask;
		}
		else {
			// use extra vector
			int vectorIndex = (position / BitCacheSize) - 1;
			if (this.extra == null) {
				int length = vectorIndex + 1;
				this.extra = new long[extraLength][];
				for (int j = 0; j < extraLength; j++) {
					this.extra[j] = new long[length];
				}
			}
			else {
				int oldLength; // might need to grow the arrays
				if (vectorIndex >= (oldLength = this.extra[0].length)) {
					for (int j = 0; j < extraLength; j++) {
						System.arraycopy(this.extra[j], 0,
							(this.extra[j] = new long[vectorIndex + 1]), 0,
							oldLength);
					}
				}
			}
			long mask;
			this.extra[0][vectorIndex] |=
				(mask = 1L << (position % BitCacheSize));
			this.extra[1][vectorIndex] |= mask;
		}
	}
}

public void markAsDefinitelyAssigned(FieldBinding field) {
	if (this != DEAD_END)
		markAsDefinitelyAssigned(field.id);
}

public void markAsDefinitelyAssigned(LocalVariableBinding local) {
	if (this != DEAD_END)
		markAsDefinitelyAssigned(local.id + this.maxFieldCount);
}

public void markAsDefinitelyNonNull(LocalVariableBinding local) {
	// protected from non-object locals in calling methods
	if (this != DEAD_END) {
    	this.tagBits |= NULL_FLAG_MASK;
    	long mask;
    	int position;
    	// position is zero-based
    	if ((position = local.id + this.maxFieldCount) < BitCacheSize) { // use bits
    		// set assigned non null
    		this.nullBit1 |= (mask = 1L << position);
    		this.nullBit3 |= mask;
    		// clear others
    		this.nullBit2 &= (mask = ~mask);
    		this.nullBit4 &= mask;
    		if (COVERAGE_TEST_FLAG) {
    			if(CoverageTestId == 22) {
	    		  	this.nullBit1 = 0;
    			}
    		}
    	}
    	else {
    		// use extra vector
    		int vectorIndex = (position / BitCacheSize) - 1;
    		if (this.extra == null) {
    			int length = vectorIndex + 1;
    			this.extra = new long[extraLength][];
    			for (int j = 0; j < extraLength; j++) {
    				this.extra[j] = new long[length];
    			}
    		}
    		else {
    			int oldLength; // might need to grow the arrays
    			if (vectorIndex >= (oldLength = this.extra[0].length)) {
    				for (int j = 0; j < extraLength; j++) {
    					System.arraycopy(this.extra[j], 0,
    						(this.extra[j] = new long[vectorIndex + 1]), 0,
    						oldLength);
    				}
    			}
    		}
    		this.extra[2][vectorIndex]
    		    |= (mask = 1L << (position % BitCacheSize));
    		this.extra[4][vectorIndex] |= mask;
    		this.extra[3][vectorIndex] &= (mask = ~mask);
    		this.extra[5][vectorIndex] &= mask;
    		if (COVERAGE_TEST_FLAG) {
    			if(CoverageTestId == 23) {
	    			this.extra[2][vectorIndex] = 0;
    			}
    		}
    	}
	}
}

public void markAsDefinitelyNull(LocalVariableBinding local) {
	// protected from non-object locals in calling methods
	if (this != DEAD_END) {
    	this.tagBits |= NULL_FLAG_MASK;
    	long mask;
    	int position;
    	// position is zero-based
    	if ((position = local.id + this.maxFieldCount) < BitCacheSize) { // use bits
    		// mark assigned null
    		this.nullBit1 |= (mask = 1L << position);
    		this.nullBit2 |= mask;
    		// clear others
    		this.nullBit3 &= (mask = ~mask);
    		this.nullBit4 &= mask;
    		if (COVERAGE_TEST_FLAG) {
    			if(CoverageTestId == 24) {
	    		  	this.nullBit4 = ~0;
    			}
    		}
    	}
    	else {
    		// use extra vector
    		int vectorIndex = (position / BitCacheSize) - 1;
    		if (this.extra == null) {
    			int length = vectorIndex + 1;
    			this.extra = new long[extraLength][];
    			for (int j = 0; j < extraLength; j++) {
    				this.extra[j] = new long[length];
    			}
    		}
    		else {
    			int oldLength; // might need to grow the arrays
    			if (vectorIndex >= (oldLength = this.extra[0].length)) {
    				for (int j = 0; j < extraLength; j++) {
    					System.arraycopy(this.extra[j], 0,
    						(this.extra[j] = new long[vectorIndex + 1]), 0,
    						oldLength);
    				}
    			}
    		}
    		this.extra[2][vectorIndex]
    		    |= (mask = 1L << (position % BitCacheSize));
    		this.extra[3][vectorIndex] |= mask;
    		this.extra[4][vectorIndex] &= (mask = ~mask);
    		this.extra[5][vectorIndex] &= mask;
    		if (COVERAGE_TEST_FLAG) {
    			if(CoverageTestId == 25) {
	    			this.extra[5][vectorIndex] = ~0;
    			}
    		}
    	}
	}
}

/**
 * Mark a local as having been assigned to an unknown value.
 * @param local the local to mark
 */
// PREMATURE may try to get closer to markAsDefinitelyAssigned, but not
//			 obvious
public void markAsDefinitelyUnknown(LocalVariableBinding local) {
	// protected from non-object locals in calling methods
	if (this != DEAD_END) {
		this.tagBits |= NULL_FLAG_MASK;
		long mask;
		int position;
		// position is zero-based
		if ((position = local.id + this.maxFieldCount) < BitCacheSize) {
			// use bits
			// mark assigned null
			this.nullBit1 |= (mask = 1L << position);
			this.nullBit4 |= mask;
			// clear others
			this.nullBit2 &= (mask = ~mask);
			this.nullBit3 &= mask;
			if (COVERAGE_TEST_FLAG) {
				if(CoverageTestId == 26) {
				  	this.nullBit4 = 0;
				}
			}
		}
		else {
			// use extra vector
			int vectorIndex = (position / BitCacheSize) - 1;
			if (this.extra == null) {
				int length = vectorIndex + 1;
				this.extra = new long[extraLength][];
				for (int j = 0; j < extraLength; j++) {
					this.extra[j] = new long[length];
				}
			}
			else {
				int oldLength; // might need to grow the arrays
				if (vectorIndex >= (oldLength = this.extra[0].length)) {
					for (int j = 0; j < extraLength; j++) {
						System.arraycopy(this.extra[j], 0,
							(this.extra[j] = new long[vectorIndex + 1]), 0,
							oldLength);
					}
				}
			}
			this.extra[2][vectorIndex]
			    |= (mask = 1L << (position % BitCacheSize));
			this.extra[5][vectorIndex] |= mask;
			this.extra[3][vectorIndex] &= (mask = ~mask);
			this.extra[4][vectorIndex] &= mask;
			if (COVERAGE_TEST_FLAG) {
				if(CoverageTestId == 27) {
					this.extra[5][vectorIndex] = 0;
				}
			}
		}
	}
}

public void resetNullInfo(LocalVariableBinding local) {
	if (this != DEAD_END) {
		this.tagBits |= NULL_FLAG_MASK;
        int position;
        long mask;
        if ((position = local.id + this.maxFieldCount) < BitCacheSize) {
            // use bits
            this.nullBit1 &= (mask = ~(1L << position));
            this.nullBit2 &= mask;
            this.nullBit3 &= mask;
            this.nullBit4 &= mask;
        } else {
    		// use extra vector
    		int vectorIndex = (position / BitCacheSize) - 1;
    		if (this.extra == null || vectorIndex >= this.extra[2].length) {
    			// in case we attempt to reset the null info of a variable that has not been encountered
    			// before and for which no null bits exist.
    			return;
    		}
    		this.extra[2][vectorIndex]
    		    &= (mask = ~(1L << (position % BitCacheSize)));
    		this.extra[3][vectorIndex] &= mask;
    		this.extra[4][vectorIndex] &= mask;
    		this.extra[5][vectorIndex] &= mask;
    	}
	}
}

/**
 * Mark a local as potentially having been assigned to an unknown value.
 * @param local the local to mark
 */
public void markPotentiallyUnknownBit(LocalVariableBinding local) {
	// protected from non-object locals in calling methods
	if (this != DEAD_END) {
		this.tagBits |= NULL_FLAG_MASK;
        int position;
        long mask;
        if ((position = local.id + this.maxFieldCount) < BitCacheSize) {
            // use bits
        	mask = 1L << position;
        	isTrue((this.nullBit1 & mask) == 0, "Adding 'unknown' mark in unexpected state"); //$NON-NLS-1$
            this.nullBit4 |= mask;
            if (COVERAGE_TEST_FLAG) {
				if(CoverageTestId == 44) {
				  	this.nullBit4 = 0;
				}
			}
        } else {
    		// use extra vector
    		int vectorIndex = (position / BitCacheSize) - 1;
    		if (this.extra == null) {
				int length = vectorIndex + 1;
				this.extra = new long[extraLength][];
				for (int j = 0; j < extraLength; j++) {
					this.extra[j] = new long[length];
				}
			}
			else {
				int oldLength; // might need to grow the arrays
				if (vectorIndex >= (oldLength = this.extra[0].length)) {
					for (int j = 0; j < extraLength; j++) {
						System.arraycopy(this.extra[j], 0,
							(this.extra[j] = new long[vectorIndex + 1]), 0,
							oldLength);
					}
				}
			}
    		mask = 1L << (position % BitCacheSize);
    		isTrue((this.extra[2][vectorIndex] & mask) == 0, "Adding 'unknown' mark in unexpected state"); //$NON-NLS-1$
    		this.extra[5][vectorIndex] |= mask;
    		if (COVERAGE_TEST_FLAG) {
				if(CoverageTestId == 45) {
					this.extra[2][vectorIndex] = ~0;
					this.extra[3][vectorIndex] = ~0;
					this.extra[4][vectorIndex] = 0;
					this.extra[5][vectorIndex] = 0;
				}
			}
    	}
	}
}

public void markPotentiallyNullBit(LocalVariableBinding local) {
	if (this != DEAD_END) {
		this.tagBits |= NULL_FLAG_MASK;
        int position;
        long mask;
        if ((position = local.id + this.maxFieldCount) < BitCacheSize) {
            // use bits
        	mask = 1L << position;
        	isTrue((this.nullBit1 & mask) == 0, "Adding 'potentially null' mark in unexpected state"); //$NON-NLS-1$
            this.nullBit2 |= mask;
            if (COVERAGE_TEST_FLAG) {
				if(CoverageTestId == 40) {
				  	this.nullBit2 = 0;
				}
			}
        } else {
    		// use extra vector
    		int vectorIndex = (position / BitCacheSize) - 1;
    		if (this.extra == null) {
				int length = vectorIndex + 1;
				this.extra = new long[extraLength][];
				for (int j = 0; j < extraLength; j++) {
					this.extra[j] = new long[length];
				}
			}
			else {
				int oldLength; // might need to grow the arrays
				if (vectorIndex >= (oldLength = this.extra[0].length)) {
					for (int j = 0; j < extraLength; j++) {
						System.arraycopy(this.extra[j], 0,
							(this.extra[j] = new long[vectorIndex + 1]), 0,
							oldLength);
					}
				}
			}
    		mask = 1L << (position % BitCacheSize);
    		this.extra[3][vectorIndex] |= mask;
    		isTrue((this.extra[2][vectorIndex] & mask) == 0, "Adding 'potentially null' mark in unexpected state"); //$NON-NLS-1$
    		if (COVERAGE_TEST_FLAG) {
				if(CoverageTestId == 41) {
					this.extra[3][vectorIndex] = 0;
				}
			}
    	}
	}
}

public void markPotentiallyNonNullBit(LocalVariableBinding local) {
	if (this != DEAD_END) {
		this.tagBits |= NULL_FLAG_MASK;
        int position;
        long mask;
        if ((position = local.id + this.maxFieldCount) < BitCacheSize) {
            // use bits
        	mask = 1L << position;
        	isTrue((this.nullBit1 & mask) == 0, "Adding 'potentially non-null' mark in unexpected state"); //$NON-NLS-1$
            this.nullBit3 |= mask;
            if (COVERAGE_TEST_FLAG) {
				if(CoverageTestId == 42) {
				  	this.nullBit1 = ~0;
				  	this.nullBit2 = 0;
				  	this.nullBit3 = ~0;
				  	this.nullBit4 = 0;
				}
			}
        } else {
    		// use extra vector
    		int vectorIndex  = (position / BitCacheSize) - 1;
    		if (this.extra == null) {
				int length = vectorIndex + 1;
				this.extra = new long[extraLength][];
				for (int j = 0; j < extraLength; j++) {
					this.extra[j] = new long[length];
				}
			}
			else {
				int oldLength; // might need to grow the arrays
				if (vectorIndex >= (oldLength = this.extra[0].length)) {
					for (int j = 0; j < extraLength; j++) {
						System.arraycopy(this.extra[j], 0,
							(this.extra[j] = new long[vectorIndex + 1]), 0,
							oldLength);
					}
				}
			}
    		mask = 1L << (position % BitCacheSize);
    		isTrue((this.extra[2][vectorIndex] & mask) == 0, "Adding 'potentially non-null' mark in unexpected state"); //$NON-NLS-1$
    		this.extra[4][vectorIndex] |= mask;
    		if (COVERAGE_TEST_FLAG) {
				if(CoverageTestId == 43) {
					this.extra[2][vectorIndex] = ~0;
					this.extra[3][vectorIndex] = 0;
					this.extra[4][vectorIndex] = ~0;
					this.extra[5][vectorIndex] = 0;
				}
			}
    	}
	}
}

public UnconditionalFlowInfo mergedWith(UnconditionalFlowInfo otherInits) {
	if ((otherInits.tagBits & UNREACHABLE_OR_DEAD) != 0 && this != DEAD_END) {
		if (COVERAGE_TEST_FLAG) {
			if(CoverageTestId == 28) {
				throw new AssertionFailedException("COVERAGE 28"); //$NON-NLS-1$
			}
		}
		return this;
	}
	if ((this.tagBits & UNREACHABLE_OR_DEAD) != 0) {
		if (COVERAGE_TEST_FLAG) {
			if(CoverageTestId == 29) {
				throw new AssertionFailedException("COVERAGE 29"); //$NON-NLS-1$
			}
		}
		return (UnconditionalFlowInfo) otherInits.copy(); // make sure otherInits won't be affected
	}

	// intersection of definitely assigned variables,
	this.definiteInits &= otherInits.definiteInits;
	// union of potentially set ones
	this.potentialInits |= otherInits.potentialInits;

	// null combinations
	boolean
		thisHasNulls = (this.tagBits & NULL_FLAG_MASK) != 0,
		otherHasNulls = (otherInits.tagBits & NULL_FLAG_MASK) != 0,
		thisHadNulls = thisHasNulls;
	long
		a1, a2, a3, a4,
		na1, na2, na3, na4,
		nb1, nb2, nb3, nb4,
		b1, b2, b3, b4;
	if ((otherInits.tagBits & FlowInfo.UNREACHABLE_BY_NULLANALYSIS) != 0) {
		otherHasNulls = false; // skip merging, otherInits is unreachable by null analysis
	} else if ((this.tagBits & FlowInfo.UNREACHABLE_BY_NULLANALYSIS) != 0) { // directly copy if this is unreachable by null analysis
		this.nullBit1 = otherInits.nullBit1;
		this.nullBit2 = otherInits.nullBit2;
		this.nullBit3 = otherInits.nullBit3;
		this.nullBit4 = otherInits.nullBit4;
		thisHadNulls = false;
		thisHasNulls = otherHasNulls;
		this.tagBits = otherInits.tagBits;
	} else if (thisHadNulls) {
    	if (otherHasNulls) {
    		this.nullBit1 = (a1 = this.nullBit1) & (b1 = otherInits.nullBit1) & (
    				((a2 = this.nullBit2) & (((b2 = otherInits.nullBit2) & 
    											~(((a3=this.nullBit3) & (a4=this.nullBit4)) ^ ((b3=otherInits.nullBit3) & (b4=otherInits.nullBit4))))
    										|(a3 & a4 & (nb2 = ~b2))))
    				|((na2 = ~a2) & ((b2 & b3 & b4)
    								|(nb2 & ((na3 = ~a3) ^ b3)))));
    		this.nullBit2 = b2 & ((nb3 = ~b3) | (nb1 = ~b1) | a3 & (a4 | (na1 = ~a1)) & (nb4 = ~b4))
        			| a2 & (b2 | (na4 = ~a4) & b3 & (b4 | nb1) | na3 | na1);
    		this.nullBit3 =   a3 & (na1 | a1 & na2 | b3 & (na4 ^ b4))
    						| b3 & (nb1 | b1 & nb2);
    		this.nullBit4 = na3 & (nb1 & nb3 & b4
              			| b1 & (nb2 & nb3 | a4 & b2 & nb4)
              			| na1 & a4 & (nb3 | b1 & b2))
        			| a3 & a4 & (b3 & b4 | b1 & nb2 | na1 & a2)
        			| na2 & (nb1 & b4 | b1 & nb3 | na1 & a4) & nb2
        			| a1 & (na3 & (nb3 & b4
                        			| b1 & b2 & b3 & nb4
                        			| na2 & (nb3 | nb2))
                			| na2 & b3 & b4
                			| a2 & (nb1 & b4 | a3 & na4 & b1) & nb3)
                	|nb1 & b2 & b3 & b4;

    		if (COVERAGE_TEST_FLAG) {
    			if(CoverageTestId == 30) {
	    		  	this.nullBit4 = ~0;
    			}
    		}
    	} else { // other has no null info
    		a1 = this.nullBit1;
      		this.nullBit1 = 0;
      		this.nullBit2 = (a2 = this.nullBit2) & (na3 = ~(a3 = this.nullBit3) | (na1 = ~a1));
      		this.nullBit3 = a3 & ((na2 = ~a2) & (a4 = this.nullBit4) | na1) | a1 & na2 & ~a4;
      		this.nullBit4 = (na3 | na2) & na1 & a4	| a1 & na3 & na2;
    		if (COVERAGE_TEST_FLAG) {
    			if(CoverageTestId == 31) {
	    		  	this.nullBit4 = ~0;
    			}
    		}
    	}
	} else if (otherHasNulls) { // only other had nulls
  		this.nullBit1 = 0;
  		this.nullBit2 = (b2 = otherInits.nullBit2) & (nb3 = ~(b3 = otherInits.nullBit3) | (nb1 = ~(b1 = otherInits.nullBit1)));
  		this.nullBit3 = b3 & ((nb2 = ~b2) & (b4 = otherInits.nullBit4) | nb1) | b1 & nb2 & ~b4;
  		this.nullBit4 = (nb3 | nb2) & nb1 & b4	| b1 & nb3 & nb2;
  		if (COVERAGE_TEST_FLAG) {
  			if(CoverageTestId == 32) {
	  		  	this.nullBit4 = ~0;
  			}
  		}
    	thisHasNulls =
    		// redundant with the three following ones
    		this.nullBit2 != 0 ||
    		this.nullBit3 != 0 ||
    		this.nullBit4 != 0;
	}

	// treating extra storage
	if (this.extra != null || otherInits.extra != null) {
		int mergeLimit = 0, copyLimit = 0, resetLimit = 0;
		int i;
		if (this.extra != null) {
			if (otherInits.extra != null) {
				// both sides have extra storage
				int length, otherLength;
				if ((length = this.extra[0].length) <
						(otherLength = otherInits.extra[0].length)) {
					// current storage is shorter -> grow current
					for (int j = 0; j < extraLength; j++) {
						System.arraycopy(this.extra[j], 0,
							(this.extra[j] = new long[otherLength]), 0, length);
					}
					mergeLimit = length;
					copyLimit = otherLength;
					if (COVERAGE_TEST_FLAG) {
						if(CoverageTestId == 33) {
							throw new AssertionFailedException("COVERAGE 33"); //$NON-NLS-1$
						}
					}
				}
				else {
					// current storage is longer
					mergeLimit = otherLength;
					resetLimit = length;
					if (COVERAGE_TEST_FLAG) {
						if(CoverageTestId == 34) {
							throw new AssertionFailedException("COVERAGE 34"); //$NON-NLS-1$
						}
					}
				}
			}
			else {
				resetLimit = this.extra[0].length;
				if (COVERAGE_TEST_FLAG) {
					if(CoverageTestId == 35) {
						throw new AssertionFailedException("COVERAGE 35"); //$NON-NLS-1$
					}
				}
			}
		}
		else if (otherInits.extra != null) {
			// no storage here, but other has extra storage.
			int otherLength = otherInits.extra[0].length;
			this.extra = new long[extraLength][];
			for (int j = 0; j < extraLength; j++) {
				this.extra[j] = new long[otherLength];
			}
			System.arraycopy(otherInits.extra[1], 0,
				this.extra[1], 0, otherLength);
			copyLimit = otherLength;
			if (COVERAGE_TEST_FLAG) {
				if(CoverageTestId == 36) {
					throw new AssertionFailedException("COVERAGE 36"); //$NON-NLS-1$
				}
			}
		}
        // MACRO :'b,'es/nullBit\(.\)/extra[\1 + 1][i]/g
		// manage definite assignment
		for (i = 0; i < mergeLimit; i++) {
	  		this.extra[0][i] &= otherInits.extra[0][i];
	  		this.extra[1][i] |= otherInits.extra[1][i];
		}
		for (; i < copyLimit; i++) {
		  	this.extra[1][i] = otherInits.extra[1][i];
		}
		for (; i < resetLimit; i++) {
		  	this.extra[0][i] = 0;
		}
		// refine null bits requirements
		if (!otherHasNulls) {
		  if (resetLimit < mergeLimit) {
			resetLimit = mergeLimit;
		  }
		  copyLimit = 0; // no need to carry inexisting nulls
		  mergeLimit = 0;
		}
		if (!thisHadNulls) {
		  resetLimit = 0; // no need to reset anything
		}
		// compose nulls
		for (i = 0; i < mergeLimit; i++) {
    		this.extra[1 + 1][i] = (a1=this.extra[1+1][i]) & (b1=otherInits.extra[1+1][i]) & (
    				((a2=this.extra[2+1][i]) & (((b2=otherInits.extra[2+1][i]) & 
    												~(((a3=this.extra[3+1][i]) & (a4=this.extra[4+1][i])) ^ ((b3=otherInits.extra[3+1][i]) & (b4=otherInits.extra[4+1][i]))))
    											|(a3 & a4 & (nb2=~b2))))
    				|((na2=~a2) & ((b2 & b3 & b4)
    						|(nb2 & ((na3=~a3) ^ b3)))));
    		this.extra[2 + 1][i] = b2 & ((nb3=~b3) | (nb1 = ~b1) | a3 & (a4 | (na1 = ~a1)) & (nb4=~b4))
        			| a2 & (b2 | (na4=~a4) & b3 & (b4 | nb1) | na3 | na1);
    		this.extra[3 + 1][i] =   a3 & (na1 | a1 & na2 | b3 & (na4 ^ b4))
								   | b3 & (nb1 | b1 & nb2);
    		this.extra[4 + 1][i] = na3 & (nb1 & nb3 & b4
              			| b1 & (nb2 & nb3 | a4 & b2 & nb4)
              			| na1 & a4 & (nb3 | b1 & b2))
        			| a3 & a4 & (b3 & b4 | b1 & nb2 | na1 & a2)
        			| na2 & (nb1 & b4 | b1 & nb3 | na1 & a4) & nb2
        			| a1 & (na3 & (nb3 & b4
                        			| b1 & b2 & b3 & nb4
                        			| na2 & (nb3 | nb2))
                			| na2 & b3 & b4
                			| a2 & (nb1 & b4 | a3 & na4 & b1) & nb3)
                	|nb1 & b2 & b3 & b4;
			thisHasNulls = thisHasNulls ||
				this.extra[3][i] != 0 ||
				this.extra[4][i] != 0 ||
				this.extra[5][i] != 0 ;
			if (COVERAGE_TEST_FLAG) {
				if(CoverageTestId == 37) {
					this.extra[5][i] = ~0;
				}
			}
		}
		for (; i < copyLimit; i++) {
    		this.extra[1 + 1][i] = 0;
    		this.extra[2 + 1][i] = (b2 = otherInits.extra[2 + 1][i]) & (nb3 = ~(b3 = otherInits.extra[3 + 1][i]) | (nb1 = ~(b1 = otherInits.extra[1 + 1][i])));
    		this.extra[3 + 1][i] = b3 & ((nb2 = ~b2) & (b4 = otherInits.extra[4 + 1][i]) | nb1) | b1 & nb2 & ~b4;
    		this.extra[4 + 1][i] = (nb3 | nb2) & nb1 & b4	| b1 & nb3 & nb2;
			thisHasNulls = thisHasNulls ||
				this.extra[3][i] != 0 ||
				this.extra[4][i] != 0 ||
				this.extra[5][i] != 0;
			if (COVERAGE_TEST_FLAG) {
				if(CoverageTestId == 38) {
					this.extra[5][i] = ~0;
				}
			}
		}
		for (; i < resetLimit; i++) {
    		a1 = this.extra[1 + 1][i];
      		this.extra[1 + 1][i] = 0;
      		this.extra[2 + 1][i] = (a2 = this.extra[2 + 1][i]) & (na3 = ~(a3 = this.extra[3 + 1][i]) | (na1 = ~a1));
      		this.extra[3 + 1][i] = a3 & ((na2 = ~a2) & (a4 = this.extra[4 + 1][i]) | na1) | a1 & na2 & ~a4;
      		this.extra[4 + 1][i] = (na3 | na2) & na1 & a4	| a1 & na3 & na2;
			thisHasNulls = thisHasNulls ||
				this.extra[3][i] != 0 ||
				this.extra[4][i] != 0 ||
				this.extra[5][i] != 0;
			if (COVERAGE_TEST_FLAG) {
				if(CoverageTestId == 39) {
					this.extra[5][i] = ~0;
				}
			}
		}
	}
	if (thisHasNulls) {
		this.tagBits |= NULL_FLAG_MASK;
	}
	else {
		this.tagBits &= ~NULL_FLAG_MASK;
	}
	return this;
}

/*
 * Answer the total number of fields in enclosing types of a given type
 */
static int numberOfEnclosingFields(ReferenceBinding type){
	int count = 0;
	type = type.enclosingType();
	while(type != null) {
		count += type.fieldCount();
		type = type.enclosingType();
	}
	return count;
}

public UnconditionalFlowInfo nullInfoLessUnconditionalCopy() {
	if (this == DEAD_END) {
		return this;
	}
	UnconditionalFlowInfo copy = new UnconditionalFlowInfo();
	copy.definiteInits = this.definiteInits;
	copy.potentialInits = this.potentialInits;
	copy.tagBits = this.tagBits & ~NULL_FLAG_MASK;
	copy.maxFieldCount = this.maxFieldCount;
	if (this.extra != null) {
		int length;
		copy.extra = new long[extraLength][];
		System.arraycopy(this.extra[0], 0,
			(copy.extra[0] =
				new long[length = this.extra[0].length]), 0, length);
		System.arraycopy(this.extra[1], 0,
			(copy.extra[1] = new long[length]), 0, length);
		for (int j = 2; j < extraLength; j++) {
			copy.extra[j] = new long[length];
		}
	}
	return copy;
}

public FlowInfo safeInitsWhenTrue() {
	return copy();
}

public FlowInfo setReachMode(int reachMode) {
	if (this == DEAD_END) {// cannot modify DEAD_END
		return this;
	}	
	if (reachMode == REACHABLE ) {
		this.tagBits &= ~UNREACHABLE;
	} else if (reachMode == UNREACHABLE_BY_NULLANALYSIS ) {
		this.tagBits |= UNREACHABLE_BY_NULLANALYSIS;	// do not interfere with definite assignment analysis
	} else {
		if ((this.tagBits & UNREACHABLE) == 0) {
			// reset optional inits when becoming unreachable
			// see InitializationTest#test090 (and others)
			this.potentialInits = 0;
			if (this.extra != null) {
				for (int i = 0, length = this.extra[0].length;
						i < length; i++) {
					this.extra[1][i] = 0;
				}
			}
		}
		this.tagBits |= reachMode;
	}
	return this;
}

public String toString(){
	// PREMATURE consider printing bit fields as 0001 0001 1000 0001...
	if (this == DEAD_END){
		return "FlowInfo.DEAD_END"; //$NON-NLS-1$
	}
	if ((this.tagBits & NULL_FLAG_MASK) != 0) {
		if (this.extra == null) {
			return "FlowInfo<def: " + this.definiteInits //$NON-NLS-1$
				+", pot: " + this.potentialInits  //$NON-NLS-1$
				+ ", reachable:" + ((this.tagBits & UNREACHABLE) == 0) //$NON-NLS-1$
				+", null: " + this.nullBit1 //$NON-NLS-1$
					+ this.nullBit2 + this.nullBit3 + this.nullBit4
				+">"; //$NON-NLS-1$
		}
		else {
			String def = "FlowInfo<def:[" + this.definiteInits, //$NON-NLS-1$
				pot = "], pot:[" + this.potentialInits, //$NON-NLS-1$
				nullS = ", null:[" + this.nullBit1 //$NON-NLS-1$
					+ this.nullBit2 + this.nullBit3 + this.nullBit4;
			int i, ceil;
			for (i = 0, ceil = this.extra[0].length > 3 ?
								3 :
								this.extra[0].length;
				i < ceil; i++) {
				def += "," + this.extra[0][i]; //$NON-NLS-1$
				pot += "," + this.extra[1][i]; //$NON-NLS-1$
				nullS += "," + this.extra[2][i] //$NON-NLS-1$
				    + this.extra[3][i] + this.extra[4][i] + this.extra[5][i];
			}
			if (ceil < this.extra[0].length) {
				def += ",..."; //$NON-NLS-1$
				pot += ",..."; //$NON-NLS-1$
				nullS += ",..."; //$NON-NLS-1$
			}
			return def + pot
				+ "], reachable:" + ((this.tagBits & UNREACHABLE) == 0) //$NON-NLS-1$
				+ nullS
				+ "]>"; //$NON-NLS-1$
		}
	}
	else {
		if (this.extra == null) {
			return "FlowInfo<def: " + this.definiteInits //$NON-NLS-1$
				+", pot: " + this.potentialInits  //$NON-NLS-1$
				+ ", reachable:" + ((this.tagBits & UNREACHABLE) == 0) //$NON-NLS-1$
				+", no null info>"; //$NON-NLS-1$
		}
		else {
			String def = "FlowInfo<def:[" + this.definiteInits, //$NON-NLS-1$
				pot = "], pot:[" + this.potentialInits; //$NON-NLS-1$
			int i, ceil;
			for (i = 0, ceil = this.extra[0].length > 3 ?
								3 :
								this.extra[0].length;
				i < ceil; i++) {
				def += "," + this.extra[0][i]; //$NON-NLS-1$
				pot += "," + this.extra[1][i]; //$NON-NLS-1$
			}
			if (ceil < this.extra[0].length) {
				def += ",..."; //$NON-NLS-1$
				pot += ",..."; //$NON-NLS-1$
			}
			return def + pot
				+ "], reachable:" + ((this.tagBits & UNREACHABLE) == 0) //$NON-NLS-1$
				+ ", no null info>"; //$NON-NLS-1$
		}
	}
}

public UnconditionalFlowInfo unconditionalCopy() {
	return (UnconditionalFlowInfo) copy();
}

public UnconditionalFlowInfo unconditionalFieldLessCopy() {
	// TODO (maxime) may consider leveraging null contribution verification as it is done in copy
	UnconditionalFlowInfo copy = new UnconditionalFlowInfo();
	copy.tagBits = this.tagBits;
	copy.maxFieldCount = this.maxFieldCount;
	int limit = this.maxFieldCount;
	if (limit < BitCacheSize) {
		long mask;
		copy.definiteInits = this.definiteInits & (mask = ~((1L << limit)-1));
		copy.potentialInits = this.potentialInits & mask;
		copy.nullBit1 = this.nullBit1 & mask;
		copy.nullBit2 = this.nullBit2 & mask;
		copy.nullBit3 = this.nullBit3 & mask;
		copy.nullBit4 = this.nullBit4 & mask;
	}
	// use extra vector
	if (this.extra == null) {
		return copy; // if vector not yet allocated, then not initialized
	}
	int vectorIndex, length, copyStart;
	if ((vectorIndex = (limit / BitCacheSize) - 1) >=
			(length = this.extra[0].length)) {
		return copy; // not enough room yet
	}
	long mask;
	copy.extra = new long[extraLength][];
	if ((copyStart = vectorIndex + 1) < length) {
		int copyLength = length - copyStart;
		for (int j = 0; j < extraLength; j++) {
			System.arraycopy(this.extra[j], copyStart,
				(copy.extra[j] = new long[length]), copyStart,
				copyLength);
		}
	}
	else if (vectorIndex >= 0) {
		for (int j = 0; j < extraLength; j++) {
			copy.extra[j] = new long[length];
		}
	}
	if (vectorIndex >= 0) {
		mask = ~((1L << (limit % BitCacheSize))-1);
		for (int j = 0; j < extraLength; j++) {
			copy.extra[j][vectorIndex] =
				this.extra[j][vectorIndex] & mask;
		}
	}
	return copy;
}

public UnconditionalFlowInfo unconditionalInits() {
	// also see conditional inits, where it requests them to merge
	return this;
}

public UnconditionalFlowInfo unconditionalInitsWithoutSideEffect() {
	return this;
}

public void resetAssignmentInfo(LocalVariableBinding local) {
	resetAssignmentInfo(local.id + this.maxFieldCount);
}

public void resetAssignmentInfo(int position) {
	if (this != DEAD_END) {
		// position is zero-based
		if (position < BitCacheSize) {
			// use bits
			long mask;
			this.definiteInits &= (mask = ~(1L << position));
			this.potentialInits &= mask;
		} else {
			// use extra vector
			int vectorIndex = (position / BitCacheSize) - 1;
			if (this.extra == null || vectorIndex >= this.extra[0].length) return;	// variable doesnt exist in flow info
			long mask;
			this.extra[0][vectorIndex] &=
				(mask = ~(1L << (position % BitCacheSize)));
			this.extra[1][vectorIndex] &= mask;
		}
	}
}
}

