package org.eclipse.jdt.internal.compiler.ast;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.codegen.CodeStream;
import org.eclipse.jdt.internal.compiler.flow.FinallyFlowContext;
import org.eclipse.jdt.internal.compiler.flow.FlowContext;
import org.eclipse.jdt.internal.compiler.flow.FlowInfo;
import org.eclipse.jdt.internal.compiler.impl.Constant;
import org.eclipse.jdt.internal.compiler.impl.ReferenceContext;
import org.eclipse.jdt.internal.compiler.lookup.Binding;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.MethodScope;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.Scope;
import org.eclipse.jdt.internal.compiler.lookup.TagBits;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
import org.eclipse.jdt.internal.compiler.problem.ProblemReporter;
import org.eclipse.jdt.internal.compiler.util.Util;
@SuppressWarnings({"rawtypes", "unchecked"})
public class FakedTrackingVariable extends LocalDeclaration {
private static final char[] UNASSIGNED_CLOSEABLE_NAME = "<unassigned Closeable value>".toCharArray();
private static final char[] UNASSIGNED_CLOSEABLE_NAME_TEMPLATE = "<unassigned Closeable value from line {0}>".toCharArray();
private static final char[] TEMPLATE_ARGUMENT = "{0}".toCharArray();
private static final int CLOSE_SEEN = 1;
private static final int SHARED_WITH_OUTSIDE = 2;
private static final int OWNED_BY_OUTSIDE = 4;
private static final int CLOSED_IN_NESTED_METHOD = 8;
private static final int REPORTED_EXPLICIT_CLOSE = 16;
private static final int REPORTED_POTENTIAL_LEAK = 32;
private static final int REPORTED_DEFINITIVE_LEAK = 64;
private static final int FOREACH_ELEMENT_VAR = 128;
public static boolean TEST_372319 = false;
private int globalClosingState = 0;
public LocalVariableBinding originalBinding;
public FakedTrackingVariable innerTracker;
public FakedTrackingVariable outerTracker;
MethodScope methodScope;
private HashMap recordedLocations;
private ASTNode currentAssignment;
private FlowContext tryContext;
public FakedTrackingVariable(LocalVariableBinding original, ASTNode location, FlowInfo flowInfo, FlowContext flowContext, int nullStatus) {
super(original.name, location.sourceStart, location.sourceEnd);
this.type = new SingleTypeReference(
TypeConstants.OBJECT,
((long)this.sourceStart <<32)+this.sourceEnd);
this.methodScope = original.declaringScope.methodScope();
this.originalBinding = original;
while (flowContext != null) {
if (flowContext instanceof FinallyFlowContext) {
this.tryContext = ((FinallyFlowContext) flowContext).tryContext;
break;
}
flowContext = flowContext.parent;
}
resolve(original.declaringScope);
if (nullStatus != 0)
flowInfo.markNullStatus(this.binding, nullStatus);
}
private FakedTrackingVariable(BlockScope scope, ASTNode location, FlowInfo flowInfo, int nullStatus) {
super(UNASSIGNED_CLOSEABLE_NAME, location.sourceStart, location.sourceEnd);
this.type = new SingleTypeReference(
TypeConstants.OBJECT,
((long)this.sourceStart <<32)+this.sourceEnd);
this.methodScope = scope.methodScope();
this.originalBinding = null;
resolve(scope);
if (nullStatus != 0)
flowInfo.markNullStatus(this.binding, nullStatus);
}
@Override
public void generateCode(BlockScope currentScope, CodeStream codeStream)
{ }
@Override
public void resolve (BlockScope scope) {
this.binding = new LocalVariableBinding(
this.name,
scope.getJavaLangObject(),
0,
false);
this.binding.closeTracker = this;
this.binding.declaringScope = scope;
this.binding.setConstant(Constant.NotAConstant);
this.binding.useFlag = LocalVariableBinding.USED;
this.binding.id = scope.registerTrackingVariable(this);
}
public static FakedTrackingVariable getCloseTrackingVariable(Expression expression, FlowInfo flowInfo, FlowContext flowContext) {
while (true) {
if (expression instanceof CastExpression)
expression = ((CastExpression) expression).expression;
else if (expression instanceof Assignment)
expression = ((Assignment) expression).expression;
else if (expression instanceof ConditionalExpression) {
FakedTrackingVariable falseTrackingVariable = getCloseTrackingVariable(((ConditionalExpression)expression).valueIfFalse, flowInfo, flowContext);
if (falseTrackingVariable != null) {
return falseTrackingVariable;
}
return getCloseTrackingVariable(((ConditionalExpression)expression).valueIfTrue, flowInfo, flowContext);
} else if (expression instanceof SwitchExpression) {
for (Expression re : ((SwitchExpression) expression).resultExpressions) {
FakedTrackingVariable fakedTrackingVariable = getCloseTrackingVariable(re, flowInfo, flowContext);
if (fakedTrackingVariable != null) {
return fakedTrackingVariable;
}
}
return null;
}
else
break;
}
if (expression instanceof SingleNameReference) {
SingleNameReference name = (SingleNameReference) expression;
if (name.binding instanceof LocalVariableBinding) {
LocalVariableBinding local = (LocalVariableBinding)name.binding;
if (local.closeTracker != null)
return local.closeTracker;
if (!isAnyCloseable(expression.resolvedType))
return null;
if ((local.tagBits & TagBits.IsResource) != 0)
return null;
Statement location = local.declaration;
local.closeTracker = new FakedTrackingVariable(local, location, flowInfo, flowContext, FlowInfo.UNKNOWN);
if (local.isParameter()) {
local.closeTracker.globalClosingState |= OWNED_BY_OUTSIDE;
}
return local.closeTracker;
}
} else if (expression instanceof AllocationExpression) {
return ((AllocationExpression) expression).closeTracker;
}
return null;
}
public static void preConnectTrackerAcrossAssignment(ASTNode location, LocalVariableBinding local, Expression rhs, FlowInfo flowInfo) {
FakedTrackingVariable closeTracker = null;
if (containsAllocation(rhs)) {
closeTracker = local.closeTracker;
if (closeTracker == null) {
if (rhs.resolvedType != TypeBinding.NULL) {
closeTracker = new FakedTrackingVariable(local, location, flowInfo, null, FlowInfo.UNKNOWN);
if (local.isParameter()) {
closeTracker.globalClosingState |= OWNED_BY_OUTSIDE;
}
}
}
if (closeTracker != null) {
closeTracker.currentAssignment = location;
preConnectTrackerAcrossAssignment(location, local, flowInfo, closeTracker, rhs);
}
}
}
private static boolean containsAllocation(SwitchExpression location) {
for (Expression re : location.resultExpressions) {
if (containsAllocation(re))
return true;
}
return false;
}
private static boolean containsAllocation(ASTNode location) {
if (location instanceof AllocationExpression)
return true;
if (location instanceof ConditionalExpression) {
ConditionalExpression conditional = (ConditionalExpression) location;
return containsAllocation(conditional.valueIfTrue) || containsAllocation(conditional.valueIfFalse);
} else if (location instanceof SwitchExpression) {
return containsAllocation((SwitchExpression) location);
}
if (location instanceof CastExpression)
return containsAllocation(((CastExpression) location).expression);
return false;
}
private static void preConnectTrackerAcrossAssignment(ASTNode location, LocalVariableBinding local, FlowInfo flowInfo,
FakedTrackingVariable closeTracker, Expression expression) {
if (expression instanceof AllocationExpression) {
preConnectTrackerAcrossAssignment(location, local, flowInfo, (AllocationExpression) expression, closeTracker);
} else if (expression instanceof ConditionalExpression) {
preConnectTrackerAcrossAssignment(location, local, flowInfo, (ConditionalExpression) expression, closeTracker);
} else if (expression instanceof SwitchExpression) {
preConnectTrackerAcrossAssignment(location, local, flowInfo, (SwitchExpression) expression, closeTracker);
} else if (expression instanceof CastExpression) {
preConnectTrackerAcrossAssignment(location, local, ((CastExpression) expression).expression, flowInfo);
}
}
private static void preConnectTrackerAcrossAssignment(ASTNode location, LocalVariableBinding local, FlowInfo flowInfo,
ConditionalExpression conditional, FakedTrackingVariable closeTracker) {
preConnectTrackerAcrossAssignment(location, local, flowInfo, closeTracker, conditional.valueIfFalse);
preConnectTrackerAcrossAssignment(location, local, flowInfo, closeTracker, conditional.valueIfTrue);
}
private static void preConnectTrackerAcrossAssignment(ASTNode location, LocalVariableBinding local, FlowInfo flowInfo,
SwitchExpression se, FakedTrackingVariable closeTracker) {
for (Expression re : se.resultExpressions) {
preConnectTrackerAcrossAssignment(location, local, flowInfo, closeTracker, re);
}
}
private static void preConnectTrackerAcrossAssignment(ASTNode location, LocalVariableBinding local, FlowInfo flowInfo,
AllocationExpression allocationExpression, FakedTrackingVariable closeTracker) {
allocationExpression.closeTracker = closeTracker;
if (allocationExpression.arguments != null && allocationExpression.arguments.length > 0) {
preConnectTrackerAcrossAssignment(location, local, allocationExpression.arguments[0], flowInfo);
}
}
public static void analyseCloseableAllocation(BlockScope scope, FlowInfo flowInfo, AllocationExpression allocation) {
if (((ReferenceBinding)allocation.resolvedType).hasTypeBit(TypeIds.BitResourceFreeCloseable)) {
if (allocation.closeTracker != null) {
allocation.closeTracker.withdraw();
allocation.closeTracker = null;
}
} else if (((ReferenceBinding)allocation.resolvedType).hasTypeBit(TypeIds.BitWrapperCloseable)) {
boolean isWrapper = true;
if (allocation.arguments != null && allocation.arguments.length > 0) {
FakedTrackingVariable innerTracker = findCloseTracker(scope, flowInfo, allocation.arguments[0]);
if (innerTracker != null) {
FakedTrackingVariable currentInner = innerTracker;
do {
if (currentInner == allocation.closeTracker)
return;
currentInner = currentInner.innerTracker;
} while (currentInner != null);
int newStatus = FlowInfo.NULL;
if (allocation.closeTracker == null) {
allocation.closeTracker = new FakedTrackingVariable(scope, allocation, flowInfo, FlowInfo.UNKNOWN);
} else {
if (scope.finallyInfo != null) {
int finallyStatus = scope.finallyInfo.nullStatus(allocation.closeTracker.binding);
if (finallyStatus != FlowInfo.UNKNOWN)
newStatus = finallyStatus;
}
}
if (allocation.closeTracker.innerTracker != null) {
innerTracker = pickMoreUnsafe(allocation.closeTracker.innerTracker, innerTracker, scope, flowInfo);
}
allocation.closeTracker.innerTracker = innerTracker;
innerTracker.outerTracker = allocation.closeTracker;
flowInfo.markNullStatus(allocation.closeTracker.binding, newStatus);
if (newStatus != FlowInfo.NULL) {
FakedTrackingVariable currentTracker = innerTracker;
while (currentTracker != null) {
flowInfo.markNullStatus(currentTracker.binding, newStatus);
currentTracker.globalClosingState |= allocation.closeTracker.globalClosingState;
currentTracker = currentTracker.innerTracker;
}
}
return;
} else {
if (!isAnyCloseable(allocation.arguments[0].resolvedType)) {
isWrapper = false;
}
}
} else {
isWrapper = false;
}
if (isWrapper) {
if (allocation.closeTracker != null) {
allocation.closeTracker.withdraw();
allocation.closeTracker = null;
}
} else {
handleRegularResource(scope, flowInfo, allocation);
}
} else {
handleRegularResource(scope, flowInfo, allocation);
}
}
private static FakedTrackingVariable pickMoreUnsafe(FakedTrackingVariable tracker1, FakedTrackingVariable tracker2, BlockScope scope, FlowInfo info) {
int status1 = info.nullStatus(tracker1.binding);
int status2 = info.nullStatus(tracker2.binding);
if (status1 == FlowInfo.NULL || status2 == FlowInfo.NON_NULL) return pick(tracker1, tracker2, scope);
if (status1 == FlowInfo.NON_NULL || status2 == FlowInfo.NULL) return pick(tracker2, tracker1, scope);
if ((status1 & FlowInfo.POTENTIALLY_NULL) != 0) return pick(tracker1, tracker2, scope);
if ((status2 & FlowInfo.POTENTIALLY_NULL) != 0) return pick(tracker2, tracker1, scope);
return pick(tracker1, tracker2, scope);
}
private static FakedTrackingVariable pick(FakedTrackingVariable tracker1, FakedTrackingVariable tracker2, BlockScope scope) {
tracker2.withdraw();
return tracker1;
}
private static void handleRegularResource(BlockScope scope, FlowInfo flowInfo, AllocationExpression allocation) {
FakedTrackingVariable presetTracker = allocation.closeTracker;
if (presetTracker != null && presetTracker.originalBinding != null) {
int closeStatus = flowInfo.nullStatus(presetTracker.binding);
if (closeStatus != FlowInfo.NON_NULL
&& closeStatus != FlowInfo.UNKNOWN
&& !flowInfo.isDefinitelyNull(presetTracker.originalBinding)
&& !(presetTracker.currentAssignment instanceof LocalDeclaration))
allocation.closeTracker.recordErrorLocation(presetTracker.currentAssignment, closeStatus);
} else {
allocation.closeTracker = new FakedTrackingVariable(scope, allocation, flowInfo, FlowInfo.UNKNOWN);
}
flowInfo.markAsDefinitelyNull(allocation.closeTracker.binding);
}
private static FakedTrackingVariable findCloseTracker(BlockScope scope, FlowInfo flowInfo, Expression arg)
{
while (arg instanceof Assignment) {
Assignment assign = (Assignment)arg;
LocalVariableBinding innerLocal = assign.localVariableBinding();
if (innerLocal != null) {
return innerLocal.closeTracker;
} else {
arg = assign.expression;
}
}
if (arg instanceof SingleNameReference) {
LocalVariableBinding local = arg.localVariableBinding();
if (local != null) {
return local.closeTracker;
}
} else if (arg instanceof AllocationExpression) {
return ((AllocationExpression)arg).closeTracker;
}
return null;
}
public static void handleResourceAssignment(BlockScope scope, FlowInfo upstreamInfo, FlowInfo flowInfo, FlowContext flowContext, ASTNode location, Expression rhs, LocalVariableBinding local)
{
FakedTrackingVariable previousTracker = null;
FakedTrackingVariable disconnectedTracker = null;
if (local.closeTracker != null) {
previousTracker = local.closeTracker;
int nullStatus = upstreamInfo.nullStatus(local);
if (nullStatus != FlowInfo.NULL && nullStatus != FlowInfo.UNKNOWN)
disconnectedTracker = previousTracker;
}
rhsAnalyis:
if (rhs.resolvedType != TypeBinding.NULL) {
FakedTrackingVariable rhsTrackVar = getCloseTrackingVariable(rhs, flowInfo, flowContext);
if (rhsTrackVar != null) {
if (local.closeTracker == null) {
if (rhsTrackVar.originalBinding != null)
local.closeTracker = rhsTrackVar;
if (rhsTrackVar.currentAssignment == location) {
rhsTrackVar.globalClosingState &= ~(SHARED_WITH_OUTSIDE|OWNED_BY_OUTSIDE|FOREACH_ELEMENT_VAR);
}
} else {
if (rhs instanceof AllocationExpression || rhs instanceof ConditionalExpression || rhs instanceof SwitchExpression) {
if (rhsTrackVar == disconnectedTracker)
return;
if (local.closeTracker == rhsTrackVar
&& ((rhsTrackVar.globalClosingState & OWNED_BY_OUTSIDE) != 0)) {
local.closeTracker = new FakedTrackingVariable(local, location, flowInfo, flowContext, FlowInfo.NULL);
break rhsAnalyis;
}
}
local.closeTracker = rhsTrackVar;
}
} else if (previousTracker != null) {
FlowContext currentFlowContext = flowContext;
checkReuseTracker : {
if (previousTracker.tryContext != null) {
while (currentFlowContext != null) {
if (previousTracker.tryContext == currentFlowContext) {
break checkReuseTracker;
}
currentFlowContext = currentFlowContext.parent;
}
}
if ((previousTracker.globalClosingState & (SHARED_WITH_OUTSIDE|OWNED_BY_OUTSIDE|FOREACH_ELEMENT_VAR)) == 0
&& flowInfo.hasNullInfoFor(previousTracker.binding))
flowInfo.markAsDefinitelyNull(previousTracker.binding);
local.closeTracker = analyseCloseableExpression(flowInfo, flowContext, local, location, rhs, previousTracker);
}
} else {
rhsTrackVar = analyseCloseableExpression(flowInfo, flowContext, local, location, rhs, null);
if (rhsTrackVar != null) {
local.closeTracker = rhsTrackVar;
if ((rhsTrackVar.globalClosingState & (SHARED_WITH_OUTSIDE|OWNED_BY_OUTSIDE|FOREACH_ELEMENT_VAR)) == 0)
flowInfo.markAsDefinitelyNull(rhsTrackVar.binding);
}
}
}
if (disconnectedTracker != null) {
if (disconnectedTracker.innerTracker != null && disconnectedTracker.innerTracker.binding.declaringScope == scope) {
disconnectedTracker.innerTracker.outerTracker = null;
scope.pruneWrapperTrackingVar(disconnectedTracker);
} else {
int upstreamStatus = upstreamInfo.nullStatus(disconnectedTracker.binding);
if (upstreamStatus != FlowInfo.NON_NULL)
disconnectedTracker.recordErrorLocation(location, upstreamStatus);
}
}
}
private static FakedTrackingVariable analyseCloseableExpression(FlowInfo flowInfo, FlowContext flowContext, LocalVariableBinding local,
ASTNode location, Expression expression, FakedTrackingVariable previousTracker)
{
while (true) {
if (expression instanceof Assignment)
expression = ((Assignment)expression).expression;
else if (expression instanceof CastExpression)
expression = ((CastExpression) expression).expression;
else
break;
}
boolean isResourceProducer = false;
if (expression.resolvedType instanceof ReferenceBinding) {
ReferenceBinding resourceType = (ReferenceBinding) expression.resolvedType;
if (resourceType.hasTypeBit(TypeIds.BitResourceFreeCloseable)) {
if (isBlacklistedMethod(expression))
isResourceProducer = true;
else
return null;
}
}
if (expression instanceof AllocationExpression) {
FakedTrackingVariable tracker = ((AllocationExpression) expression).closeTracker;
if (tracker != null && tracker.originalBinding == null) {
return null;
}
return tracker;
} else if (expression instanceof MessageSend
|| expression instanceof ArrayReference)
{
FakedTrackingVariable tracker = new FakedTrackingVariable(local, location, flowInfo, flowContext, FlowInfo.POTENTIALLY_NULL);
if (!isResourceProducer)
tracker.globalClosingState |= SHARED_WITH_OUTSIDE;
return tracker;
} else if (
(expression.bits & RestrictiveFlagMASK) == Binding.FIELD
||((expression instanceof QualifiedNameReference)
&& ((QualifiedNameReference) expression).isFieldAccess()))
{
FakedTrackingVariable tracker = new FakedTrackingVariable(local, location, flowInfo, flowContext, FlowInfo.UNKNOWN);
tracker.globalClosingState |= OWNED_BY_OUTSIDE;
return tracker;
}
if (local.closeTracker != null)
return local.closeTracker;
FakedTrackingVariable newTracker = new FakedTrackingVariable(local, location, flowInfo, flowContext, FlowInfo.UNKNOWN);
LocalVariableBinding rhsLocal = expression.localVariableBinding();
if (rhsLocal != null && rhsLocal.isParameter()) {
newTracker.globalClosingState |= OWNED_BY_OUTSIDE;
}
return newTracker;
}
private static boolean isBlacklistedMethod(Expression expression) {
if (expression instanceof MessageSend) {
MethodBinding method = ((MessageSend) expression).binding;
if (method != null && method.isValidBinding())
return CharOperation.equals(method.declaringClass.compoundName, TypeConstants.JAVA_NIO_FILE_FILES);
}
return false;
}
public static void cleanUpAfterAssignment(BlockScope currentScope, int lhsBits, Expression expression) {
while (true) {
if (expression instanceof Assignment)
expression = ((Assignment)expression).expression;
else if (expression instanceof CastExpression)
expression = ((CastExpression) expression).expression;
else
break;
}
if (expression instanceof AllocationExpression) {
FakedTrackingVariable tracker = ((AllocationExpression) expression).closeTracker;
if (tracker != null && tracker.originalBinding == null) {
tracker.withdraw();
((AllocationExpression) expression).closeTracker = null;
}
} else {
LocalVariableBinding local = expression.localVariableBinding();
if (local != null && local.closeTracker != null && ((lhsBits & Binding.FIELD) != 0))
local.closeTracker.withdraw();
}
}
public static boolean isAnyCloseable(TypeBinding typeBinding) {
return typeBinding instanceof ReferenceBinding
&& ((ReferenceBinding)typeBinding).hasTypeBit(TypeIds.BitAutoCloseable|TypeIds.BitCloseable);
}
public int findMostSpecificStatus(FlowInfo flowInfo, BlockScope currentScope, BlockScope locationScope) {
int status = FlowInfo.UNKNOWN;
FakedTrackingVariable currentTracker = this;
while (currentTracker != null) {
LocalVariableBinding currentVar = currentTracker.binding;
int currentStatus = getNullStatusAggressively(currentVar, flowInfo);
if (locationScope != null)
currentStatus = mergeCloseStatus(locationScope, currentStatus, currentVar, currentScope);
if (currentStatus == FlowInfo.NON_NULL) {
status = currentStatus;
break;
} else if (status == FlowInfo.NULL || status == FlowInfo.UNKNOWN) {
status = currentStatus;
}
currentTracker = currentTracker.innerTracker;
}
return status;
}
private int getNullStatusAggressively(LocalVariableBinding local, FlowInfo flowInfo) {
if (flowInfo == FlowInfo.DEAD_END) {
return FlowInfo.UNKNOWN;
}
int reachMode = flowInfo.reachMode();
int status = 0;
try {
if (reachMode != FlowInfo.REACHABLE)
flowInfo.tagBits &= ~FlowInfo.UNREACHABLE;
status = flowInfo.nullStatus(local);
if (TEST_372319) {
try {
Thread.sleep(5);
} catch (InterruptedException e) { }
}
} finally {
flowInfo.tagBits |= reachMode;
}
if ((status & FlowInfo.NULL) != 0) {
if ((status & (FlowInfo.NON_NULL | FlowInfo.POTENTIALLY_NON_NULL)) != 0)
return FlowInfo.POTENTIALLY_NULL;
return FlowInfo.NULL;
} else if ((status & FlowInfo.NON_NULL) != 0) {
if ((status & FlowInfo.POTENTIALLY_NULL) != 0)
return FlowInfo.POTENTIALLY_NULL;
return FlowInfo.NON_NULL;
} else if ((status & FlowInfo.POTENTIALLY_NULL) != 0)
return FlowInfo.POTENTIALLY_NULL;
return status;
}
public int mergeCloseStatus(BlockScope currentScope, int status, LocalVariableBinding local, BlockScope outerScope) {
if (status != FlowInfo.NON_NULL) {
if (currentScope.finallyInfo != null) {
int finallyStatus = currentScope.finallyInfo.nullStatus(local);
if (finallyStatus == FlowInfo.NON_NULL)
return finallyStatus;
if (finallyStatus != FlowInfo.NULL && currentScope.finallyInfo.hasNullInfoFor(local))
status = FlowInfo.POTENTIALLY_NULL;
}
if (currentScope != outerScope && currentScope.parent instanceof BlockScope)
return mergeCloseStatus(((BlockScope) currentScope.parent), status, local, outerScope);
}
return status;
}
public void markClose(FlowInfo flowInfo, FlowContext flowContext) {
FakedTrackingVariable current = this;
do {
flowInfo.markAsDefinitelyNonNull(current.binding);
current.globalClosingState |= CLOSE_SEEN;
flowContext.markFinallyNullStatus(current.binding, FlowInfo.NON_NULL);
current = current.innerTracker;
} while (current != null);
}
public void markClosedInNestedMethod() {
this.globalClosingState |= CLOSED_IN_NESTED_METHOD;
}
public static FlowInfo markPassedToOutside(BlockScope scope, Expression expression, FlowInfo flowInfo, FlowContext flowContext, boolean owned) {
FakedTrackingVariable trackVar = getCloseTrackingVariable(expression, flowInfo, flowContext);
if (trackVar != null) {
FlowInfo infoResourceIsClosed = owned ? flowInfo : flowInfo.copy();
int flag = owned ? OWNED_BY_OUTSIDE : SHARED_WITH_OUTSIDE;
do {
trackVar.globalClosingState |= flag;
if (scope.methodScope() != trackVar.methodScope)
trackVar.globalClosingState |= CLOSED_IN_NESTED_METHOD;
infoResourceIsClosed.markAsDefinitelyNonNull(trackVar.binding);
} while ((trackVar = trackVar.innerTracker) != null);
if (owned) {
return infoResourceIsClosed;
} else {
return FlowInfo.conditional(flowInfo, infoResourceIsClosed);
}
}
return flowInfo;
}
public static void markForeachElementVar(LocalDeclaration local) {
if (local.binding != null && local.binding.closeTracker != null) {
local.binding.closeTracker.globalClosingState |= FOREACH_ELEMENT_VAR;
}
}
public static class IteratorForReporting implements Iterator<FakedTrackingVariable> {
private final Set<FakedTrackingVariable> varSet;
private final Scope scope;
private final boolean atExit;
private Stage stage;
private Iterator<FakedTrackingVariable> iterator;
private FakedTrackingVariable next;
enum Stage {
OuterLess,
InnerOfProcessed,
InnerOfNotEnclosing,
AtExit
}
public IteratorForReporting(List<FakedTrackingVariable> variables, Scope scope, boolean atExit) {
this.varSet = new HashSet<>(variables);
this.scope = scope;
this.atExit = atExit;
setUpForStage(Stage.OuterLess);
}
@Override
public boolean hasNext() {
FakedTrackingVariable trackingVar;
switch (this.stage) {
case OuterLess:
while (this.iterator.hasNext()) {
trackingVar = this.iterator.next();
if (trackingVar.outerTracker == null)
return found(trackingVar);
}
setUpForStage(Stage.InnerOfProcessed);
case InnerOfProcessed:
while (this.iterator.hasNext()) {
trackingVar = this.iterator.next();
FakedTrackingVariable outer = trackingVar.outerTracker;
if (outer.binding.declaringScope == this.scope && !this.varSet.contains(outer))
return found(trackingVar);
}
setUpForStage(Stage.InnerOfNotEnclosing);
case InnerOfNotEnclosing:
searchAlien: while (this.iterator.hasNext()) {
trackingVar = this.iterator.next();
FakedTrackingVariable outer = trackingVar.outerTracker;
if (!this.varSet.contains(outer)) {
Scope outerTrackerScope = outer.binding.declaringScope;
Scope currentScope = this.scope;
while ((currentScope = currentScope.parent) instanceof BlockScope) {
if (outerTrackerScope == currentScope)
break searchAlien;
}
return found(trackingVar);
}
}
setUpForStage(Stage.AtExit);
case AtExit:
if (this.atExit && this.iterator.hasNext())
return found(this.iterator.next());
return false;
default: throw new IllegalStateException("Unexpected Stage "+this.stage);
}
}
private boolean found(FakedTrackingVariable trackingVar) {
this.iterator.remove();
this.next = trackingVar;
return true;
}
private void setUpForStage(Stage nextStage) {
this.iterator = this.varSet.iterator();
this.stage = nextStage;
}
@Override
public FakedTrackingVariable next() {
return this.next;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
public boolean hasDefinitelyNoResource(FlowInfo flowInfo) {
if (this.originalBinding == null) return false;
if (flowInfo.isDefinitelyNull(this.originalBinding)) {
return true;
}
if (!(flowInfo.isDefinitelyAssigned(this.originalBinding)
|| flowInfo.isPotentiallyAssigned(this.originalBinding))) {
return true;
}
return false;
}
public boolean isClosedInFinallyOfEnclosing(BlockScope scope) {
BlockScope currentScope = scope;
while (true) {
if (currentScope.finallyInfo != null
&& currentScope.finallyInfo.isDefinitelyNonNull(this.binding)) {
return true;
}
if (!(currentScope.parent instanceof BlockScope)) {
return false;
}
currentScope = (BlockScope) currentScope.parent;
}
}
public boolean isResourceBeingReturned(FakedTrackingVariable returnedResource) {
FakedTrackingVariable current = this;
do {
if (current == returnedResource) {
this.globalClosingState |= REPORTED_DEFINITIVE_LEAK;
return true;
}
current = current.innerTracker;
} while (current != null);
return false;
}
public void withdraw() {
this.binding.declaringScope.removeTrackingVar(this);
}
public void recordErrorLocation(ASTNode location, int nullStatus) {
if ((this.globalClosingState & OWNED_BY_OUTSIDE) != 0) {
return;
}
if (this.recordedLocations == null)
this.recordedLocations = new HashMap();
this.recordedLocations.put(location, Integer.valueOf(nullStatus));
}
public boolean reportRecordedErrors(Scope scope, int mergedStatus, boolean atDeadEnd) {
FakedTrackingVariable current = this;
while (current.globalClosingState == 0) {
current = current.innerTracker;
if (current == null) {
if (atDeadEnd && neverClosedAtLocations())
mergedStatus = FlowInfo.NULL;
if ((mergedStatus & (FlowInfo.NULL|FlowInfo.POTENTIALLY_NULL|FlowInfo.POTENTIALLY_NON_NULL)) != 0) {
reportError(scope.problemReporter(), null, mergedStatus);
return true;
} else {
break;
}
}
}
boolean hasReported = false;
if (this.recordedLocations != null) {
Iterator locations = this.recordedLocations.entrySet().iterator();
int reportFlags = 0;
while (locations.hasNext()) {
Map.Entry entry = (Entry) locations.next();
reportFlags |= reportError(scope.problemReporter(), (ASTNode)entry.getKey(), ((Integer)entry.getValue()).intValue());
hasReported = true;
}
if (reportFlags != 0) {
current = this;
do {
current.globalClosingState |= reportFlags;
} while ((current = current.innerTracker) != null);
}
}
return hasReported;
}
private boolean neverClosedAtLocations() {
if (this.recordedLocations != null) {
for (Object value : this.recordedLocations.values())
if (!value.equals(FlowInfo.NULL))
return false;
}
return true;
}
public int reportError(ProblemReporter problemReporter, ASTNode location, int nullStatus) {
if ((this.globalClosingState & OWNED_BY_OUTSIDE) != 0) {
return 0;
}
boolean isPotentialProblem = false;
if (nullStatus == FlowInfo.NULL) {
if ((this.globalClosingState & CLOSED_IN_NESTED_METHOD) != 0)
isPotentialProblem = true;
} else if ((nullStatus & (FlowInfo.POTENTIALLY_NULL|FlowInfo.POTENTIALLY_NON_NULL)) != 0) {
isPotentialProblem = true;
}
if (isPotentialProblem) {
if ((this.globalClosingState & (REPORTED_POTENTIAL_LEAK|REPORTED_DEFINITIVE_LEAK)) != 0)
return 0;
problemReporter.potentiallyUnclosedCloseable(this, location);
} else {
if ((this.globalClosingState & (REPORTED_DEFINITIVE_LEAK)) != 0)
return 0;
problemReporter.unclosedCloseable(this, location);
}
int reportFlag = isPotentialProblem ? REPORTED_POTENTIAL_LEAK : REPORTED_DEFINITIVE_LEAK;
if (location == null) {
FakedTrackingVariable current = this;
do {
current.globalClosingState |= reportFlag;
} while ((current = current.innerTracker) != null);
}
return reportFlag;
}
public void reportExplicitClosing(ProblemReporter problemReporter) {
if ((this.globalClosingState & (OWNED_BY_OUTSIDE|REPORTED_EXPLICIT_CLOSE|FOREACH_ELEMENT_VAR)) == 0) {
this.globalClosingState |= REPORTED_EXPLICIT_CLOSE;
problemReporter.explicitlyClosedAutoCloseable(this);
}
}
public String nameForReporting(ASTNode location, ReferenceContext referenceContext) {
if (this.name == UNASSIGNED_CLOSEABLE_NAME) {
if (location != null && referenceContext != null) {
CompilationResult compResult = referenceContext.compilationResult();
if (compResult != null) {
int[] lineEnds = compResult.getLineSeparatorPositions();
int resourceLine = Util.getLineNumber(this.sourceStart, lineEnds , 0, lineEnds.length-1);
int reportLine = Util.getLineNumber(location.sourceStart, lineEnds , 0, lineEnds.length-1);
if (resourceLine != reportLine) {
char[] replacement = Integer.toString(resourceLine).toCharArray();
return String.valueOf(CharOperation.replace(UNASSIGNED_CLOSEABLE_NAME_TEMPLATE, TEMPLATE_ARGUMENT, replacement));
}
}
}
}
return String.valueOf(this.name);
}
}