/******************************************************************************* | |
* Copyright (c) 2000, 2004 IBM Corporation and others. | |
* All rights reserved. This program and the accompanying materials | |
* are made available under the terms of the Common Public License v1.0 | |
* which accompanies this distribution, and is available at | |
* http://www.eclipse.org/legal/cpl-v10.html | |
* | |
* Contributors: | |
* IBM Corporation - initial API and implementation | |
*******************************************************************************/ | |
package org.eclipse.wst.jsdt.internal.compiler.flow; | |
import org.eclipse.wst.jsdt.internal.compiler.lookup.FieldBinding; | |
import org.eclipse.wst.jsdt.internal.compiler.lookup.LocalVariableBinding; | |
import org.eclipse.wst.jsdt.internal.compiler.lookup.ReferenceBinding; | |
/** | |
* Record initialization status during definite assignment analysis | |
* | |
* No caching of pre-allocated instances. | |
*/ | |
public class UnconditionalFlowInfo extends FlowInfo { | |
public long definiteInits; | |
public long potentialInits; | |
public long extraDefiniteInits[]; | |
public long extraPotentialInits[]; | |
public int reachMode; // by default | |
public int maxFieldCount; | |
// Constants | |
public static final int BitCacheSize = 64; // 64 bits in a long. | |
UnconditionalFlowInfo() { | |
this.reachMode = REACHABLE; | |
} | |
// unions of both sets of initialization - used for try/finally | |
public FlowInfo addInitializationsFrom(FlowInfo inits) { | |
if (this == DEAD_END) | |
return this; | |
UnconditionalFlowInfo otherInits = inits.unconditionalInits(); | |
if (otherInits == DEAD_END) | |
return this; | |
// union of definitely assigned variables, | |
definiteInits |= otherInits.definiteInits; | |
// union of potentially set ones | |
potentialInits |= otherInits.potentialInits; | |
// treating extra storage | |
if (extraDefiniteInits != null) { | |
if (otherInits.extraDefiniteInits != null) { | |
// both sides have extra storage | |
int i = 0, length, otherLength; | |
if ((length = extraDefiniteInits.length) < (otherLength = otherInits.extraDefiniteInits.length)) { | |
// current storage is shorter -> grow current (could maybe reuse otherInits extra storage?) | |
System.arraycopy(extraDefiniteInits, 0, (extraDefiniteInits = new long[otherLength]), 0, length); | |
System.arraycopy(extraPotentialInits, 0, (extraPotentialInits = new long[otherLength]), 0, length); | |
while (i < length) { | |
extraDefiniteInits[i] |= otherInits.extraDefiniteInits[i]; | |
extraPotentialInits[i] |= otherInits.extraPotentialInits[i++]; | |
} | |
while (i < otherLength) { | |
extraPotentialInits[i] = otherInits.extraPotentialInits[i++]; | |
} | |
} else { | |
// current storage is longer | |
while (i < otherLength) { | |
extraDefiniteInits[i] |= otherInits.extraDefiniteInits[i]; | |
extraPotentialInits[i] |= otherInits.extraPotentialInits[i++]; | |
} | |
while (i < length) | |
extraDefiniteInits[i++] = 0; | |
} | |
} else { | |
// no extra storage on otherInits | |
} | |
} else | |
if (otherInits.extraDefiniteInits != null) { | |
// no storage here, but other has extra storage. | |
int otherLength; | |
System.arraycopy(otherInits.extraDefiniteInits, 0, (extraDefiniteInits = new long[otherLength = otherInits.extraDefiniteInits.length]), 0, otherLength); | |
System.arraycopy(otherInits.extraPotentialInits, 0, (extraPotentialInits = new long[otherLength]), 0, otherLength); | |
} | |
return this; | |
} | |
// unions of both sets of initialization - used for try/finally | |
public FlowInfo addPotentialInitializationsFrom(FlowInfo inits) { | |
if (this == DEAD_END){ | |
return this; | |
} | |
UnconditionalFlowInfo otherInits = inits.unconditionalInits(); | |
if (otherInits == DEAD_END){ | |
return this; | |
} | |
// union of potentially set ones | |
potentialInits |= otherInits.potentialInits; | |
// treating extra storage | |
if (extraDefiniteInits != null) { | |
if (otherInits.extraDefiniteInits != null) { | |
// both sides have extra storage | |
int i = 0, length, otherLength; | |
if ((length = extraDefiniteInits.length) < (otherLength = otherInits.extraDefiniteInits.length)) { | |
// current storage is shorter -> grow current (could maybe reuse otherInits extra storage?) | |
System.arraycopy(extraDefiniteInits, 0, (extraDefiniteInits = new long[otherLength]), 0, length); | |
System.arraycopy(extraPotentialInits, 0, (extraPotentialInits = new long[otherLength]), 0, length); | |
while (i < length) { | |
extraPotentialInits[i] |= otherInits.extraPotentialInits[i++]; | |
} | |
while (i < otherLength) { | |
extraPotentialInits[i] = otherInits.extraPotentialInits[i++]; | |
} | |
} else { | |
// current storage is longer | |
while (i < otherLength) { | |
extraPotentialInits[i] |= otherInits.extraPotentialInits[i++]; | |
} | |
} | |
} | |
} else | |
if (otherInits.extraDefiniteInits != null) { | |
// no storage here, but other has extra storage. | |
int otherLength; | |
extraDefiniteInits = new long[otherLength = otherInits.extraDefiniteInits.length]; | |
System.arraycopy(otherInits.extraPotentialInits, 0, (extraPotentialInits = new long[otherLength]), 0, otherLength); | |
} | |
return this; | |
} | |
/** | |
* Answers a copy of the current instance | |
*/ | |
public FlowInfo copy() { | |
// do not clone the DeadEnd | |
if (this == DEAD_END) | |
return this; | |
// look for an unused preallocated object | |
UnconditionalFlowInfo copy = new UnconditionalFlowInfo(); | |
// copy slots | |
copy.definiteInits = this.definiteInits; | |
copy.potentialInits = this.potentialInits; | |
copy.reachMode = this.reachMode; | |
copy.maxFieldCount = this.maxFieldCount; | |
if (this.extraDefiniteInits != null) { | |
int length; | |
System.arraycopy(this.extraDefiniteInits, 0, (copy.extraDefiniteInits = new long[ (length = extraDefiniteInits.length)]), 0, length); | |
System.arraycopy(this.extraPotentialInits, 0, (copy.extraPotentialInits = new long[length]), 0, length); | |
} | |
return copy; | |
} | |
public UnconditionalFlowInfo discardFieldInitializations(){ | |
int limit = this.maxFieldCount; | |
if (limit < BitCacheSize) { | |
long mask = (1L << limit)-1; | |
this.definiteInits &= ~mask; | |
this.potentialInits &= ~mask; | |
return this; | |
} | |
this.definiteInits = 0; | |
this.potentialInits = 0; | |
// use extra vector | |
if (extraDefiniteInits == null) { | |
return this; // if vector not yet allocated, then not initialized | |
} | |
int vectorIndex, length = this.extraDefiniteInits.length; | |
if ((vectorIndex = (limit / BitCacheSize) - 1) >= length) { | |
return this; // not enough room yet | |
} | |
for (int i = 0; i < vectorIndex; i++) { | |
this.extraDefiniteInits[i] = 0L; | |
this.extraPotentialInits[i] = 0L; | |
} | |
long mask = (1L << (limit % BitCacheSize))-1; | |
this.extraDefiniteInits[vectorIndex] &= ~mask; | |
this.extraPotentialInits[vectorIndex] &= ~mask; | |
return this; | |
} | |
public UnconditionalFlowInfo discardNonFieldInitializations(){ | |
int limit = this.maxFieldCount; | |
if (limit < BitCacheSize) { | |
long mask = (1L << limit)-1; | |
this.definiteInits &= mask; | |
this.potentialInits &= mask; | |
return this; | |
} | |
// use extra vector | |
if (extraDefiniteInits == null) { | |
return this; // if vector not yet allocated, then not initialized | |
} | |
int vectorIndex, length = this.extraDefiniteInits.length; | |
if ((vectorIndex = (limit / BitCacheSize) - 1) >= length) { | |
return this; // not enough room yet | |
} | |
long mask = (1L << (limit % BitCacheSize))-1; | |
this.extraDefiniteInits[vectorIndex] &= mask; | |
this.extraPotentialInits[vectorIndex] &= mask; | |
for (int i = vectorIndex+1; i < length; i++) { | |
this.extraDefiniteInits[i] = 0L; | |
this.extraPotentialInits[i] = 0L; | |
} | |
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) { | |
// Dependant of CodeStream.isDefinitelyAssigned(..) | |
// id is zero-based | |
if (position < BitCacheSize) { | |
return (definiteInits & (1L << position)) != 0; // use bits | |
} | |
// use extra vector | |
if (extraDefiniteInits == null) | |
return false; // if vector not yet allocated, then not initialized | |
int vectorIndex; | |
if ((vectorIndex = (position / BitCacheSize) - 1) >= extraDefiniteInits.length) | |
return false; // if not enough room in vector, then not initialized | |
return ((extraDefiniteInits[vectorIndex]) & (1L << (position % BitCacheSize))) != 0; | |
} | |
/** | |
* Check status of definite assignment for a field. | |
*/ | |
final public boolean isDefinitelyAssigned(FieldBinding field) { | |
// Dependant of CodeStream.isDefinitelyAssigned(..) | |
// We do not want to complain in unreachable code | |
if ((this.reachMode & UNREACHABLE) != 0) | |
return true; | |
return isDefinitelyAssigned(field.id); | |
} | |
/** | |
* Check status of definite assignment for a local. | |
*/ | |
final public boolean isDefinitelyAssigned(LocalVariableBinding local) { | |
// Dependant of CodeStream.isDefinitelyAssigned(..) | |
// We do not want to complain in unreachable code | |
if ((this.reachMode & UNREACHABLE) != 0) | |
return true; | |
if (local.isArgument) { | |
return true; | |
} | |
// final constants are inlined, and thus considered as always initialized | |
if (local.isConstantValue()) { | |
return true; | |
} | |
return isDefinitelyAssigned(local.id + maxFieldCount); | |
} | |
public boolean isReachable() { | |
return this.reachMode == REACHABLE; | |
} | |
/** | |
* Check status of potential assignment at a given position. | |
* It deals with the dual representation of the InitializationInfo3: | |
* bits for the first 64 entries, then an array of booleans. | |
*/ | |
final private boolean isPotentiallyAssigned(int position) { | |
// id is zero-based | |
if (position < BitCacheSize) { | |
// use bits | |
return (potentialInits & (1L << position)) != 0; | |
} | |
// use extra vector | |
if (extraPotentialInits == null) | |
return false; // if vector not yet allocated, then not initialized | |
int vectorIndex; | |
if ((vectorIndex = (position / BitCacheSize) - 1) >= extraPotentialInits.length) | |
return false; // if not enough room in vector, then not initialized | |
return ((extraPotentialInits[vectorIndex]) & (1L << (position % BitCacheSize))) != 0; | |
} | |
/** | |
* Check status of definite assignment for a field. | |
*/ | |
final public boolean isPotentiallyAssigned(FieldBinding field) { | |
return isPotentiallyAssigned(field.id); | |
} | |
/** | |
* Check status of potential assignment for a local. | |
*/ | |
final public boolean isPotentiallyAssigned(LocalVariableBinding local) { | |
if (local.isArgument) { | |
return true; | |
} | |
// final constants are inlined, and thus considered as always initialized | |
if (local.isConstantValue()) { | |
return true; | |
} | |
return isPotentiallyAssigned(local.id + maxFieldCount); | |
} | |
/** | |
* Record a 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 void markAsDefinitelyAssigned(int position) { | |
if (this != DEAD_END) { | |
// position is zero-based | |
if (position < BitCacheSize) { | |
// use bits | |
long mask; | |
definiteInits |= (mask = 1L << position); | |
potentialInits |= mask; | |
} else { | |
// use extra vector | |
int vectorIndex = (position / BitCacheSize) - 1; | |
if (extraDefiniteInits == null) { | |
int length; | |
extraDefiniteInits = new long[length = vectorIndex + 1]; | |
extraPotentialInits = new long[length]; | |
} else { | |
int oldLength; // might need to grow the arrays | |
if (vectorIndex >= (oldLength = extraDefiniteInits.length)) { | |
System.arraycopy(extraDefiniteInits, 0, (extraDefiniteInits = new long[vectorIndex + 1]), 0, oldLength); | |
System.arraycopy(extraPotentialInits, 0, (extraPotentialInits = new long[vectorIndex + 1]), 0, oldLength); | |
} | |
} | |
long mask; | |
extraDefiniteInits[vectorIndex] |= (mask = 1L << (position % BitCacheSize)); | |
extraPotentialInits[vectorIndex] |= mask; | |
} | |
} | |
} | |
/** | |
* Record a field got definitely assigned. | |
*/ | |
public void markAsDefinitelyAssigned(FieldBinding field) { | |
if (this != DEAD_END) | |
markAsDefinitelyAssigned(field.id); | |
} | |
/** | |
* Record a local got definitely assigned. | |
*/ | |
public void markAsDefinitelyAssigned(LocalVariableBinding local) { | |
if (this != DEAD_END) | |
markAsDefinitelyAssigned(local.id + maxFieldCount); | |
} | |
/** | |
* Clear initialization information 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 void markAsDefinitelyNotAssigned(int position) { | |
if (this != DEAD_END) { | |
// position is zero-based | |
if (position < BitCacheSize) { | |
// use bits | |
long mask; | |
definiteInits &= ~(mask = 1L << position); | |
potentialInits &= ~mask; | |
} else { | |
// use extra vector | |
int vectorIndex = (position / BitCacheSize) - 1; | |
if (extraDefiniteInits == null) { | |
return; // nothing to do, it was not yet set | |
} | |
// might need to grow the arrays | |
if (vectorIndex >= extraDefiniteInits.length) { | |
return; // nothing to do, it was not yet set | |
} | |
long mask; | |
extraDefiniteInits[vectorIndex] &= ~(mask = 1L << (position % BitCacheSize)); | |
extraPotentialInits[vectorIndex] &= ~mask; | |
} | |
} | |
} | |
/** | |
* Clear the initialization info for a field | |
*/ | |
public void markAsDefinitelyNotAssigned(FieldBinding field) { | |
if (this != DEAD_END) | |
markAsDefinitelyNotAssigned(field.id); | |
} | |
/** | |
* Clear the initialization info for a local variable | |
*/ | |
public void markAsDefinitelyNotAssigned(LocalVariableBinding local) { | |
if (this != DEAD_END) | |
markAsDefinitelyNotAssigned(local.id + maxFieldCount); | |
} | |
/** | |
* Returns the receiver updated in the following way: <ul> | |
* <li> intersection of definitely assigned variables, | |
* <li> union of potentially assigned variables. | |
* </ul> | |
*/ | |
public UnconditionalFlowInfo mergedWith(UnconditionalFlowInfo otherInits) { | |
if (this == DEAD_END) return otherInits; | |
if (otherInits == DEAD_END) return this; | |
if ((this.reachMode & UNREACHABLE) != (otherInits.reachMode & UNREACHABLE)){ | |
if ((this.reachMode & UNREACHABLE) != 0){ | |
return otherInits; | |
} | |
return this; | |
} | |
// if one branch is not fake reachable, then the merged one is reachable | |
this.reachMode &= otherInits.reachMode; | |
// intersection of definitely assigned variables, | |
this.definiteInits &= otherInits.definiteInits; | |
// union of potentially set ones | |
this.potentialInits |= otherInits.potentialInits; | |
// treating extra storage | |
if (this.extraDefiniteInits != null) { | |
if (otherInits.extraDefiniteInits != null) { | |
// both sides have extra storage | |
int i = 0, length, otherLength; | |
if ((length = this.extraDefiniteInits.length) < (otherLength = otherInits.extraDefiniteInits.length)) { | |
// current storage is shorter -> grow current (could maybe reuse otherInits extra storage?) | |
System.arraycopy(this.extraDefiniteInits, 0, (this.extraDefiniteInits = new long[otherLength]), 0, length); | |
System.arraycopy(this.extraPotentialInits, 0, (this.extraPotentialInits = new long[otherLength]), 0, length); | |
while (i < length) { | |
this.extraDefiniteInits[i] &= otherInits.extraDefiniteInits[i]; | |
this.extraPotentialInits[i] |= otherInits.extraPotentialInits[i++]; | |
} | |
while (i < otherLength) { | |
this.extraPotentialInits[i] = otherInits.extraPotentialInits[i++]; | |
} | |
} else { | |
// current storage is longer | |
while (i < otherLength) { | |
this.extraDefiniteInits[i] &= otherInits.extraDefiniteInits[i]; | |
this.extraPotentialInits[i] |= otherInits.extraPotentialInits[i++]; | |
} | |
while (i < length) | |
this.extraDefiniteInits[i++] = 0; | |
} | |
} else { | |
// no extra storage on otherInits | |
int i = 0, length = this.extraDefiniteInits.length; | |
while (i < length) | |
this.extraDefiniteInits[i++] = 0; | |
} | |
} else | |
if (otherInits.extraDefiniteInits != null) { | |
// no storage here, but other has extra storage. | |
int otherLength; | |
this.extraDefiniteInits = new long[otherLength = otherInits.extraDefiniteInits.length]; | |
System.arraycopy(otherInits.extraPotentialInits, 0, (this.extraPotentialInits = new long[otherLength]), 0, otherLength); | |
} | |
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 int reachMode(){ | |
return this.reachMode; | |
} | |
public FlowInfo setReachMode(int reachMode) { | |
if (this == DEAD_END) return this; // cannot modify DEAD_END | |
// reset optional inits when becoming unreachable | |
if ((this.reachMode & UNREACHABLE) == 0 && (reachMode & UNREACHABLE) != 0) { | |
this.potentialInits = 0; | |
if (this.extraPotentialInits != null){ | |
for (int i = 0, length = this.extraPotentialInits.length; i < length; i++){ | |
this.extraPotentialInits[i] = 0; | |
} | |
} | |
} | |
this.reachMode = reachMode; | |
return this; | |
} | |
public String toString(){ | |
if (this == DEAD_END){ | |
return "FlowInfo.DEAD_END"; //$NON-NLS-1$ | |
} | |
return "FlowInfo<def: "+ this.definiteInits //$NON-NLS-1$ | |
+", pot: " + this.potentialInits //$NON-NLS-1$ | |
+ ", reachable:" + ((this.reachMode & UNREACHABLE) == 0) //$NON-NLS-1$ | |
+">"; //$NON-NLS-1$ | |
} | |
public UnconditionalFlowInfo unconditionalInits() { | |
// also see conditional inits, where it requests them to merge | |
return this; | |
} | |
} |