| /******************************************************************************* |
| * 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; |
| } |
| } |
| } |
| } |
| } |
| } |
| |