blob: 14707955b9b4477d7e624decd2a5ac8895bef272 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2011 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> - Contribution for bug 320170
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.flow;
import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
/**
* A degenerate form of UnconditionalFlowInfo explicitly meant to capture
* the effects of null related operations within try blocks. Given the fact
* that a try block might exit at any time, a null related operation that
* occurs within such a block mitigates whatever we know about the previous
* null status of involved variables. NullInfoRegistry handles that
* by negating upstream definite information that clashes with what a given
* statement contends about the same variable. It also implements
* {@link #mitigateNullInfoOf(FlowInfo) mitigateNullInfo} so as to elaborate the
* flow info presented in input of finally blocks.
*/
public class NullInfoRegistry extends UnconditionalFlowInfo {
// significant states at this level:
// def. non null, def. null, def. unknown, prot. non null
// PREMATURE implement coverage and low level tests
/**
* Make a new null info registry, using an upstream flow info. All definite
* assignments of the upstream are carried forward, since a try block may
* exit before its first statement.
* @param upstream - UnconditionalFlowInfo: the flow info before we enter the
* try block; only definite assignments are considered; this parameter is
* not modified by this constructor
*/
public NullInfoRegistry(UnconditionalFlowInfo upstream) {
this.maxFieldCount = upstream.maxFieldCount;
if ((upstream.tagBits & NULL_FLAG_MASK) != 0) {
long u1, u2, u3, u4, nu2, nu3, nu4;
this.nullBit2 = (u1 = upstream.nullBit1)
& (u2 = upstream.nullBit2)
& (nu3 = ~(u3 = upstream.nullBit3))
& (nu4 = ~(u4 = upstream.nullBit4));
this.nullBit3 = u1 & (nu2 = ~u2) & u3 & nu4;
this.nullBit4 = u1 & nu2 &nu3 & u4;
if ((this.nullBit2 | this.nullBit3 | this.nullBit4) != 0) {
this.tagBits |= NULL_FLAG_MASK;
}
if (upstream.extra != null) {
this.extra = new long[extraLength][];
int length = upstream.extra[2].length;
for (int i = 2; i < extraLength; i++) {
this.extra[i] = new long[length];
}
for (int i = 0; i < length; i++) {
this.extra[2 + 1][i] = (u1 = upstream.extra[1 + 1][i])
& (u2 = upstream.extra[2 + 1][i])
& (nu3 = ~(u3 = upstream.extra[3 + 1][i]))
& (nu4 = ~(u4 = upstream.extra[4 + 1][i]));
this.extra[3 + 1][i] = u1 & (nu2 = ~u2) & u3 & nu4;
this.extra[4 + 1][i] = u1 & nu2 &nu3 & u4;
if ((this.extra[2 + 1][i] | this.extra[3 + 1][i] | this.extra[4 + 1][i]) != 0) {
this.tagBits |= NULL_FLAG_MASK;
}
}
}
}
}
/**
* Add the information held by another NullInfoRegistry instance to this,
* then return this.
* @param other - NullInfoRegistry: the information to add to this
* @return this, modified to carry the information held by other
*/
public NullInfoRegistry add(NullInfoRegistry other) {
if ((other.tagBits & NULL_FLAG_MASK) == 0) {
return this;
}
this.tagBits |= NULL_FLAG_MASK;
this.nullBit1 |= other.nullBit1;
this.nullBit2 |= other.nullBit2;
this.nullBit3 |= other.nullBit3;
this.nullBit4 |= other.nullBit4;
if (other.extra != null) {
if (this.extra == null) {
this.extra = new long[extraLength][];
for (int i = 2, length = other.extra[2].length; i < extraLength; i++) {
System.arraycopy(other.extra[i], 0,
(this.extra[i] = new long[length]), 0, length);
}
} else {
int length = this.extra[2].length, otherLength = other.extra[2].length;
if (otherLength > length) {
for (int i = 2; i < extraLength; i++) {
System.arraycopy(this.extra[i], 0,
(this.extra[i] = new long[otherLength]), 0, length);
System.arraycopy(other.extra[i], length,
this.extra[i], length, otherLength - length);
}
} else if (otherLength < length) {
length = otherLength;
}
for (int i = 2; i < extraLength; i++) {
for (int j = 0; j < length; j++) {
this.extra[i][j] |= other.extra[i][j];
}
}
}
}
return this;
}
public void markAsComparedEqualToNonNull(LocalVariableBinding local) {
// protected from non-object locals in calling methods
if (this != DEAD_END) {
this.tagBits |= NULL_FLAG_MASK;
int position;
// position is zero-based
if ((position = local.id + this.maxFieldCount) < BitCacheSize) { // use bits
// set protected non null
this.nullBit1 |= (1L << position);
if (COVERAGE_TEST_FLAG) {
if (CoverageTestId == 290) {
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 = 2; j < extraLength; j++) {
this.extra[j] = new long[length];
}
}
else {
int oldLength; // might need to grow the arrays
if (vectorIndex >= (oldLength = this.extra[2].length)) {
for (int j = 2; j < extraLength; j++) {
System.arraycopy(this.extra[j], 0,
(this.extra[j] = new long[vectorIndex + 1]), 0,
oldLength);
}
}
}
this.extra[2][vectorIndex] |= (1L << (position % BitCacheSize));
if (COVERAGE_TEST_FLAG) {
if (CoverageTestId == 300) {
this.extra[5][vectorIndex] = ~0;
}
}
}
}
}
public void markAsDefinitelyNonNull(LocalVariableBinding local) {
// protected from non-object locals in calling methods
if (this != DEAD_END) {
this.tagBits |= NULL_FLAG_MASK;
int position;
// position is zero-based
if ((position = local.id + this.maxFieldCount) < BitCacheSize) { // use bits
// set assigned non null
this.nullBit3 |= (1L << position);
if (COVERAGE_TEST_FLAG) {
if (CoverageTestId == 290) {
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 = 2; j < extraLength; j++) {
this.extra[j] = new long[length];
}
}
else {
int oldLength; // might need to grow the arrays
if (vectorIndex >= (oldLength = this.extra[2].length)) {
for (int j = 2; j < extraLength; j++) {
System.arraycopy(this.extra[j], 0,
(this.extra[j] = new long[vectorIndex + 1]), 0,
oldLength);
}
}
}
this.extra[4][vectorIndex] |= (1L << (position % BitCacheSize));
if (COVERAGE_TEST_FLAG) {
if (CoverageTestId == 300) {
this.extra[5][vectorIndex] = ~0;
}
}
}
}
}
// PREMATURE consider ignoring extra 0 to 2 included - means a1 should not be used either
// PREMATURE project protected non null onto something else
public void markAsDefinitelyNull(LocalVariableBinding local) {
// protected from non-object locals in calling methods
if (this != DEAD_END) {
this.tagBits |= NULL_FLAG_MASK;
int position;
// position is zero-based
if ((position = local.id + this.maxFieldCount) < BitCacheSize) { // use bits
// set assigned null
this.nullBit2 |= (1L << position);
if (COVERAGE_TEST_FLAG) {
if (CoverageTestId == 290) {
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 = 2; j < extraLength; j++) {
this.extra[j] = new long[length];
}
}
else {
int oldLength; // might need to grow the arrays
if (vectorIndex >= (oldLength = this.extra[2].length)) {
for (int j = 2; j < extraLength; j++) {
System.arraycopy(this.extra[j], 0,
(this.extra[j] = new long[vectorIndex + 1]), 0,
oldLength);
}
}
}
this.extra[3][vectorIndex] |= (1L << (position % BitCacheSize));
if (COVERAGE_TEST_FLAG) {
if (CoverageTestId == 300) {
this.extra[5][vectorIndex] = ~0;
}
}
}
}
}
public void markAsDefinitelyUnknown(LocalVariableBinding local) {
// protected from non-object locals in calling methods
if (this != DEAD_END) {
this.tagBits |= NULL_FLAG_MASK;
int position;
// position is zero-based
if ((position = local.id + this.maxFieldCount) < BitCacheSize) { // use bits
// set assigned unknown
this.nullBit4 |= (1L << position);
if (COVERAGE_TEST_FLAG) {
if (CoverageTestId == 290) {
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 = 2; j < extraLength; j++) {
this.extra[j] = new long[length];
}
}
else {
int oldLength; // might need to grow the arrays
if (vectorIndex >= (oldLength = this.extra[2].length)) {
for (int j = 2; j < extraLength; j++) {
System.arraycopy(this.extra[j], 0,
(this.extra[j] = new long[vectorIndex + 1]), 0,
oldLength);
}
}
}
this.extra[5][vectorIndex] |= (1L << (position % BitCacheSize));
if (COVERAGE_TEST_FLAG) {
if (CoverageTestId == 300) {
this.extra[5][vectorIndex] = ~0;
}
}
}
}
}
/**
* Mitigate the definite and protected info of flowInfo, depending on what
* this null info registry knows about potential assignments and messages
* sends involving locals. May return flowInfo unchanged, or a modified,
* fresh copy of flowInfo.
* @param flowInfo - FlowInfo: the flow information that this null info
* registry may mitigate
* @return a copy of flowInfo carrying mitigated information, or else
* flowInfo unchanged
*/
public UnconditionalFlowInfo mitigateNullInfoOf(FlowInfo flowInfo) {
if ((this.tagBits & NULL_FLAG_MASK) == 0) {
return flowInfo.unconditionalInits();
}
long m, m1, nm1, m2, nm2, m3, a2, a3, a4, s1, s2, ns2, s3, ns3, s4, ns4;
boolean newCopy = false;
UnconditionalFlowInfo source = flowInfo.unconditionalInits();
// clear incompatible protections
m1 = (s1 = source.nullBit1) & (s3 = source.nullBit3)
& (s4 = source.nullBit4)
// prot. non null
& ((a2 = this.nullBit2) | (a4 = this.nullBit4));
// null or unknown
m2 = s1 & (s2 = this.nullBit2) & (s3 ^ s4) // TODO(stephan): potential typo: should this be "s2 = source.nullBit2"???
// prot. null
& ((a3 = this.nullBit3) | a4);
// non null or unknown
// clear incompatible assignments
// PREMATURE check effect of protected non null (no NPE on call)
// TODO (maxime) code extensive implementation tests
m3 = s1 & (s2 & (ns3 = ~s3) & (ns4 = ~s4) & (a3 | a4)
| (ns2 = ~s2) & s3 & ns4 & (a2 | a4)
| ns2 & ns3 & s4 & (a2 | a3));
if ((m = (m1 | m2 | m3)) != 0) {
newCopy = true;
source = source.unconditionalCopy();
source.nullBit1 &= ~m;
source.nullBit2 &= (nm1 = ~m1) & ((nm2 = ~m2) | a4);
source.nullBit3 &= (nm1 | a2) & nm2;
source.nullBit4 &= nm1 & nm2;
// any variable that is (pot n, pot nn, pot un) at end of try (as captured by *this* NullInfoRegistry)
// has the same uncertainty also for the mitigated case (function result)
// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=320170 - [compiler] [null] Whitebox issues in null analysis
// and org.eclipse.jdt.core.tests.compiler.regression.NullReferenceTest.test0536_try_finally()
long x = ~this.nullBit1 & a2 & a3 & a4; // x is set for all variable ids that have state 0111 (pot n, pot nn, pot un)
if (x != 0) {
// restore state 0111 for all variable ids in x:
source.nullBit1 &= ~x;
source.nullBit2 |= x;
source.nullBit3 |= x;
source.nullBit4 |= x;
}
}
if (this.extra != null && source.extra != null) {
int length = this.extra[2].length, sourceLength = source.extra[0].length;
if (sourceLength < length) {
length = sourceLength;
}
for (int i = 0; i < length; i++) {
m1 = (s1 = source.extra[1 + 1][i]) & (s3 = source.extra[3 + 1][i])
& (s4 = source.extra[4 + 1][i])
& ((a2 = this.extra[2 + 1][i]) | (a4 = this.extra[4 + 1][i]));
m2 = s1 & (s2 = this.extra[2 + 1][i]) & (s3 ^ s4)
& ((a3 = this.extra[3 + 1][i]) | a4);
m3 = s1 & (s2 & (ns3 = ~s3) & (ns4 = ~s4) & (a3 | a4)
| (ns2 = ~s2) & s3 & ns4 & (a2 | a4)
| ns2 & ns3 & s4 & (a2 | a3));
if ((m = (m1 | m2 | m3)) != 0) {
if (! newCopy) {
newCopy = true;
source = source.unconditionalCopy();
}
source.extra[1 + 1][i] &= ~m;
source.extra[2 + 1][i] &= (nm1 = ~m1) & ((nm2 = ~m2) | a4);
source.extra[3 + 1][i] &= (nm1 | a2) & nm2;
source.extra[4 + 1][i] &= nm1 & nm2;
}
}
}
return source;
}
public String toString(){
if (this.extra == null) {
return "NullInfoRegistry<" + this.nullBit1 //$NON-NLS-1$
+ this.nullBit2 + this.nullBit3 + this.nullBit4
+ ">"; //$NON-NLS-1$
}
else {
String nullS = "NullInfoRegistry<[" + 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++) {
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) {
nullS += ",..."; //$NON-NLS-1$
}
return nullS + "]>"; //$NON-NLS-1$
}
}
/**
* 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 == 46) {
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 = 2; j < extraLength; j++) {
this.extra[j] = new long[length];
}
} else {
int oldLength; // might need to grow the arrays
if (vectorIndex >= (oldLength = this.extra[2].length)) {
for (int j = 2; 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 == 47) {
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.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 = 2; j < extraLength; j++) {
this.extra[j] = new long[length];
}
} else {
int oldLength; // might need to grow the arrays
if (vectorIndex >= (oldLength = this.extra[2].length)) {
for (int j = 2; 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[5][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.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 = 2; j < extraLength; j++) {
this.extra[j] = new long[length];
}
} else {
int oldLength; // might need to grow the arrays
if (vectorIndex >= (oldLength = this.extra[2].length)) {
for (int j = 2; 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[5][vectorIndex] = ~0;
}
}
}
}
}
}