/******************************************************************************* * Copyright (c) 2006, 2009 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 *******************************************************************************/ 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) // 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; } 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$ } } }